Add new Address field to views containing deprecated address (#6205)

as per title, following introduction of new Address field, we want to
display the new field next to the deprecated field, for users to notice
the new field.

<img width="983" alt="Capture d’écran 2024-07-10 à 17 44 25"
src="https://github.com/twentyhq/twenty/assets/51697796/7b5309b4-b22d-4f32-8054-68bc7b0f3bb3">
This commit is contained in:
Marie 2024-07-11 14:39:38 +02:00 committed by GitHub
parent 70f46242b4
commit 8e25a107fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 249 additions and 12 deletions

View File

@ -0,0 +1,230 @@
import { Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import chalk from 'chalk';
import isEmpty from 'lodash.isempty';
import { Command, CommandRunner, Option } from 'nest-commander';
import { Repository } from 'typeorm';
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
import {
BillingSubscription,
SubscriptionStatus,
} from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import {
FeatureFlagEntity,
FeatureFlagKeys,
} from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { ViewFieldWorkspaceEntity } from 'src/modules/view/standard-objects/view-field.workspace-entity';
interface AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommandOptions {
workspaceId?: string;
}
@Command({
name: 'migrate-0.22:add-new-address-field-to-views-with-deprecated-address-field',
description: 'Adding new field Address to views containing old address field',
})
export class AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand extends CommandRunner {
private readonly logger = new Logger(
AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand.name,
);
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(BillingSubscription, 'core')
private readonly billingSubscriptionRepository: Repository<BillingSubscription>,
@InjectRepository(FeatureFlagEntity, 'core')
private readonly featureFlagRepository: Repository<FeatureFlagEntity>,
@InjectRepository(FieldMetadataEntity, 'metadata')
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly typeORMService: TypeORMService,
private readonly dataSourceService: DataSourceService,
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
private readonly twentyORMManager: TwentyORMManager,
) {
super();
}
@Option({
flags: '-w, --workspace-id [workspace_id]',
description: 'workspace id. Command runs on all workspaces if not provided',
required: false,
})
parseWorkspaceId(value: string): string {
return value;
}
async run(
_passedParam: string[],
options: AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommandOptions,
): Promise<void> {
// This command can be generic-ified turning the below consts in options
const deprecatedFieldStandardId =
COMPANY_STANDARD_FIELD_IDS.address_deprecated;
const newFieldStandardId = COMPANY_STANDARD_FIELD_IDS.address;
this.logger.log('running');
let workspaceIds: string[] = [];
if (options.workspaceId) {
workspaceIds = [options.workspaceId];
} else {
const workspaces = await this.workspaceRepository.find();
const activeWorkspaceIds = (
await Promise.all(
workspaces.map(async (workspace) => {
const isActive = await this.workspaceIsActive(workspace);
return { workspace, isActive };
}),
)
)
.filter((result) => result.isActive)
.map((result) => result.workspace.id);
workspaceIds = activeWorkspaceIds;
}
if (!workspaceIds.length) {
this.logger.log(chalk.yellow('No workspace found'));
return;
} else {
this.logger.log(
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
);
}
for (const workspaceId of workspaceIds) {
const viewFieldRepository =
await this.twentyORMManager.getRepositoryForWorkspace(
workspaceId,
ViewFieldWorkspaceEntity,
);
const dataSourceMetadatas =
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
workspaceId,
);
for (const dataSourceMetadata of dataSourceMetadatas) {
const workspaceDataSource =
await this.typeORMService.connectToDataSource(dataSourceMetadata);
if (workspaceDataSource) {
try {
const newAddressField = await this.fieldMetadataRepository.findBy({
workspaceId,
standardId: newFieldStandardId,
});
if (isEmpty(newAddressField)) {
this.logger.log(
`Error - missing new Address standard field of type Address, please run workspace-sync-metadata on your workspace (${workspaceId}) before running this command`,
);
continue;
}
const addressDeprecatedField =
await this.fieldMetadataRepository.findOneBy({
workspaceId,
standardId: deprecatedFieldStandardId,
});
if (isEmpty(addressDeprecatedField)) {
continue;
}
const viewsWithAddressDeprecatedField =
await viewFieldRepository.find({
where: {
fieldMetadataId: addressDeprecatedField.id,
isVisible: true,
},
});
for (const viewWithAddressDeprecatedField of viewsWithAddressDeprecatedField) {
const viewId = viewWithAddressDeprecatedField.viewId;
const newAddressFieldInThisView =
await viewFieldRepository.findBy({
fieldMetadataId: newAddressField[0].id,
viewId: viewWithAddressDeprecatedField.viewId as string,
isVisible: true,
});
if (!isEmpty(newAddressFieldInThisView)) {
continue;
}
this.logger.log(
`Adding new address field to view ${viewId} for workspace ${workspaceId}...`,
);
const newViewField = viewFieldRepository.create({
viewId: viewWithAddressDeprecatedField.viewId,
fieldMetadataId: newAddressField[0].id,
position: viewWithAddressDeprecatedField.position - 0.5,
isVisible: true,
});
await viewFieldRepository.save(newViewField);
this.logger.log(
`New address field successfully added to view ${viewId} for workspace ${workspaceId}`,
);
}
} catch (error) {
this.logger.log(
chalk.red(`Running command on workspace ${workspaceId} failed`),
);
throw error;
}
}
}
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
this.logger.log(
chalk.green(`Running command on workspace ${workspaceId} done`),
);
}
this.logger.log(chalk.green(`Command completed!`));
}
private async workspaceIsActive(workspace: Workspace): Promise<boolean> {
const billingSupscriptionForWorkspace =
await this.billingSubscriptionRepository.findOne({
where: { workspaceId: workspace.id },
});
if (
billingSupscriptionForWorkspace?.status &&
[
SubscriptionStatus.PastDue,
SubscriptionStatus.Active,
SubscriptionStatus.Trialing,
].includes(billingSupscriptionForWorkspace.status as SubscriptionStatus)
) {
return true;
}
const freeAccessEnabledFeatureFlagForWorkspace =
await this.featureFlagRepository.findOne({
where: {
workspaceId: workspace.id,
key: FeatureFlagKeys.IsFreeAccessEnabled,
value: true,
},
});
return !!freeAccessEnabledFeatureFlagForWorkspace;
}
}

