[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:
+8
-2
@@ -14,9 +14,9 @@ import {
|
||||
import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager/workspace-migration-v2/utils/aggregate-orchestrator-actions-report.util';
|
||||
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
|
||||
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
|
||||
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
|
||||
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service';
|
||||
import { WorkspaceMigrationV2RouteTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
|
||||
@@ -224,6 +224,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
|
||||
optimisticAllFlatEntityMaps.flatViewMaps =
|
||||
viewResult.optimisticFlatEntityMaps;
|
||||
optimisticAllFlatEntityMaps.flatObjectMetadataMaps =
|
||||
viewResult.dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
|
||||
|
||||
if (viewResult.status === 'fail') {
|
||||
orchestratorFailureReport.view.push(...viewResult.errors);
|
||||
@@ -242,6 +244,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
to: toFlatViewFieldMaps,
|
||||
buildOptions,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatObjectMetadataMaps:
|
||||
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps:
|
||||
optimisticAllFlatEntityMaps.flatFieldMetadataMaps,
|
||||
flatViewMaps: optimisticAllFlatEntityMaps.flatViewMaps,
|
||||
@@ -254,6 +258,8 @@ export class WorkspaceMigrationBuildOrchestratorService {
|
||||
viewFieldResult.optimisticFlatEntityMaps;
|
||||
optimisticAllFlatEntityMaps.flatViewMaps =
|
||||
viewFieldResult.dependencyOptimisticFlatEntityMaps.flatViewMaps;
|
||||
optimisticAllFlatEntityMaps.flatFieldMetadataMaps =
|
||||
viewFieldResult.dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps;
|
||||
|
||||
if (viewFieldResult.status === 'fail') {
|
||||
orchestratorFailureReport.viewField.push(...viewFieldResult.errors);
|
||||
|
||||
+4
-2
@@ -1,3 +1,5 @@
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
import { type ViewEntity } from 'src/engine/core-modules/view/entities/view.entity';
|
||||
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
|
||||
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
|
||||
@@ -5,7 +7,6 @@ import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-meta
|
||||
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { type FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
|
||||
import { type ObjectPermissionEntity } from 'src/engine/metadata-modules/object-permission/object-permission.entity';
|
||||
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
|
||||
|
||||
export type MetadataEntitiesRelationTarget =
|
||||
| ObjectMetadataEntity
|
||||
@@ -15,4 +16,5 @@ export type MetadataEntitiesRelationTarget =
|
||||
| DataSourceEntity
|
||||
| ApplicationEntity
|
||||
| IndexMetadataEntity
|
||||
| ObjectPermissionEntity;
|
||||
| ObjectPermissionEntity
|
||||
| ViewEntity;
|
||||
|
||||
-3
@@ -19,9 +19,6 @@ export type ObjectMetadataRelatedFlatEntityMaps = Pick<
|
||||
'flatFieldMetadataMaps'
|
||||
>;
|
||||
|
||||
// TODO find a solution in order to handle label indentifier validation
|
||||
// export type ObjectMetadataRelatedFlatEntityMaps = undefined;
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationV2ObjectActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
|
||||
'objectMetadata',
|
||||
+1
-1
@@ -2,5 +2,5 @@ import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all
|
||||
|
||||
export type ViewFieldRelatedFlatEntityMaps = Pick<
|
||||
AllFlatEntityMaps,
|
||||
'flatViewMaps' | 'flatFieldMetadataMaps'
|
||||
'flatViewMaps' | 'flatFieldMetadataMaps' | 'flatObjectMetadataMaps'
|
||||
>;
|
||||
|
||||
+43
-5
@@ -48,7 +48,7 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
|
||||
>
|
||||
> {
|
||||
const validationResult =
|
||||
await this.flatViewFieldValidatorService.validateFlatViewFieldCreation({
|
||||
this.flatViewFieldValidatorService.validateFlatViewFieldCreation({
|
||||
dependencyOptimisticFlatEntityMaps,
|
||||
flatViewFieldToValidate,
|
||||
optimisticFlatViewFieldMaps,
|
||||
@@ -61,12 +61,30 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
|
||||
};
|
||||
}
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewFieldToValidate.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedFlatFieldMetadataMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatFieldMetadata,
|
||||
viewFieldIds: [
|
||||
...flatFieldMetadata.viewFieldIds,
|
||||
flatViewFieldToValidate.id,
|
||||
],
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const flatView = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewFieldToValidate.viewId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
|
||||
});
|
||||
|
||||
const updatedFlatViewFields = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
const updatedFlatViewMaps = replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatView,
|
||||
viewFieldIds: [...flatView.viewFieldIds, flatViewFieldToValidate.id],
|
||||
@@ -82,7 +100,8 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
...dependencyOptimisticFlatEntityMaps,
|
||||
flatViewMaps: updatedFlatViewFields,
|
||||
flatViewMaps: updatedFlatViewMaps,
|
||||
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
|
||||
},
|
||||
};
|
||||
}
|
||||
@@ -115,12 +134,30 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
|
||||
};
|
||||
}
|
||||
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatViewFieldToValidate.fieldMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedFlatFieldMetadataMaps = isDefined(flatFieldMetadata)
|
||||
? replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatFieldMetadata,
|
||||
viewFieldIds: flatFieldMetadata.viewFieldIds.filter(
|
||||
(id) => id !== flatViewFieldToValidate.id,
|
||||
),
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps,
|
||||
})
|
||||
: dependencyOptimisticFlatEntityMaps.flatFieldMetadataMaps;
|
||||
|
||||
const flatView = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatViewFieldToValidate.viewId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatViewMaps,
|
||||
});
|
||||
|
||||
const updatedFlatViewFields = isDefined(flatView)
|
||||
const updatedFlatViewMaps = isDefined(flatView)
|
||||
? replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatView,
|
||||
@@ -140,7 +177,8 @@ export class WorkspaceMigrationV2ViewFieldActionsBuilderService extends Workspac
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
...dependencyOptimisticFlatEntityMaps,
|
||||
flatViewMaps: updatedFlatViewFields,
|
||||
flatViewMaps: updatedFlatViewMaps,
|
||||
flatFieldMetadataMaps: updatedFlatFieldMetadataMaps,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
+42
-2
@@ -1,6 +1,11 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { 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 { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
|
||||
import { compareTwoFlatView } from 'src/engine/core-modules/view/flat-view/utils/compare-two-flat-view.util';
|
||||
import {
|
||||
@@ -56,6 +61,19 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
|
||||
...validationResult,
|
||||
};
|
||||
}
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityId: flatViewToValidate.objectMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
|
||||
});
|
||||
const updatedFlatObjectMetadataMaps =
|
||||
replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatObjectMetadata,
|
||||
viewIds: [...flatObjectMetadata.viewIds, flatViewToValidate.id],
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
@@ -63,7 +81,9 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
|
||||
type: 'create_view',
|
||||
view: flatViewToValidate,
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -92,13 +112,33 @@ export class WorkspaceMigrationV2ViewActionsBuilderService extends WorkspaceEnti
|
||||
};
|
||||
}
|
||||
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatViewToValidate.objectMetadataId,
|
||||
flatEntityMaps: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
const updatedFlatObjectMetadataMaps = isDefined(flatObjectMetadata)
|
||||
? replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: {
|
||||
...flatObjectMetadata,
|
||||
viewIds: flatObjectMetadata.viewIds.filter(
|
||||
(id) => id !== flatViewToValidate.id,
|
||||
),
|
||||
},
|
||||
flatEntityMaps:
|
||||
dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps,
|
||||
})
|
||||
: dependencyOptimisticFlatEntityMaps.flatObjectMetadataMaps;
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
action: {
|
||||
type: 'delete_view',
|
||||
viewId: flatViewToValidate.id,
|
||||
},
|
||||
dependencyOptimisticFlatEntityMaps,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatObjectMetadataMaps: updatedFlatObjectMetadataMaps,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+7
-5
@@ -15,14 +15,16 @@ export type CreateViewFieldAction = {
|
||||
viewField: FlatViewField;
|
||||
};
|
||||
|
||||
export type FlatViewFieldPropertiesUpdates = Array<
|
||||
{
|
||||
[P in FlatViewFieldPropertiesToCompare]: FlatViewFieldPropertyUpdate<P>;
|
||||
}[FlatViewFieldPropertiesToCompare]
|
||||
>;
|
||||
|
||||
export type UpdateViewFieldAction = {
|
||||
type: 'update_view_field';
|
||||
viewFieldId: string;
|
||||
updates: Array<
|
||||
{
|
||||
[P in FlatViewFieldPropertiesToCompare]: FlatViewFieldPropertyUpdate<P>;
|
||||
}[FlatViewFieldPropertiesToCompare]
|
||||
>;
|
||||
updates: FlatViewFieldPropertiesUpdates;
|
||||
};
|
||||
|
||||
export type DeleteViewFieldAction = {
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import { validateFlatObjectMetadataIdentifiers } from 'src/engine/metadata-modul
|
||||
import { validateFlatObjectMetadataNameAndLabels } from 'src/engine/metadata-modules/flat-object-metadata/validators/utils/validate-flat-object-metadata-name-and-labels.util';
|
||||
import { ObjectMetadataExceptionCode } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { isStandardMetadata } from 'src/engine/metadata-modules/utils/is-standard-metadata.util';
|
||||
import { ObjectMetadataRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
|
||||
import { ObjectMetadataRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service';
|
||||
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
import { WorkspaceMigrationBuilderOptions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-builder-options.type';
|
||||
import { FlatObjectPropertiesUpdates } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-object-action-v2';
|
||||
|
||||
+164
-55
@@ -4,11 +4,14 @@ import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
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 { ViewExceptionCode } from 'src/engine/core-modules/view/exceptions/view.exception';
|
||||
import { FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
|
||||
import { FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
import { isViewFieldInLowestPosition } from 'src/engine/core-modules/view/flat-view/utils/is-view-field-in-lowest-position.util';
|
||||
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
import { ViewFieldRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/types/view-field-related-flat-entity-maps.type';
|
||||
import { validateLabelIdentifierFieldMetadataIdFlatViewField } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/utils/validate-label-identifier-field-metadata-id-flat-view-field.util';
|
||||
|
||||
type ViewFieldValidationArgs = {
|
||||
flatViewFieldToValidate: FlatViewField;
|
||||
@@ -19,53 +22,119 @@ type ViewFieldValidationArgs = {
|
||||
export class FlatViewFieldValidatorService {
|
||||
constructor() {}
|
||||
|
||||
// Should implement strict checking on field compared field and their integrity passing the update array would be the best
|
||||
public validateFlatViewFieldUpdate({
|
||||
flatViewFieldToValidate: updatedFlatViewField,
|
||||
optimisticFlatViewFieldMaps,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatViewMaps,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
}: ViewFieldValidationArgs): FailedFlatEntityValidation<FlatViewField> {
|
||||
const errors = [];
|
||||
const validationResult: FailedFlatEntityValidation<FlatViewField> = {
|
||||
type: 'update_view_field',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: updatedFlatViewField.id,
|
||||
},
|
||||
};
|
||||
|
||||
const existingFlatViewField =
|
||||
optimisticFlatViewFieldMaps.byId[updatedFlatViewField.id];
|
||||
|
||||
if (!isDefined(existingFlatViewField)) {
|
||||
errors.push({
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field to update not found`,
|
||||
userFriendlyMessage: t`View field to update not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'update_view_field',
|
||||
errors,
|
||||
flatEntityMinimalInformation: {
|
||||
id: updatedFlatViewField.id,
|
||||
viewId: updatedFlatViewField.viewId,
|
||||
fieldMetadataId: updatedFlatViewField.fieldMetadataId,
|
||||
},
|
||||
validationResult.flatEntityMinimalInformation = {
|
||||
id: updatedFlatViewField.id,
|
||||
viewId: updatedFlatViewField.viewId,
|
||||
fieldMetadataId: updatedFlatViewField.fieldMetadataId,
|
||||
};
|
||||
|
||||
const flatView = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: updatedFlatViewField.viewId,
|
||||
flatEntityMaps: flatViewMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatView)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field to update parent view not found`,
|
||||
userFriendlyMessage: t`View field to update parent view not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatView.objectMetadataId,
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field to update parent view object not found`,
|
||||
userFriendlyMessage: t`View field to update parent view object not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
if (
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataId ===
|
||||
updatedFlatViewField.fieldMetadataId
|
||||
) {
|
||||
const otherFlatViewFields = findManyFlatEntityByIdInFlatEntityMapsOrThrow(
|
||||
{
|
||||
flatEntityIds: flatView.viewFieldIds.filter(
|
||||
(flatViewId) => flatViewId !== updatedFlatViewField.id,
|
||||
),
|
||||
flatEntityMaps: optimisticFlatViewFieldMaps,
|
||||
},
|
||||
);
|
||||
|
||||
validationResult.errors.push(
|
||||
...validateLabelIdentifierFieldMetadataIdFlatViewField({
|
||||
otherFlatViewFields,
|
||||
flatViewFieldToValidate: updatedFlatViewField,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public validateFlatViewFieldDeletion({
|
||||
optimisticFlatViewFieldMaps,
|
||||
flatViewFieldToValidate: { id: viewFieldIdToDelete },
|
||||
}: ViewFieldValidationArgs): FailedFlatEntityValidation<FlatViewField> {
|
||||
const errors = [];
|
||||
const validationResult: FailedFlatEntityValidation<FlatViewField> = {
|
||||
type: 'delete_view_field',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: viewFieldIdToDelete,
|
||||
},
|
||||
};
|
||||
|
||||
const existingFlatViewField =
|
||||
optimisticFlatViewFieldMaps.byId[viewFieldIdToDelete];
|
||||
|
||||
if (!isDefined(existingFlatViewField)) {
|
||||
errors.push({
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field to delete not found`,
|
||||
userFriendlyMessage: t`View field to delete not found`,
|
||||
});
|
||||
} else {
|
||||
if (!isDefined(existingFlatViewField.deletedAt)) {
|
||||
errors.push({
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field to delete has not been soft deleted`,
|
||||
userFriendlyMessage: t`View field to delete has not been soft deleted`,
|
||||
@@ -73,28 +142,31 @@ export class FlatViewFieldValidatorService {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'delete_view_field',
|
||||
errors,
|
||||
flatEntityMinimalInformation: {
|
||||
id: viewFieldIdToDelete,
|
||||
},
|
||||
};
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
public async validateFlatViewFieldCreation({
|
||||
public validateFlatViewFieldCreation({
|
||||
flatViewFieldToValidate,
|
||||
optimisticFlatViewFieldMaps,
|
||||
dependencyOptimisticFlatEntityMaps: {
|
||||
flatViewMaps: optimisticFlatViewMaps,
|
||||
flatViewMaps,
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
}: {
|
||||
flatViewFieldToValidate: FlatViewField;
|
||||
optimisticFlatViewFieldMaps: FlatViewFieldMaps;
|
||||
dependencyOptimisticFlatEntityMaps: ViewFieldRelatedFlatEntityMaps;
|
||||
}): Promise<FailedFlatEntityValidation<FlatViewField>> {
|
||||
const errors = [];
|
||||
}): FailedFlatEntityValidation<FlatViewField> {
|
||||
const validationResult: FailedFlatEntityValidation<FlatViewField> = {
|
||||
type: 'create_view_field',
|
||||
errors: [],
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatViewFieldToValidate.id,
|
||||
viewId: flatViewFieldToValidate.viewId,
|
||||
fieldMetadataId: flatViewFieldToValidate.fieldMetadataId,
|
||||
},
|
||||
};
|
||||
|
||||
const existingFlatViewField =
|
||||
optimisticFlatViewFieldMaps.byId[flatViewFieldToValidate.id];
|
||||
@@ -102,63 +174,100 @@ export class FlatViewFieldValidatorService {
|
||||
if (isDefined(existingFlatViewField)) {
|
||||
const flatViewFieldId = flatViewFieldToValidate.id;
|
||||
|
||||
errors.push({
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field metadata with id ${flatViewFieldId} already exists`,
|
||||
userFriendlyMessage: t`View field metadata already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
const relatedFlatView =
|
||||
optimisticFlatViewMaps.byId[flatViewFieldToValidate.viewId];
|
||||
|
||||
if (!isDefined(relatedFlatView)) {
|
||||
errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View not found`,
|
||||
userFriendlyMessage: t`View not found`,
|
||||
});
|
||||
}
|
||||
|
||||
const relatedFlatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
const flatFieldMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityId: flatViewFieldToValidate.fieldMetadataId,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
if (!isDefined(relatedFlatFieldMetadata)) {
|
||||
errors.push({
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Field metadata not found`,
|
||||
userFriendlyMessage: t`Field metadata not found`,
|
||||
});
|
||||
}
|
||||
|
||||
const allFlatViewFields = Object.values(
|
||||
optimisticFlatViewFieldMaps.byId,
|
||||
).filter(isDefined);
|
||||
const matchingFlatView = allFlatViewFields.find(
|
||||
const flatView = flatViewMaps.byId[flatViewFieldToValidate.viewId];
|
||||
|
||||
if (!isDefined(flatView)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View not found`,
|
||||
userFriendlyMessage: t`View not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
const otherFlatViewFields = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityIds: flatView.viewFieldIds,
|
||||
flatEntityMaps: optimisticFlatViewFieldMaps,
|
||||
});
|
||||
const equivalentExistingFlatViewFieldExists = otherFlatViewFields.some(
|
||||
(flatViewField) =>
|
||||
flatViewField.viewId === flatViewFieldToValidate.viewId &&
|
||||
flatViewField.fieldMetadataId ===
|
||||
flatViewFieldToValidate.fieldMetadataId,
|
||||
);
|
||||
|
||||
if (isDefined(matchingFlatView)) {
|
||||
errors.push({
|
||||
if (equivalentExistingFlatViewFieldExists) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field with same fieldmetadataId and viewId already exists`,
|
||||
userFriendlyMessage: t`View field already exists`,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'create_view_field',
|
||||
errors,
|
||||
flatEntityMinimalInformation: {
|
||||
id: flatViewFieldToValidate.id,
|
||||
viewId: flatViewFieldToValidate.viewId,
|
||||
fieldMetadataId: flatViewFieldToValidate.fieldMetadataId,
|
||||
},
|
||||
};
|
||||
const flatObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
flatEntityId: flatView.objectMetadataId,
|
||||
});
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field related view object metadata not found`,
|
||||
userFriendlyMessage: t`View field related view object metadata not found`,
|
||||
});
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
|
||||
if (
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataId ===
|
||||
flatViewFieldToValidate.fieldMetadataId
|
||||
) {
|
||||
validationResult.errors.push(
|
||||
...validateLabelIdentifierFieldMetadataIdFlatViewField({
|
||||
flatViewFieldToValidate,
|
||||
otherFlatViewFields,
|
||||
}),
|
||||
);
|
||||
} else if (
|
||||
otherFlatViewFields.some(
|
||||
(flatViewField) =>
|
||||
flatViewField.fieldMetadataId ===
|
||||
flatObjectMetadata.labelIdentifierFieldMetadataId,
|
||||
) &&
|
||||
isViewFieldInLowestPosition({
|
||||
flatViewField: flatViewFieldToValidate,
|
||||
otherFlatViewFields,
|
||||
})
|
||||
) {
|
||||
validationResult.errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`View field position cannot be lower than label identifier view field position`,
|
||||
userFriendlyMessage: t`View field position cannot be lower than label identifier view field position`,
|
||||
});
|
||||
}
|
||||
|
||||
return validationResult;
|
||||
}
|
||||
}
|
||||
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { ViewExceptionCode } from 'src/engine/core-modules/view/exceptions/view.exception';
|
||||
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
|
||||
import { isViewFieldInLowestPosition } from 'src/engine/core-modules/view/flat-view/utils/is-view-field-in-lowest-position.util';
|
||||
import { type FlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
|
||||
|
||||
type ValidateLabelIdentifierFieldMetadataIdFlatViewFieldArgs = {
|
||||
otherFlatViewFields: FlatViewField[];
|
||||
flatViewFieldToValidate: FlatViewField;
|
||||
};
|
||||
export const validateLabelIdentifierFieldMetadataIdFlatViewField = ({
|
||||
otherFlatViewFields,
|
||||
flatViewFieldToValidate,
|
||||
}: ValidateLabelIdentifierFieldMetadataIdFlatViewFieldArgs): FlatEntityValidationError[] => {
|
||||
const errors: FlatEntityValidationError[] = [];
|
||||
|
||||
if (
|
||||
otherFlatViewFields.length > 0 &&
|
||||
!isViewFieldInLowestPosition({
|
||||
flatViewField: flatViewFieldToValidate,
|
||||
otherFlatViewFields,
|
||||
})
|
||||
) {
|
||||
errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Label identifier view field has to be in the lowest position`,
|
||||
userFriendlyMessage: t`Label identifier view field has to be in the lowest position`,
|
||||
});
|
||||
}
|
||||
|
||||
if (flatViewFieldToValidate.isVisible === false) {
|
||||
errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Label identifier view field has to be visible`,
|
||||
userFriendlyMessage: t`Label identifier view field has to be visible`,
|
||||
});
|
||||
}
|
||||
|
||||
if (isDefined(flatViewFieldToValidate.deletedAt)) {
|
||||
errors.push({
|
||||
code: ViewExceptionCode.INVALID_VIEW_DATA,
|
||||
message: t`Label identifier view field cannot be deleted`,
|
||||
userFriendlyMessage: t`Label identifier view field cannot be deleted`,
|
||||
});
|
||||
}
|
||||
|
||||
return errors;
|
||||
};
|
||||
+2
-2
@@ -4,9 +4,9 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { WorkspaceMigrationV2CronTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/cron-trigger/workspace-migration-v2-cron-trigger-action-builder.service';
|
||||
import { WorkspaceMigrationV2DatabaseEventTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/database-event-trigger/workspace-migration-v2-database-event-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/services/workspace-migration-v2-field-actions-builder.service';
|
||||
import { WorkspaceMigrationV2FieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/field/workspace-migration-v2-field-actions-builder.service';
|
||||
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/services/workspace-migration-v2-object-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ObjectActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/object/workspace-migration-v2-object-actions-builder.service';
|
||||
import { WorkspaceMigrationV2RouteTriggerActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/route-trigger/workspace-migration-v2-route-trigger-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ServerlessFunctionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/serverless-function/workspace-migration-v2-serverless-function-actions-builder.service';
|
||||
import { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
|
||||
|
||||
+6
-5
@@ -8,7 +8,7 @@ import {
|
||||
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
|
||||
import { deleteFlatEntityFromFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/delete-flat-entity-from-flat-entity-maps-or-throw.util';
|
||||
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-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 { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -90,10 +90,11 @@ export class DeleteObjectActionHandlerService extends WorkspaceMigrationRunnerAc
|
||||
schemaName,
|
||||
tableName,
|
||||
});
|
||||
const { objectFlatFieldMetadatas } = findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: flatObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
const enumOrCompositeFlatFieldMetadatas = objectFlatFieldMetadatas.filter(
|
||||
(field) =>
|
||||
isEnumFlatFieldMetadata(field) || isCompositeFlatFieldMetadata(field),
|
||||
|
||||
+5
-5
@@ -7,8 +7,8 @@ import {
|
||||
|
||||
import { 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 { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { findObjectFlatFieldMetadatasOrThrow } from 'src/engine/metadata-modules/flat-field-metadata/utils/find-object-fields-in-flat-field-metadata-maps-or-throw.util';
|
||||
import { isCompositeFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-composite-flat-field-metadata.util';
|
||||
import { isEnumFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-flat-field-metadata.util';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
@@ -121,10 +121,10 @@ export class UpdateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
|
||||
newTableName,
|
||||
});
|
||||
|
||||
const { objectFlatFieldMetadatas } =
|
||||
findObjectFlatFieldMetadatasOrThrow({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadata: updatedObjectMetadata,
|
||||
const objectFlatFieldMetadatas =
|
||||
findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
flatEntityIds: updatedObjectMetadata.fieldMetadataIds,
|
||||
});
|
||||
const enumOrCompositeFlatFieldMetadatas =
|
||||
objectFlatFieldMetadatas.filter(
|
||||
|
||||
+8
-2
@@ -85,7 +85,6 @@ export class WorkspaceMigrationRunnerV2Service {
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
this.logger.timeEnd('Runner', 'Transaction execution');
|
||||
this.logger.time('Runner', 'Cache invalidation');
|
||||
|
||||
const flatEntitiesCacheToInvalidate = [
|
||||
...new Set([
|
||||
@@ -94,6 +93,10 @@ export class WorkspaceMigrationRunnerV2Service {
|
||||
]),
|
||||
];
|
||||
|
||||
this.logger.time(
|
||||
'Runner',
|
||||
`Cache invalidation ${flatEntitiesCacheToInvalidate.join()}`,
|
||||
);
|
||||
if (
|
||||
flatEntitiesCacheToInvalidate.includes('flatObjectMetadataMaps') ||
|
||||
flatEntitiesCacheToInvalidate.includes('flatFieldMetadataMaps')
|
||||
@@ -119,7 +122,10 @@ export class WorkspaceMigrationRunnerV2Service {
|
||||
],
|
||||
});
|
||||
|
||||
this.logger.timeEnd('Runner', 'Cache invalidation');
|
||||
this.logger.timeEnd(
|
||||
'Runner',
|
||||
`Cache invalidation ${flatEntitiesCacheToInvalidate.join()}`,
|
||||
);
|
||||
this.logger.timeEnd('Runner', 'Total execution');
|
||||
|
||||
return allFlatEntityMaps;
|
||||
|
||||
+2
@@ -137,6 +137,7 @@ export class StandardFieldFactory {
|
||||
|
||||
return [
|
||||
{
|
||||
viewFields: [],
|
||||
type: workspaceFieldMetadataArgs.type,
|
||||
standardId: workspaceFieldMetadataArgs.standardId,
|
||||
name: workspaceFieldMetadataArgs.name,
|
||||
@@ -186,6 +187,7 @@ export class StandardFieldFactory {
|
||||
}
|
||||
|
||||
fieldMetadataCollection.push({
|
||||
viewFields: [],
|
||||
type: FieldMetadataType.RELATION,
|
||||
standardId: workspaceRelationMetadataArgs.standardId,
|
||||
name: workspaceRelationMetadataArgs.name,
|
||||
|
||||
+1
@@ -44,6 +44,7 @@ export const computeStandardFields = (
|
||||
fields.push({
|
||||
...data,
|
||||
...rest,
|
||||
viewFields: [],
|
||||
standardId: relationStandardId,
|
||||
defaultValue: null,
|
||||
isNullable: true,
|
||||
|
||||
Reference in New Issue
Block a user