dashboard workspace standard seeds (#16962)
# Introduction In this pull request we're introducing new standard page layout, tabs and widget ( 1 page layout, 1 tab and 8 widgets ) and also a new opportunity field Also now prefilling new records, 6 opportunities and a dashboard. ## Standard declaration ### New workspace creation Relies on existing standard declaration builder ### Backfill command We've been hacking through the standard builder in order to extract only the standard page layout entities, updated their entity dependencies to match the workspace ids so the validation passes ## Remark - Refactored the `PageLayoutWidget` configuration type to be dynamically typed through a generic discriminated union --------- Co-authored-by: prastoin <paul@twenty.com>
This commit is contained in:
+207
@@ -0,0 +1,207 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { 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 { 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 { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
|
||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service';
|
||||
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
|
||||
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';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:backfill-opportunity-owner-field',
|
||||
description: 'Backfill owner field for opportunity object',
|
||||
})
|
||||
export class BackfillOpportunityOwnerFieldCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(
|
||||
BackfillOpportunityOwnerFieldCommand.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectRepository(FieldMetadataEntity)
|
||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly fieldMetadataService: FieldMetadataService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Starting backfill of owner field for opportunity in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const { flatFieldMetadataMaps, flatObjectMetadataMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
const opportunityObjectMetadata = Object.values(
|
||||
flatObjectMetadataMaps.byId,
|
||||
).find(
|
||||
(obj) =>
|
||||
isDefined(obj) && obj.standardId === STANDARD_OBJECT_IDS.opportunity,
|
||||
);
|
||||
|
||||
if (!isDefined(opportunityObjectMetadata)) {
|
||||
this.logger.log(
|
||||
`Opportunity object not found for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const workspaceMemberObjectMetadata = Object.values(
|
||||
flatObjectMetadataMaps.byId,
|
||||
).find(
|
||||
(obj) =>
|
||||
isDefined(obj) &&
|
||||
obj.standardId === STANDARD_OBJECT_IDS.workspaceMember,
|
||||
);
|
||||
|
||||
if (!isDefined(workspaceMemberObjectMetadata)) {
|
||||
this.logger.log(
|
||||
`WorkspaceMember object not found for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const flatFieldMetadatas = findManyFlatEntityByIdInFlatEntityMapsOrThrow({
|
||||
flatEntityIds: opportunityObjectMetadata.fieldMetadataIds,
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
});
|
||||
|
||||
const ownerUniversalIdentifier =
|
||||
STANDARD_OBJECTS.opportunity.fields.owner.universalIdentifier;
|
||||
|
||||
const ownerField = flatFieldMetadatas.find(
|
||||
(field) => field.universalIdentifier === ownerUniversalIdentifier,
|
||||
);
|
||||
|
||||
if (isDefined(ownerField)) {
|
||||
this.logger.log(
|
||||
`Opportunity already has owner field in workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create owner field for opportunity in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const createFieldInput: CreateFieldInput = {
|
||||
workspaceId,
|
||||
objectMetadataId: opportunityObjectMetadata.id,
|
||||
name: 'owner',
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'Owner',
|
||||
description: 'Opportunity owner',
|
||||
icon: 'IconUserCircle',
|
||||
isNullable: true,
|
||||
isCustom: false,
|
||||
isSystem: false,
|
||||
isActive: true,
|
||||
standardId: ownerUniversalIdentifier,
|
||||
universalIdentifier: ownerUniversalIdentifier,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
relationCreationPayload: {
|
||||
type: RelationType.MANY_TO_ONE,
|
||||
targetObjectMetadataId: workspaceMemberObjectMetadata.id,
|
||||
targetFieldLabel: 'Owned opportunities',
|
||||
targetFieldIcon: 'IconTargetArrow',
|
||||
},
|
||||
};
|
||||
|
||||
const ownedOpportunitiesUniversalIdentifier =
|
||||
STANDARD_OBJECTS.workspaceMember.fields.ownedOpportunities
|
||||
.universalIdentifier;
|
||||
|
||||
try {
|
||||
await this.fieldMetadataService.createManyFields({
|
||||
createFieldInputs: [createFieldInput],
|
||||
workspaceId,
|
||||
isSystemBuild: true,
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Created owner field for opportunity in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
const ownedOpportunitiesField =
|
||||
await this.fieldMetadataRepository.findOne({
|
||||
where: {
|
||||
workspaceId,
|
||||
objectMetadataId: workspaceMemberObjectMetadata.id,
|
||||
name: 'ownedOpportunities',
|
||||
},
|
||||
});
|
||||
|
||||
if (isDefined(ownedOpportunitiesField)) {
|
||||
await this.fieldMetadataRepository.update(
|
||||
{ id: ownedOpportunitiesField.id },
|
||||
{
|
||||
universalIdentifier: ownedOpportunitiesUniversalIdentifier,
|
||||
standardId: ownedOpportunitiesUniversalIdentifier,
|
||||
applicationId: twentyStandardFlatApplication.id,
|
||||
},
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`Updated ownedOpportunities field universalIdentifier in workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
} else {
|
||||
throw new Error(
|
||||
`Should never occur could not find the created ownedOpportunities field`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled owner field for opportunity in workspace ${workspaceId}`,
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Failed to create owner field for opportunity in workspace ${workspaceId}`,
|
||||
error,
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { 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 { updateStandardPageLayoutWidgetsWithWorkspaceFieldIds } from 'src/database/commands/upgrade-version-command/1-16/utils/update-standard-page-layout-widgets-with-workspace-field-ids.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 { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util';
|
||||
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 { MY_FIRST_DASHBOARD_ID } from 'src/engine/workspace-manager/standard-objects-prefill-data/prefill-dashboards';
|
||||
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
|
||||
import { computeTwentyStandardApplicationAllFlatEntityMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/twenty-standard-application-all-flat-entity-maps.constant';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service';
|
||||
import { DashboardWorkspaceEntity } from 'src/modules/dashboard/standard-objects/dashboard.workspace-entity';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-16:backfill-standard-page-layouts',
|
||||
description: 'Backfill standard page layouts and dashboard records',
|
||||
})
|
||||
export class BackfillStandardPageLayoutsCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
protected readonly logger = new Logger(
|
||||
BackfillStandardPageLayoutsCommand.name,
|
||||
);
|
||||
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly applicationService: ApplicationService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
this.logger.log(
|
||||
`Starting backfill of standard page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.backfillStandardPageLayoutMetadata({ workspaceId, options });
|
||||
await this.backfillDashboardRecord({ workspaceId, options });
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled standard page layouts for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillStandardPageLayoutMetadata({
|
||||
workspaceId,
|
||||
options,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
options: RunOnWorkspaceArgs['options'];
|
||||
}): Promise<void> {
|
||||
const {
|
||||
flatPageLayoutMaps: fromFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: fromFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: fromFlatPageLayoutWidgetMaps,
|
||||
featureFlagsMap,
|
||||
flatObjectMetadataMaps,
|
||||
flatFieldMetadataMaps,
|
||||
} = await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
'flatPageLayoutWidgetMaps',
|
||||
'flatPageLayoutTabMaps',
|
||||
'featureFlagsMap',
|
||||
'flatObjectMetadataMaps',
|
||||
'flatFieldMetadataMaps',
|
||||
]);
|
||||
|
||||
const flatPageLayout = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: fromFlatPageLayoutMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier,
|
||||
});
|
||||
|
||||
if (isDefined(flatPageLayout)) {
|
||||
this.logger.log(
|
||||
`Standard page layout already exists in workspace ${workspaceId}, skipping metadata backfill`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { twentyStandardFlatApplication } =
|
||||
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
|
||||
{
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
const {
|
||||
flatPageLayoutMaps: toFlatPageLayoutMaps,
|
||||
flatPageLayoutTabMaps: toFlatPageLayoutTabMaps,
|
||||
flatPageLayoutWidgetMaps: rawToFlatPageLayoutWidgetMaps,
|
||||
} = computeTwentyStandardApplicationAllFlatEntityMaps({
|
||||
now: new Date().toISOString(),
|
||||
workspaceId,
|
||||
twentyStandardApplicationId: twentyStandardFlatApplication.id,
|
||||
});
|
||||
|
||||
const toFlatPageLayoutWidgetMaps =
|
||||
updateStandardPageLayoutWidgetsWithWorkspaceFieldIds({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: rawToFlatPageLayoutWidgetMaps,
|
||||
});
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create standard page layout entities ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const validateAndbuildResult =
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigrationFromTo(
|
||||
{
|
||||
additionalCacheDataMaps: {
|
||||
featureFlagsMap,
|
||||
},
|
||||
buildOptions: {
|
||||
isSystemBuild: true,
|
||||
},
|
||||
fromToAllFlatEntityMaps: {
|
||||
flatPageLayoutMaps: {
|
||||
from: fromFlatPageLayoutMaps,
|
||||
to: toFlatPageLayoutMaps,
|
||||
},
|
||||
flatPageLayoutTabMaps: {
|
||||
from: fromFlatPageLayoutTabMaps,
|
||||
to: toFlatPageLayoutTabMaps,
|
||||
},
|
||||
flatPageLayoutWidgetMaps: {
|
||||
from: fromFlatPageLayoutWidgetMaps,
|
||||
to: toFlatPageLayoutWidgetMaps,
|
||||
},
|
||||
},
|
||||
workspaceId,
|
||||
dependencyAllFlatEntityMaps: {
|
||||
flatObjectMetadataMaps,
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (isDefined(validateAndbuildResult)) {
|
||||
this.logger.error(
|
||||
`Failed to create many fields \n ${JSON.stringify(validateAndbuildResult, null, 2)}`,
|
||||
);
|
||||
throw new Error(
|
||||
`Failed to create standard page layout/tab/widgets on workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled standard page layout metadata for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
|
||||
private async backfillDashboardRecord({
|
||||
workspaceId,
|
||||
options,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
options: RunOnWorkspaceArgs['options'];
|
||||
}): Promise<void> {
|
||||
const dashboardRepository =
|
||||
await this.twentyORMGlobalManager.getRepository<DashboardWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'dashboard',
|
||||
{ shouldBypassPermissionChecks: true },
|
||||
);
|
||||
|
||||
const existingDashboard = await dashboardRepository.findOne({
|
||||
where: { id: MY_FIRST_DASHBOARD_ID },
|
||||
});
|
||||
|
||||
if (isDefined(existingDashboard)) {
|
||||
this.logger.log(
|
||||
`Dashboard already exists for workspace ${workspaceId}, skipping dashboard backfill`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (options.dryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would create dashboard record for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const { flatPageLayoutMaps } =
|
||||
await this.workspaceCacheService.getOrRecompute(workspaceId, [
|
||||
'flatPageLayoutMaps',
|
||||
]);
|
||||
|
||||
const myFirstDashboardPageLayout = findFlatEntityByUniversalIdentifier({
|
||||
flatEntityMaps: flatPageLayoutMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier,
|
||||
});
|
||||
|
||||
if (!isDefined(myFirstDashboardPageLayout)) {
|
||||
throw new Error(
|
||||
`Page layout with universalIdentifier '${STANDARD_PAGE_LAYOUTS.myFirstDashboard.universalIdentifier}' not found`,
|
||||
);
|
||||
}
|
||||
|
||||
await dashboardRepository.insert({
|
||||
id: MY_FIRST_DASHBOARD_ID,
|
||||
title: 'My First Dashboard',
|
||||
pageLayoutId: myFirstDashboardPageLayout.id,
|
||||
position: 0,
|
||||
createdBy: {
|
||||
source: FieldActorSource.SYSTEM,
|
||||
workspaceMemberId: null,
|
||||
name: 'System',
|
||||
context: {},
|
||||
},
|
||||
updatedBy: {
|
||||
source: FieldActorSource.SYSTEM,
|
||||
workspaceMemberId: null,
|
||||
name: 'System',
|
||||
context: {},
|
||||
},
|
||||
});
|
||||
|
||||
this.logger.log(
|
||||
`Successfully backfilled dashboard record for workspace ${workspaceId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
+22
-3
@@ -1,20 +1,39 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command';
|
||||
import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.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 { 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';
|
||||
import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace-migration/workspace-migration.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([WorkspaceEntity]),
|
||||
TypeOrmModule.forFeature([WorkspaceEntity, FieldMetadataEntity]),
|
||||
DataSourceModule,
|
||||
WorkspaceCacheModule,
|
||||
FieldMetadataModule,
|
||||
ApplicationModule,
|
||||
GlobalWorkspaceDataSourceModule,
|
||||
TwentyStandardApplicationModule,
|
||||
WorkspaceMigrationModule,
|
||||
],
|
||||
providers: [
|
||||
UpdateTaskOnDeleteActionCommand,
|
||||
BackfillOpportunityOwnerFieldCommand,
|
||||
BackfillStandardPageLayoutsCommand,
|
||||
],
|
||||
exports: [
|
||||
UpdateTaskOnDeleteActionCommand,
|
||||
BackfillOpportunityOwnerFieldCommand,
|
||||
BackfillStandardPageLayoutsCommand,
|
||||
],
|
||||
providers: [UpdateTaskOnDeleteActionCommand],
|
||||
exports: [UpdateTaskOnDeleteActionCommand],
|
||||
})
|
||||
export class V1_16_UpgradeVersionCommandModule {}
|
||||
|
||||
+422
@@ -0,0 +1,422 @@
|
||||
import { isDefined, type RecordFilter } from 'twenty-shared/utils';
|
||||
|
||||
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
|
||||
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
|
||||
import { replaceFlatEntityInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/replace-flat-entity-in-flat-entity-maps-or-throw.util';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { type FlatPageLayoutWidget } from 'src/engine/metadata-modules/flat-page-layout-widget/types/flat-page-layout-widget.type';
|
||||
import { isFlatPageLayoutWidgetConfigurationOfType } from 'src/engine/metadata-modules/flat-page-layout-widget/utils/is-flat-page-layout-widget-configuration-of-type.util';
|
||||
import { WidgetConfigurationType } from 'src/engine/metadata-modules/page-layout-widget/enums/widget-configuration-type.type';
|
||||
import { STANDARD_OBJECTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-object.constant';
|
||||
import { STANDARD_PAGE_LAYOUTS } from 'src/engine/workspace-manager/twenty-standard-application/constants/standard-page-layout.constant';
|
||||
|
||||
type UpdateStandardPageLayoutWidgetsArgs = {
|
||||
flatFieldMetadataMaps: FlatEntityMaps<FlatFieldMetadata>;
|
||||
flatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
|
||||
flatPageLayoutWidgetMaps: FlatEntityMaps<FlatPageLayoutWidget>;
|
||||
};
|
||||
|
||||
const findWidgetByUniversalIdentifierOrThrow = <
|
||||
T extends WidgetConfigurationType,
|
||||
>({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier,
|
||||
widgetDescription,
|
||||
expectedConfigurationType,
|
||||
}: {
|
||||
flatPageLayoutWidgetMaps: FlatEntityMaps<FlatPageLayoutWidget>;
|
||||
universalIdentifier: string;
|
||||
widgetDescription: string;
|
||||
expectedConfigurationType: T;
|
||||
}): FlatPageLayoutWidget<T> => {
|
||||
const flatPageLayoutWidget = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
universalIdentifier,
|
||||
});
|
||||
|
||||
if (
|
||||
!isFlatPageLayoutWidgetConfigurationOfType(
|
||||
flatPageLayoutWidget,
|
||||
expectedConfigurationType,
|
||||
)
|
||||
) {
|
||||
throw new Error(
|
||||
`Widget ${widgetDescription} has unexpected configuration type ${flatPageLayoutWidget.configuration.configurationType}, expected ${expectedConfigurationType}`,
|
||||
);
|
||||
}
|
||||
|
||||
return flatPageLayoutWidget;
|
||||
};
|
||||
|
||||
// Widget updater: dealsByCompany (PIE_CHART)
|
||||
const updateDealsByCompanyWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityCompanyField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets.dealsByCompany
|
||||
.universalIdentifier,
|
||||
widgetDescription: 'dealsByCompany',
|
||||
expectedConfigurationType: WidgetConfigurationType.PIE_CHART,
|
||||
});
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.PIE_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityIdField.id,
|
||||
groupByFieldMetadataId: opportunityCompanyField.id,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
// Widget updater: pipelineValueByStage (BAR_CHART)
|
||||
const updatePipelineValueByStageWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityStageField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.stage.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityCompanyField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.company.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets
|
||||
.pipelineValueByStage.universalIdentifier,
|
||||
widgetDescription: 'pipelineValueByStage',
|
||||
expectedConfigurationType: WidgetConfigurationType.BAR_CHART,
|
||||
});
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.BAR_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityAmountField.id,
|
||||
primaryAxisGroupByFieldMetadataId: opportunityStageField.id,
|
||||
secondaryAxisGroupByFieldMetadataId: opportunityCompanyField.id,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
// Widget updater: revenueTimeline (LINE_CHART)
|
||||
const updateRevenueTimelineWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityCloseDateField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.closeDate.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets.revenueTimeline
|
||||
.universalIdentifier,
|
||||
widgetDescription: 'revenueTimeline',
|
||||
expectedConfigurationType: WidgetConfigurationType.LINE_CHART,
|
||||
});
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.LINE_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityAmountField.id,
|
||||
primaryAxisGroupByFieldMetadataId: opportunityCloseDateField.id,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
// Widget updater: opportunitiesByOwner (BAR_CHART)
|
||||
const updateOpportunitiesByOwnerWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityOwnerField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.owner.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets
|
||||
.opportunitiesByOwner.universalIdentifier,
|
||||
widgetDescription: 'opportunitiesByOwner',
|
||||
expectedConfigurationType: WidgetConfigurationType.BAR_CHART,
|
||||
});
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.BAR_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityIdField.id,
|
||||
primaryAxisGroupByFieldMetadataId: opportunityOwnerField.id,
|
||||
secondaryAxisGroupByFieldMetadataId: opportunityOwnerField.id,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
// Widget updater: dealsCreatedThisMonth (AGGREGATE_CHART)
|
||||
const updateDealsCreatedThisMonthWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityIdField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.id.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityCreatedAtField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets
|
||||
.dealsCreatedThisMonth.universalIdentifier,
|
||||
widgetDescription: 'dealsCreatedThisMonth',
|
||||
expectedConfigurationType: WidgetConfigurationType.AGGREGATE_CHART,
|
||||
});
|
||||
|
||||
const updatedRecordFilters = widget.configuration.filter?.recordFilters?.map(
|
||||
(filter: RecordFilter) => ({
|
||||
...filter,
|
||||
fieldMetadataId: opportunityCreatedAtField.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.AGGREGATE_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityIdField.id,
|
||||
filter: isDefined(widget.configuration.filter)
|
||||
? {
|
||||
...widget.configuration.filter,
|
||||
recordFilters: updatedRecordFilters,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
// Widget updater: dealValueCreatedThisMonth (AGGREGATE_CHART)
|
||||
const updateDealValueCreatedThisMonthWidget = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
const opportunityAmountField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.amount.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityCreatedAtField = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatFieldMetadataMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_OBJECTS.opportunity.fields.createdAt.universalIdentifier,
|
||||
});
|
||||
|
||||
const opportunityObject = findFlatEntityByUniversalIdentifierOrThrow({
|
||||
flatEntityMaps: flatObjectMetadataMaps,
|
||||
universalIdentifier: STANDARD_OBJECTS.opportunity.universalIdentifier,
|
||||
});
|
||||
|
||||
const widget = findWidgetByUniversalIdentifierOrThrow({
|
||||
flatPageLayoutWidgetMaps,
|
||||
universalIdentifier:
|
||||
STANDARD_PAGE_LAYOUTS.myFirstDashboard.tabs.tab1.widgets
|
||||
.dealValueCreatedThisMonth.universalIdentifier,
|
||||
widgetDescription: 'dealValueCreatedThisMonth',
|
||||
expectedConfigurationType: WidgetConfigurationType.AGGREGATE_CHART,
|
||||
});
|
||||
|
||||
const updatedRecordFilters = widget.configuration.filter?.recordFilters?.map(
|
||||
(filter: RecordFilter) => ({
|
||||
...filter,
|
||||
fieldMetadataId: opportunityCreatedAtField.id,
|
||||
}),
|
||||
);
|
||||
|
||||
const updatedWidget: FlatPageLayoutWidget<WidgetConfigurationType.AGGREGATE_CHART> =
|
||||
{
|
||||
...widget,
|
||||
objectMetadataId: opportunityObject.id,
|
||||
configuration: {
|
||||
...widget.configuration,
|
||||
aggregateFieldMetadataId: opportunityAmountField.id,
|
||||
filter: isDefined(widget.configuration.filter)
|
||||
? {
|
||||
...widget.configuration.filter,
|
||||
recordFilters: updatedRecordFilters,
|
||||
}
|
||||
: undefined,
|
||||
},
|
||||
};
|
||||
|
||||
return replaceFlatEntityInFlatEntityMapsOrThrow({
|
||||
flatEntity: updatedWidget,
|
||||
flatEntityMaps: flatPageLayoutWidgetMaps,
|
||||
});
|
||||
};
|
||||
|
||||
export const updateStandardPageLayoutWidgetsWithWorkspaceFieldIds = ({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps,
|
||||
}: UpdateStandardPageLayoutWidgetsArgs): FlatEntityMaps<FlatPageLayoutWidget> => {
|
||||
let updatedMaps = flatPageLayoutWidgetMaps;
|
||||
|
||||
updatedMaps = updateDealsByCompanyWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
updatedMaps = updatePipelineValueByStageWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
updatedMaps = updateRevenueTimelineWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
updatedMaps = updateOpportunitiesByOwnerWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
updatedMaps = updateDealsCreatedThisMonthWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
updatedMaps = updateDealValueCreatedThisMonthWidget({
|
||||
flatFieldMetadataMaps,
|
||||
flatObjectMetadataMaps,
|
||||
flatPageLayoutWidgetMaps: updatedMaps,
|
||||
});
|
||||
|
||||
return updatedMaps;
|
||||
};
|
||||
+6
@@ -22,6 +22,8 @@ import { AddWorkspaceForeignKeysMigrationCommand } from 'src/database/commands/u
|
||||
import { BackfillUpdatedByFieldCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-backfill-updated-by-field.command';
|
||||
import { FixNanPositionValuesInNotesCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-fix-nan-position-values-in-notes.command';
|
||||
import { MigratePageLayoutWidgetConfigurationCommand } from 'src/database/commands/upgrade-version-command/1-15/1-15-migrate-page-layout-widget-configuration.command';
|
||||
import { BackfillOpportunityOwnerFieldCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-opportunity-owner-field.command';
|
||||
import { BackfillStandardPageLayoutsCommand } from 'src/database/commands/upgrade-version-command/1-16/1-16-backfill-standard-page-layouts.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';
|
||||
@@ -63,6 +65,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
|
||||
// 1.16 Commands
|
||||
protected readonly updateTaskOnDeleteActionCommand: UpdateTaskOnDeleteActionCommand,
|
||||
protected readonly backfillOpportunityOwnerFieldCommand: BackfillOpportunityOwnerFieldCommand,
|
||||
protected readonly backfillStandardPageLayoutsCommand: BackfillStandardPageLayoutsCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -98,6 +102,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
|
||||
const commands_1160: VersionCommands = [
|
||||
this.updateTaskOnDeleteActionCommand,
|
||||
this.backfillOpportunityOwnerFieldCommand,
|
||||
this.backfillStandardPageLayoutsCommand,
|
||||
];
|
||||
|
||||
this.allCommands = {
|
||||
|
||||
Reference in New Issue
Block a user