Index v2 runner (#14537)

## Introduction
https://github.com/twentyhq/twenty/pull/14363
In this PR we're creating builder and runner for index metadata
Removing its previous integration within object builder

## Next:
- implem index impact on field and object metadata api
- Add integration testing coverage add integration test on index
- Implement uniqueness toggle in field metadata api
- add integration test on uniqueness

related to https://github.com/twentyhq/core-team-issues/issues/1344
This commit is contained in:
Paul Rastoin
2025-09-17 13:02:43 +00:00
committed by GitHub
parent 6e47c1b5c6
commit 2d8669ae71
55 changed files with 1692 additions and 1064 deletions
@@ -0,0 +1,25 @@
import { type MigrationInterface, type QueryRunner } from 'typeorm';
export class AddUniversalIdentifierToIndexMetadata1758038863448
implements MigrationInterface
{
name = 'AddUniversalIdentifierToIndexMetadata1758038863448';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" ADD "universalIdentifier" uuid`,
);
await queryRunner.query(
`CREATE UNIQUE INDEX "IDX_b27c681286ac581f81498c5d4b" ON "core"."indexMetadata" ("workspaceId", "universalIdentifier") `,
);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`DROP INDEX "core"."IDX_b27c681286ac581f81498c5d4b"`,
);
await queryRunner.query(
`ALTER TABLE "core"."indexMetadata" DROP COLUMN "universalIdentifier"`,
);
}
}
@@ -4,4 +4,5 @@ export const ALL_FLAT_ENTITY_MAPS_PROPERTIES = [
'flatObjectMetadataMaps',
'flatViewFieldMaps',
'flatViewMaps',
'flatIndexMaps',
] as const satisfies (keyof AllFlatEntityMaps)[];
@@ -6,6 +6,7 @@ export const EMPTY_ALL_FLAT_ENTITY_MAPS = {
...EMPTY_FLAT_ENTITY_MAPS,
idByNameSingular: {},
},
flatIndexMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewFieldMaps: EMPTY_FLAT_ENTITY_MAPS,
flatViewMaps: EMPTY_FLAT_ENTITY_MAPS,
} as const satisfies AllFlatEntityMaps;
@@ -1,5 +1,6 @@
import { type FlatViewField } from 'src/engine/core-modules/view/flat-view/types/flat-view-field.type';
import { type FlatView } from 'src/engine/core-modules/view/flat-view/types/flat-view.type';
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';
export type AllFlatEntitiesByMetadataEngineName = {
@@ -7,4 +8,5 @@ export type AllFlatEntitiesByMetadataEngineName = {
objectMetadata: FlatObjectMetadata;
view: FlatView;
viewField: FlatViewField;
index: FlatIndexMetadata;
};
@@ -1,13 +1,14 @@
import { type FlatViewFieldMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-field-maps.type';
import { type FlatViewMaps } from 'src/engine/core-modules/view/flat-view/types/flat-view-maps.type';
import { type AllFlatEntitiesByMetadataEngineName } from 'src/engine/core-modules/common/types/all-flat-entities-by-metadata-engine-name.type';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
export type AllFlatEntityMaps = {
export type AllFlatEntityMaps = Omit<
{
[P in keyof AllFlatEntitiesByMetadataEngineName as `flat${Capitalize<P>}Maps`]: FlatEntityMaps<
AllFlatEntitiesByMetadataEngineName[P]
>;
},
'flatObjectMetadataMaps'
> & {
flatObjectMetadataMaps: FlatObjectMetadataMaps;
flatViewMaps: FlatViewMaps;
flatViewFieldMaps: FlatViewFieldMaps;
};
// export type AllFlatEntityMaps = {
// [P in keyof AllFlatEntities as `${P}Maps`]: FlatEntityMaps<AllFlatEntities[P]>;
// };
@@ -24,6 +24,7 @@ export const throwOnFieldInputTranspilationsError: ThrowOnFieldInputTranspilatio
throw new WorkspaceMigrationBuilderExceptionV2(
{
report: {
index: [],
view: [],
viewField: [],
objectMetadata: [
@@ -1,24 +0,0 @@
import { faker } from '@faker-js/faker';
import { type FlatIndexFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-field-metadata';
type FlatIndexFieldMetadataOverrides = Required<
Pick<
FlatIndexFieldMetadata,
'fieldMetadataId' | 'indexMetadataId' | 'universalIdentifier'
>
> &
Partial<FlatIndexFieldMetadata>;
export const getFlatIndexFieldMetadataMock = (
overrides: FlatIndexFieldMetadataOverrides,
): FlatIndexFieldMetadata => {
const createdAt = '2024-01-01T00:00:00.000Z' as unknown as Date;
return {
createdAt,
id: faker.string.uuid(),
order: faker.number.int(),
updatedAt: createdAt,
...overrides,
};
};
@@ -17,7 +17,7 @@ export const getFlatIndexMetadataMock = (
createdAt,
id: faker.string.uuid(),
indexType: IndexType.BTREE,
indexWhereClause: undefined,
indexWhereClause: null,
isCustom: false,
isUnique: false,
name: 'defaultFlatIndexMetadataName',
@@ -0,0 +1,9 @@
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
export const FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE = [
'indexType',
'indexWhereClause',
'flatIndexFieldMetadatas',
'isUnique',
'name',
] as const satisfies (keyof FlatIndexMetadata)[];
@@ -0,0 +1,6 @@
import { type IndexMetadataRelationProperties } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
export const INDEX_METADATA_ENTITY_RELATION_PROPERTIES = [
'objectMetadata',
'indexFieldMetadatas',
] as const satisfies readonly IndexMetadataRelationProperties[];
@@ -0,0 +1,10 @@
export enum IndexExceptionCode {
INDEX_EMPTY_FIELDS = 'INDEX_EMPTY_FIELDS',
INDEX_FIELD_NOT_FOUND = 'INDEX_FIELD_NOT_FOUND',
INDEX_FIELD_WRONG_OBJECT = 'INDEX_FIELD_WRONG_OBJECT',
INDEX_FIELD_INVALID_REFERENCE = 'INDEX_FIELD_INVALID_REFERENCE',
INDEX_FIELD_ID_DUPLICATE = 'INDEX_FIELD_ID_DUPLICATE',
INDEX_ALREADY_EXISTS = 'INDEX_ALREADY_EXISTS',
INDEX_NOT_FOUND = 'INDEX_NOT_FOUND',
INDEX_OBJECT_NOT_FOUND = 'INDEX_OBJECT_NOT_FOUND',
}
@@ -0,0 +1,64 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
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 { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { fromIndexMetadataEntityToFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/from-index-metadata-entity-to-flat-index-metadata.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
@WorkspaceFlatMapCache('flatIndexMaps')
export class WorkspaceFlatIndexMapCacheService extends WorkspaceFlatMapCacheService<
FlatEntityMaps<FlatIndexMetadata>
> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
cacheStorageService: CacheStorageService,
@InjectRepository(IndexMetadataEntity)
private readonly indexMetadataRepository: Repository<IndexMetadataEntity>,
) {
super(cacheStorageService);
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatEntityMaps<FlatIndexMetadata>> {
const indexes = await this.indexMetadataRepository.find({
where: {
workspaceId,
},
withDeleted: true,
relations: ['indexFieldMetadatas'],
});
const flatIndexMaps = indexes.reduce<FlatEntityMaps<FlatIndexMetadata>>(
(flatEntityMaps, index) => {
const flatIndex = fromIndexMetadataEntityToFlatIndexMetadata(index);
return {
byId: {
...flatEntityMaps.byId,
[flatIndex.id]: flatIndex,
},
idByUniversalIdentifier: {
...flatEntityMaps.idByUniversalIdentifier,
[flatIndex.universalIdentifier]: flatIndex.id,
},
};
},
EMPTY_FLAT_ENTITY_MAPS,
);
return flatIndexMaps;
}
}
@@ -1,15 +0,0 @@
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type';
type IndexFieldMetadataEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
IndexFieldMetadataEntity,
MetadataEntitiesRelationTarget
>;
export type FlatIndexFieldMetadata = Partial<
Omit<IndexFieldMetadataEntity, IndexFieldMetadataEntityRelationProperties>
> & {
universalIdentifier: string;
};
@@ -0,0 +1,4 @@
import { type FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-index-metadata/constants/flat-index-metadata-properties-to-compare.constant';
export type FlatIndexMetadataPropertiesToCompare =
(typeof FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE)[number];
@@ -1,18 +1,24 @@
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type FlatIndexFieldMetadata } from 'src/engine/workspace-manager/workspace-migration-v2/types/flat-index-field-metadata';
import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type';
type IndexMetadataRelationProperties = ExtractRecordTypeOrmRelationProperties<
IndexMetadataEntity,
MetadataEntitiesRelationTarget
>;
export type IndexMetadataRelationProperties =
ExtractRecordTypeOrmRelationProperties<
IndexMetadataEntity,
MetadataEntitiesRelationTarget
>;
// TODO prastoin refactor FlatIndexMetadata to not be a Partial extension of IndexMetadataEntity
export type FlatIndexMetadata = Partial<
Omit<IndexMetadataEntity, IndexMetadataRelationProperties>
export type FlatIndexMetadata = Omit<
IndexMetadataEntity,
IndexMetadataRelationProperties
> & {
id: string;
flatIndexFieldMetadatas: FlatIndexFieldMetadata[];
universalIdentifier: string;
flatIndexFieldMetadatas: Omit<
IndexFieldMetadataEntity,
ExtractRecordTypeOrmRelationProperties<
IndexFieldMetadataEntity,
MetadataEntitiesRelationTarget
>
>[];
};
@@ -1,37 +1,32 @@
import diff from 'microdiff';
import { type FromTo } from 'twenty-shared/types';
import { parseJson } from 'twenty-shared/utils';
import { FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE } from 'src/engine/metadata-modules/flat-index-metadata/constants/flat-index-metadata-properties-to-compare.constant';
import { type FlatIndexMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata-properties-to-compare.type';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
const flatIndexMetadataPropertiesToCompare = [
'flatIndexFieldMetadatas', // Comparing this as whole ? should iterate on each keys ? => TBD should only map over cols as before ?
'indexType',
'indexWhereClause',
'isUnique',
'name',
] as const satisfies (keyof FlatIndexMetadata)[];
type FlatIndexMetadataPropertiesToCompare =
(typeof flatIndexMetadataPropertiesToCompare)[number];
// Should also handle indexFieldMetadata comparison ?
/**
* This comparator handles update on colliding universalIdentifier flatIndexMetadata
*/
export const compareTwoFlatIndexMetadata = ({
from,
to,
}: FromTo<FlatIndexMetadata>) => {
fromFlatIndexMetadata,
toFlatIndexMetadata,
}: FromTo<FlatIndexMetadata, 'flatIndexMetadata'>) => {
const transformOptions = {
propertiesToStringify: ['flatIndexFieldMetadatas'] as const,
shouldIgnoreProperty: (property: string) =>
!flatIndexMetadataPropertiesToCompare.includes(
!FLAT_INDEX_METADATA_PROPERTIES_TO_COMPARE.includes(
property as FlatIndexMetadataPropertiesToCompare,
),
};
const fromCompare = transformMetadataForComparison(from, transformOptions);
const toCompare = transformMetadataForComparison(to, transformOptions);
const fromCompare = transformMetadataForComparison(
fromFlatIndexMetadata,
transformOptions,
);
const toCompare = transformMetadataForComparison(
toFlatIndexMetadata,
transformOptions,
);
const flatIndexeDifferences = diff(fromCompare, toCompare);
@@ -40,17 +35,20 @@ export const compareTwoFlatIndexMetadata = ({
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatIndexMetadataPropertiesToCompare;
const property = path[0];
if (typeof property === 'number') {
return [];
if (property === 'flatIndexFieldMetadatas') {
return {
from: parseJson(oldValue),
to: parseJson(value),
property,
};
}
return {
from: oldValue,
property,
to: value,
property,
};
}
case 'CREATE':
@@ -0,0 +1,28 @@
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { INDEX_METADATA_ENTITY_RELATION_PROPERTIES } from 'src/engine/metadata-modules/flat-index-metadata/constants/index-metadata-entity-relation-properties.constant';
import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
export const fromIndexMetadataEntityToFlatIndexMetadata = (
indexMetadataEntity: IndexMetadataEntity,
): FlatIndexMetadata => {
const indexMetadataEntityWithoutRelations = removePropertiesFromRecord(
indexMetadataEntity,
[...INDEX_METADATA_ENTITY_RELATION_PROPERTIES],
);
return {
...indexMetadataEntityWithoutRelations,
universalIdentifier:
indexMetadataEntityWithoutRelations.universalIdentifier ??
indexMetadataEntityWithoutRelations.id,
flatIndexFieldMetadatas: indexMetadataEntity.indexFieldMetadatas.map(
(indexFieldMetadata) =>
removePropertiesFromRecord(indexFieldMetadata, [
'indexMetadata',
'fieldMetadata',
]),
),
};
};
@@ -14,7 +14,9 @@ import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
@Entity('indexFieldMetadata')
export class IndexFieldMetadataEntity {
export class IndexFieldMetadataEntity
implements Required<IndexFieldMetadataEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -12,6 +12,8 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { SyncableEntity } from 'src/engine/workspace-manager/workspace-sync/interfaces/syncable-entity.interface';
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -26,7 +28,10 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat
'objectMetadataId',
])
@Entity('indexMetadata')
export class IndexMetadataEntity {
export class IndexMetadataEntity
extends SyncableEntity
implements Required<IndexMetadataEntity>
{
@PrimaryGeneratedColumn('uuid')
id: string;
@@ -15,48 +15,34 @@ export class WorkspaceSchemaIndexManagerService {
tableName: string;
index: WorkspaceSchemaIndexDefinition;
}): Promise<void> {
try {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeIndexName = removeSqlDDLInjection(index.name);
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeIndexName = removeSqlDDLInjection(index.name);
const quotedColumns = index.columns.map(
(column) => `"${removeSqlDDLInjection(column)}"`,
);
const isUnique = index.isUnique ? 'UNIQUE' : '';
const indexType =
index.type && index.type !== 'BTREE' ? `USING ${index.type}` : '';
const whereClause = index.where ? `WHERE ${index.where}` : ''; // TODO: to sanitize
const includeClause = index.include?.length
? `INCLUDE (${index.include
.map((col) => `"${removeSqlDDLInjection(col)}"`)
.join(', ')})`
: '';
const quotedColumns = index.columns.map(
(column) => `"${removeSqlDDLInjection(column)}"`,
);
const isUnique = index.isUnique ? 'UNIQUE' : '';
const indexType =
index.type && index.type !== 'BTREE' ? `USING ${index.type}` : '';
const whereClause = index.where ? `WHERE ${index.where}` : ''; // TODO: to sanitize -> might search for a lib to sanitize sql queries
const sql = [
'CREATE',
isUnique && 'UNIQUE',
'INDEX IF NOT EXISTS',
`"${safeIndexName}"`,
'ON',
`"${safeSchemaName}"."${safeTableName}"`,
indexType,
`(${quotedColumns.join(', ')})`,
includeClause,
whereClause,
]
.filter(Boolean)
.join(' ')
.trim();
const sql = [
'CREATE',
isUnique && 'UNIQUE',
'INDEX IF NOT EXISTS',
`"${safeIndexName}"`,
'ON',
`"${safeSchemaName}"."${safeTableName}"`,
indexType,
`(${quotedColumns.join(', ')})`,
whereClause,
]
.filter(Boolean)
.join(' ')
.trim();
await queryRunner.query(sql);
} catch (error: unknown) {
// Ignore error if index already exists
if (error instanceof Error && 'code' in error && error.code === '42P07') {
return;
}
throw error;
}
await queryRunner.query(sql);
}
async dropIndex({
@@ -67,80 +53,10 @@ export class WorkspaceSchemaIndexManagerService {
queryRunner: QueryRunner;
schemaName: string;
indexName: string;
}): Promise<void> {
try {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeIndexName = removeSqlDDLInjection(indexName);
const sql = `DROP INDEX IF EXISTS "${safeSchemaName}"."${safeIndexName}"`;
await queryRunner.query(sql);
} catch (error: unknown) {
// Ignore error if index does not exist
if (error instanceof Error && 'code' in error && error.code === '42704') {
return;
}
throw error;
}
}
async renameIndex({
queryRunner,
schemaName,
oldIndexName,
newIndexName,
}: {
queryRunner: QueryRunner;
schemaName: string;
oldIndexName: string;
newIndexName: string;
}): Promise<void> {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeOldIndexName = removeSqlDDLInjection(oldIndexName);
const safeNewIndexName = removeSqlDDLInjection(newIndexName);
const sql = `ALTER INDEX "${safeSchemaName}"."${safeOldIndexName}" RENAME TO "${safeNewIndexName}"`;
await queryRunner.query(sql);
}
async createUniqueConstraint({
queryRunner,
schemaName,
tableName,
constraintName,
columnNames,
}: {
queryRunner: QueryRunner;
schemaName: string;
tableName: string;
constraintName: string;
columnNames: string[];
}): Promise<void> {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeConstraintName = removeSqlDDLInjection(constraintName);
const quotedColumns = columnNames
.map((col) => `"${removeSqlDDLInjection(col)}"`)
.join(', ');
const sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" ADD CONSTRAINT "${safeConstraintName}" UNIQUE (${quotedColumns})`;
await queryRunner.query(sql);
}
async dropUniqueConstraint({
queryRunner,
schemaName,
tableName,
constraintName,
}: {
queryRunner: QueryRunner;
schemaName: string;
tableName: string;
constraintName: string;
}): Promise<void> {
const safeSchemaName = removeSqlDDLInjection(schemaName);
const safeTableName = removeSqlDDLInjection(tableName);
const safeConstraintName = removeSqlDDLInjection(constraintName);
const sql = `ALTER TABLE "${safeSchemaName}"."${safeTableName}" DROP CONSTRAINT IF EXISTS "${safeConstraintName}"`;
const safeIndexName = removeSqlDDLInjection(indexName);
const sql = `DROP INDEX IF EXISTS "${safeSchemaName}"."${safeIndexName}"`;
await queryRunner.query(sql);
}
@@ -12,5 +12,4 @@ export type WorkspaceSchemaIndexDefinition = {
type?: WorkspaceSchemaIndexType;
isUnique?: boolean;
where?: string;
include?: string[];
};
@@ -23,7 +23,7 @@ export type ValidationErrorResponse = {
summary: {
totalErrors: number;
} & {
[P in keyof AllFlatEntitiesByMetadataEngineName as `invalid${Capitalize<P>}s`]: number;
[P in keyof AllFlatEntitiesByMetadataEngineName as `invalid${Capitalize<P>}`]: number;
};
errors: OrchestratorFailureReport;
};
@@ -6,12 +6,14 @@ export const fromWorkspaceMigrationBuilderExceptionToValidationResponseError = (
): ValidationErrorResponse => {
const emptyResponseError: ValidationErrorResponse = {
summary: {
invalidObjectMetadatas: 0,
invalidViews: 0,
invalidViewFields: 0,
invalidObjectMetadata: 0,
invalidView: 0,
invalidViewField: 0,
invalidIndex: 0,
totalErrors: 0,
},
errors: {
index: [],
objectMetadata: [],
view: [],
viewField: [],
@@ -13,14 +13,11 @@ export const workspaceMigrationBuilderExceptionV2Formatter = (
const { errors, summary } =
fromWorkspaceMigrationBuilderExceptionToValidationResponseError(error);
const invalidObjects = summary.invalidObjectMetadatas;
const invalidFields = summary.invalidViewFields;
throw new BaseGraphQLError(error.message, ErrorCode.BAD_USER_INPUT, {
code: 'METADATA_VALIDATION_ERROR',
errors,
summary,
message: `Validation failed for ${invalidObjects} object(s) and ${invalidFields} field(s)`,
userFriendlyMessage: t`Validation failed for ${invalidObjects} object(s) and ${invalidFields} field(s)`,
message: `Validation failed for 0 object(s) and 0 field(s)`,
userFriendlyMessage: t`Validation failed for 0 object(s) and 0 field(s)`,
});
};
@@ -10,6 +10,7 @@ import {
WorkspaceMigrationOrchestratorFailedResult,
WorkspaceMigrationOrchestratorSuccessfulResult,
} from 'src/engine/workspace-manager/workspace-migration-v2/types/workspace-migration-orchestrator.type';
import { WorkspaceMigrationV2IndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-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';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-migration-builder-v2.service';
@@ -23,11 +24,11 @@ export class WorkspaceMigrationBuildOrchestratorService {
constructor(
private readonly workspaceMigrationBuilderV2Service: WorkspaceMigrationBuilderV2Service,
private readonly workspaceMigrationV2IndexActionsBuilderService: WorkspaceMigrationV2IndexActionsBuilderService,
private readonly workspaceMigrationV2ViewActionsBuilderService: WorkspaceMigrationV2ViewActionsBuilderService,
private readonly workspaceMigrationV2ViewFieldActionsBuilderService: WorkspaceMigrationV2ViewFieldActionsBuilderService,
) {}
// This does not handle dependency maps correctly
private setupOptimisticCache({
fromToAllFlatEntityMaps,
dependencyAllFlatEntityMaps,
@@ -73,14 +74,19 @@ export class WorkspaceMigrationBuildOrchestratorService {
objectMetadata: [],
view: [],
viewField: [],
index: [],
};
const optimisticAllFlatEntityMaps = this.setupOptimisticCache({
fromToAllFlatEntityMaps,
dependencyAllFlatEntityMaps,
});
const { flatObjectMetadataMaps, flatViewFieldMaps, flatViewMaps } =
fromToAllFlatEntityMaps;
const {
flatObjectMetadataMaps,
flatViewFieldMaps,
flatViewMaps,
flatIndexMaps,
} = fromToAllFlatEntityMaps;
if (isDefined(flatObjectMetadataMaps)) {
const { from: fromFlatObjectMetadataMaps, to: toFlatObjectMetadataMaps } =
@@ -104,6 +110,31 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatIndexMaps)) {
const { from: fromFlatIndexMaps, to: toFlatIndexMaps } = flatIndexMaps;
const indexResult =
await this.workspaceMigrationV2IndexActionsBuilderService.validateAndBuild(
{
from: fromFlatIndexMaps,
to: toFlatIndexMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: {
flatObjectMetadataMaps:
optimisticAllFlatEntityMaps.flatObjectMetadataMaps,
},
},
);
optimisticAllFlatEntityMaps.flatIndexMaps =
indexResult.optimisticFlatEntityMaps;
if (indexResult.status === 'fail') {
orchestratorFailureReport.index.push(...indexResult.errors);
} else {
allActions.push(...indexResult.actions);
}
}
if (isDefined(flatViewMaps)) {
const { from: fromFlatViewMaps, to: toFlatViewMaps } = flatViewMaps;
const viewResult =
@@ -1,15 +0,0 @@
import { type IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-field-metadata.entity';
import { type ExtractRecordTypeOrmRelationProperties } from 'src/engine/workspace-manager/workspace-migration-v2/types/extract-record-typeorm-relation-properties.type';
import { type MetadataEntitiesRelationTarget } from 'src/engine/workspace-manager/workspace-migration-v2/types/metadata-entities-relation-targets.type';
type IndexFieldMetadataEntityRelationProperties =
ExtractRecordTypeOrmRelationProperties<
IndexFieldMetadataEntity,
MetadataEntitiesRelationTarget
>;
export type FlatIndexFieldMetadata = Partial<
Omit<IndexFieldMetadataEntity, IndexFieldMetadataEntityRelationProperties>
> & {
universalIdentifier: string;
};
@@ -0,0 +1,5 @@
import { type FromTo } from 'twenty-shared/types';
export type PropertyUpdate<T, P extends keyof T> = {
property: P;
} & FromTo<T[P]>;
@@ -342,65 +342,6 @@ exports[`Workspace migration builder field actions test suite It should build an
exports[`Workspace migration builder field actions test suite It should not infer any actions as from and to fields are identical 1`] = `[]`;
exports[`Workspace migration builder index actions test suite It should build a delete_index action 1`] = `
[
{
"flatIndexMetadataId": Any<String>,
"type": "delete_index",
},
]
`;
exports[`Workspace migration builder index actions test suite It should build an create_index action 1`] = `
[
{
"flatIndexMetadata": {
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [],
"id": Any<String>,
"indexType": "BTREE",
"indexWhereClause": undefined,
"isCustom": false,
"isUnique": false,
"name": "defaultFlatIndexMetadataName",
"objectMetadataId": Any<String>,
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"type": "create_index",
},
]
`;
exports[`Workspace migration builder index actions test suite It should build an delete_index and a create_index action ( the way we handle update ) 1`] = `
[
{
"flatIndexMetadataId": Any<String>,
"type": "delete_index",
},
{
"flatIndexMetadata": {
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [],
"id": Any<String>,
"indexType": "BTREE",
"indexWhereClause": "new index where clause",
"isCustom": false,
"isUnique": false,
"name": "new index name",
"objectMetadataId": Any<String>,
"universalIdentifier": Any<String>,
"updatedAt": Any<ClockDate>,
"workspaceId": Any<String>,
},
"type": "create_index",
},
]
`;
exports[`Workspace migration builder index actions test suite It should not infer any actions as from and to indexes are identical 1`] = `[]`;
exports[`Workspace migration builder object actions test suite It should build a create_object action with custom object 1`] = `
[
{
@@ -2981,7 +2922,7 @@ exports[`Workspace migration builder object actions test suite It should build a
"flatIndexFieldMetadatas": [],
"id": Any<String>,
"indexType": "BTREE",
"indexWhereClause": undefined,
"indexWhereClause": null,
"isCustom": false,
"isUnique": false,
"name": "defaultFlatIndexMetadataName",
@@ -1,113 +0,0 @@
import { getFlatIndexMetadataMock } from 'src/engine/metadata-modules/flat-index-metadata/__mocks__/get-flat-index-metadata.mock';
import { FLAT_OBJECT_METADATA_MAPS_MOCKS } from 'src/engine/metadata-modules/flat-object-metadata-maps/mocks/flat-object-metadata-maps.mock';
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { getFlatObjectMetadataMock } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/get-flat-object-metadata.mock';
import { NOTE_FLAT_OBJECT_MOCK } from 'src/engine/metadata-modules/flat-object-metadata/__mocks__/note-flat-object.mock';
import { type WorkspaceMigrationBuilderTestCase } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/types/workspace-migration-builder-test-case.type';
export const WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES: WorkspaceMigrationBuilderTestCase[] =
[
{
title: 'It should build an create_index action',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
universalIdentifier: 'field-metadata-unique-identifier-1',
}),
],
}),
}),
},
expectedActionsTypeCounter: {
createIndex: 1,
},
},
},
{
title:
'It should build an delete_index and a create_index action ( the way we handle update )',
context: {
input: () => {
const flatIndexMetadata = getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
universalIdentifier: 'field-metadata-unique-identifier-1',
});
const fromFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [flatIndexMetadata],
}),
});
const toFlatObjectMetadataMaps =
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
{
...flatIndexMetadata,
name: 'new index name',
isUnique: false,
indexWhereClause: 'new index where clause',
universalIdentifier: 'field-metadata-unique-identifier-1',
},
],
}),
});
return {
fromFlatObjectMetadataMaps,
toFlatObjectMetadataMaps,
};
},
expectedActionsTypeCounter: {
createIndex: 1,
deleteIndex: 1,
},
},
},
{
title: 'It should build a delete_index action',
context: {
input: {
fromFlatObjectMetadataMaps:
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
flatObjectMetadata: getFlatObjectMetadataMock({
...NOTE_FLAT_OBJECT_MOCK,
flatIndexMetadatas: [
getFlatIndexMetadataMock({
objectMetadataId: NOTE_FLAT_OBJECT_MOCK.id,
universalIdentifier: 'field-metadata-unique-identifier-1',
}),
],
}),
}),
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
expectedActionsTypeCounter: {
deleteIndex: 1,
},
},
},
{
title:
'It should not infer any actions as from and to indexes are identical',
context: {
input: {
fromFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
toFlatObjectMetadataMaps: FLAT_OBJECT_METADATA_MAPS_MOCKS,
},
},
},
];
@@ -7,7 +7,6 @@ import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modul
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
import { WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-field-test-case';
import { WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-index-test-case';
import { WORKSPACE_MIGRATION_OBJECT_BUILDER_TEST_CASES } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/__tests__/common/workspace-migration-builder-object-test-case';
import {
type CamelCasedWorkspaceMigrationActionsType,
@@ -31,10 +30,6 @@ const allWorkspaceBuilderTestCases: {
label: 'field',
testCases: WORKSPACE_MIGRATION_FIELD_BUILDER_TEST_CASES,
},
{
label: 'index',
testCases: WORKSPACE_MIGRATION_INDEX_BUILDER_TEST_CASES,
},
];
// TODO prastoin add coverage to infer deletion from missing entities
@@ -0,0 +1,160 @@
import { Injectable } from '@nestjs/common';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { compareTwoFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util';
import {
FlatEntityUpdateValidationArgs,
FlatEntityValidationArgs,
FlatEntityValidationReturnType,
WorkspaceEntityMigrationBuilderV2Service,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/services/workspace-entity-migration-builder-v2.service';
import { WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import {
getWorkspaceMigrationV2CreateIndexAction,
getWorkspaceMigrationV2DeleteIndexAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions';
import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service';
export type IndexRelatedFlatEntityMaps = Pick<
AllFlatEntityMaps,
'flatObjectMetadataMaps'
>;
@Injectable()
export class WorkspaceMigrationV2IndexActionsBuilderService extends WorkspaceEntityMigrationBuilderV2Service<
FlatIndexMetadata,
WorkspaceMigrationIndexActionV2,
IndexRelatedFlatEntityMaps
> {
constructor(
private readonly flatIndexValidatorService: FlatIndexValidatorService,
) {
super();
}
protected async validateFlatEntityCreation({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatIndexToValidate,
optimisticFlatEntityMaps: optimisticFlatIndexMaps,
}: FlatEntityValidationArgs<
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
>
> {
const validationResult =
this.flatIndexValidatorService.validateFlatIndexCreation({
dependencyOptimisticFlatEntityMaps,
flatIndexToValidate,
optimisticFlatIndexMaps,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: getWorkspaceMigrationV2CreateIndexAction(flatIndexToValidate),
};
}
protected async validateFlatEntityDeletion({
dependencyOptimisticFlatEntityMaps,
flatEntityToValidate: flatIndexToValidate,
optimisticFlatEntityMaps: optimisticFlatIndexMaps,
}: FlatEntityValidationArgs<
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>): Promise<
FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
>
> {
const validationResult =
this.flatIndexValidatorService.validateFlatIndexDeletion({
dependencyOptimisticFlatEntityMaps,
flatIndexToValidate,
optimisticFlatIndexMaps,
});
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
return {
status: 'success',
action: getWorkspaceMigrationV2DeleteIndexAction(flatIndexToValidate),
};
}
protected async validateFlatEntityUpdate({
dependencyOptimisticFlatEntityMaps,
flatEntityUpdate: { from: fromFlatIndex, to: toFlatIndex },
optimisticFlatEntityMaps: optimisticFlatIndexMaps,
}: FlatEntityUpdateValidationArgs<
FlatIndexMetadata,
IndexRelatedFlatEntityMaps
>): Promise<
| FlatEntityValidationReturnType<
WorkspaceMigrationIndexActionV2,
FlatIndexMetadata
>
| undefined
> {
const indexUpdatedProperties = compareTwoFlatIndexMetadata({
fromFlatIndexMetadata: fromFlatIndex,
toFlatIndexMetadata: toFlatIndex,
});
if (indexUpdatedProperties.length === 0) {
return undefined;
}
const deletionValidationResult =
this.flatIndexValidatorService.validateFlatIndexDeletion({
dependencyOptimisticFlatEntityMaps,
flatIndexToValidate: fromFlatIndex,
optimisticFlatIndexMaps,
});
if (deletionValidationResult.errors.length > 0) {
return {
status: 'fail',
...deletionValidationResult,
};
}
const creationValidationResult =
this.flatIndexValidatorService.validateFlatIndexCreation({
dependencyOptimisticFlatEntityMaps,
flatIndexToValidate: toFlatIndex,
optimisticFlatIndexMaps,
});
if (creationValidationResult.errors.length > 0) {
return {
status: 'fail',
...creationValidationResult,
};
}
return {
status: 'success',
action: [
getWorkspaceMigrationV2DeleteIndexAction(fromFlatIndex),
getWorkspaceMigrationV2CreateIndexAction(toFlatIndex),
],
};
}
}
@@ -1,57 +0,0 @@
import { compareTwoFlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/utils/compare-two-flat-index-metadata.util';
import { type WorkspaceMigrationIndexActionV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import { type UpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util';
import {
getWorkspaceMigrationV2CreateIndexAction,
getWorkspaceMigrationV2DeleteIndexAction,
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions';
type BuildWorkspaceMigrationIndexActionsArgs = {
objectMetadataDeletedCreatedUpdatedIndex: UpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix[];
inferDeletionFromMissingObjectFieldIndex: boolean;
};
export const buildWorkspaceMigrationIndexActions = ({
inferDeletionFromMissingObjectFieldIndex,
objectMetadataDeletedCreatedUpdatedIndex,
}: BuildWorkspaceMigrationIndexActionsArgs): WorkspaceMigrationIndexActionV2[] => {
let allUpdatedObjectMetadataIndexActions: WorkspaceMigrationIndexActionV2[] =
[];
for (const {
createdIndexMetadata,
deletedIndexMetadata,
updatedIndexMetadata,
} of objectMetadataDeletedCreatedUpdatedIndex) {
const updatedDeleteAndCreateIndexActions =
updatedIndexMetadata.flatMap<WorkspaceMigrationIndexActionV2>(
({ to, from }) => {
const updates = compareTwoFlatIndexMetadata({ from, to });
if (updates.length === 0) {
return [];
}
return [
getWorkspaceMigrationV2DeleteIndexAction(from),
getWorkspaceMigrationV2CreateIndexAction(to),
];
},
);
const createIndexActions = createdIndexMetadata.map(
getWorkspaceMigrationV2CreateIndexAction,
);
const deleteIndexActions = inferDeletionFromMissingObjectFieldIndex
? deletedIndexMetadata.map(getWorkspaceMigrationV2DeleteIndexAction)
: [];
allUpdatedObjectMetadataIndexActions =
allUpdatedObjectMetadataIndexActions.concat([
...createIndexActions,
...deleteIndexActions,
...updatedDeleteAndCreateIndexActions,
]);
}
return allUpdatedObjectMetadataIndexActions;
};
@@ -8,6 +8,7 @@ export type FlatEntityValidationError<TCode extends string = string> = {
value?: unknown;
};
// Should be improved to be scoped to a given set of actions
export type FailedFlatEntityValidation<T extends FlatEntity> = {
type: WorkspaceMigrationActionTypeV2;
errors: FlatEntityValidationError[];
@@ -1,6 +1,7 @@
import { type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type AllFlatEntities } from 'src/engine/core-modules/common/types/all-flat-entities.type';
import { type AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { type FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/core-modules/common/types/flat-entity.type';
@@ -86,14 +87,14 @@ export type FlatEntityValidationReturnType<
> =
| {
status: 'success';
action: TActions;
action: TActions | TActions[];
}
| ({
status: 'fail';
} & FailedFlatEntityValidation<TFlatEntity>);
export abstract class WorkspaceEntityMigrationBuilderV2Service<
TFlatEntity extends FlatEntity,
TFlatEntity extends AllFlatEntities,
TActions extends WorkspaceMigrationActionV2,
TRelatedFlatEntityMaps extends Partial<AllFlatEntityMaps>,
> {
@@ -147,7 +148,11 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
flatEntityMaps: optimisticFlatEntityMaps,
});
validateAndBuildResult.created.push(validationResult.action);
validateAndBuildResult.created.push(
...(Array.isArray(validationResult.action)
? validationResult.action
: [validationResult.action]),
);
}
for (const flatEntityToDelete of buildOptions.inferDeletionFromMissingEntities
@@ -169,7 +174,11 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
flatEntityMaps: optimisticFlatEntityMaps,
});
validateAndBuildResult.deleted.push(validationResult.action);
validateAndBuildResult.deleted.push(
...(Array.isArray(validationResult.action)
? validationResult.action
: [validationResult.action]),
);
}
for (const flatEntityUpdate of updated) {
@@ -193,7 +202,11 @@ export abstract class WorkspaceEntityMigrationBuilderV2Service<
flatEntityMaps: optimisticFlatEntityMaps,
});
validateAndBuildResult.updated.push(validationResult.action);
validateAndBuildResult.updated.push(
...(Array.isArray(validationResult.action)
? validationResult.action
: [validationResult.action]),
);
}
if (validateAndBuildResult.failed.length > 0) {
@@ -7,12 +7,10 @@ import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-ob
import { fromFlatObjectMetadataMapsToFlatObjectMetadatas } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-maps-to-flat-object-metadatas.util';
import { deletedCreatedUpdatedMatrixDispatcher } from 'src/engine/workspace-manager/workspace-migration-v2/utils/deleted-created-updated-matrix-dispatcher.util';
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 { buildWorkspaceMigrationIndexActions } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder';
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 { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
import { WorkspaceMigrationV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-v2';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-field-matrix.util';
import { computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/compute-updated-object-metadata-deleted-created-updated-index-matrix.util';
import { getWorkspaceMigrationV2CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/get-workspace-migration-v2-index-actions';
export type WorkspaceMigrationV2BuilderOptions = {
@@ -70,12 +68,6 @@ export class WorkspaceMigrationBuilderV2Service {
to: toFlatObjectMetadatas,
});
// Should be handled separately from objects
const objectMetadataDeletedCreatedUpdatedIndex =
computeUpdatedObjectMetadataDeletedCreatedUpdatedIndexMatrix(
updatedFlatObjectMetadatas,
);
// Should be handled separately from objects
const objectMetadataDeletedCreatedUpdatedFields =
computeUpdatedObjectMetadataDeletedCreatedUpdatedFieldMatrix(
@@ -114,12 +106,6 @@ export class WorkspaceMigrationBuilderV2Service {
),
);
const indexWorkspaceMigrationActions = buildWorkspaceMigrationIndexActions({
objectMetadataDeletedCreatedUpdatedIndex,
inferDeletionFromMissingObjectFieldIndex:
buildOptions.inferDeletionFromMissingEntities,
});
const allValidateAndBuildResultFailures = [
...objectActionsValidateAndBuildResult.failed,
...fieldActionsValidateAndBuildResult.failed,
@@ -146,7 +132,6 @@ export class WorkspaceMigrationBuilderV2Service {
...fieldActionsValidateAndBuildResult.created, // Should be handled separately from objects
...fieldActionsValidateAndBuildResult.updated, // Should be handled separately from objects
...createdObjectMetadataCreateIndexActions,
...indexWorkspaceMigrationActions, // Should be handled separately from objects
],
},
optimisticFlatObjectMetadataMaps:
@@ -1,11 +0,0 @@
import { type FieldMetadataType, type FromTo } from 'twenty-shared/types';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
export type FlatFieldMetadataPropertyUpdate<
P extends FlatFieldMetadataPropertiesToCompare,
T extends FieldMetadataType = FieldMetadataType,
> = {
property: P;
} & FromTo<FieldMetadataEntity<T>[P]>;
@@ -10,7 +10,6 @@ export type WorkspaceMigrationActionV2 =
| WorkspaceMigrationIndexActionV2
| WorkspaceMigrationViewActionV2
| WorkspaceMigrationViewFieldActionV2;
export type WorkspaceMigrationActionTypeV2 = WorkspaceMigrationActionV2['type'];
export type ExtractAction<T extends WorkspaceMigrationActionTypeV2> = Extract<
@@ -1,6 +1,6 @@
import { type FlatFieldMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-properties-to-compare.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { type FlatFieldMetadataPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-field-metadata-property-update.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
export type CreateFieldAction = {
type: 'create_field';
@@ -14,7 +14,10 @@ export type UpdateFieldAction = {
objectMetadataId: string;
updates: Array<
{
[P in FlatFieldMetadataPropertiesToCompare]: FlatFieldMetadataPropertyUpdate<P>;
[P in FlatFieldMetadataPropertiesToCompare]: PropertyUpdate<
FlatFieldMetadata,
P
>;
}[FlatFieldMetadataPropertiesToCompare]
>;
};
@@ -1,8 +1,9 @@
import { type FromTo } from 'twenty-shared/types';
import { type FlatObjectMetadataPropertiesToCompare } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata-properties-to-compare.type';
import { type FlatObjectMetadataWithoutFields } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import {
type FlatObjectMetadata,
type FlatObjectMetadataWithoutFields,
} from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
import { type CreateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
export type CreateObjectAction = {
@@ -16,9 +17,10 @@ export type UpdateObjectAction = {
objectMetadataId: string;
updates: Array<
{
[P in FlatObjectMetadataPropertiesToCompare]: {
property: P;
} & FromTo<ObjectMetadataEntity[P]>;
[P in FlatObjectMetadataPropertiesToCompare]: PropertyUpdate<
FlatObjectMetadata,
P
>;
}[FlatObjectMetadataPropertiesToCompare]
>;
};
@@ -0,0 +1,168 @@
import { Injectable } from '@nestjs/common';
import { t } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { FlatEntityMapsExceptionCode } from 'src/engine/core-modules/common/exceptions/flat-entity-maps.exception';
import { FlatEntityMaps } from 'src/engine/core-modules/common/types/flat-entity-maps.type';
import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/core-modules/common/utils/find-flat-entity-by-id-in-flat-entity-maps.util';
import { IndexExceptionCode } from 'src/engine/metadata-modules/flat-index-metadata/exceptions/index-exception-code';
import { FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-metadata/types/flat-index-metadata.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import { IndexRelatedFlatEntityMaps } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/index/workspace-migration-v2-index-actions-builder.service';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/types/failed-flat-entity-validation.type';
type IndexValidationArgs = {
flatIndexToValidate: FlatIndexMetadata;
optimisticFlatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
dependencyOptimisticFlatEntityMaps: IndexRelatedFlatEntityMaps;
};
@Injectable()
export class FlatIndexValidatorService {
public validateFlatIndexDeletion({
optimisticFlatIndexMaps,
flatIndexToValidate: { id: indexIdToDelete },
}: IndexValidationArgs): FailedFlatEntityValidation<FlatIndexMetadata> {
const validationResult: FailedFlatEntityValidation<FlatIndexMetadata> = {
type: 'delete_index',
errors: [],
flatEntityMinimalInformation: {
id: indexIdToDelete,
},
};
const existingFlatIndexToDelete = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: indexIdToDelete,
flatEntityMaps: optimisticFlatIndexMaps,
});
if (!isDefined(existingFlatIndexToDelete)) {
validationResult.errors.push({
code: FlatEntityMapsExceptionCode.ENTITY_NOT_FOUND,
message: t`Index to delete not found`,
});
}
return validationResult;
}
public validateFlatIndexCreation({
flatIndexToValidate,
optimisticFlatIndexMaps,
dependencyOptimisticFlatEntityMaps: { flatObjectMetadataMaps },
}: {
flatIndexToValidate: FlatIndexMetadata;
optimisticFlatIndexMaps: FlatEntityMaps<FlatIndexMetadata>;
dependencyOptimisticFlatEntityMaps: IndexRelatedFlatEntityMaps;
}): FailedFlatEntityValidation<FlatIndexMetadata> {
const validationResult: FailedFlatEntityValidation<FlatIndexMetadata> = {
type: 'create_index',
errors: [],
flatEntityMinimalInformation: {
id: flatIndexToValidate.id,
name: flatIndexToValidate.name,
},
};
const existingFlatIndex = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatIndexToValidate.id,
flatEntityMaps: optimisticFlatIndexMaps,
});
if (isDefined(existingFlatIndex)) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_ALREADY_EXISTS,
message: t`Index with same id already exists`,
userFriendlyMessage: t`Index already exists`,
});
}
const relatedObjectMetadata = findFlatEntityByIdInFlatEntityMaps({
flatEntityId: flatIndexToValidate.objectMetadataId,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(relatedObjectMetadata)) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_OBJECT_NOT_FOUND,
message: t`Could not find index related object metadata`,
userFriendlyMessage: t`Index related object not found`,
});
}
const allExistingFlatIndex = Object.values(
optimisticFlatIndexMaps.byId,
).filter(isDefined);
const existingFlatIndexOnName = allExistingFlatIndex.some(
(flatIndexMetadata) =>
flatIndexMetadata.name.toLocaleUpperCase() ===
flatIndexToValidate.name.toLocaleUpperCase(),
);
if (isDefined(existingFlatIndexOnName)) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_EMPTY_FIELDS,
message: t`Index with same name already exists`,
userFriendlyMessage: t`Index with same name already exists`,
});
}
if (flatIndexToValidate.flatIndexFieldMetadatas.length === 0) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_EMPTY_FIELDS,
message: t`Index must have at least one field`,
userFriendlyMessage: t`An index must contain at least one field`,
});
} else {
flatIndexToValidate.flatIndexFieldMetadatas.forEach((flatIndexField) => {
const relatedFlatField =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: flatIndexField.fieldMetadataId,
flatObjectMetadataMaps,
});
if (!isDefined(relatedFlatField)) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_FIELD_NOT_FOUND,
message: t`Could not find index field related field metadata`,
userFriendlyMessage: t`Field referenced in index does not exist`,
});
} else {
if (
relatedFlatField.objectMetadataId !==
flatIndexToValidate.objectMetadataId
) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_FIELD_WRONG_OBJECT,
message: t`Field does not belong to the indexed object`,
userFriendlyMessage: t`Field cannot be indexed as it belongs to a different object`,
});
}
}
if (flatIndexField.indexMetadataId !== flatIndexToValidate.id) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_FIELD_INVALID_REFERENCE,
message: t`Index field references incorrect index metadata`,
userFriendlyMessage: t`Index field has an invalid reference`,
});
}
if (
allExistingFlatIndex.some(({ flatIndexFieldMetadatas }) =>
flatIndexFieldMetadatas.some(({ id }) => id === flatIndexField.id),
)
) {
validationResult.errors.push({
code: IndexExceptionCode.INDEX_FIELD_ID_DUPLICATE,
message: t`Index field ID already exists in another index`,
userFriendlyMessage: t`Field ID is already used in another index`,
});
}
});
}
return validationResult;
}
}
@@ -1,10 +1,19 @@
import { Module } from '@nestjs/common';
import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-index-metadata-validator.service';
import { FlatViewFieldValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-field-validator.service';
import { FlatViewValidatorService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/validators/services/flat-view-validator.service';
@Module({
providers: [FlatViewValidatorService, FlatViewFieldValidatorService],
exports: [FlatViewValidatorService, FlatViewFieldValidatorService],
providers: [
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatIndexValidatorService,
],
exports: [
FlatViewValidatorService,
FlatViewFieldValidatorService,
FlatIndexValidatorService,
],
})
export class WorkspaceMigrationBuilderValidatorsModule {}
@@ -5,6 +5,7 @@ import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modul
import { FlatFieldMetadataValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.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 { 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 { WorkspaceMigrationV2ViewFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view-field/workspace-migration-v2-view-field-actions-builder.service';
import { WorkspaceMigrationV2ViewActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/builders/view/workspace-migration-v2-view-actions-builder.service';
@@ -24,12 +25,14 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationV2FieldActionsBuilderService,
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
FlatViewValidatorService,
FlatViewFieldValidatorService,
],
exports: [
WorkspaceMigrationBuilderV2Service,
WorkspaceMigrationV2ViewActionsBuilderService,
WorkspaceMigrationV2IndexActionsBuilderService,
FlatViewValidatorService,
WorkspaceMigrationV2ViewFieldActionsBuilderService,
FlatViewFieldValidatorService,
@@ -57,7 +57,7 @@ export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
const { flatFieldMetadata } = action;
await fieldMetadataRepository.save(flatFieldMetadata);
await fieldMetadataRepository.insert(flatFieldMetadata);
}
async executeForWorkspaceSchema(
@@ -29,7 +29,7 @@ import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src
import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util';
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
import { isMorphOrRelationFieldMetadataType } from 'src/engine/utils/is-morph-or-relation-field-metadata-type.util';
import { FlatFieldMetadataPropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/flat-field-metadata-property-update.type';
import { PropertyUpdate } from 'src/engine/workspace-manager/workspace-migration-v2/types/property-update.type';
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { serializeDefaultValueV2 } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/utils/serialize-default-value-v2.util';
import {
@@ -54,7 +54,7 @@ type UpdateFieldPropertyUpdateHandlerArgs<
schemaName: string;
tableName: string;
flatFieldMetadata: FlatFieldMetadata<T>;
update: FlatFieldMetadataPropertyUpdate<P, T>;
update: PropertyUpdate<FlatFieldMetadata<T>, P>;
};
@Injectable()
@@ -199,9 +199,9 @@ export class UpdateFieldActionHandlerService extends WorkspaceMigrationRunnerAct
queryRunner,
schemaName,
tableName,
update: update as FlatFieldMetadataPropertyUpdate<
'settings',
MorphOrRelationFieldMetadataType
update: update as PropertyUpdate<
FlatFieldMetadata<MorphOrRelationFieldMetadataType>,
'settings'
>,
});
}
@@ -6,28 +6,90 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
import { AllFlatEntityMaps } from 'src/engine/core-modules/common/types/all-flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/core-modules/common/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { WorkspaceSchemaManagerService } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.service';
import { type CreateIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
@Injectable()
export class CreateIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create_index',
) {
optimisticallyApplyActionOnAllFlatEntityMaps(
_args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateIndexAction>,
): Partial<AllFlatEntityMaps> {
return {};
constructor(
private readonly workspaceSchemaManagerService: WorkspaceSchemaManagerService,
) {
super();
}
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps: { flatIndexMaps },
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<CreateIndexAction>): Partial<AllFlatEntityMaps> {
return {
flatIndexMaps: addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: action.flatIndexMetadata,
flatEntityMaps: flatIndexMaps,
}),
};
}
async executeForMetadata(
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
): Promise<void> {
throw new Error('Not implemented');
const { action, queryRunner } = context;
const indexMetadataRepository =
queryRunner.manager.getRepository<IndexMetadataEntity>(
IndexMetadataEntity,
);
const {
flatIndexMetadata: { flatIndexFieldMetadatas, ...rest },
} = action;
await indexMetadataRepository.insert({
indexFieldMetadatas: flatIndexFieldMetadatas,
...rest,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
context: WorkspaceMigrationActionRunnerArgs<CreateIndexAction>,
): Promise<void> {
throw new Error('Not implemented');
const {
allFlatEntityMaps: { flatObjectMetadataMaps },
action: { flatIndexMetadata },
queryRunner,
workspaceId,
} = context;
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatIndexMetadata.objectMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata,
});
const quotedColumns = flatIndexMetadata.flatIndexFieldMetadatas.map(
(column) => `"${column}"`,
);
await this.workspaceSchemaManagerService.indexManager.createIndex({
index: {
columns: quotedColumns,
name: flatIndexMetadata.name,
isUnique: flatIndexMetadata.isUnique,
type: flatIndexMetadata.indexType,
where: flatIndexMetadata.indexWhereClause ?? undefined,
},
queryRunner,
schemaName,
tableName,
});
}
}
@@ -6,28 +6,76 @@ import {
} from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/interfaces/workspace-migration-runner-action-handler-service.interface';
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 { findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { type DeleteIndexAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-index-action-v2';
import { type WorkspaceMigrationActionRunnerArgs } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/types/workspace-migration-action-runner-args.type';
import { getWorkspaceSchemaContextForMigration } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/utils/get-workspace-schema-context-for-migration.util';
@Injectable()
export class DeleteIndexActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete_index',
) {
optimisticallyApplyActionOnAllFlatEntityMaps(
_args: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteIndexAction>,
): Partial<AllFlatEntityMaps> {
return {};
optimisticallyApplyActionOnAllFlatEntityMaps({
action,
allFlatEntityMaps: { flatIndexMaps },
}: OptimisticallyApplyActionOnAllFlatEntityMapsArgs<DeleteIndexAction>): Partial<AllFlatEntityMaps> {
return {
flatIndexMaps: deleteFlatEntityFromFlatEntityMapsOrThrow({
entityToDeleteId: action.flatIndexMetadataId,
flatEntityMaps: flatIndexMaps,
}),
};
}
async executeForMetadata(
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
): Promise<void> {
throw new Error('Not implemented');
const { action, queryRunner } = context;
const indexMetadataRepository =
queryRunner.manager.getRepository<IndexMetadataEntity>(
IndexMetadataEntity,
);
const { flatIndexMetadataId } = action;
await indexMetadataRepository.delete({
id: flatIndexMetadataId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
context: WorkspaceMigrationActionRunnerArgs<DeleteIndexAction>,
): Promise<void> {
throw new Error('Not implemented');
const {
action,
allFlatEntityMaps: { flatObjectMetadataMaps, flatIndexMaps },
queryRunner,
workspaceId,
} = context;
const flatIndexMetadataToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityId: action.flatIndexMetadataId,
flatEntityMaps: flatIndexMaps,
},
);
const flatObjectMetadata =
findFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
flatObjectMetadataMaps,
objectMetadataId: flatIndexMetadataToDelete.objectMetadataId,
});
const { schemaName, tableName } = getWorkspaceSchemaContextForMigration({
workspaceId,
flatObjectMetadata,
});
await queryRunner.dropIndex(
`${schemaName}.${tableName}`,
flatIndexMetadataToDelete.name,
);
}
}
@@ -80,7 +80,7 @@ export class CreateObjectActionHandlerService extends WorkspaceMigrationRunnerAc
order: { createdAt: 'DESC' },
});
await objectMetadataRepository.save({
await objectMetadataRepository.insert({
...flatObjectMetadataWithoutFields,
dataSourceId: lastDataSourceMetadata.id,
targetTableName: 'DEPRECATED',
@@ -45,7 +45,7 @@ export class CreateViewFieldActionHandlerService extends WorkspaceMigrationRunne
const viewFieldRepository =
queryRunner.manager.getRepository<ViewFieldEntity>(ViewFieldEntity);
await viewFieldRepository.save({
await viewFieldRepository.insert({
...viewField,
workspaceId,
});
@@ -45,7 +45,7 @@ export class CreateViewActionHandlerService extends WorkspaceMigrationRunnerActi
const viewRepository =
queryRunner.manager.getRepository<ViewEntity>(ViewEntity);
await viewRepository.save({
await viewRepository.insert({
...view,
workspaceId,
});
@@ -2,7 +2,12 @@ import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-meta
export type PartialIndexMetadata = Omit<
IndexMetadataEntity,
'id' | 'objectMetadata' | 'indexFieldMetadatas' | 'createdAt' | 'updatedAt'
| 'id'
| 'objectMetadata'
| 'indexFieldMetadatas'
| 'createdAt'
| 'updatedAt'
| 'universalIdentifier'
> & {
columns: string[];
};
@@ -5,6 +5,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [],
"view": [],
"viewField": [
@@ -28,9 +29,10 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -45,6 +47,7 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [],
"view": [],
"viewField": [
@@ -68,9 +71,10 @@ exports[`View Field Resolver - Failing Create Operation - v2 should fail to crea
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -5,6 +5,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -44,9 +45,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -61,6 +63,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -84,9 +87,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 Morh re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -101,6 +105,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -119,9 +124,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -136,6 +142,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -154,9 +161,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -171,6 +179,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -200,9 +209,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -217,6 +227,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -240,9 +251,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -257,6 +269,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -280,9 +293,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -297,6 +311,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -326,9 +341,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -343,6 +359,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -369,9 +386,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -386,6 +404,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -412,9 +431,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -429,6 +449,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -454,9 +475,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -471,6 +493,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -489,9 +512,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -506,6 +530,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -524,9 +549,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -541,6 +567,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -559,9 +586,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -576,6 +604,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -597,9 +626,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -614,6 +644,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -633,9 +664,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -650,6 +682,7 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -668,9 +701,10 @@ exports[`failing createOne FieldMetadataService morph relation fields v2 it shou
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -5,6 +5,7 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -27,9 +28,10 @@ exports[`Object metadata creation should fail v2 when labelPlural contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -44,6 +46,7 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -66,9 +69,10 @@ exports[`Object metadata creation should fail v2 when labelPlural exceeds maximu
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -83,6 +87,7 @@ exports[`Object metadata creation should fail v2 when labelPlural is empty 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -105,9 +110,10 @@ exports[`Object metadata creation should fail v2 when labelPlural is empty 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -122,6 +128,7 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -144,9 +151,10 @@ exports[`Object metadata creation should fail v2 when labelSingular contains onl
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -161,6 +169,7 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -183,9 +192,10 @@ exports[`Object metadata creation should fail v2 when labelSingular exceeds maxi
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -200,6 +210,7 @@ exports[`Object metadata creation should fail v2 when labelSingular is empty 1`]
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -222,9 +233,10 @@ exports[`Object metadata creation should fail v2 when labelSingular is empty 1`]
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -239,6 +251,7 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -262,9 +275,10 @@ exports[`Object metadata creation should fail v2 when labels are identical 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -279,6 +293,7 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -302,9 +317,10 @@ exports[`Object metadata creation should fail v2 when labels with whitespaces re
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -319,6 +335,7 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -421,9 +438,10 @@ exports[`Object metadata creation should fail v2 when name exceeds maximum lengt
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -438,6 +456,7 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -460,9 +479,10 @@ exports[`Object metadata creation should fail v2 when namePlural has invalid cha
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -477,6 +497,7 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -499,9 +520,10 @@ exports[`Object metadata creation should fail v2 when namePlural is a reserved k
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -516,6 +538,7 @@ exports[`Object metadata creation should fail v2 when namePlural is an empty str
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -543,9 +566,10 @@ exports[`Object metadata creation should fail v2 when namePlural is an empty str
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -560,6 +584,7 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -587,9 +612,10 @@ exports[`Object metadata creation should fail v2 when namePlural is not camelCas
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -604,6 +630,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -741,9 +768,10 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -758,6 +786,7 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -895,9 +924,10 @@ exports[`Object metadata creation should fail v2 when nameSingular contains only
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -912,6 +942,7 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1014,9 +1045,10 @@ exports[`Object metadata creation should fail v2 when nameSingular has invalid c
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -1031,6 +1063,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1133,9 +1166,10 @@ exports[`Object metadata creation should fail v2 when nameSingular is a reserved
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -1150,6 +1184,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is an empty s
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1287,9 +1322,10 @@ exports[`Object metadata creation should fail v2 when nameSingular is an empty s
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -1304,6 +1340,7 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1441,9 +1478,10 @@ exports[`Object metadata creation should fail v2 when nameSingular is not camelC
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -1458,6 +1496,7 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] =
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1481,9 +1520,10 @@ exports[`Object metadata creation should fail v2 when names are identical 1`] =
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
@@ -1498,6 +1538,7 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res
"extensions": {
"code": "BAD_USER_INPUT",
"errors": {
"index": [],
"objectMetadata": [
{
"errors": [
@@ -1521,9 +1562,10 @@ exports[`Object metadata creation should fail v2 when names with whitespaces res
},
"message": "Validation failed for 0 object(s) and 0 field(s)",
"summary": {
"invalidObjectMetadatas": 0,
"invalidViewFields": 0,
"invalidViews": 0,
"invalidIndex": 0,
"invalidObjectMetadata": 0,
"invalidView": 0,
"invalidViewField": 0,
"totalErrors": 0,
},
"userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",