Fix flatFieldMetadata transpiler to fit real cache typing (#13529)
# Introduction
The cache is not accurately typed regarding the `fieldMetadataMaps` they
do not contain relations
In this way we:
- Created dedicated transpilation tools for cache manipulation
- Avoided recursivity in transpilation tools
- Fix and finalized the createMany fields metadata service v2
- Refactor the validator to handle validation :)
## What's next:
- Implem integration tests to make things run 🙃
- migrate existing settings valdiation
- Finish the create object metadata service
- Handle update input transpilation and validation
This commit is contained in:
+50
-27
@@ -13,10 +13,12 @@ import {
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
|
||||
import { fromCreateFieldInputToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util';
|
||||
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
|
||||
import { mergeTwoFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/merge-two-flat-object-metadatas.util';
|
||||
import { getFieldMetadataEntityFromCachedObjectMetadataMaps } from 'src/engine/metadata-modules/utils/get-field-metadata-entity-from-cached-object-metadata-maps.util';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
@@ -66,29 +68,34 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
|
||||
const existingFlatObjectMetadatas =
|
||||
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
|
||||
|
||||
const flatFieldToCreateAndItsFlatObjectMetadataArray = await Promise.all(
|
||||
fieldMetadataInputs.map(
|
||||
async (fieldMetadataInput) =>
|
||||
await fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata({
|
||||
existingFlatObjectMetadatas,
|
||||
rawCreateFieldInput: fieldMetadataInput,
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
let flatObjectMetadatasWithNewFields: FlatObjectMetadata[] = [];
|
||||
|
||||
for (const fieldMetadataInput of fieldMetadataInputs) {
|
||||
const createdFlatFieldsMetadataAndParentFlatObjectMetadata =
|
||||
await fromCreateFieldInputToFlatFieldMetadata({
|
||||
existingFlatObjectMetadatas,
|
||||
rawCreateFieldInput: fieldMetadataInput,
|
||||
});
|
||||
|
||||
for (const flatFieldMetadataAndParentPair of flatFieldToCreateAndItsFlatObjectMetadataArray) {
|
||||
const flatFieldMetadataValidationPromises =
|
||||
flatFieldMetadataAndParentPair.flatMap<
|
||||
Promise<FailedFlatFieldMetadataValidationExceptions[]>
|
||||
>(({ flatFieldMetadata: flatFieldMetadataToValidate }) =>
|
||||
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata({
|
||||
existingFlatObjectMetadatas,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
}),
|
||||
);
|
||||
const createdFlatFieldMetadataValidationResult = (
|
||||
await Promise.all(
|
||||
createdFlatFieldsMetadataAndParentFlatObjectMetadata.map(
|
||||
({ flatFieldMetadata: flatFieldMetadataToValidate }) =>
|
||||
this.flatFieldMetadataValidatorService.validateOneFlatFieldMetadata(
|
||||
{
|
||||
existingFlatObjectMetadatas,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
},
|
||||
),
|
||||
),
|
||||
)
|
||||
).filter(isDefined);
|
||||
await Promise.all(flatFieldMetadataValidationPromises)
|
||||
)
|
||||
.flat()
|
||||
.filter(isDefined);
|
||||
|
||||
if (createdFlatFieldMetadataValidationResult.length > 0) {
|
||||
const errors = createdFlatFieldMetadataValidationResult.flat();
|
||||
@@ -100,7 +107,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
|
||||
}
|
||||
|
||||
const updatedFlatObjectMetadatas =
|
||||
createdFlatFieldsMetadataAndParentFlatObjectMetadata.map<FlatObjectMetadata>(
|
||||
flatFieldMetadataAndParentPair.map<FlatObjectMetadata>(
|
||||
({ flatFieldMetadata, parentFlatObjectMetadata }) => {
|
||||
return {
|
||||
...parentFlatObjectMetadata,
|
||||
@@ -129,11 +136,27 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
|
||||
// const recomputedCache =
|
||||
// await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
// { workspaceId },
|
||||
// );
|
||||
const recomputedCache =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeMetadataMaps(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
return []; //TODO to retrieve from cache or directly from find
|
||||
return flatFieldToCreateAndItsFlatObjectMetadataArray.flatMap<FieldMetadataEntity>(
|
||||
(createdFlatFieldAndItsParentFlatObject) => {
|
||||
return createdFlatFieldAndItsParentFlatObject
|
||||
.map(
|
||||
({
|
||||
flatFieldMetadata: { id: fieldMetadataId },
|
||||
parentFlatObjectMetadata: { id: objectMetadataId },
|
||||
}) =>
|
||||
getFieldMetadataEntityFromCachedObjectMetadataMaps({
|
||||
fieldMetadataId,
|
||||
objectMetadataId,
|
||||
objectMetadataMaps: recomputedCache.objectMetadataMaps,
|
||||
}),
|
||||
)
|
||||
.filter(isDefined);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export type FlatRelationTargetFieldMetadata = FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>['flatRelationTargetFieldMetadata'];
|
||||
+3
-3
@@ -24,18 +24,18 @@ type FromCreateFieldInputToFlatObjectMetadata = {
|
||||
rawCreateFieldInput: CreateFieldInput;
|
||||
existingFlatObjectMetadatas: FlatObjectMetadata[];
|
||||
};
|
||||
export type FlatFieldMetadataAndParentFlatObjectMetadata<
|
||||
export type FlatFieldAndItsFlatObjectMetadata<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
> = {
|
||||
flatFieldMetadata: FlatFieldMetadata<T>;
|
||||
parentFlatObjectMetadata: FlatObjectMetadata;
|
||||
};
|
||||
|
||||
export const fromCreateFieldInputToFlatFieldMetadata = async ({
|
||||
export const fromCreateFieldInputToFlatFieldAndItsFlatObjectMetadata = async ({
|
||||
existingFlatObjectMetadatas,
|
||||
rawCreateFieldInput,
|
||||
}: FromCreateFieldInputToFlatObjectMetadata): Promise<
|
||||
FlatFieldMetadataAndParentFlatObjectMetadata[]
|
||||
FlatFieldAndItsFlatObjectMetadata[]
|
||||
> => {
|
||||
if (rawCreateFieldInput.isRemoteCreation) {
|
||||
throw new FieldMetadataException(
|
||||
+40
-24
@@ -1,32 +1,42 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined, removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationAndMorphRelationFieldMetadataType,
|
||||
} from 'twenty-shared/types';
|
||||
import { removePropertiesFromRecord } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import {
|
||||
FieldMetadataEntityRelationProperties,
|
||||
FlatFieldMetadata,
|
||||
fieldMetadataRelationProperties,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
|
||||
import { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type';
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { fromFlatObjectMetadataToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-flat-object-metadata-to-flat-object-metadata-without-fields.util';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util';
|
||||
|
||||
export const fromFieldMetadataEntityToFlatRelationTargetFieldMetadata = (
|
||||
fieldMetadataEntity: FieldMetadataEntity<RelationAndMorphRelationFieldMetadataType>,
|
||||
): FlatRelationTargetFieldMetadata => {
|
||||
return {
|
||||
uniqueIdentifier: fieldMetadataEntity.standardId ?? fieldMetadataEntity.id,
|
||||
...removePropertiesFromRecord(fieldMetadataEntity, [
|
||||
'relationTargetObjectMetadata',
|
||||
'relationTargetFieldMetadata',
|
||||
]),
|
||||
};
|
||||
};
|
||||
|
||||
// TODO refactor this method should not be recursive but depth 1
|
||||
export const fromFieldMetadataEntityToFlatFieldMetadata = <
|
||||
T extends FieldMetadataType,
|
||||
>(
|
||||
fieldMetadataEntity: FieldMetadataEntity<T>,
|
||||
/**
|
||||
* private depth bottleneck
|
||||
*/
|
||||
_depth?: number,
|
||||
// This is intended to be abstract
|
||||
): FlatFieldMetadata => {
|
||||
if (isDefined(_depth) && _depth > 1) {
|
||||
throw new Error(
|
||||
'fromFieldMetadataEntityToFlatFieldMetadata entering a possible infinite loop',
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
isFieldMetadataEntityOfType(
|
||||
fieldMetadataEntity,
|
||||
@@ -44,20 +54,26 @@ export const fromFieldMetadataEntityToFlatFieldMetadata = <
|
||||
FieldMetadataEntityRelationProperties
|
||||
>(fieldMetadataEntity, fieldMetadataRelationProperties);
|
||||
|
||||
const newDepth = isDefined(_depth) ? _depth + 1 : 1;
|
||||
const flatRelationTargetFieldMetadata =
|
||||
fromFieldMetadataEntityToFlatFieldMetadata(
|
||||
if (
|
||||
!isFieldMetadataEntityOfType(
|
||||
fieldMetadataEntity.relationTargetFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
throw new FieldMetadataException(
|
||||
'Relation target field is not a field metadata type relation',
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED,
|
||||
);
|
||||
}
|
||||
|
||||
const flatRelationTargetFieldMetadata =
|
||||
fromFieldMetadataEntityToFlatRelationTargetFieldMetadata(
|
||||
fieldMetadataEntity.relationTargetFieldMetadata,
|
||||
newDepth,
|
||||
);
|
||||
|
||||
const flatObjectTargetFieldMetadata =
|
||||
fromObjectMetadataEntityToFlatObjectMetadata(
|
||||
fieldMetadataEntity.relationTargetObjectMetadata,
|
||||
);
|
||||
const flatRelationTargetObjectMetadata =
|
||||
fromFlatObjectMetadataToFlatObjectMetadataWithoutFields(
|
||||
flatObjectTargetFieldMetadata,
|
||||
fromObjectMetadataEntityToFlatObjectMetadataWithoutFields(
|
||||
fieldMetadataEntity.relationTargetObjectMetadata,
|
||||
);
|
||||
|
||||
return {
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { validateRelationCreationPayloadOrThrow } from 'src/engine/metadata-modules/field-metadata/utils/validate-relation-creation-payload.util';
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { FlatFieldMetadataAndParentFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-metadata.util';
|
||||
import { FlatFieldAndItsFlatObjectMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/from-create-field-input-to-flat-field-and-its-flat-object-metadata.util';
|
||||
import { getDefaultFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/get-default-flat-field-metadata-from-create-field-input.util';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
@@ -24,7 +24,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
|
||||
sourceParentFlatObjectMetadata,
|
||||
createFieldInput,
|
||||
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
|
||||
FlatFieldMetadataAndParentFlatObjectMetadata[]
|
||||
FlatFieldAndItsFlatObjectMetadata[]
|
||||
> => {
|
||||
const { relationCreationPayload } = createFieldInput;
|
||||
|
||||
@@ -104,5 +104,5 @@ export const fromRelationCreateFieldInputToFlatFieldMetadata = async ({
|
||||
flatFieldMetadata: targetFlatFieldMetadata,
|
||||
parentFlatObjectMetadata: targetParentFlatObjectMetadata,
|
||||
},
|
||||
] satisfies FlatFieldMetadataAndParentFlatObjectMetadata<FieldMetadataType.RELATION>[];
|
||||
] satisfies FlatFieldAndItsFlatObjectMetadata<FieldMetadataType.RELATION>[];
|
||||
};
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { FlatRelationTargetFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-relation-target-field-metadata.type';
|
||||
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata-without-fields.util';
|
||||
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import {
|
||||
WorkspaceMetadataCacheException,
|
||||
WorkspaceMetadataCacheExceptionCode,
|
||||
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
|
||||
import { isCachedFieldMetadataEntityOfType } from 'src/engine/utils/is-cached-field-metadata-of-type.util';
|
||||
|
||||
const fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata = <
|
||||
T extends FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION,
|
||||
>(
|
||||
cachedFieldMetadataEntity: CachedFieldMetadataEntity<T>,
|
||||
): FlatRelationTargetFieldMetadata => {
|
||||
return {
|
||||
...cachedFieldMetadataEntity,
|
||||
uniqueIdentifier:
|
||||
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
|
||||
type: cachedFieldMetadataEntity.type,
|
||||
};
|
||||
};
|
||||
|
||||
type CachedFieldMetadataEntityAndObjectMetadataMap<
|
||||
T extends FieldMetadataType,
|
||||
> = {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
cachedFieldMetadataEntity: CachedFieldMetadataEntity<T>;
|
||||
};
|
||||
|
||||
export const fromCachedFieldMetadataEntityToFlatFieldMetadata = <
|
||||
T extends FieldMetadataType,
|
||||
>(
|
||||
{
|
||||
cachedFieldMetadataEntity,
|
||||
objectMetadataMaps,
|
||||
}: CachedFieldMetadataEntityAndObjectMetadataMap<T>,
|
||||
// This is intended to be abstract return type
|
||||
): FlatFieldMetadata => {
|
||||
if (
|
||||
isCachedFieldMetadataEntityOfType(
|
||||
cachedFieldMetadataEntity,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isCachedFieldMetadataEntityOfType(
|
||||
cachedFieldMetadataEntity,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationTargetObjectMetadataItemWithFieldsMaps =
|
||||
objectMetadataMaps.byId[
|
||||
cachedFieldMetadataEntity.relationTargetObjectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadataItemWithFieldsMaps)) {
|
||||
throw new WorkspaceMetadataCacheException(
|
||||
'Cache object is not up to date',
|
||||
WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
const flatRelationTargetObjectMetadata =
|
||||
fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields(
|
||||
relationTargetObjectMetadataItemWithFieldsMaps,
|
||||
);
|
||||
|
||||
const relationTargetCachedFieldMetadata =
|
||||
relationTargetObjectMetadataItemWithFieldsMaps.fieldsById[
|
||||
cachedFieldMetadataEntity.relationTargetFieldMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(relationTargetCachedFieldMetadata)) {
|
||||
throw new WorkspaceMetadataCacheException(
|
||||
'Cache field is not up to date',
|
||||
WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!isCachedFieldMetadataEntityOfType(
|
||||
relationTargetCachedFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
)
|
||||
) {
|
||||
throw new WorkspaceMetadataCacheException(
|
||||
'Cache field is invalid',
|
||||
WorkspaceMetadataCacheExceptionCode.FIELD_METADATA_INVALID,
|
||||
);
|
||||
}
|
||||
|
||||
const flatRelationTargetFieldMetadata =
|
||||
fromCachedRelationFieldMetadataEntityToFlatRelationTargetFieldMetadata(
|
||||
relationTargetCachedFieldMetadata,
|
||||
);
|
||||
|
||||
return {
|
||||
...cachedFieldMetadataEntity,
|
||||
uniqueIdentifier:
|
||||
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
|
||||
flatRelationTargetFieldMetadata,
|
||||
flatRelationTargetObjectMetadata,
|
||||
type: cachedFieldMetadataEntity.type,
|
||||
} satisfies FlatFieldMetadata<
|
||||
FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION
|
||||
>;
|
||||
}
|
||||
|
||||
return {
|
||||
...cachedFieldMetadataEntity,
|
||||
uniqueIdentifier:
|
||||
cachedFieldMetadataEntity.standardId ?? cachedFieldMetadataEntity.id,
|
||||
flatRelationTargetFieldMetadata: null,
|
||||
flatRelationTargetObjectMetadata: null,
|
||||
};
|
||||
};
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadataWithoutFields } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/from-object-metadata-entity-to-flat-object-metadata-without-fields.util';
|
||||
|
||||
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadataWithoutFields =
|
||||
(
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
): FlatObjectMetadataWithoutFields => {
|
||||
const {
|
||||
fieldsById: _fieldsById,
|
||||
fieldIdByJoinColumnName: _fieldIdByJoinColumnName,
|
||||
fieldIdByName: _fieldIdByName,
|
||||
indexMetadatas,
|
||||
...rest
|
||||
} = objectMetadataItemWithFieldMaps;
|
||||
|
||||
return fromObjectMetadataEntityToFlatObjectMetadataWithoutFields({
|
||||
...rest,
|
||||
fields: [],
|
||||
indexMetadatas,
|
||||
});
|
||||
};
|
||||
+28
-9
@@ -1,21 +1,40 @@
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
|
||||
import { fromCachedFieldMetadataEntityToFlatFieldMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-cached-field-metadata-entity-to-flat-field-metadata.util';
|
||||
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
|
||||
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = (
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
||||
): FlatObjectMetadata => {
|
||||
type FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs = {
|
||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps;
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
};
|
||||
export const fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata = ({
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
}: FromObjectMetadataItemWithFieldMapsToFlatObjectMetadataArgs): FlatObjectMetadata => {
|
||||
const {
|
||||
fieldsById,
|
||||
fieldIdByJoinColumnName: _fieldIdByJoinColumnName,
|
||||
fieldIdByName: _fieldIdByName,
|
||||
indexMetadatas,
|
||||
indexMetadatas: _indexMetadatas,
|
||||
...rest
|
||||
} = objectMetadataItemWithFieldMaps;
|
||||
|
||||
return fromObjectMetadataEntityToFlatObjectMetadata({
|
||||
const cachedFieldMetadataEntities =
|
||||
Object.values<CachedFieldMetadataEntity>(fieldsById);
|
||||
|
||||
const flatFieldMetadatas = cachedFieldMetadataEntities.map(
|
||||
(cachedFieldMetadataEntity) =>
|
||||
fromCachedFieldMetadataEntityToFlatFieldMetadata({
|
||||
cachedFieldMetadataEntity,
|
||||
objectMetadataMaps,
|
||||
}),
|
||||
);
|
||||
|
||||
return {
|
||||
...rest,
|
||||
fields: Object.values(fieldsById),
|
||||
indexMetadatas,
|
||||
});
|
||||
flatFieldMetadatas,
|
||||
uniqueIdentifier: rest.standardId ?? rest.id,
|
||||
flatIndexMetadatas: [], // prastoin TODO convert from indexMetadatas to flatIndexMetadatas
|
||||
};
|
||||
};
|
||||
|
||||
+15
-4
@@ -3,6 +3,10 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-item-with-field-maps-to-flat-object-metadata.util';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
import {
|
||||
WorkspaceMetadataCacheException,
|
||||
WorkspaceMetadataCacheExceptionCode,
|
||||
} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception';
|
||||
|
||||
export const fromObjectMetadataMapsToFlatObjectMetadatas = (
|
||||
objectMetadataMaps: ObjectMetadataMaps,
|
||||
@@ -10,12 +14,19 @@ export const fromObjectMetadataMapsToFlatObjectMetadatas = (
|
||||
const objectMetadataIds = Object.keys(objectMetadataMaps.byId);
|
||||
|
||||
return objectMetadataIds.flatMap<FlatObjectMetadata>((objectMetadataId) => {
|
||||
const occurrence = objectMetadataMaps.byId[objectMetadataId];
|
||||
const objectMetadataItemWithFieldMaps =
|
||||
objectMetadataMaps.byId[objectMetadataId];
|
||||
|
||||
if (!isDefined(occurrence)) {
|
||||
return [];
|
||||
if (!isDefined(objectMetadataItemWithFieldMaps)) {
|
||||
throw new WorkspaceMetadataCacheException(
|
||||
'Object metadata not found in cache',
|
||||
WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata(occurrence);
|
||||
return fromObjectMetadataItemWithFieldMapsToFlatObjectMetadata({
|
||||
objectMetadataItemWithFieldMaps,
|
||||
objectMetadataMaps,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
+2
-2
@@ -41,8 +41,8 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
|
||||
fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
|
||||
const existingFlatObjectMetadatas =
|
||||
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
|
||||
// @ts-expect-error TODO implement validateFlatObjectMetadataData
|
||||
const createdFlatObjectMetadata = validateFlatObjectMetadataData({
|
||||
// @ts-expect-error TODO implement validateFlatObjectMetadata
|
||||
const createdFlatObjectMetadata = validateFlatObjectMetadata({
|
||||
existing:
|
||||
// Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
|
||||
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps),
|
||||
|
||||
-44
@@ -13,12 +13,8 @@ import {
|
||||
Repository,
|
||||
} from 'typeorm';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util';
|
||||
import { fromObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-maps-to-flat-object-metadatas.util';
|
||||
import { IndexMetadataService } from 'src/engine/metadata-modules/index-metadata/index-metadata.service';
|
||||
import { DeleteOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/delete-object.input';
|
||||
import {
|
||||
@@ -50,8 +46,6 @@ import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target
|
||||
import { isFieldMetadataEntityOfType } from 'src/engine/utils/is-field-metadata-of-type.util';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { isSearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
|
||||
|
||||
@@ -76,9 +70,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
private readonly indexMetadataService: IndexMetadataService,
|
||||
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service,
|
||||
private readonly featureFlagService: FeatureFlagService,
|
||||
private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service,
|
||||
) {
|
||||
super(objectMetadataRepository);
|
||||
}
|
||||
@@ -125,41 +116,6 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
|
||||
objectMetadataInput.workspaceId,
|
||||
);
|
||||
|
||||
const isWorkspaceMigrationV2Enabled =
|
||||
await this.featureFlagService.isFeatureEnabled(
|
||||
FeatureFlagKey.IS_WORKSPACE_MIGRATION_V2_ENABLED,
|
||||
objectMetadataInput.workspaceId,
|
||||
);
|
||||
|
||||
if (isWorkspaceMigrationV2Enabled) {
|
||||
const createdRawFlatObjectMetadata =
|
||||
fromCreateObjectInputToFlatObjectMetadata(objectMetadataInput);
|
||||
const existingFlatObjectMetadatas =
|
||||
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps);
|
||||
// @ts-expect-error TODO implement validateFlatObjectMetadataData
|
||||
const createdFlatObjectMetadata = validateFlatObjectMetadataData({
|
||||
existing:
|
||||
// Here we assume that EVERYTHING is in cache and up to date, this is very critical, also race condition prone :thinking:
|
||||
fromObjectMetadataMapsToFlatObjectMetadatas(objectMetadataMaps),
|
||||
toValidate: [createdRawFlatObjectMetadata],
|
||||
});
|
||||
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
objectMetadataFromToInputs: {
|
||||
from: existingFlatObjectMetadatas,
|
||||
to: [createdFlatObjectMetadata],
|
||||
},
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
workspaceId: objectMetadataInput.workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
|
||||
// What to return exactly ? We now won't have access to the entity directly
|
||||
// We could still retrieve it afterwards using a find on object metadata id or return a flat now
|
||||
return createdFlatObjectMetadata;
|
||||
}
|
||||
|
||||
objectMetadataInput.labelSingular = capitalize(
|
||||
objectMetadataInput.labelSingular,
|
||||
);
|
||||
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataEntityRelationProperties } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
|
||||
export type CachedFieldMetadataEntity<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
> = Omit<FieldMetadataEntity<T>, FieldMetadataEntityRelationProperties>;
|
||||
@@ -1,3 +1,3 @@
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export type FieldMetadataMap = Record<string, FieldMetadataEntity>;
|
||||
export type FieldMetadataMap = Record<string, FieldMetadataEntity>; // TODO refactor Should be CachedFieldMetadataEntity or best FlatFieldMetadata
|
||||
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
|
||||
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
||||
|
||||
export const getFieldMetadataEntityFromCachedObjectMetadataMaps = ({
|
||||
fieldMetadataId,
|
||||
objectMetadataId,
|
||||
objectMetadataMaps,
|
||||
}: {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
objectMetadataId: string;
|
||||
fieldMetadataId: string;
|
||||
}): FieldMetadataEntity | undefined => {
|
||||
const objectMetadataFromCache = objectMetadataMaps.byId[objectMetadataId];
|
||||
|
||||
if (!isDefined(objectMetadataFromCache)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const fieldMetadataFromCache: CachedFieldMetadataEntity =
|
||||
objectMetadataFromCache.fieldsById[fieldMetadataId];
|
||||
|
||||
if (!isDefined(fieldMetadataFromCache)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We need to determine if graphql will be able to handle relations crafting byitself
|
||||
return {
|
||||
...fieldMetadataFromCache,
|
||||
object: {} as ObjectMetadataEntity,
|
||||
relationTargetFieldMetadata: null,
|
||||
relationTargetObjectMetadata: null,
|
||||
indexFieldMetadatas: {} as IndexFieldMetadataEntity,
|
||||
fieldPermissions: [],
|
||||
createdAt: new Date(fieldMetadataFromCache.createdAt),
|
||||
updatedAt: new Date(fieldMetadataFromCache.updatedAt),
|
||||
};
|
||||
};
|
||||
+2
@@ -4,5 +4,7 @@ export class WorkspaceMetadataCacheException extends CustomException<WorkspaceMe
|
||||
|
||||
export enum WorkspaceMetadataCacheExceptionCode {
|
||||
OBJECT_METADATA_MAP_NOT_FOUND = 'Object Metadata map not found',
|
||||
FIELD_METADATA_NOT_FOUND = 'Field Metadata not found',
|
||||
FIELD_METADATA_INVALID = 'Field Metadata is invalid',
|
||||
OBJECT_METADATA_COLLECTION_NOT_FOUND = 'Object Metadata collection not found',
|
||||
}
|
||||
|
||||
+3
-3
@@ -15,7 +15,7 @@ import {
|
||||
} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
|
||||
type getExistingOrRecomputeMetadataMapsResult = {
|
||||
type GetExistingOrRecomputeMetadataMapsResult = {
|
||||
objectMetadataMaps: ObjectMetadataMaps;
|
||||
metadataVersion: number;
|
||||
};
|
||||
@@ -38,7 +38,7 @@ export class WorkspaceMetadataCacheService {
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<getExistingOrRecomputeMetadataMapsResult> {
|
||||
}): Promise<GetExistingOrRecomputeMetadataMapsResult> {
|
||||
const currentCacheVersion =
|
||||
await this.getMetadataVersionFromCache(workspaceId);
|
||||
|
||||
@@ -84,7 +84,7 @@ export class WorkspaceMetadataCacheService {
|
||||
workspaceId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
}): Promise<getExistingOrRecomputeMetadataMapsResult> {
|
||||
}): Promise<GetExistingOrRecomputeMetadataMapsResult> {
|
||||
const currentDatabaseVersion =
|
||||
await this.getMetadataVersionFromDatabase(workspaceId);
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { CachedFieldMetadataEntity } from 'src/engine/metadata-modules/types/cached-field-metadata-entity';
|
||||
|
||||
export function isCachedFieldMetadataEntityOfType<
|
||||
Field extends CachedFieldMetadataEntity<FieldMetadataType>,
|
||||
Type extends FieldMetadataType,
|
||||
>(
|
||||
fieldMetadata: Pick<Field, 'type'>,
|
||||
type: Type,
|
||||
): fieldMetadata is CachedFieldMetadataEntity &
|
||||
CachedFieldMetadataEntity<Type> {
|
||||
return fieldMetadata.type === type;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
import { FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromObjectMetadataEntityToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-object-metadata-entity-to-flat-object-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
export const fromObjectMetadataEntityToFlatObjectMetadataWithoutFields = (
|
||||
objectMetadataEntity: ObjectMetadataEntity,
|
||||
): FlatObjectMetadataWithoutFields => {
|
||||
return fromObjectMetadataEntityToFlatObjectMetadata({
|
||||
...objectMetadataEntity,
|
||||
fields: [],
|
||||
});
|
||||
};
|
||||
+1
@@ -36,6 +36,7 @@ export class WorkspaceMigrationBuilderV2Service {
|
||||
createdObjectMetadata,
|
||||
deletedObjectMetadata,
|
||||
updatedObjectMetadata,
|
||||
inferDeletionFromMissingObjectFieldIndex,
|
||||
});
|
||||
|
||||
const createdObjectMetadataCreateIndexActions =
|
||||
|
||||
+7
-4
@@ -13,11 +13,14 @@ import {
|
||||
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-object-actions';
|
||||
|
||||
export type CreatedDeletedUpdatedObjectMetadataInputMatrix =
|
||||
CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata>;
|
||||
CustomDeletedCreatedUpdatedMatrix<'objectMetadata', FlatObjectMetadata> & {
|
||||
inferDeletionFromMissingObjectFieldIndex: boolean;
|
||||
};
|
||||
export const buildWorkspaceMigrationV2ObjectActions = ({
|
||||
createdObjectMetadata,
|
||||
deletedObjectMetadata,
|
||||
updatedObjectMetadata,
|
||||
inferDeletionFromMissingObjectFieldIndex,
|
||||
}: CreatedDeletedUpdatedObjectMetadataInputMatrix): WorkspaceMigrationObjectActionV2[] => {
|
||||
const createdObjectActions = createdObjectMetadata.map(
|
||||
(flatObjectMetadata) => {
|
||||
@@ -36,9 +39,9 @@ export const buildWorkspaceMigrationV2ObjectActions = ({
|
||||
},
|
||||
);
|
||||
|
||||
const deletedObjectActions = deletedObjectMetadata.map(
|
||||
getWorkspaceMigrationV2ObjectDeleteAction,
|
||||
);
|
||||
const deletedObjectActions = inferDeletionFromMissingObjectFieldIndex
|
||||
? deletedObjectMetadata.map(getWorkspaceMigrationV2ObjectDeleteAction)
|
||||
: [];
|
||||
|
||||
const updatedObjectActions =
|
||||
updatedObjectMetadata.flatMap<UpdateObjectAction>(({ from, to }) => {
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
import { FieldMetadataType } from './FieldMetadataType';
|
||||
|
||||
export type RelationAndMorphRelationFieldMetadataType =
|
||||
| FieldMetadataType.RELATION
|
||||
| FieldMetadataType.MORPH_RELATION;
|
||||
@@ -17,6 +17,7 @@ export type { NonNullableRequired } from './NonNullableRequired';
|
||||
export type { NullablePartial } from './NullablePartial';
|
||||
export type { ObjectRecordsPermissions } from './ObjectRecordsPermissions';
|
||||
export type { ObjectRecordsPermissionsByRoleId } from './ObjectRecordsPermissionsByRoleId';
|
||||
export type { RelationAndMorphRelationFieldMetadataType } from './RelationAndMorphRelationFieldMetadataType';
|
||||
export type { RestrictedField } from './RestrictedField';
|
||||
export type { RestrictedFields } from './RestrictedFields';
|
||||
export type { StepFilterGroup, StepFilter } from './StepFilters';
|
||||
|
||||
Reference in New Issue
Block a user