View File

@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm'; import { TypeOrmModule } from '@nestjs/typeorm';
import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command'; import { UpdateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/0-20-update-message-channel-sync-status-enum.command';
import { AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand } from 'src/database/commands/0-22-add-new-address-field-to-views-with-deprecated-address.command';
import { StartDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command'; import { StartDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/start-data-seed-demo-workspace.cron.command';
import { StopDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command'; import { StopDataSeedDemoWorkspaceCronCommand } from 'src/database/commands/data-seed-demo-workspace/crons/stop-data-seed-demo-workspace.cron.command';
import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command'; import { DataSeedDemoWorkspaceCommand } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace-command';
@ -12,6 +13,8 @@ import { UpdateMessageChannelVisibilityEnumCommand } from 'src/database/commands
import { UpgradeTo0_22CommandModule } from 'src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module'; import { UpgradeTo0_22CommandModule } from 'src/database/commands/upgrade-version/0-22/0-22-upgrade-version.module';
import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command'; import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module'; import { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@ -28,7 +31,10 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
WorkspaceManagerModule, WorkspaceManagerModule,
DataSourceModule, DataSourceModule,
TypeORMModule, TypeORMModule,
TypeOrmModule.forFeature([Workspace], 'core'), TypeOrmModule.forFeature(
[Workspace, BillingSubscription, FeatureFlagEntity],
'core',
),
TypeOrmModule.forFeature( TypeOrmModule.forFeature(
[FieldMetadataEntity, ObjectMetadataEntity], [FieldMetadataEntity, ObjectMetadataEntity],
'metadata', 'metadata',
@ -52,6 +58,7 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
StopDataSeedDemoWorkspaceCronCommand, StopDataSeedDemoWorkspaceCronCommand,
UpdateMessageChannelVisibilityEnumCommand, UpdateMessageChannelVisibilityEnumCommand,
UpdateMessageChannelSyncStatusEnumCommand, UpdateMessageChannelSyncStatusEnumCommand,
AddNewAddressFieldToViewsWithDeprecatedAddressFieldCommand,
], ],
}) })
export class DatabaseCommandModule {} export class DatabaseCommandModule {}

View File

@ -41,7 +41,7 @@ export class EntityEventsToDbListener {
// .... // ....
private async handle(payload: ObjectRecordBaseEvent) { private async handle(payload: ObjectRecordBaseEvent) {
if (!payload.objectMetadata.isAuditLogged) { if (!payload.objectMetadata?.isAuditLogged) {
return; return;
} }

View File

@ -9,6 +9,14 @@ import {
RelationMetadataType, RelationMetadataType,
RelationOnDeleteAction, RelationOnDeleteAction,
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids'; import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity'; import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
@ -16,16 +24,8 @@ import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objec
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity'; import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity'; import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity'; import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity'; import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity'; import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
import { WorkspaceIsDeprecated } from 'src/engine/twenty-orm/decorators/workspace-is-deprecated.decorator';
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
@WorkspaceEntity({ @WorkspaceEntity({
standardId: STANDARD_OBJECT_IDS.company, standardId: STANDARD_OBJECT_IDS.company,
@ -101,7 +101,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
type: FieldMetadataType.TEXT, type: FieldMetadataType.TEXT,
label: 'Address (deprecated) ', label: 'Address (deprecated) ',
description: description:
'Address of the company - deprecated in favor of new address field', "This standard field has been deprecated and migrated as a custom field. Please consider using the new 'address' field type.",
icon: 'IconMap', icon: 'IconMap',
}) })
@WorkspaceIsDeprecated() @WorkspaceIsDeprecated()