Identify standard view fields and views (#17118)
# Introduction Related https://github.com/twentyhq/core-team-issues/issues/1989 1/ Migration, applicationId and universalIdentifier are required on entity ( save point migration + upgrade command fallback pattern ) 2/ Backfill using previous `standardId` or `isCustom` ## Test Both tested on prod extract Some view field are set as non custom is prod whereas they should for several manually handle-able workspace amount
This commit is contained in:
+6
-6
@@ -216,17 +216,17 @@ export class IdentifyFieldMetadataCommand extends WorkspacesMigrationCommandRunn
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('fieldMetadata');
|
||||
const cacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
const relatedCacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(
|
||||
workspaceId,
|
||||
cacheKeysToInvalidate,
|
||||
`Invalidating caches: flatFieldMetadataMaps ${relatedCacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatFieldMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
]);
|
||||
|
||||
this.logger.log(
|
||||
`Applied ${totalUpdates} field metadata update(s) for workspace ${workspaceId}`,
|
||||
|
||||
+6
-6
@@ -208,17 +208,17 @@ export class IdentifyObjectMetadataCommand extends WorkspacesMigrationCommandRun
|
||||
|
||||
const relatedMetadataNames =
|
||||
getMetadataRelatedMetadataNames('objectMetadata');
|
||||
const cacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
const relatedCacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${cacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(
|
||||
workspaceId,
|
||||
cacheKeysToInvalidate,
|
||||
`Invalidating caches: flatObjectMetadataMaps ${relatedCacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
]);
|
||||
|
||||
this.logger.log(
|
||||
`Applied ${totalUpdates} object metadata update(s) for workspace ${workspaceId}`,
|
||||
|
||||
+378
@@ -0,0 +1,378 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
RunOnWorkspaceArgs,
|
||||
WorkspacesMigrationCommandRunner,
|
||||
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { computeFormattedViewName } from 'src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant';
|
||||
|
||||
type CustomViewFieldMetadata = {
|
||||
viewFieldEntity: ViewFieldEntity;
|
||||
fromStandard: boolean;
|
||||
};
|
||||
|
||||
type StandardViewFieldMetadata = {
|
||||
viewFieldEntity: ViewFieldEntity;
|
||||
universalIdentifier: string;
|
||||
};
|
||||
|
||||
type AllWarnings =
|
||||
| 'standard_object_has_no_standard_views'
|
||||
| 'unknown_view'
|
||||
| 'unknown_standard_view_field';
|
||||
|
||||
type ViewFieldMetadataWarning = {
|
||||
viewFieldEntity: ViewFieldEntity;
|
||||
warning: AllWarnings;
|
||||
objectNameSingular?: string;
|
||||
viewName?: string;
|
||||
fieldName?: string;
|
||||
};
|
||||
|
||||
type AllExceptions =
|
||||
| 'existing_universal_id_mismatch'
|
||||
| 'view_not_found'
|
||||
| 'object_not_found'
|
||||
| 'field_not_found';
|
||||
|
||||
type ViewFieldMetadataException = {
|
||||
viewFieldEntity: ViewFieldEntity;
|
||||
exception: AllExceptions;
|
||||
objectNameSingular?: string;
|
||||
viewName?: string;
|
||||
fieldName?: string;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:identify-view-field-metadata',
|
||||
description: 'Identify standard view field metadata',
|
||||
})
|
||||
export class IdentifyViewFieldMetadataCommand extends WorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(ViewFieldEntity)
|
||||
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly applicationService: ApplicationService,
|
||||
protected readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
]);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running identify standard view field metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const allViewFieldEntities = await this.viewFieldRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
universalIdentifier: true,
|
||||
applicationId: true,
|
||||
viewId: true,
|
||||
fieldMetadataId: true,
|
||||
},
|
||||
where: {
|
||||
workspaceId,
|
||||
applicationId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
const { flatObjectMetadataMaps, flatFieldMetadataMaps, flatViewMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
'flatViewMaps',
|
||||
],
|
||||
},
|
||||
);
|
||||
|
||||
const customViewFieldMetadataEntities: CustomViewFieldMetadata[] = [];
|
||||
const standardViewFieldMetadataEntities: StandardViewFieldMetadata[] = [];
|
||||
const warnings: ViewFieldMetadataWarning[] = [];
|
||||
const exceptions: ViewFieldMetadataException[] = [];
|
||||
|
||||
for (const viewFieldEntity of allViewFieldEntities) {
|
||||
const flatView = flatViewMaps.byId[viewFieldEntity.viewId];
|
||||
|
||||
if (!isDefined(flatView)) {
|
||||
exceptions.push({
|
||||
viewFieldEntity,
|
||||
exception: 'view_not_found',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (flatView.isCustom) {
|
||||
customViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
fromStandard: false,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[flatView.objectMetadataId];
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
exceptions.push({
|
||||
viewFieldEntity,
|
||||
exception: 'object_not_found',
|
||||
viewName: flatView.name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatFieldMetadata =
|
||||
flatFieldMetadataMaps.byId[viewFieldEntity.fieldMetadataId];
|
||||
|
||||
if (!isDefined(flatFieldMetadata)) {
|
||||
exceptions.push({
|
||||
viewFieldEntity,
|
||||
exception: 'field_not_found',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
flatObjectMetadata.applicationId !== twentyStandardFlatApplication.id
|
||||
) {
|
||||
customViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectConfig =
|
||||
STANDARD_OBJECTS[
|
||||
flatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECTS
|
||||
];
|
||||
|
||||
if (!isDefined(objectConfig)) {
|
||||
exceptions.push({
|
||||
viewFieldEntity,
|
||||
exception: 'object_not_found',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectViews =
|
||||
'views' in objectConfig
|
||||
? (objectConfig.views as Record<
|
||||
string,
|
||||
| {
|
||||
universalIdentifier: string;
|
||||
viewFields?: Record<
|
||||
string,
|
||||
{ universalIdentifier: string } | undefined
|
||||
>;
|
||||
}
|
||||
| undefined
|
||||
>)
|
||||
: null;
|
||||
|
||||
if (!isDefined(objectViews)) {
|
||||
warnings.push({
|
||||
viewFieldEntity,
|
||||
warning: 'standard_object_has_no_standard_views',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
customViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const formattedViewName = computeFormattedViewName({
|
||||
flatObjectMetadata,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
const viewConfig = objectViews[formattedViewName];
|
||||
|
||||
if (!isDefined(viewConfig) || !isDefined(viewConfig.viewFields)) {
|
||||
warnings.push({
|
||||
viewFieldEntity,
|
||||
warning: 'unknown_view',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
customViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const viewFieldConfig = viewConfig.viewFields[flatFieldMetadata.name];
|
||||
const universalIdentifier = viewFieldConfig?.universalIdentifier;
|
||||
|
||||
if (!isDefined(universalIdentifier)) {
|
||||
warnings.push({
|
||||
viewFieldEntity,
|
||||
warning: 'unknown_standard_view_field',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
fieldName: flatFieldMetadata.name,
|
||||
});
|
||||
customViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(viewFieldEntity.universalIdentifier) &&
|
||||
viewFieldEntity.universalIdentifier !== universalIdentifier
|
||||
) {
|
||||
exceptions.push({
|
||||
viewFieldEntity,
|
||||
exception: 'existing_universal_id_mismatch',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
viewName: flatView.name,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
standardViewFieldMetadataEntities.push({
|
||||
viewFieldEntity,
|
||||
universalIdentifier:
|
||||
viewFieldEntity.universalIdentifier ?? universalIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
const totalUpdates =
|
||||
customViewFieldMetadataEntities.length +
|
||||
standardViewFieldMetadataEntities.length;
|
||||
|
||||
if (warnings.length > 0) {
|
||||
this.logger.warn(
|
||||
`Found ${warnings.length} warning(s) while processing view field metadata for workspace ${workspaceId}. These view fields will become custom.`,
|
||||
);
|
||||
|
||||
for (const {
|
||||
viewFieldEntity,
|
||||
warning,
|
||||
objectNameSingular,
|
||||
viewName,
|
||||
fieldName,
|
||||
} of warnings) {
|
||||
this.logger.warn(
|
||||
`Warning for view field on object "${objectNameSingular ?? 'unknown'}" in view "${viewName ?? 'unknown'}" for field ${fieldName ?? 'unknown'} (id=${viewFieldEntity.id}): ${warning}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.length > 0) {
|
||||
this.logger.error(
|
||||
`Found ${exceptions.length} exception(s) while processing view field metadata for workspace ${workspaceId}. No updates will be applied.`,
|
||||
);
|
||||
|
||||
for (const {
|
||||
viewFieldEntity,
|
||||
exception,
|
||||
objectNameSingular,
|
||||
viewName,
|
||||
fieldName,
|
||||
} of exceptions) {
|
||||
this.logger.error(
|
||||
`Exception for view field on object "${objectNameSingular ?? 'unknown'}" in view "${viewName ?? 'unknown'}" for field ${fieldName ?? 'unknown'} (id=${viewFieldEntity.id}): ${exception}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Aborting migration for workspace ${workspaceId} due to ${exceptions.length} exception(s). See logs above for details.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully validated ${totalUpdates}/${allViewFieldEntities.length} view field metadata update(s) for workspace ${workspaceId} (${customViewFieldMetadataEntities.length} custom, ${standardViewFieldMetadataEntities.length} standard)`,
|
||||
);
|
||||
|
||||
if (!options.dryRun) {
|
||||
const customUpdates = customViewFieldMetadataEntities.map(
|
||||
({ viewFieldEntity }) => ({
|
||||
id: viewFieldEntity.id,
|
||||
universalIdentifier: viewFieldEntity.universalIdentifier ?? v4(),
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const standardUpdates = standardViewFieldMetadataEntities.map(
|
||||
({ viewFieldEntity, universalIdentifier }) => ({
|
||||
id: viewFieldEntity.id,
|
||||
universalIdentifier,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
}),
|
||||
);
|
||||
|
||||
await this.viewFieldRepository.save([
|
||||
...customUpdates,
|
||||
...standardUpdates,
|
||||
]);
|
||||
|
||||
const relatedMetadataNames = getMetadataRelatedMetadataNames('viewField');
|
||||
const relatedCacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${relatedCacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatViewFieldMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
]);
|
||||
|
||||
this.logger.log(
|
||||
`Applied ${totalUpdates} view field metadata update(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
} else {
|
||||
this.logger.log(
|
||||
`Dry run: would apply ${totalUpdates} view field metadata update(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+291
@@ -0,0 +1,291 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
|
||||
import { IsNull, Repository } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import {
|
||||
RunOnWorkspaceArgs,
|
||||
WorkspacesMigrationCommandRunner,
|
||||
} from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { computeFormattedViewName } from 'src/database/commands/upgrade-version-command/1-16/utils/compute-formatted-view-name.util';
|
||||
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
|
||||
import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util';
|
||||
import { getMetadataRelatedMetadataNames } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-related-metadata-names.util';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant';
|
||||
|
||||
type CustomViewMetadata = {
|
||||
viewEntity: ViewEntity;
|
||||
fromStandard: boolean;
|
||||
};
|
||||
|
||||
type StandardViewMetadata = {
|
||||
viewEntity: ViewEntity;
|
||||
universalIdentifier: string;
|
||||
};
|
||||
|
||||
type AllWarnings = 'unknown_object';
|
||||
|
||||
type ViewMetadataWarning = {
|
||||
viewEntity: ViewEntity;
|
||||
warning: AllWarnings;
|
||||
objectNameSingular?: string;
|
||||
};
|
||||
|
||||
type AllExceptions =
|
||||
| 'existing_universal_id_mismatch'
|
||||
| 'not_found_object'
|
||||
| 'unknown_standard_view_name';
|
||||
|
||||
type ViewMetadataException = {
|
||||
viewEntity: ViewEntity;
|
||||
exception: AllExceptions;
|
||||
objectNameSingular?: string;
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:identify-view-metadata',
|
||||
description: 'Identify standard view metadata',
|
||||
})
|
||||
export class IdentifyViewMetadataCommand extends WorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(ViewEntity)
|
||||
private readonly viewRepository: Repository<ViewEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
protected readonly applicationService: ApplicationService,
|
||||
protected readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService, [
|
||||
WorkspaceActivationStatus.ACTIVE,
|
||||
WorkspaceActivationStatus.SUSPENDED,
|
||||
WorkspaceActivationStatus.ONGOING_CREATION,
|
||||
]);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Running identify standard view metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{ workspaceId },
|
||||
);
|
||||
|
||||
const allViewEntities = await this.viewRepository.find({
|
||||
select: {
|
||||
id: true,
|
||||
universalIdentifier: true,
|
||||
applicationId: true,
|
||||
name: true,
|
||||
objectMetadataId: true,
|
||||
isCustom: true,
|
||||
},
|
||||
where: {
|
||||
workspaceId,
|
||||
applicationId: IsNull(),
|
||||
},
|
||||
});
|
||||
|
||||
const { flatObjectMetadataMaps } =
|
||||
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
|
||||
{
|
||||
workspaceId,
|
||||
flatMapsKeys: ['flatObjectMetadataMaps'],
|
||||
},
|
||||
);
|
||||
|
||||
const customViewMetadataEntities: CustomViewMetadata[] = [];
|
||||
const standardViewMetadataEntities: StandardViewMetadata[] = [];
|
||||
const warnings: ViewMetadataWarning[] = [];
|
||||
const exceptions: ViewMetadataException[] = [];
|
||||
|
||||
for (const viewEntity of allViewEntities) {
|
||||
// TODO double check that index view are not custom clearly not sure sure about that
|
||||
if (viewEntity.isCustom) {
|
||||
customViewMetadataEntities.push({
|
||||
viewEntity,
|
||||
fromStandard: false,
|
||||
});
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
const flatObjectMetadata =
|
||||
flatObjectMetadataMaps.byId[viewEntity.objectMetadataId];
|
||||
|
||||
if (!isDefined(flatObjectMetadata)) {
|
||||
exceptions.push({
|
||||
viewEntity,
|
||||
exception: 'not_found_object',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectConfig =
|
||||
STANDARD_OBJECTS[
|
||||
flatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECTS
|
||||
];
|
||||
|
||||
if (!isDefined(objectConfig)) {
|
||||
warnings.push({
|
||||
viewEntity,
|
||||
warning: 'unknown_object',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
});
|
||||
customViewMetadataEntities.push({
|
||||
viewEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectViews =
|
||||
'views' in objectConfig
|
||||
? (objectConfig.views as Record<
|
||||
string,
|
||||
{ universalIdentifier: string } | undefined
|
||||
>)
|
||||
: null;
|
||||
|
||||
if (!isDefined(objectViews)) {
|
||||
warnings.push({
|
||||
viewEntity,
|
||||
warning: 'unknown_object',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
});
|
||||
customViewMetadataEntities.push({
|
||||
viewEntity,
|
||||
fromStandard: true,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
const formattedViewName = computeFormattedViewName({
|
||||
viewName: viewEntity.name,
|
||||
flatObjectMetadata,
|
||||
});
|
||||
|
||||
this.logger.log(formattedViewName);
|
||||
const viewConfig = objectViews[formattedViewName];
|
||||
const universalIdentifier = viewConfig?.universalIdentifier;
|
||||
|
||||
if (!isDefined(universalIdentifier)) {
|
||||
exceptions.push({
|
||||
viewEntity,
|
||||
exception: 'unknown_standard_view_name',
|
||||
objectNameSingular: flatObjectMetadata.nameSingular,
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
if (
|
||||
isDefined(viewEntity.universalIdentifier) &&
|
||||
viewEntity.universalIdentifier !== universalIdentifier
|
||||
) {
|
||||
exceptions.push({
|
||||
viewEntity,
|
||||
exception: 'existing_universal_id_mismatch',
|
||||
});
|
||||
continue;
|
||||
}
|
||||
|
||||
standardViewMetadataEntities.push({
|
||||
viewEntity,
|
||||
universalIdentifier:
|
||||
viewEntity.universalIdentifier ?? universalIdentifier,
|
||||
});
|
||||
}
|
||||
|
||||
const totalUpdates =
|
||||
customViewMetadataEntities.length + standardViewMetadataEntities.length;
|
||||
|
||||
if (warnings.length > 0) {
|
||||
this.logger.warn(
|
||||
`Found ${warnings.length} warning(s) while processing view metadata for workspace ${workspaceId}. These views will become custom.`,
|
||||
);
|
||||
|
||||
for (const { viewEntity, warning, objectNameSingular } of warnings) {
|
||||
this.logger.warn(
|
||||
`Warning for view "${viewEntity.name}" on object "${objectNameSingular ?? 'unknown'}" (id=${viewEntity.id}): ${warning}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (exceptions.length > 0) {
|
||||
this.logger.error(
|
||||
`Found ${exceptions.length} exception(s) while processing view metadata for workspace ${workspaceId}. No updates will be applied.`,
|
||||
);
|
||||
|
||||
for (const { viewEntity, exception, objectNameSingular } of exceptions) {
|
||||
this.logger.error(
|
||||
`Exception for view "${viewEntity.name}" on object "${objectNameSingular ?? 'unknown'}" (id=${viewEntity.id}): ${exception}`,
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`Aborting migration for workspace ${workspaceId} due to ${exceptions.length} exception(s). See logs above for details.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully validated ${totalUpdates}/${allViewEntities.length} view metadata update(s) for workspace ${workspaceId} (${customViewMetadataEntities.length} custom, ${standardViewMetadataEntities.length} standard)`,
|
||||
);
|
||||
|
||||
if (!options.dryRun) {
|
||||
const customUpdates = customViewMetadataEntities.map(
|
||||
({ viewEntity }) => ({
|
||||
id: viewEntity.id,
|
||||
universalIdentifier: viewEntity.universalIdentifier ?? v4(),
|
||||
applicationId: workspaceCustomFlatApplication.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const standardUpdates = standardViewMetadataEntities.map(
|
||||
({ viewEntity, universalIdentifier }) => ({
|
||||
id: viewEntity.id,
|
||||
universalIdentifier,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
}),
|
||||
);
|
||||
|
||||
await this.viewRepository.save([...customUpdates, ...standardUpdates]);
|
||||
|
||||
const relatedMetadataNames = getMetadataRelatedMetadataNames('view');
|
||||
const relatedCacheKeysToInvalidate = relatedMetadataNames.map(
|
||||
getMetadataFlatEntityMapsKey,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Invalidating caches: ${relatedCacheKeysToInvalidate.join(' ')}`,
|
||||
);
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatViewMaps',
|
||||
...relatedCacheKeysToInvalidate,
|
||||
]);
|
||||
|
||||
this.logger.log(
|
||||
`Applied ${totalUpdates} view metadata update(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
} else {
|
||||
this.logger.log(
|
||||
`Dry run: would apply ${totalUpdates} view metadata update(s) for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:make-view-field-universal-identifier-and-application-id-not-nullable-migration',
|
||||
description:
|
||||
'Make universalIdentifier and applicationId columns NOT NULL on viewField table',
|
||||
})
|
||||
export class MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
private hasRunOnce = false;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
if (this.hasRunOnce) {
|
||||
this.logger.warn(
|
||||
'Skipping has already been run once MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
await makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
this.logger.log(
|
||||
'Successfully run MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
this.hasRunOnce = true;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Rolling back MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${error.message}`,
|
||||
);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { makeViewUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:make-view-universal-identifier-and-application-id-not-nullable-migration',
|
||||
description:
|
||||
'Make universalIdentifier and applicationId columns NOT NULL on view table',
|
||||
})
|
||||
export class MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
private hasRunOnce = false;
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
if (this.hasRunOnce) {
|
||||
this.logger.warn(
|
||||
'Skipping has already been run once MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
await makeViewUniversalIdentifierAndApplicationIdNotNullableQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.commitTransaction();
|
||||
this.logger.log(
|
||||
'Successfully run MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand',
|
||||
);
|
||||
this.hasRunOnce = true;
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.error(
|
||||
`Rolling back MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: ${error.message}`,
|
||||
);
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
@@ -5,15 +5,22 @@ import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgr
|
||||
import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command';
|
||||
import { IdentifyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command';
|
||||
import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command';
|
||||
import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command';
|
||||
import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command';
|
||||
import { MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-field-metadata-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-object-metadata-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command';
|
||||
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
|
||||
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { GlobalWorkspaceDataSourceModule } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-datasource.module';
|
||||
import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module';
|
||||
import { TwentyStandardApplicationModule } from 'src/engine/workspace-manager/twenty-standard-application/twenty-standard-application.module';
|
||||
@@ -25,6 +32,8 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
WorkspaceEntity,
|
||||
FieldMetadataEntity,
|
||||
ObjectMetadataEntity,
|
||||
ViewEntity,
|
||||
ViewFieldEntity,
|
||||
]),
|
||||
DataSourceModule,
|
||||
WorkspaceCacheModule,
|
||||
@@ -33,6 +42,7 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
GlobalWorkspaceDataSourceModule,
|
||||
TwentyStandardApplicationModule,
|
||||
WorkspaceMigrationModule,
|
||||
WorkspaceManyOrAllFlatEntityMapsCacheModule,
|
||||
],
|
||||
providers: [
|
||||
UpdateTaskOnDeleteActionCommand,
|
||||
@@ -40,8 +50,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
BackfillStandardPageLayoutsCommand,
|
||||
IdentifyFieldMetadataCommand,
|
||||
IdentifyObjectMetadataCommand,
|
||||
IdentifyViewMetadataCommand,
|
||||
IdentifyViewFieldMetadataCommand,
|
||||
MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
],
|
||||
exports: [
|
||||
UpdateTaskOnDeleteActionCommand,
|
||||
@@ -49,8 +63,12 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace
|
||||
BackfillStandardPageLayoutsCommand,
|
||||
IdentifyFieldMetadataCommand,
|
||||
IdentifyObjectMetadataCommand,
|
||||
IdentifyViewMetadataCommand,
|
||||
IdentifyViewFieldMetadataCommand,
|
||||
MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
],
|
||||
})
|
||||
export class V1_16_UpgradeVersionCommandModule {}
|
||||
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { capitalize, uncapitalize } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
|
||||
export const ALL_ENTITY_VIEW_NAME = 'All {objectLabelPlural}';
|
||||
|
||||
export const computeFormattedViewName = ({
|
||||
viewName,
|
||||
flatObjectMetadata,
|
||||
}: {
|
||||
viewName: string;
|
||||
flatObjectMetadata: FlatObjectMetadata;
|
||||
}) =>
|
||||
viewName === ALL_ENTITY_VIEW_NAME
|
||||
? `all${capitalize(flatObjectMetadata.namePlural)}`
|
||||
: uncapitalize(viewName.split(' ').map(capitalize).join(''));
|
||||
+14
@@ -26,8 +26,12 @@ import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgr
|
||||
import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.command';
|
||||
import { IdentifyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-field-metadata.command';
|
||||
import { IdentifyObjectMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-object-metadata.command';
|
||||
import { IdentifyViewFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-field-metadata.command';
|
||||
import { IdentifyViewMetadataCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-identify-view-metadata.command';
|
||||
import { MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-field-metadata-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-object-metadata-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-field-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-make-view-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { UpdateTaskOnDeleteActionCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-update-task-on-delete-action.command';
|
||||
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -73,8 +77,12 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly backfillStandardPageLayoutsCommand: BackfillStandardPageLayoutsCommand,
|
||||
protected readonly identifyFieldMetadataCommand: IdentifyFieldMetadataCommand,
|
||||
protected readonly identifyObjectMetadataCommand: IdentifyObjectMetadataCommand,
|
||||
protected readonly identifyViewMetadataCommand: IdentifyViewMetadataCommand,
|
||||
protected readonly identifyViewFieldMetadataCommand: IdentifyViewFieldMetadataCommand,
|
||||
protected readonly makeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly makeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly makeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
protected readonly makeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand: MakeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -114,10 +122,16 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.backfillStandardPageLayoutsCommand,
|
||||
this.identifyFieldMetadataCommand,
|
||||
this.identifyObjectMetadataCommand,
|
||||
this.identifyViewMetadataCommand,
|
||||
this.identifyViewFieldMetadataCommand,
|
||||
this
|
||||
.makeFieldMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
this
|
||||
.makeObjectMetadataUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
this
|
||||
.makeViewUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
this
|
||||
.makeViewFieldUniversalIdentifierAndApplicationIdNotNullableMigrationCommand,
|
||||
];
|
||||
|
||||
this.allCommands = {
|
||||
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { makeViewUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174271-makeViewUniversalIdentifierAndApplicationIdNotNullable.util';
|
||||
|
||||
export class MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271
|
||||
implements MigrationInterface
|
||||
{
|
||||
name = 'MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const savepointName =
|
||||
'sp_make_view_universal_identifier_and_application_id_not_nullable';
|
||||
|
||||
try {
|
||||
await queryRunner.query(`SAVEPOINT ${savepointName}`);
|
||||
|
||||
await makeViewUniversalIdentifierAndApplicationIdNotNullableQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (e) {
|
||||
try {
|
||||
await queryRunner.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (rollbackError) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to rollback to savepoint in MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271',
|
||||
rollbackError,
|
||||
);
|
||||
throw rollbackError;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Swallowing MakeViewUniversalIdentifierAndApplicationIdNotNullable1768213174271 error',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_552aa6908966e980099b3e5ebf"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ALTER COLUMN "applicationId" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ALTER COLUMN "universalIdentifier" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_552aa6908966e980099b3e5ebf" ON "core"."view" ("workspaceId", "universalIdentifier") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { type MigrationInterface, type QueryRunner } from 'typeorm';
|
||||
|
||||
import { makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries } from 'src/database/typeorm/core/migrations/utils/1768213174272-makeViewFieldUniversalIdentifierAndApplicationIdNotNullable.util';
|
||||
|
||||
export class MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272
|
||||
implements MigrationInterface
|
||||
{
|
||||
name =
|
||||
'MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272';
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
const savepointName =
|
||||
'sp_make_view_field_universal_identifier_and_application_id_not_nullable';
|
||||
|
||||
try {
|
||||
await queryRunner.query(`SAVEPOINT ${savepointName}`);
|
||||
|
||||
await makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries(
|
||||
queryRunner,
|
||||
);
|
||||
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (e) {
|
||||
try {
|
||||
await queryRunner.query(`ROLLBACK TO SAVEPOINT ${savepointName}`);
|
||||
await queryRunner.query(`RELEASE SAVEPOINT ${savepointName}`);
|
||||
} catch (rollbackError) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Failed to rollback to savepoint in MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272',
|
||||
rollbackError,
|
||||
);
|
||||
throw rollbackError;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
'Swallowing MakeViewFieldUniversalIdentifierAndApplicationIdNotNullable1768213174272 error',
|
||||
e,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_b86af4ea24cae518dee8eae996"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ALTER COLUMN "applicationId" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ALTER COLUMN "universalIdentifier" DROP NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_b86af4ea24cae518dee8eae996" ON "core"."viewField" ("workspaceId", "universalIdentifier") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
export const makeViewUniversalIdentifierAndApplicationIdNotNullableQueries =
|
||||
async (queryRunner: QueryRunner): Promise<void> => {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" DROP CONSTRAINT "FK_348e25d584c7e51417f4e097941"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_552aa6908966e980099b3e5ebf"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ALTER COLUMN "universalIdentifier" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ALTER COLUMN "applicationId" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_552aa6908966e980099b3e5ebf" ON "core"."view" ("workspaceId", "universalIdentifier") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."view" ADD CONSTRAINT "FK_348e25d584c7e51417f4e097941" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
};
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import { type QueryRunner } from 'typeorm';
|
||||
|
||||
export const makeViewFieldUniversalIdentifierAndApplicationIdNotNullableQueries =
|
||||
async (queryRunner: QueryRunner): Promise<void> => {
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" DROP CONSTRAINT "FK_b560ea62a958deff0c6059caa45"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`DROP INDEX "core"."IDX_b86af4ea24cae518dee8eae996"`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ALTER COLUMN "universalIdentifier" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ALTER COLUMN "applicationId" SET NOT NULL`,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`CREATE UNIQUE INDEX "IDX_b86af4ea24cae518dee8eae996" ON "core"."viewField" ("workspaceId", "universalIdentifier") `,
|
||||
);
|
||||
await queryRunner.query(
|
||||
`ALTER TABLE "core"."viewField" ADD CONSTRAINT "FK_b560ea62a958deff0c6059caa45" FOREIGN KEY ("applicationId") REFERENCES "core"."application"("id") ON DELETE CASCADE ON UPDATE NO ACTION`,
|
||||
);
|
||||
};
|
||||
+2
-2
@@ -14,7 +14,7 @@ import {
|
||||
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ViewEntity } from 'src/engine/metadata-modules/view/entities/view.entity';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface';
|
||||
|
||||
@Entity({ name: 'viewField', schema: 'core' })
|
||||
@Index('IDX_VIEW_FIELD_WORKSPACE_ID_VIEW_ID', ['workspaceId', 'viewId'])
|
||||
@@ -30,7 +30,7 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
|
||||
},
|
||||
)
|
||||
export class ViewFieldEntity
|
||||
extends SyncableEntity
|
||||
extends SyncableEntityRequired
|
||||
implements Required<ViewFieldEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
|
||||
@@ -27,7 +27,7 @@ import { ViewKey } from 'src/engine/metadata-modules/view/enums/view-key.enum';
|
||||
import { ViewOpenRecordIn } from 'src/engine/metadata-modules/view/enums/view-open-record-in';
|
||||
import { ViewType } from 'src/engine/metadata-modules/view/enums/view-type.enum';
|
||||
import { ViewVisibility } from 'src/engine/metadata-modules/view/enums/view-visibility.enum';
|
||||
import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface';
|
||||
import { SyncableEntityRequired } from 'src/engine/workspace-manager/types/syncable-entity-required.interface';
|
||||
|
||||
// We could refactor this type to be dynamic to view type
|
||||
@Entity({ name: 'view', schema: 'core' })
|
||||
@@ -40,7 +40,10 @@ import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-enti
|
||||
'CHK_VIEW_CALENDAR_INTEGRITY',
|
||||
`("type" != 'CALENDAR' OR ("calendarLayout" IS NOT NULL AND "calendarFieldMetadataId" IS NOT NULL))`,
|
||||
)
|
||||
export class ViewEntity extends SyncableEntity implements Required<ViewEntity> {
|
||||
export class ViewEntity
|
||||
extends SyncableEntityRequired
|
||||
implements Required<ViewEntity>
|
||||
{
|
||||
@PrimaryGeneratedColumn('uuid')
|
||||
id: string;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user