mirror of
https://github.com/twentyhq/twenty.git
synced 2024-12-25 13:02:15 +03:00
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:
parent
70f46242b4
commit
8e25a107fd
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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 {}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
|
Loading…
Reference in New Issue
Block a user