Activity as standard object (#6219)
In this PR I layout the first steps to migrate Activity to a traditional Standard objects Since this is a big transition, I'd rather split it into several deployments / PRs <img width="1512" alt="image" src="https://github.com/user-attachments/assets/012e2bbf-9d1b-4723-aaf6-269ef588b050"> --------- Co-authored-by: Charles Bochet <charles@twenty.com> Co-authored-by: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Co-authored-by: Weiko <corentin@twenty.com> Co-authored-by: Faisal-imtiyaz123 <142205282+Faisal-imtiyaz123@users.noreply.github.com> Co-authored-by: Prateek Jain <prateekj1171998@gmail.com>
This commit is contained in:
@ -8,6 +8,7 @@ import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-dem
|
||||
import { DataSeedWorkspaceCommand } from 'src/database/commands/data-seed-dev-workspace.command';
|
||||
import { ConfirmationQuestion } from 'src/database/commands/questions/confirmation.question';
|
||||
import { UpgradeTo0_23CommandModule } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.module';
|
||||
import { UpgradeVersionModule } from 'src/database/commands/upgrade-version/upgrade-version.module';
|
||||
import { WorkspaceAddTotalCountCommand } from 'src/database/commands/workspace-add-total-count.command';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { BillingSubscription } from 'src/engine/core-modules/billing/entities/billing-subscription.entity';
|
||||
@ -44,8 +45,8 @@ import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/worksp
|
||||
ObjectMetadataModule,
|
||||
DataSeedDemoWorkspaceModule,
|
||||
WorkspaceCacheVersionModule,
|
||||
// Upgrades
|
||||
UpgradeTo0_23CommandModule,
|
||||
UpgradeVersionModule,
|
||||
],
|
||||
providers: [
|
||||
DataSeedWorkspaceCommand,
|
||||
|
||||
@ -0,0 +1,464 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import { QueryRunner } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { TypeORMService } from 'src/database/typeorm/typeorm.service';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataService } from 'src/engine/metadata-modules/object-metadata/object-metadata.service';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||
import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view';
|
||||
import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view';
|
||||
import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view';
|
||||
import { WorkspaceStatusService } from 'src/engine/workspace-manager/workspace-status/services/workspace-status.service';
|
||||
import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/activity.workspace-entity';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
interface UpdateActivitiesCommandOptions {
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
type CoreLogicFunction = (params: {
|
||||
workspaceId: string;
|
||||
queryRunner?: QueryRunner;
|
||||
schema?: string;
|
||||
}) => Promise<void>;
|
||||
|
||||
@Command({
|
||||
name: 'migrate-0.23:update-activities-type',
|
||||
description: 'Migrate Activity object to Note and Task objects',
|
||||
})
|
||||
export class UpdateActivitiesCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(UpdateActivitiesCommand.name);
|
||||
|
||||
constructor(
|
||||
private readonly workspaceStatusService: WorkspaceStatusService,
|
||||
private readonly typeORMService: TypeORMService,
|
||||
private readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceCacheVersionService: WorkspaceCacheVersionService,
|
||||
private readonly objectMetadataService: ObjectMetadataService,
|
||||
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||
) {
|
||||
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: UpdateActivitiesCommandOptions,
|
||||
): Promise<void> {
|
||||
const updateActivities = async ({
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
schema,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
queryRunner: QueryRunner;
|
||||
schema: string;
|
||||
}): Promise<void> => {
|
||||
/***********************
|
||||
// Transfer Activities to NOTE + Tasks
|
||||
***********************/
|
||||
|
||||
const activityRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<ActivityWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'activity',
|
||||
);
|
||||
const noteRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<NoteWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'note',
|
||||
);
|
||||
const noteTargetRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<NoteTargetWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'noteTarget',
|
||||
);
|
||||
const taskRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<TaskWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'task',
|
||||
);
|
||||
const taskTargetRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<TaskTargetWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'taskTarget',
|
||||
);
|
||||
const timelineActivityRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<TimelineActivityWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'timelineActivity',
|
||||
);
|
||||
const attachmentRepository =
|
||||
await this.twentyORMGlobalManager.getRepositoryForWorkspace<AttachmentWorkspaceEntity>(
|
||||
workspaceId,
|
||||
'attachment',
|
||||
);
|
||||
|
||||
const objectMetadata =
|
||||
await this.objectMetadataService.findManyWithinWorkspace(workspaceId);
|
||||
|
||||
const noteObjectMetadataId = objectMetadata.find(
|
||||
(object) => object.nameSingular === 'note',
|
||||
)?.id;
|
||||
|
||||
const taskObjectMetadataId = objectMetadata.find(
|
||||
(object) => object.nameSingular === 'task',
|
||||
)?.id;
|
||||
|
||||
const activityObjectMetadataId = objectMetadata.find(
|
||||
(object) => object.nameSingular === 'activity',
|
||||
)?.id;
|
||||
|
||||
const activitiesToTransfer = await activityRepository.find({
|
||||
order: { createdAt: 'ASC' },
|
||||
relations: ['activityTargets'],
|
||||
});
|
||||
|
||||
for (let i = 0; i < activitiesToTransfer.length; i++) {
|
||||
const activity = activitiesToTransfer[i];
|
||||
|
||||
if (activity.type === 'Note') {
|
||||
const note = noteRepository.create({
|
||||
id: activity.id,
|
||||
title: activity.title,
|
||||
body: activity.body,
|
||||
createdAt: activity.createdAt,
|
||||
updatedAt: activity.updatedAt,
|
||||
position: i,
|
||||
});
|
||||
|
||||
await noteRepository.save(note);
|
||||
|
||||
if (activity.activityTargets && activity.activityTargets.length > 0) {
|
||||
const noteTargets = activity.activityTargets.map(
|
||||
(activityTarget) => {
|
||||
const { activityId, ...activityTargetData } = activityTarget;
|
||||
|
||||
return noteTargetRepository.create({
|
||||
noteId: activityId,
|
||||
...activityTargetData,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await noteTargetRepository.save(noteTargets);
|
||||
}
|
||||
|
||||
await timelineActivityRepository.update(
|
||||
{
|
||||
linkedObjectMetadataId: activityObjectMetadataId,
|
||||
linkedRecordId: activity.id,
|
||||
},
|
||||
{
|
||||
linkedObjectMetadataId: noteObjectMetadataId,
|
||||
},
|
||||
);
|
||||
|
||||
await attachmentRepository.update(
|
||||
{
|
||||
activityId: activity.id,
|
||||
},
|
||||
{
|
||||
activityId: null,
|
||||
noteId: activity.id,
|
||||
},
|
||||
);
|
||||
} else if (activity.type === 'Task') {
|
||||
const task = taskRepository.create({
|
||||
id: activity.id,
|
||||
title: activity.title,
|
||||
body: activity.body,
|
||||
status: activity.completedAt ? 'DONE' : 'TODO',
|
||||
dueAt: activity.dueAt,
|
||||
assigneeId: activity.assigneeId,
|
||||
position: i,
|
||||
createdAt: activity.createdAt,
|
||||
updatedAt: activity.updatedAt,
|
||||
});
|
||||
|
||||
await taskRepository.save(task);
|
||||
|
||||
if (activity.activityTargets && activity.activityTargets.length > 0) {
|
||||
const taskTargets = activity.activityTargets.map(
|
||||
(activityTarget) => {
|
||||
const { activityId, ...activityTargetData } = activityTarget;
|
||||
|
||||
return taskTargetRepository.create({
|
||||
taskId: activityId,
|
||||
...activityTargetData,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
await taskTargetRepository.save(taskTargets);
|
||||
}
|
||||
|
||||
await timelineActivityRepository.update(
|
||||
{
|
||||
linkedObjectMetadataId: activityObjectMetadataId,
|
||||
linkedRecordId: activity.id,
|
||||
},
|
||||
{
|
||||
linkedObjectMetadataId: taskObjectMetadataId,
|
||||
},
|
||||
);
|
||||
await attachmentRepository.update(
|
||||
{
|
||||
activityId: activity.id,
|
||||
},
|
||||
{
|
||||
activityId: null,
|
||||
taskId: activity.id,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
throw new Error(`Unknown activity type: ${activity.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Hack to make sure the command is indempotent and return if one of the view exists
|
||||
const viewExists = await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.select()
|
||||
.from(`${schema}.view`, 'view')
|
||||
.where('name = :name', { name: 'All Notes' })
|
||||
.getRawOne();
|
||||
|
||||
if (!viewExists) {
|
||||
await this.createViews(
|
||||
objectMetadata,
|
||||
queryRunner,
|
||||
schema,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return this.sharedBoilerplate(_passedParam, options, updateActivities);
|
||||
}
|
||||
|
||||
private async createViews(
|
||||
objectMetadata: ObjectMetadataEntity[],
|
||||
queryRunner: QueryRunner,
|
||||
schema: string,
|
||||
workspaceId: string,
|
||||
) {
|
||||
const objectMetadataMap = objectMetadata.reduce((acc, object) => {
|
||||
acc[object.standardId ?? ''] = {
|
||||
id: object.id,
|
||||
fields: object.fields.reduce((acc, field) => {
|
||||
acc[field.standardId ?? ''] = field.id;
|
||||
|
||||
return acc;
|
||||
}, {}),
|
||||
};
|
||||
|
||||
return acc;
|
||||
}, {}) as Record<string, ObjectMetadataEntity>;
|
||||
|
||||
const viewDefinitions = [
|
||||
await notesAllView(objectMetadataMap),
|
||||
await tasksAllView(objectMetadataMap),
|
||||
await tasksByStatusView(objectMetadataMap),
|
||||
];
|
||||
|
||||
const viewDefinitionsWithId = viewDefinitions.map((viewDefinition) => ({
|
||||
...viewDefinition,
|
||||
id: v4(),
|
||||
}));
|
||||
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schema}.view`, [
|
||||
'id',
|
||||
'name',
|
||||
'objectMetadataId',
|
||||
'type',
|
||||
'key',
|
||||
'position',
|
||||
'icon',
|
||||
'kanbanFieldMetadataId',
|
||||
])
|
||||
.values(
|
||||
viewDefinitionsWithId.map(
|
||||
({
|
||||
id,
|
||||
name,
|
||||
objectMetadataId,
|
||||
type,
|
||||
key,
|
||||
position,
|
||||
icon,
|
||||
kanbanFieldMetadataId,
|
||||
}) => ({
|
||||
id,
|
||||
name,
|
||||
objectMetadataId,
|
||||
type,
|
||||
key,
|
||||
position,
|
||||
icon,
|
||||
kanbanFieldMetadataId,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
for (const viewDefinition of viewDefinitionsWithId) {
|
||||
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schema}.viewField`, [
|
||||
'fieldMetadataId',
|
||||
'position',
|
||||
'isVisible',
|
||||
'size',
|
||||
'viewId',
|
||||
])
|
||||
.values(
|
||||
viewDefinition.fields.map((field) => ({
|
||||
fieldMetadataId: field.fieldMetadataId,
|
||||
position: field.position,
|
||||
isVisible: field.isVisible,
|
||||
size: field.size,
|
||||
viewId: viewDefinition.id,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
if (viewDefinition.filters && viewDefinition.filters.length > 0) {
|
||||
await queryRunner.manager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schema}.viewFilter`, [
|
||||
'fieldMetadataId',
|
||||
'displayValue',
|
||||
'operand',
|
||||
'value',
|
||||
'viewId',
|
||||
])
|
||||
.values(
|
||||
viewDefinition.filters.map((filter: any) => ({
|
||||
fieldMetadataId: filter.fieldMetadataId,
|
||||
displayValue: filter.displayValue,
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
viewId: viewDefinition.id,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
await this.workspaceCacheVersionService.incrementVersion(workspaceId);
|
||||
}
|
||||
}
|
||||
|
||||
// This is an attempt to do something more generic that could be reused in every command
|
||||
// Next step if it works well for a few command is to isolated it into a file so
|
||||
// it can be reused and not copy-pasted.
|
||||
async sharedBoilerplate(
|
||||
_passedParam: string[],
|
||||
options: UpdateActivitiesCommandOptions,
|
||||
coreLogic: CoreLogicFunction,
|
||||
) {
|
||||
const workspaceIds = options.workspaceId
|
||||
? [options.workspaceId]
|
||||
: await this.workspaceStatusService.getActiveWorkspaceIds();
|
||||
|
||||
if (!workspaceIds.length) {
|
||||
this.logger.log(chalk.yellow('No workspace found'));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Running command on ${workspaceIds.length} workspaces`),
|
||||
);
|
||||
|
||||
const requiresQueryRunner =
|
||||
coreLogic.toString().includes('queryRunner') ||
|
||||
coreLogic.toString().includes('schema');
|
||||
|
||||
for (const workspaceId of workspaceIds) {
|
||||
try {
|
||||
if (requiresQueryRunner) {
|
||||
await this.executeWithQueryRunner(workspaceId, coreLogic);
|
||||
} else {
|
||||
await coreLogic({ workspaceId });
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
chalk.green(`Running command on workspace ${workspaceId} done`),
|
||||
);
|
||||
} catch (error) {
|
||||
this.logger.error(
|
||||
`Migration failed for workspace ${workspaceId}: ${error.message}, ${error.stack}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Command completed!`));
|
||||
}
|
||||
|
||||
private async executeWithQueryRunner(
|
||||
workspaceId: string,
|
||||
coreLogic: CoreLogicFunction,
|
||||
) {
|
||||
const dataSourceMetadatas =
|
||||
await this.dataSourceService.getDataSourcesMetadataFromWorkspaceId(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
for (const dataSourceMetadata of dataSourceMetadatas) {
|
||||
const workspaceDataSource =
|
||||
await this.typeORMService.connectToDataSource(dataSourceMetadata);
|
||||
|
||||
if (workspaceDataSource) {
|
||||
const queryRunner = workspaceDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
await queryRunner.startTransaction();
|
||||
|
||||
try {
|
||||
await coreLogic({
|
||||
workspaceId,
|
||||
queryRunner,
|
||||
schema: dataSourceMetadata.schema,
|
||||
});
|
||||
await queryRunner.commitTransaction();
|
||||
} catch (error) {
|
||||
await queryRunner.rollbackTransaction();
|
||||
this.logger.log(
|
||||
chalk.red(`Running command on workspace ${workspaceId} failed`),
|
||||
);
|
||||
throw error;
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,6 +4,7 @@ import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/u
|
||||
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
|
||||
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
|
||||
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
|
||||
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
|
||||
|
||||
interface Options {
|
||||
workspaceId?: string;
|
||||
@ -19,6 +20,7 @@ export class UpgradeTo0_23Command extends CommandRunner {
|
||||
private readonly migrateDomainNameFromTextToLinks: MigrateDomainNameFromTextToLinksCommand,
|
||||
private readonly migrateMessageChannelSyncStatusEnumCommand: MigrateMessageChannelSyncStatusEnumCommand,
|
||||
private readonly setWorkspaceActivationStatusCommand: SetWorkspaceActivationStatusCommand,
|
||||
private readonly updateActivitiesCommand: UpdateActivitiesCommand,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@ -41,5 +43,6 @@ export class UpgradeTo0_23Command extends CommandRunner {
|
||||
options,
|
||||
);
|
||||
await this.setWorkspaceActivationStatusCommand.run(_passedParam, options);
|
||||
await this.updateActivitiesCommand.run(_passedParam, options);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/u
|
||||
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
|
||||
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
|
||||
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
|
||||
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
|
||||
import { UpgradeTo0_23Command } from 'src/database/commands/upgrade-version/0-23/0-23-upgrade-version.command';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.module';
|
||||
@ -13,6 +14,7 @@ import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-s
|
||||
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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
|
||||
import { ViewModule } from 'src/modules/view/view.module';
|
||||
@ -29,12 +31,14 @@ import { ViewModule } from 'src/modules/view/view.module';
|
||||
TypeORMModule,
|
||||
ViewModule,
|
||||
BillingModule,
|
||||
ObjectMetadataModule,
|
||||
],
|
||||
providers: [
|
||||
MigrateLinkFieldsToLinksCommand,
|
||||
MigrateDomainNameFromTextToLinksCommand,
|
||||
MigrateMessageChannelSyncStatusEnumCommand,
|
||||
SetWorkspaceActivationStatusCommand,
|
||||
UpdateActivitiesCommand,
|
||||
UpgradeTo0_23Command,
|
||||
],
|
||||
})
|
||||
|
||||
@ -0,0 +1,128 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { isUndefined } from '@nestjs/common/utils/shared.utils';
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { Command, CommandRunner, Option } from 'nest-commander';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command';
|
||||
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
|
||||
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
|
||||
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
|
||||
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
|
||||
|
||||
interface UpgradeCommandOptions {
|
||||
workspaceId?: string;
|
||||
}
|
||||
|
||||
type VersionUpgradeMap = {
|
||||
[version: string]: CommandRunner[];
|
||||
};
|
||||
|
||||
@Command({
|
||||
name: 'upgrade-version',
|
||||
description: 'Upgrade to a specific version',
|
||||
})
|
||||
export class UpgradeVersionCommand extends CommandRunner {
|
||||
private readonly logger = new Logger(UpgradeVersionCommand.name);
|
||||
|
||||
constructor(
|
||||
private readonly migrateLinkFieldsToLinksCommand: MigrateLinkFieldsToLinksCommand,
|
||||
private readonly migrateDomainNameFromTextToLinksCommand: MigrateDomainNameFromTextToLinksCommand,
|
||||
private readonly migrateMessageChannelSyncStatusEnumCommand: MigrateMessageChannelSyncStatusEnumCommand,
|
||||
private readonly setWorkspaceActivationStatusCommand: SetWorkspaceActivationStatusCommand,
|
||||
private readonly updateActivitiesCommand: UpdateActivitiesCommand,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@Option({
|
||||
flags: '-v, --version <version>',
|
||||
description: 'Version to upgrade to',
|
||||
required: true,
|
||||
})
|
||||
parseVersion(value: string): string {
|
||||
return value;
|
||||
}
|
||||
|
||||
@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(
|
||||
passedParams: string[],
|
||||
options: UpgradeCommandOptions & { version: string },
|
||||
): Promise<void> {
|
||||
const { version, ...upgradeOptions } = options;
|
||||
|
||||
const versionUpgradeMap = {
|
||||
'0.23': [
|
||||
this.migrateLinkFieldsToLinksCommand,
|
||||
this.migrateDomainNameFromTextToLinksCommand,
|
||||
this.migrateMessageChannelSyncStatusEnumCommand,
|
||||
this.setWorkspaceActivationStatusCommand,
|
||||
this.updateActivitiesCommand,
|
||||
],
|
||||
};
|
||||
|
||||
await this.validateVersions(version, versionUpgradeMap);
|
||||
|
||||
if (!versionUpgradeMap[version]) {
|
||||
throw new Error(
|
||||
`No migration commands found for version ${version}. This could mean there were no database changes required for this version.`,
|
||||
);
|
||||
}
|
||||
|
||||
for (const command of versionUpgradeMap[version]) {
|
||||
await command.run(passedParams, upgradeOptions);
|
||||
}
|
||||
|
||||
this.logger.log(chalk.green(`Successfully upgraded to version ${version}`));
|
||||
}
|
||||
|
||||
private async getCurrentCodeVersion(): Promise<string> {
|
||||
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
||||
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
||||
|
||||
return packageJson.version;
|
||||
}
|
||||
|
||||
private async validateVersions(
|
||||
targetVersion: string,
|
||||
versionUpgradeMap: VersionUpgradeMap,
|
||||
): Promise<void> {
|
||||
const currentVersion = await this.getCurrentCodeVersion();
|
||||
|
||||
const cleanCurrentVersion = semver.coerce(currentVersion);
|
||||
const cleanTargetVersion = semver.coerce(targetVersion);
|
||||
|
||||
if (!cleanCurrentVersion || !cleanTargetVersion) {
|
||||
throw new Error(
|
||||
`Invalid version format. Current Code: ${currentVersion}, Target: ${targetVersion}`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetMajorMinor = `${cleanTargetVersion.major}.${cleanTargetVersion.minor}`;
|
||||
|
||||
if (
|
||||
semver.gt(cleanTargetVersion, cleanCurrentVersion) &&
|
||||
isUndefined(versionUpgradeMap[targetMajorMinor])
|
||||
) {
|
||||
throw new Error(
|
||||
`Cannot upgrade to ${cleanTargetVersion}. Your current code version is ${cleanCurrentVersion}. Please update your codebase or upgrade your Docker image first.`,
|
||||
);
|
||||
}
|
||||
|
||||
this.logger.log(
|
||||
`Current Code Version: ${currentVersion}, Target: ${targetVersion}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,62 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
|
||||
import { DataSeedDemoWorkspaceModule } from 'src/database/commands/data-seed-demo-workspace/data-seed-demo-workspace.module';
|
||||
import { MigrateDomainNameFromTextToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-domain-to-links.command';
|
||||
import { MigrateLinkFieldsToLinksCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-link-fields-to-links.command';
|
||||
import { MigrateMessageChannelSyncStatusEnumCommand } from 'src/database/commands/upgrade-version/0-23/0-23-migrate-message-channel-sync-status-enum.command';
|
||||
import { SetWorkspaceActivationStatusCommand } from 'src/database/commands/upgrade-version/0-23/0-23-set-workspace-activation-status.command';
|
||||
import { UpdateActivitiesCommand } from 'src/database/commands/upgrade-version/0-23/0-23-update-activities.command';
|
||||
import { UpgradeVersionCommand } from 'src/database/commands/upgrade-version/upgrade-version.command';
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { BillingModule } from 'src/engine/core-modules/billing/billing.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 { WorkspaceModule } from 'src/engine/core-modules/workspace/workspace.module';
|
||||
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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
|
||||
import { WorkspaceCacheVersionModule } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceManagerModule } from 'src/engine/workspace-manager/workspace-manager.module';
|
||||
import { WorkspaceStatusModule } from 'src/engine/workspace-manager/workspace-status/workspace-manager.module';
|
||||
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
|
||||
import { ViewModule } from 'src/modules/view/view.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceManagerModule,
|
||||
DataSourceModule,
|
||||
TypeORMModule,
|
||||
TypeOrmModule.forFeature(
|
||||
[Workspace, BillingSubscription, FeatureFlagEntity],
|
||||
'core',
|
||||
),
|
||||
TypeOrmModule.forFeature(
|
||||
[FieldMetadataEntity, ObjectMetadataEntity],
|
||||
'metadata',
|
||||
),
|
||||
WorkspaceModule,
|
||||
WorkspaceDataSourceModule,
|
||||
WorkspaceSyncMetadataModule,
|
||||
WorkspaceStatusModule,
|
||||
ObjectMetadataModule,
|
||||
DataSeedDemoWorkspaceModule,
|
||||
WorkspaceCacheVersionModule,
|
||||
FieldMetadataModule,
|
||||
ViewModule,
|
||||
BillingModule,
|
||||
],
|
||||
providers: [
|
||||
UpgradeVersionCommand,
|
||||
MigrateLinkFieldsToLinksCommand,
|
||||
MigrateDomainNameFromTextToLinksCommand,
|
||||
MigrateMessageChannelSyncStatusEnumCommand,
|
||||
SetWorkspaceActivationStatusCommand,
|
||||
UpdateActivitiesCommand,
|
||||
],
|
||||
})
|
||||
export class UpgradeVersionModule {}
|
||||
@ -1,5 +1,7 @@
|
||||
import { DataSource } from 'typeorm';
|
||||
|
||||
import { WorkspaceActivationStatus } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
|
||||
const tableName = 'workspace';
|
||||
|
||||
export const seedWorkspaces = async (
|
||||
@ -16,6 +18,7 @@ export const seedWorkspaces = async (
|
||||
'domainName',
|
||||
'inviteHash',
|
||||
'logo',
|
||||
'activationStatus',
|
||||
])
|
||||
.orIgnore()
|
||||
.values([
|
||||
@ -25,6 +28,7 @@ export const seedWorkspaces = async (
|
||||
domainName: 'demo.dev',
|
||||
inviteHash: 'demo.dev-invite-hash',
|
||||
logo: 'https://twentyhq.github.io/placeholder-images/workspaces/apple-logo.png',
|
||||
activationStatus: WorkspaceActivationStatus.ACTIVE,
|
||||
},
|
||||
])
|
||||
.execute();
|
||||
|
||||
@ -189,6 +189,13 @@ const fieldRawJsonMock = {
|
||||
defaultValue: null,
|
||||
};
|
||||
|
||||
const fieldRichTextMock = {
|
||||
name: 'fieldRichText',
|
||||
type: FieldMetadataType.RICH_TEXT,
|
||||
isNullable: true,
|
||||
defaultValue: null,
|
||||
};
|
||||
|
||||
export const fields = [
|
||||
fieldUuidMock,
|
||||
fieldTextMock,
|
||||
@ -210,6 +217,7 @@ export const fields = [
|
||||
fieldPositionMock,
|
||||
fieldAddressMock,
|
||||
fieldRawJsonMock,
|
||||
fieldRichTextMock,
|
||||
];
|
||||
|
||||
export const objectMetadataItemMock = {
|
||||
|
||||
@ -16,25 +16,25 @@ import {
|
||||
|
||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
|
||||
import {
|
||||
StringFilterType,
|
||||
BigFloatFilterType,
|
||||
BooleanFilterType,
|
||||
DateFilterType,
|
||||
FloatFilterType,
|
||||
BooleanFilterType,
|
||||
BigFloatFilterType,
|
||||
RawJsonFilterType,
|
||||
StringFilterType,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input';
|
||||
import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum';
|
||||
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||
import {
|
||||
BigFloatScalarType,
|
||||
UUIDScalarType,
|
||||
} from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { PositionScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/position.scalar';
|
||||
import { RawJSONScalar } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars/raw-json.scalar';
|
||||
import { IDFilterType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/input/id-filter.input-type';
|
||||
import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-filter-type.util';
|
||||
import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
export interface TypeOptions<T = any> {
|
||||
nullable?: boolean;
|
||||
@ -74,6 +74,7 @@ export class TypeMapperService {
|
||||
[FieldMetadataType.NUMERIC, BigFloatScalarType],
|
||||
[FieldMetadataType.POSITION, PositionScalarType],
|
||||
[FieldMetadataType.RAW_JSON, RawJSONScalar],
|
||||
[FieldMetadataType.RICH_TEXT, GraphQLString],
|
||||
]);
|
||||
|
||||
return typeScalarMapping.get(fieldMetadataType);
|
||||
@ -109,6 +110,7 @@ export class TypeMapperService {
|
||||
[FieldMetadataType.NUMERIC, BigFloatFilterType],
|
||||
[FieldMetadataType.POSITION, FloatFilterType],
|
||||
[FieldMetadataType.RAW_JSON, RawJsonFilterType],
|
||||
[FieldMetadataType.RICH_TEXT, StringFilterType],
|
||||
]);
|
||||
|
||||
return typeFilterMapping.get(fieldMetadataType);
|
||||
@ -132,6 +134,7 @@ export class TypeMapperService {
|
||||
[FieldMetadataType.MULTI_SELECT, OrderByDirectionType],
|
||||
[FieldMetadataType.POSITION, OrderByDirectionType],
|
||||
[FieldMetadataType.RAW_JSON, OrderByDirectionType],
|
||||
[FieldMetadataType.RICH_TEXT, OrderByDirectionType],
|
||||
]);
|
||||
|
||||
return typeOrderByMapping.get(fieldMetadataType);
|
||||
|
||||
@ -30,6 +30,7 @@ export const mapFieldMetadataToGraphqlQuery = (
|
||||
FieldMetadataType.MULTI_SELECT,
|
||||
FieldMetadataType.POSITION,
|
||||
FieldMetadataType.RAW_JSON,
|
||||
FieldMetadataType.RICH_TEXT,
|
||||
].includes(fieldType);
|
||||
|
||||
if (fieldIsSimpleValue) {
|
||||
|
||||
@ -1,10 +1,10 @@
|
||||
import { computeSchemaComponents } from 'src/engine/core-modules/open-api/utils/components.utils';
|
||||
import {
|
||||
fields,
|
||||
objectMetadataItemMock,
|
||||
} from 'src/engine/api/__mocks__/object-metadata-item.mock';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { computeSchemaComponents } from 'src/engine/core-modules/open-api/utils/components.utils';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
|
||||
describe('computeSchemaComponents', () => {
|
||||
it('should test all field types', () => {
|
||||
@ -134,6 +134,9 @@ describe('computeSchemaComponents', () => {
|
||||
fieldRawJson: {
|
||||
type: 'object',
|
||||
},
|
||||
fieldRichText: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
'ObjectName with Relations': {
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
import { OpenAPIV3_1 } from 'openapi-types';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
import {
|
||||
computeDepthParameters,
|
||||
computeEndingBeforeParameters,
|
||||
@ -13,6 +10,9 @@ import {
|
||||
computeStartingAfterParameters,
|
||||
} from 'src/engine/core-modules/open-api/utils/parameters.utils';
|
||||
import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
type Property = OpenAPIV3_1.SchemaObject;
|
||||
|
||||
@ -26,6 +26,7 @@ const getFieldProperties = (type: FieldMetadataType): Property => {
|
||||
return { type: 'string', format: 'uuid' };
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.PHONE:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
return { type: 'string' };
|
||||
case FieldMetadataType.EMAIL:
|
||||
return { type: 'string', format: 'email' };
|
||||
|
||||
@ -33,6 +33,11 @@ export class FieldMetadataDefaultValueRawJson {
|
||||
value: object | null;
|
||||
}
|
||||
|
||||
export class FieldMetadataDefaultValueRichText {
|
||||
@ValidateIf((_object, value) => value !== null)
|
||||
@IsString()
|
||||
value: string | null;
|
||||
}
|
||||
export class FieldMetadataDefaultValueNumber {
|
||||
@ValidateIf((object, value) => value !== null)
|
||||
@IsNumber()
|
||||
@ -105,6 +110,7 @@ export class FieldMetadataDefaultValueNowFunction {
|
||||
@IsNotEmpty()
|
||||
value: typeof fieldMetadataDefaultValueFunctionName.NOW;
|
||||
}
|
||||
|
||||
export class FieldMetadataDefaultValueAddress {
|
||||
@ValidateIf((_object, value) => value !== null)
|
||||
@IsString()
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
import {
|
||||
Entity,
|
||||
Unique,
|
||||
PrimaryGeneratedColumn,
|
||||
Column,
|
||||
ManyToOne,
|
||||
JoinColumn,
|
||||
OneToOne,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
Relation,
|
||||
Entity,
|
||||
JoinColumn,
|
||||
ManyToOne,
|
||||
OneToMany,
|
||||
OneToOne,
|
||||
PrimaryGeneratedColumn,
|
||||
Relation,
|
||||
Unique,
|
||||
UpdateDateColumn,
|
||||
} from 'typeorm';
|
||||
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface';
|
||||
import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { IndexFieldMetadataEntity } from 'src/engine/metadata-modules/index-field-metadata/index-field-metadata.entity';
|
||||
|
||||
export enum FieldMetadataType {
|
||||
UUID = 'UUID',
|
||||
@ -42,6 +42,7 @@ export enum FieldMetadataType {
|
||||
POSITION = 'POSITION',
|
||||
ADDRESS = 'ADDRESS',
|
||||
RAW_JSON = 'RAW_JSON',
|
||||
RICH_TEXT = 'RICH_TEXT',
|
||||
}
|
||||
|
||||
@Entity('fieldMetadata')
|
||||
|
||||
@ -4,13 +4,14 @@ import {
|
||||
FieldMetadataDefaultValueCurrency,
|
||||
FieldMetadataDefaultValueDateTime,
|
||||
FieldMetadataDefaultValueFullName,
|
||||
FieldMetadataDefaultValueRawJson,
|
||||
FieldMetadataDefaultValueLink,
|
||||
FieldMetadataDefaultValueLinks,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
FieldMetadataDefaultValueNumber,
|
||||
FieldMetadataDefaultValueRawJson,
|
||||
FieldMetadataDefaultValueRichText,
|
||||
FieldMetadataDefaultValueString,
|
||||
FieldMetadataDefaultValueUuidFunction,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
FieldMetadataDefaultValueLinks,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
|
||||
@ -44,6 +45,7 @@ type FieldMetadataDefaultValueMapping = {
|
||||
[FieldMetadataType.SELECT]: FieldMetadataDefaultValueString;
|
||||
[FieldMetadataType.MULTI_SELECT]: FieldMetadataDefaultValueString;
|
||||
[FieldMetadataType.RAW_JSON]: FieldMetadataDefaultValueRawJson;
|
||||
[FieldMetadataType.RICH_TEXT]: FieldMetadataDefaultValueRichText;
|
||||
};
|
||||
|
||||
export type FieldMetadataClassValidation =
|
||||
|
||||
@ -6,23 +6,23 @@ import {
|
||||
FieldMetadataDefaultValue,
|
||||
} from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
FieldMetadataDefaultValueAddress,
|
||||
FieldMetadataDefaultValueBoolean,
|
||||
FieldMetadataDefaultValueCurrency,
|
||||
FieldMetadataDefaultValueDate,
|
||||
FieldMetadataDefaultValueDateTime,
|
||||
FieldMetadataDefaultValueFullName,
|
||||
FieldMetadataDefaultValueRawJson,
|
||||
FieldMetadataDefaultValueLink,
|
||||
FieldMetadataDefaultValueLinks,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
FieldMetadataDefaultValueNumber,
|
||||
FieldMetadataDefaultValueRawJson,
|
||||
FieldMetadataDefaultValueString,
|
||||
FieldMetadataDefaultValueStringArray,
|
||||
FieldMetadataDefaultValueNowFunction,
|
||||
FieldMetadataDefaultValueUuidFunction,
|
||||
FieldMetadataDefaultValueDate,
|
||||
FieldMetadataDefaultValueLinks,
|
||||
} from 'src/engine/metadata-modules/field-metadata/dtos/default-value.input';
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util';
|
||||
|
||||
export const defaultValueValidatorsMap = {
|
||||
@ -48,6 +48,7 @@ export const defaultValueValidatorsMap = {
|
||||
[FieldMetadataType.SELECT]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.MULTI_SELECT]: [FieldMetadataDefaultValueStringArray],
|
||||
[FieldMetadataType.ADDRESS]: [FieldMetadataDefaultValueAddress],
|
||||
[FieldMetadataType.RICH_TEXT]: [FieldMetadataDefaultValueString],
|
||||
[FieldMetadataType.RAW_JSON]: [FieldMetadataDefaultValueRawJson],
|
||||
[FieldMetadataType.LINKS]: [FieldMetadataDefaultValueLinks],
|
||||
};
|
||||
|
||||
@ -15,6 +15,7 @@ export const fieldMetadataTypeToColumnType = <Type extends FieldMetadataType>(
|
||||
case FieldMetadataType.UUID:
|
||||
return 'uuid';
|
||||
case FieldMetadataType.TEXT:
|
||||
case FieldMetadataType.RICH_TEXT:
|
||||
return 'text';
|
||||
case FieldMetadataType.PHONE:
|
||||
case FieldMetadataType.EMAIL:
|
||||
|
||||
@ -1,17 +1,17 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { WorkspaceColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-factory.interface';
|
||||
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
||||
import { WorkspaceColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-factory.interface';
|
||||
import { WorkspaceColumnActionOptions } from 'src/engine/metadata-modules/workspace-migration/interfaces/workspace-column-action-options.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
||||
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||
import {
|
||||
WorkspaceMigrationColumnAction,
|
||||
WorkspaceMigrationColumnActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory';
|
||||
import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory';
|
||||
import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory';
|
||||
import {
|
||||
WorkspaceMigrationException,
|
||||
WorkspaceMigrationExceptionCode,
|
||||
@ -72,6 +72,7 @@ export class WorkspaceMigrationFactory {
|
||||
[FieldMetadataType.NUMBER, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.POSITION, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.RAW_JSON, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.RICH_TEXT, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.BOOLEAN, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.DATE_TIME, { factory: this.basicColumnActionFactory }],
|
||||
[FieldMetadataType.DATE, { factory: this.basicColumnActionFactory }],
|
||||
|
||||
@ -24,13 +24,29 @@ export class WorkspaceMigrationService {
|
||||
public async getPendingMigrations(
|
||||
workspaceId: string,
|
||||
): Promise<WorkspaceMigrationEntity[]> {
|
||||
return await this.workspaceMigrationRepository.find({
|
||||
const pendingMigrations = await this.workspaceMigrationRepository.find({
|
||||
order: { createdAt: 'ASC', name: 'ASC' },
|
||||
where: {
|
||||
appliedAt: IsNull(),
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const typeOrder = { delete: 1, update: 2, create: 3 };
|
||||
|
||||
const getType = (name: string) =>
|
||||
name.split('-')[1] as keyof typeof typeOrder;
|
||||
|
||||
return pendingMigrations.sort((a, b) => {
|
||||
if (a.createdAt.getTime() !== b.createdAt.getTime()) {
|
||||
return a.createdAt.getTime() - b.createdAt.getTime();
|
||||
}
|
||||
|
||||
return (
|
||||
(typeOrder[getType(a.name)] || 4) - (typeOrder[getType(b.name)] || 4) ||
|
||||
a.name.localeCompare(b.name)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -3,17 +3,19 @@ import {
|
||||
RelationMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceCustomObject } from 'src/engine/twenty-orm/decorators/workspace-custom-object.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 { WorkspaceCustomObject } from 'src/engine/twenty-orm/decorators/workspace-custom-object.decorator';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { CUSTOM_OBJECT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceCustomObject()
|
||||
export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@ -51,6 +53,32 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceIsNullable()
|
||||
activityTargets: ActivityTargetWorkspaceEntity[];
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.noteTargets,
|
||||
label: 'Notes',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
description: (objectMetadata) =>
|
||||
`Notes tied to the ${objectMetadata.labelSingular}`,
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
noteTargets: NoteTargetWorkspaceEntity[];
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.taskTargets,
|
||||
label: 'Tasks',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
description: (objectMetadata) =>
|
||||
`Tasks tied to the ${objectMetadata.labelSingular}`,
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
taskTargets: TaskTargetWorkspaceEntity[];
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.favorites,
|
||||
label: 'Favorites',
|
||||
|
||||
@ -1,41 +0,0 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
export const pipelineStepPrefillData = async (
|
||||
entityManager: EntityManager,
|
||||
schemaName: string,
|
||||
) => {
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.pipelineStep`, ['name', 'color', 'position'])
|
||||
.orIgnore()
|
||||
.values([
|
||||
{
|
||||
name: 'NEW',
|
||||
color: 'red',
|
||||
position: 0,
|
||||
},
|
||||
{
|
||||
name: 'SCREENING',
|
||||
color: 'purple',
|
||||
position: 1,
|
||||
},
|
||||
{
|
||||
name: 'MEETING',
|
||||
color: 'sky',
|
||||
position: 2,
|
||||
},
|
||||
{
|
||||
name: 'PROPOSAL',
|
||||
color: 'turquoise',
|
||||
position: 3,
|
||||
},
|
||||
{
|
||||
name: 'CUSTOMER',
|
||||
color: 'yellow',
|
||||
position: 4,
|
||||
},
|
||||
])
|
||||
.returning('*')
|
||||
.execute();
|
||||
};
|
||||
@ -1,84 +0,0 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
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';
|
||||
|
||||
export const viewCompanyFields = (
|
||||
viewId: string,
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.domainName
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.accountOwner
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.employees
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.address
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,61 +0,0 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { OPPORTUNITY_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';
|
||||
|
||||
export const viewOpportunityFields = (
|
||||
viewId: string,
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.amount
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,104 +0,0 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
PERSON_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';
|
||||
|
||||
export const viewPersonFields = (
|
||||
viewId: string,
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.email
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.phone
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.city
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.jobTitle
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.xLink
|
||||
],
|
||||
viewId: viewId,
|
||||
position: 8,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
];
|
||||
};
|
||||
@ -1,21 +1,42 @@
|
||||
import { EntityManager } from 'typeorm';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { viewCompanyFields } from 'src/engine/workspace-manager/standard-objects-prefill-data/view-company-fields';
|
||||
import { viewOpportunityFields } from 'src/engine/workspace-manager/standard-objects-prefill-data/view-opportunity-fields';
|
||||
import { viewPersonFields } from 'src/engine/workspace-manager/standard-objects-prefill-data/view-person-fields';
|
||||
import { OPPORTUNITY_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 { activitiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/activities-all.view';
|
||||
import { companiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/companies-all.view';
|
||||
import { notesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/notes-all.view';
|
||||
import { opportunitiesAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunities-all.view';
|
||||
import { opportunitiesByStageView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/opportunity-by-stage.view';
|
||||
import { peopleAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/people-all.view';
|
||||
import { tasksAllView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-all.view';
|
||||
import { tasksByStatusView } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/tasks-by-status.view';
|
||||
|
||||
export const viewPrefillData = async (
|
||||
entityManager: EntityManager,
|
||||
schemaName: string,
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
const createdViews = await entityManager
|
||||
const viewDefinitions = [
|
||||
await companiesAllView(objectMetadataMap),
|
||||
await peopleAllView(objectMetadataMap),
|
||||
await opportunitiesAllView(objectMetadataMap),
|
||||
await opportunitiesByStageView(objectMetadataMap),
|
||||
await activitiesAllView(objectMetadataMap),
|
||||
await notesAllView(objectMetadataMap),
|
||||
await tasksAllView(objectMetadataMap),
|
||||
await tasksByStatusView(objectMetadataMap),
|
||||
];
|
||||
|
||||
const viewDefinitionsWithId = viewDefinitions.map((viewDefinition) => ({
|
||||
...viewDefinition,
|
||||
id: v4(),
|
||||
}));
|
||||
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.view`, [
|
||||
'id',
|
||||
'name',
|
||||
'objectMetadataId',
|
||||
'type',
|
||||
@ -24,74 +45,77 @@ export const viewPrefillData = async (
|
||||
'icon',
|
||||
'kanbanFieldMetadataId',
|
||||
])
|
||||
.values([
|
||||
{
|
||||
name: 'All Companies',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.company].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
kanbanFieldMetadataId: '',
|
||||
},
|
||||
{
|
||||
name: 'All People',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.person].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconUser',
|
||||
kanbanFieldMetadataId: '',
|
||||
},
|
||||
{
|
||||
name: 'All Opportunities',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconTargetArrow',
|
||||
kanbanFieldMetadataId: '',
|
||||
},
|
||||
{
|
||||
name: 'By Stage',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].id,
|
||||
type: 'kanban',
|
||||
key: null,
|
||||
position: 1,
|
||||
icon: 'IconLayoutKanban',
|
||||
kanbanFieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.stage
|
||||
],
|
||||
},
|
||||
])
|
||||
.values(
|
||||
viewDefinitionsWithId.map(
|
||||
({
|
||||
id,
|
||||
name,
|
||||
objectMetadataId,
|
||||
type,
|
||||
key,
|
||||
position,
|
||||
icon,
|
||||
kanbanFieldMetadataId,
|
||||
}) => ({
|
||||
id,
|
||||
name,
|
||||
objectMetadataId,
|
||||
type,
|
||||
key,
|
||||
position,
|
||||
icon,
|
||||
kanbanFieldMetadataId,
|
||||
}),
|
||||
),
|
||||
)
|
||||
.returning('*')
|
||||
.execute();
|
||||
|
||||
const viewIdMap = createdViews.raw.reduce((acc, view) => {
|
||||
acc[view.name] = view.id;
|
||||
for (const viewDefinition of viewDefinitionsWithId) {
|
||||
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.viewField`, [
|
||||
'fieldMetadataId',
|
||||
'position',
|
||||
'isVisible',
|
||||
'size',
|
||||
'viewId',
|
||||
])
|
||||
.values(
|
||||
viewDefinition.fields.map((field) => ({
|
||||
fieldMetadataId: field.fieldMetadataId,
|
||||
position: field.position,
|
||||
isVisible: field.isVisible,
|
||||
size: field.size,
|
||||
viewId: viewDefinition.id,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.viewField`, [
|
||||
'fieldMetadataId',
|
||||
'viewId',
|
||||
'position',
|
||||
'isVisible',
|
||||
'size',
|
||||
])
|
||||
.values([
|
||||
...viewCompanyFields(viewIdMap['All Companies'], objectMetadataMap),
|
||||
...viewPersonFields(viewIdMap['All People'], objectMetadataMap),
|
||||
...viewOpportunityFields(
|
||||
viewIdMap['All Opportunities'],
|
||||
objectMetadataMap,
|
||||
),
|
||||
...viewOpportunityFields(viewIdMap['By Stage'], objectMetadataMap),
|
||||
])
|
||||
.execute();
|
||||
if (viewDefinition.filters && viewDefinition.filters.length > 0) {
|
||||
await entityManager
|
||||
.createQueryBuilder()
|
||||
.insert()
|
||||
.into(`${schemaName}.viewFilter`, [
|
||||
'fieldMetadataId',
|
||||
'displayValue',
|
||||
'operand',
|
||||
'value',
|
||||
'viewId',
|
||||
])
|
||||
.values(
|
||||
viewDefinition.filters.map((filter: any) => ({
|
||||
fieldMetadataId: filter.fieldMetadataId,
|
||||
displayValue: filter.displayValue,
|
||||
operand: filter.operand,
|
||||
value: filter.value,
|
||||
viewId: viewDefinition.id,
|
||||
})),
|
||||
)
|
||||
.execute();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -0,0 +1,71 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
ACTIVITY_STANDARD_FIELD_IDS,
|
||||
BASE_OBJECT_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';
|
||||
|
||||
export const activitiesAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.activity].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 1,
|
||||
icon: 'IconList',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
ACTIVITY_STANDARD_FIELD_IDS.title
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
ACTIVITY_STANDARD_FIELD_IDS.type
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
ACTIVITY_STANDARD_FIELD_IDS.body
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
/*
|
||||
TODO: Add later, since we don't have real-time it probably doesn't work well?
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
*/
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,86 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
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';
|
||||
|
||||
export const companiesAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.company].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconList',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 180,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.domainName
|
||||
],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 100,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.accountOwner
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.employees
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.company].fields[
|
||||
COMPANY_STANDARD_FIELD_IDS.address
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 170,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,62 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
NOTE_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';
|
||||
|
||||
export const notesAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All Notes',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.note].id,
|
||||
type: 'table',
|
||||
key: null,
|
||||
position: 0,
|
||||
icon: 'IconNotes',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
NOTE_STANDARD_FIELD_IDS.title
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
NOTE_STANDARD_FIELD_IDS.body
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
/*
|
||||
TODO: Add later, since we don't have real-time it probably doesn't work well?
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.activity].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
*/
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,65 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { OPPORTUNITY_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';
|
||||
|
||||
export const opportunitiesAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconList',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.amount
|
||||
],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,68 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { OPPORTUNITY_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';
|
||||
|
||||
export const opportunitiesByStageView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'By Stage',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].id,
|
||||
type: 'kanban',
|
||||
key: null,
|
||||
position: 1,
|
||||
icon: 'IconLayoutKanban',
|
||||
kanbanFieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.stage
|
||||
],
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.amount
|
||||
],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.closeDate
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.opportunity].fields[
|
||||
OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,104 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
PERSON_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';
|
||||
|
||||
export const peopleAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.person].id,
|
||||
type: 'table',
|
||||
key: 'INDEX',
|
||||
position: 0,
|
||||
icon: 'IconList',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [],
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.name
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.email
|
||||
],
|
||||
position: 1,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.company
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.phone
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.city
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.jobTitle
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.linkedinLink
|
||||
],
|
||||
position: 7,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.person].fields[
|
||||
PERSON_STANDARD_FIELD_IDS.xLink
|
||||
],
|
||||
position: 8,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,99 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
TASK_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';
|
||||
|
||||
export const tasksAllView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'All Tasks',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].id,
|
||||
type: 'table',
|
||||
key: null,
|
||||
position: 0,
|
||||
icon: 'IconCheckbox',
|
||||
kanbanFieldMetadataId: '',
|
||||
filters: [] /* [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.type
|
||||
],
|
||||
displayValue: 'Task',
|
||||
operand: 'is',
|
||||
value: '["TASK"]',
|
||||
},
|
||||
],*/,
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.title
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.status
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.dueAt
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.assignee
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.body
|
||||
],
|
||||
position: 5,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
/*
|
||||
TODO: Add later, since we don't have real-time it probably doesn't work well?
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
*/
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1,93 @@
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import {
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS,
|
||||
TASK_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';
|
||||
|
||||
export const tasksByStatusView = async (
|
||||
objectMetadataMap: Record<string, ObjectMetadataEntity>,
|
||||
) => {
|
||||
return {
|
||||
name: 'By status',
|
||||
objectMetadataId: objectMetadataMap[STANDARD_OBJECT_IDS.task].id,
|
||||
type: 'kanban',
|
||||
key: null,
|
||||
position: 0,
|
||||
icon: 'IconLayoutKanban',
|
||||
kanbanFieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.status
|
||||
],
|
||||
filters: [] /* [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.type
|
||||
],
|
||||
displayValue: 'Task',
|
||||
operand: 'is',
|
||||
value: '["TASK"]',
|
||||
},
|
||||
],*/,
|
||||
fields: [
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.title
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.status
|
||||
],
|
||||
position: 2,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.dueAt
|
||||
],
|
||||
position: 3,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
TASK_STANDARD_FIELD_IDS.assignee
|
||||
],
|
||||
position: 4,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt
|
||||
],
|
||||
position: 6,
|
||||
isVisible: true,
|
||||
size: 150,
|
||||
},
|
||||
/*
|
||||
TODO: Add later, since we don't have real-time it probably doesn't work well?
|
||||
{
|
||||
fieldMetadataId:
|
||||
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[
|
||||
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
|
||||
],
|
||||
position: 0,
|
||||
isVisible: true,
|
||||
size: 210,
|
||||
},
|
||||
*/
|
||||
],
|
||||
};
|
||||
};
|
||||
@ -0,0 +1 @@
|
||||
// TODO
|
||||
@ -7,15 +7,15 @@ import {
|
||||
FieldMetadataType,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import {
|
||||
WorkspaceMigrationColumnActionType,
|
||||
WorkspaceMigrationEntity,
|
||||
WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationTableActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
import { WorkspaceMigrationFactory } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.factory';
|
||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
|
||||
|
||||
export interface FieldMetadataUpdate {
|
||||
current: FieldMetadataEntity;
|
||||
@ -60,17 +60,17 @@ export class WorkspaceMigrationFieldFactory {
|
||||
|
||||
switch (action) {
|
||||
case WorkspaceMigrationBuilderAction.CREATE:
|
||||
return this.createFieldMigration(
|
||||
return await this.createFieldMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity[],
|
||||
);
|
||||
case WorkspaceMigrationBuilderAction.UPDATE:
|
||||
return this.updateFieldMigration(
|
||||
return await this.updateFieldMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataUpdate[],
|
||||
);
|
||||
case WorkspaceMigrationBuilderAction.DELETE:
|
||||
return this.deleteFieldMigration(
|
||||
return await this.deleteFieldMigration(
|
||||
originalObjectMetadataMap,
|
||||
fieldMetadataCollectionOrFieldMetadataUpdateCollection as FieldMetadataEntity[],
|
||||
);
|
||||
|
||||
@ -9,27 +9,27 @@ import {
|
||||
TableUnique,
|
||||
} from 'typeorm';
|
||||
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import {
|
||||
WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationColumnAction,
|
||||
WorkspaceMigrationColumnActionType,
|
||||
WorkspaceMigrationColumnAlter,
|
||||
WorkspaceMigrationColumnCreate,
|
||||
WorkspaceMigrationColumnCreateRelation,
|
||||
WorkspaceMigrationColumnAlter,
|
||||
WorkspaceMigrationColumnDropRelation,
|
||||
WorkspaceMigrationTableActionType,
|
||||
WorkspaceMigrationForeignTable,
|
||||
WorkspaceMigrationIndexAction,
|
||||
WorkspaceMigrationIndexActionType,
|
||||
WorkspaceMigrationTableAction,
|
||||
WorkspaceMigrationTableActionType,
|
||||
} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service';
|
||||
import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.service';
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { WorkspaceMigrationEnumService } from 'src/engine/workspace-manager/workspace-migration-runner/services/workspace-migration-enum.service';
|
||||
import { convertOnDeleteActionToOnDelete } from 'src/engine/workspace-manager/workspace-migration-runner/utils/convert-on-delete-action-to-on-delete.util';
|
||||
|
||||
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
|
||||
import { WorkspaceMigrationTypeService } from './services/workspace-migration-type.service';
|
||||
import { customTableDefaultColumns } from './utils/custom-table-default-column.util';
|
||||
|
||||
@Injectable()
|
||||
export class WorkspaceMigrationRunnerService {
|
||||
|
||||
@ -40,6 +40,8 @@ export const ATTACHMENT_STANDARD_FIELD_IDS = {
|
||||
type: '20202020-a417-49b8-a40b-f6a7874caa0d',
|
||||
author: '20202020-6501-4ac5-a4ef-b2f8522ef6cd',
|
||||
activity: '20202020-b569-481b-a13f-9b94e47e54fe',
|
||||
task: '20202020-51e5-4621-9cf8-215487951c4b',
|
||||
note: '20202020-4f4b-4503-a6fc-6b982f3dffb5',
|
||||
person: '20202020-0158-4aa2-965c-5cdafe21ffa2',
|
||||
company: '20202020-ceab-4a28-b546-73b06b4c08d5',
|
||||
opportunity: '20202020-7374-499d-bea3-9354890755b5',
|
||||
@ -127,6 +129,8 @@ export const COMPANY_STANDARD_FIELD_IDS = {
|
||||
people: '20202020-3213-4ddf-9494-6422bcff8d7c',
|
||||
accountOwner: '20202020-95b8-4e10-9881-edb5d4765f9d',
|
||||
activityTargets: '20202020-c2a5-4c9b-9d9a-582bcd57fbc8',
|
||||
taskTargets: '20202020-cb17-4a61-8f8f-3be6730480de',
|
||||
noteTargets: '20202020-bae0-4556-a74a-a9c686f77a88',
|
||||
opportunities: '20202020-add3-4658-8e23-d70dccb6d0ec',
|
||||
favorites: '20202020-4d1d-41ac-b13b-621631298d55',
|
||||
attachments: '20202020-c1b5-4120-b0f0-987ca401ed53',
|
||||
@ -182,6 +186,8 @@ export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = {
|
||||
person: '20202020-c414-45b9-a60a-ac27aa96229f',
|
||||
company: '20202020-04ad-4221-a744-7a8278a5ce21',
|
||||
opportunity: '20202020-7664-4a35-a3df-580d389fd527',
|
||||
task: '20202020-b2f5-415c-9135-a31dfe49501b',
|
||||
note: '20202020-ec55-4135-8da5-3a20badc0156',
|
||||
workflow: '20202020-9e59-4030-aa27-55abd676c3c8',
|
||||
custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14',
|
||||
linkedRecordCachedName: '20202020-cfdb-4bef-bbce-a29f41230934',
|
||||
@ -251,6 +257,23 @@ export const MESSAGE_STANDARD_FIELD_IDS = {
|
||||
messageChannelMessageAssociations: '20202020-3cef-43a3-82c6-50e7cfbc9ae4',
|
||||
};
|
||||
|
||||
export const NOTE_STANDARD_FIELD_IDS = {
|
||||
position: '20202020-368d-4dc2-943f-ed8a49c7fdfb',
|
||||
title: '20202020-faeb-4c76-8ba6-ccbb0b4a965f',
|
||||
body: '20202020-e63d-4e70-95be-a78cd9abe7ef',
|
||||
noteTargets: '20202020-1f25-43fe-8b00-af212fdde823',
|
||||
attachments: '20202020-4986-4c92-bf19-39934b149b16',
|
||||
timelineActivities: '20202020-7030-42f8-929c-1a57b25d6bce',
|
||||
};
|
||||
|
||||
export const NOTE_TARGET_STANDARD_FIELD_IDS = {
|
||||
note: '20202020-57f3-4f50-9599-fc0f671df003',
|
||||
person: '20202020-38ca-4aab-92f5-8a605ca2e4c5',
|
||||
company: 'c500fbc0-d6f2-4982-a959-5a755431696c',
|
||||
opportunity: '20202020-4e42-417a-a705-76581c9ade79',
|
||||
custom: '20202020-3d12-4579-94ee-7117c1bad492',
|
||||
};
|
||||
|
||||
export const OPPORTUNITY_STANDARD_FIELD_IDS = {
|
||||
name: '20202020-8609-4f65-a2d9-44009eb422b5',
|
||||
amount: '20202020-583e-4642-8533-db761d5fa82f',
|
||||
@ -262,6 +285,8 @@ export const OPPORTUNITY_STANDARD_FIELD_IDS = {
|
||||
company: '20202020-cbac-457e-b565-adece5fc815f',
|
||||
favorites: '20202020-a1c2-4500-aaae-83ba8a0e827a',
|
||||
activityTargets: '20202020-220a-42d6-8261-b2102d6eab35',
|
||||
taskTargets: '20202020-59c0-4179-a208-4a255f04a5be',
|
||||
noteTargets: '20202020-dd3f-42d5-a382-db58aabf43d3',
|
||||
attachments: '20202020-87c7-4118-83d6-2f4031005209',
|
||||
timelineActivities: '20202020-30e2-421f-96c7-19c69d1cf631',
|
||||
};
|
||||
@ -279,6 +304,8 @@ export const PERSON_STANDARD_FIELD_IDS = {
|
||||
company: '20202020-e2f3-448e-b34c-2d625f0025fd',
|
||||
pointOfContactForOpportunities: '20202020-911b-4a7d-b67b-918aa9a5b33a',
|
||||
activityTargets: '20202020-dee7-4b7f-b50a-1f50bd3be452',
|
||||
taskTargets: '20202020-584b-4d3e-88b6-53ab1fa03c3a',
|
||||
noteTargets: '20202020-c8fc-4258-8250-15905d3fcfec',
|
||||
favorites: '20202020-4073-4117-9cf1-203bcdc91cbd',
|
||||
attachments: '20202020-cd97-451f-87fa-bcb789bdbf3a',
|
||||
messageParticipants: '20202020-498e-4c61-8158-fa04f0638334',
|
||||
@ -286,6 +313,26 @@ export const PERSON_STANDARD_FIELD_IDS = {
|
||||
timelineActivities: '20202020-a43e-4873-9c23-e522de906ce5',
|
||||
};
|
||||
|
||||
export const TASK_STANDARD_FIELD_IDS = {
|
||||
position: '20202020-7d47-4690-8a98-98b9a0c05dd8',
|
||||
title: '20202020-b386-4cb7-aa5a-08d4a4d92680',
|
||||
body: '20202020-ce13-43f4-8821-69388fe1fd26',
|
||||
dueAt: '20202020-fd99-40da-951b-4cb9a352fce3',
|
||||
status: '20202020-70bc-48f9-89c5-6aa730b151e0',
|
||||
taskTargets: '20202020-de9c-4d0e-a452-713d4a3e5fc7',
|
||||
attachments: '20202020-794d-4783-a8ff-cecdb15be139',
|
||||
assignee: '20202020-065a-4f42-a906-e20422c1753f',
|
||||
timelineActivities: '20202020-c778-4278-99ee-23a2837aee64',
|
||||
};
|
||||
|
||||
export const TASK_TARGET_STANDARD_FIELD_IDS = {
|
||||
task: '20202020-e881-457a-8758-74aaef4ae78a',
|
||||
person: '20202020-c8a0-4e85-a016-87e2349cfbec',
|
||||
company: '20202020-4703-4a4e-948c-487b0c60a92c',
|
||||
opportunity: '20202020-6cb2-4c01-a9a5-aca3dbc11d41',
|
||||
custom: '20202020-41c1-4c9a-8c75-be0971ef89af',
|
||||
};
|
||||
|
||||
export const VIEW_FIELD_STANDARD_FIELD_IDS = {
|
||||
fieldMetadataId: '20202020-135f-4c5b-b361-15f24870473c',
|
||||
isVisible: '20202020-e966-473c-9c18-f00d3347e0ba',
|
||||
@ -360,6 +407,7 @@ export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = {
|
||||
userId: '20202020-75a9-4dfc-bf25-2e4b43e89820',
|
||||
authoredActivities: '20202020-f139-4f13-a82f-a65a8d290a74',
|
||||
assignedActivities: '20202020-5c97-42b6-8ca9-c07622cbb33f',
|
||||
assignedTasks: '20202020-61dc-4a1c-99e8-38ebf8d2bbeb',
|
||||
favorites: '20202020-f3c1-4faf-b343-cf7681038757',
|
||||
accountOwnerForCompanies: '20202020-dc29-4bd4-a3c1-29eafa324bee',
|
||||
authoredAttachments: '20202020-000f-4947-917f-1b09851024fe',
|
||||
@ -379,6 +427,8 @@ export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
|
||||
name: '20202020-ba07-4ffd-ba63-009491f5749c',
|
||||
position: '20202020-c2bd-4e16-bb9a-c8b0411bf49d',
|
||||
activityTargets: '20202020-7f42-40ae-b96c-c8a61acc83bf',
|
||||
noteTargets: '20202020-01fd-4f37-99dc-9427a444018a',
|
||||
taskTargets: '20202020-0860-4566-b865-bff3c626c303',
|
||||
favorites: '20202020-a4a7-4686-b296-1c6c3482ee21',
|
||||
attachments: '20202020-8d59-46ca-b7b2-73d167712134',
|
||||
timelineActivities: '20202020-f1ef-4ba4-8f33-1a4577afa477',
|
||||
|
||||
@ -26,8 +26,12 @@ export const STANDARD_OBJECT_IDS = {
|
||||
messageParticipant: '20202020-a433-4456-aa2d-fd9cb26b774a',
|
||||
messageThread: '20202020-849a-4c3e-84f5-a25a7d802271',
|
||||
message: '20202020-3f6b-4425-80ab-e468899ab4b2',
|
||||
note: '20202020-0b00-45cd-b6f6-6cd806fc6804',
|
||||
noteTarget: '20202020-fff0-4b44-be82-bda313884400',
|
||||
opportunity: '20202020-9549-49dd-b2b2-883999db8938',
|
||||
person: '20202020-e674-48e5-a542-72570eee7213',
|
||||
task: '20202020-1ba1-48ba-bc83-ef7e5990ed10',
|
||||
taskTarget: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
timelineActivity: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
viewField: '20202020-4d19-4655-95bf-b2a04cf206d4',
|
||||
viewFilter: '20202020-6fb6-4631-aded-b7d67e952ec8',
|
||||
|
||||
@ -2,24 +2,24 @@ import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import {
|
||||
ComparatorAction,
|
||||
FieldComparatorResult,
|
||||
} from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/comparator.interface';
|
||||
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
|
||||
import { WorkspaceMigrationBuilderAction } from 'src/engine/workspace-manager/workspace-migration-builder/interfaces/workspace-migration-builder-action.interface';
|
||||
import { WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||
import { WorkspaceMigrationEntity } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity';
|
||||
import { WorkspaceFieldComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
import { WorkspaceMigrationFieldFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
import { computeStandardFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-fields.util';
|
||||
import { WorkspaceMigrationFieldFactory } from 'src/engine/workspace-manager/workspace-migration-builder/factories/workspace-migration-field.factory';
|
||||
import { WorkspaceFieldComparator } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/workspace-field.comparator';
|
||||
import { StandardFieldFactory } from 'src/engine/workspace-manager/workspace-sync-metadata/factories/standard-field.factory';
|
||||
import { WorkspaceMetadataUpdaterService } from 'src/engine/workspace-manager/workspace-sync-metadata/services/workspace-metadata-updater.service';
|
||||
import { standardObjectMetadataDefinitions } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-objects';
|
||||
import { WorkspaceSyncStorage } from 'src/engine/workspace-manager/workspace-sync-metadata/storage/workspace-sync.storage';
|
||||
import { computeStandardFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-standard-fields.util';
|
||||
import { mapObjectMetadataByUniqueIdentifier } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/sync-metadata.util';
|
||||
|
||||
@Injectable()
|
||||
@ -81,11 +81,11 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
|
||||
this.logger.log('Generating migrations');
|
||||
|
||||
const createFieldWorkspaceMigrations =
|
||||
const deleteFieldWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
metadataFieldUpdaterResult.createdFieldMetadataCollection,
|
||||
WorkspaceMigrationBuilderAction.CREATE,
|
||||
storage.fieldMetadataDeleteCollection,
|
||||
WorkspaceMigrationBuilderAction.DELETE,
|
||||
);
|
||||
|
||||
const updateFieldWorkspaceMigrations =
|
||||
@ -95,19 +95,19 @@ export class WorkspaceSyncFieldMetadataService {
|
||||
WorkspaceMigrationBuilderAction.UPDATE,
|
||||
);
|
||||
|
||||
const deleteFieldWorkspaceMigrations =
|
||||
const createFieldWorkspaceMigrations =
|
||||
await this.workspaceMigrationFieldFactory.create(
|
||||
originalObjectMetadataCollection,
|
||||
storage.fieldMetadataDeleteCollection,
|
||||
WorkspaceMigrationBuilderAction.DELETE,
|
||||
metadataFieldUpdaterResult.createdFieldMetadataCollection,
|
||||
WorkspaceMigrationBuilderAction.CREATE,
|
||||
);
|
||||
|
||||
this.logger.log('Saving migrations');
|
||||
|
||||
return [
|
||||
...createFieldWorkspaceMigrations,
|
||||
...updateFieldWorkspaceMigrations,
|
||||
...deleteFieldWorkspaceMigrations,
|
||||
...updateFieldWorkspaceMigrations,
|
||||
...createFieldWorkspaceMigrations,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@ -16,8 +16,12 @@ import { MessageChannelWorkspaceEntity } from 'src/modules/messaging/common/stan
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
||||
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.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 { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity';
|
||||
import { BehavioralEventWorkspaceEntity } from 'src/modules/timeline/standard-objects/behavioral-event.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
@ -65,4 +69,16 @@ export const standardObjectMetadataDefinitions = [
|
||||
MessageChannelWorkspaceEntity,
|
||||
MessageParticipantWorkspaceEntity,
|
||||
MessageChannelMessageAssociationWorkspaceEntity,
|
||||
NoteWorkspaceEntity,
|
||||
NoteTargetWorkspaceEntity,
|
||||
OpportunityWorkspaceEntity,
|
||||
PersonWorkspaceEntity,
|
||||
TaskWorkspaceEntity,
|
||||
TaskTargetWorkspaceEntity,
|
||||
TimelineActivityWorkspaceEntity,
|
||||
ViewFieldWorkspaceEntity,
|
||||
ViewFilterWorkspaceEntity,
|
||||
ViewSortWorkspaceEntity,
|
||||
ViewWorkspaceEntity,
|
||||
WebhookWorkspaceEntity,
|
||||
];
|
||||
|
||||
@ -144,6 +144,7 @@ export class WorkspaceSyncMetadataService {
|
||||
await queryRunner.commitTransaction();
|
||||
|
||||
// Execute migrations
|
||||
this.logger.log('Executing pending migrations');
|
||||
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
|
||||
context.workspaceId,
|
||||
);
|
||||
|
||||
@ -18,8 +18,10 @@ import { ATTACHMENT_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/work
|
||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||
import { ActivityWorkspaceEntity } from 'src/modules/activity/standard-objects/activity.workspace-entity';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.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 { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@ -91,6 +93,36 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceJoinColumn('activity')
|
||||
activityId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.task,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Task',
|
||||
description: 'Attachment task',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => TaskWorkspaceEntity,
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
task: Relation<TaskWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('task')
|
||||
taskId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.note,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Note',
|
||||
description: 'Attachment note',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteWorkspaceEntity,
|
||||
inverseSideFieldKey: 'attachments',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
note: Relation<NoteWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('note')
|
||||
noteId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.person,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
||||
@ -22,8 +22,10 @@ import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync
|
||||
import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-objects/activity-target.workspace-entity';
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.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 { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@ -169,8 +171,31 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
activityTargets: Relation<ActivityTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.taskTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Tasks',
|
||||
description: 'Tasks tied to the company',
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
taskTargets: Relation<TaskTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.noteTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
description: 'Notes tied to the company',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
noteTargets: Relation<NoteTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.opportunities,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.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 { NOTE_TARGET_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 { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.workspace-entity';
|
||||
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.noteTarget,
|
||||
namePlural: 'noteTargets',
|
||||
labelSingular: 'Note Target',
|
||||
labelPlural: 'Note Targets',
|
||||
description: 'A note target',
|
||||
icon: 'IconCheckbox',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
export class NoteTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.note,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Note',
|
||||
description: 'NoteTarget note',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteWorkspaceEntity,
|
||||
inverseSideFieldKey: 'noteTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
note: Relation<NoteWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('note')
|
||||
noteId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.person,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Person',
|
||||
description: 'NoteTarget person',
|
||||
icon: 'IconUser',
|
||||
inverseSideTarget: () => PersonWorkspaceEntity,
|
||||
inverseSideFieldKey: 'noteTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('person')
|
||||
personId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.company,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Company',
|
||||
description: 'NoteTarget company',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
inverseSideTarget: () => CompanyWorkspaceEntity,
|
||||
inverseSideFieldKey: 'noteTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('company')
|
||||
companyId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.opportunity,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Opportunity',
|
||||
description: 'NoteTarget opportunity',
|
||||
icon: 'IconTargetArrow',
|
||||
inverseSideTarget: () => OpportunityWorkspaceEntity,
|
||||
inverseSideFieldKey: 'noteTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('opportunity')
|
||||
opportunityId: string | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
argsFactory: (oppositeObjectMetadata) => ({
|
||||
standardId: NOTE_TARGET_STANDARD_FIELD_IDS.custom,
|
||||
name: oppositeObjectMetadata.nameSingular,
|
||||
label: oppositeObjectMetadata.labelSingular,
|
||||
description: `NoteTarget ${oppositeObjectMetadata.labelSingular}`,
|
||||
joinColumn: `${oppositeObjectMetadata.nameSingular}Id`,
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
}),
|
||||
inverseSideTarget: () => CustomWorkspaceEntity,
|
||||
inverseSideFieldKey: 'noteTargets',
|
||||
})
|
||||
custom: Relation<CustomWorkspaceEntity>;
|
||||
}
|
||||
@ -0,0 +1,96 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} 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 { 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 { NOTE_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 { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.note,
|
||||
namePlural: 'notes',
|
||||
labelSingular: 'Note',
|
||||
labelPlural: 'Notes',
|
||||
description: 'A note',
|
||||
icon: 'IconNotes',
|
||||
labelIdentifierStandardId: NOTE_STANDARD_FIELD_IDS.title,
|
||||
})
|
||||
export class NoteWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.position,
|
||||
type: FieldMetadataType.POSITION,
|
||||
label: 'Position',
|
||||
description: 'Note record position',
|
||||
icon: 'IconHierarchy2',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
position: number | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.title,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Title',
|
||||
description: 'Note title',
|
||||
icon: 'IconNotes',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.body,
|
||||
type: FieldMetadataType.RICH_TEXT,
|
||||
label: 'Body',
|
||||
description: 'Note body',
|
||||
icon: 'IconFilePencil',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
body: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.noteTargets,
|
||||
label: 'Targets',
|
||||
description: 'Note targets',
|
||||
icon: 'IconCheckbox',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
inverseSideTarget: () => NoteTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
noteTargets: Relation<NoteTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.attachments,
|
||||
label: 'Attachments',
|
||||
description: 'Note attachments',
|
||||
icon: 'IconFileImport',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
inverseSideTarget: () => AttachmentWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
attachments: Relation<AttachmentWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: NOTE_STANDARD_FIELD_IDS.timelineActivities,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Timeline Activities',
|
||||
description: 'Timeline Activities linked to the note.',
|
||||
icon: 'IconTimelineEvent',
|
||||
inverseSideTarget: () => TimelineActivityWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
}
|
||||
@ -21,7 +21,9 @@ import { ActivityTargetWorkspaceEntity } from 'src/modules/activity/standard-obj
|
||||
import { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
@ -152,8 +154,31 @@ export class OpportunityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
activityTargets: Relation<ActivityTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.taskTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Tasks',
|
||||
description: 'Tasks tied to the opportunity',
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
taskTargets: Relation<TaskTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.noteTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
description: 'Notes tied to the opportunity',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
noteTargets: Relation<NoteTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: OPPORTUNITY_STANDARD_FIELD_IDS.attachments,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
||||
@ -22,7 +22,9 @@ import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/co
|
||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { NoteTargetWorkspaceEntity } from 'src/modules/note/standard-objects/note-target.workspace-entity';
|
||||
import { OpportunityWorkspaceEntity } from 'src/modules/opportunity/standard-objects/opportunity.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
@ -159,8 +161,31 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
inverseSideTarget: () => ActivityTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
activityTargets: Relation<ActivityTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.taskTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Tasks',
|
||||
description: 'Tasks tied to the contact',
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
taskTargets: Relation<TaskTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.noteTargets,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Notes',
|
||||
description: 'Notes tied to the contact',
|
||||
icon: 'IconNotes',
|
||||
inverseSideTarget: () => NoteTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.CASCADE,
|
||||
})
|
||||
noteTargets: Relation<NoteTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.favorites,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
||||
@ -0,0 +1,103 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { RelationMetadataType } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { CustomWorkspaceEntity } from 'src/engine/twenty-orm/custom.workspace-entity';
|
||||
import { WorkspaceDynamicRelation } from 'src/engine/twenty-orm/decorators/workspace-dynamic-relation.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.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 { TASK_TARGET_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 { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.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 { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.taskTarget,
|
||||
namePlural: 'taskTargets',
|
||||
labelSingular: 'Task Target',
|
||||
labelPlural: 'Task Targets',
|
||||
description: 'An task target',
|
||||
icon: 'IconCheckbox',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
export class TaskTargetWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_TARGET_STANDARD_FIELD_IDS.task,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Task',
|
||||
description: 'TaskTarget task',
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskWorkspaceEntity,
|
||||
inverseSideFieldKey: 'taskTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
task: Relation<TaskWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('task')
|
||||
taskId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_TARGET_STANDARD_FIELD_IDS.person,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Person',
|
||||
description: 'TaskTarget person',
|
||||
icon: 'IconUser',
|
||||
inverseSideTarget: () => PersonWorkspaceEntity,
|
||||
inverseSideFieldKey: 'taskTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
person: Relation<PersonWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('person')
|
||||
personId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_TARGET_STANDARD_FIELD_IDS.company,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Company',
|
||||
description: 'TaskTarget company',
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
inverseSideTarget: () => CompanyWorkspaceEntity,
|
||||
inverseSideFieldKey: 'taskTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
company: Relation<CompanyWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('company')
|
||||
companyId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_TARGET_STANDARD_FIELD_IDS.opportunity,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Opportunity',
|
||||
description: 'TaskTarget opportunity',
|
||||
icon: 'IconTargetArrow',
|
||||
inverseSideTarget: () => OpportunityWorkspaceEntity,
|
||||
inverseSideFieldKey: 'taskTargets',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
opportunity: Relation<OpportunityWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('opportunity')
|
||||
opportunityId: string | null;
|
||||
|
||||
@WorkspaceDynamicRelation({
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
argsFactory: (oppositeObjectMetadata) => ({
|
||||
standardId: TASK_TARGET_STANDARD_FIELD_IDS.custom,
|
||||
name: oppositeObjectMetadata.nameSingular,
|
||||
label: oppositeObjectMetadata.labelSingular,
|
||||
description: `TaskTarget ${oppositeObjectMetadata.labelSingular}`,
|
||||
joinColumn: `${oppositeObjectMetadata.nameSingular}Id`,
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
}),
|
||||
inverseSideTarget: () => CustomWorkspaceEntity,
|
||||
inverseSideFieldKey: 'taskTargets',
|
||||
})
|
||||
custom: Relation<CustomWorkspaceEntity>;
|
||||
}
|
||||
@ -0,0 +1,150 @@
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
||||
import {
|
||||
RelationMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} 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 { 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 { TASK_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 { AttachmentWorkspaceEntity } from 'src/modules/attachment/standard-objects/attachment.workspace-entity';
|
||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.task,
|
||||
namePlural: 'tasks',
|
||||
labelSingular: 'Task',
|
||||
labelPlural: 'Tasks',
|
||||
description: 'A task',
|
||||
icon: 'IconCheckbox',
|
||||
labelIdentifierStandardId: TASK_STANDARD_FIELD_IDS.title,
|
||||
})
|
||||
export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.position,
|
||||
type: FieldMetadataType.POSITION,
|
||||
label: 'Position',
|
||||
description: 'Task record position',
|
||||
icon: 'IconHierarchy2',
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
position: number | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.title,
|
||||
type: FieldMetadataType.TEXT,
|
||||
label: 'Title',
|
||||
description: 'Task title',
|
||||
icon: 'IconNotes',
|
||||
})
|
||||
title: string;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.body,
|
||||
type: FieldMetadataType.RICH_TEXT,
|
||||
label: 'Body',
|
||||
description: 'Task body',
|
||||
icon: 'IconFilePencil',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
body: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.dueAt,
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
label: 'Due Date',
|
||||
description: 'Task due date',
|
||||
icon: 'IconCalendarEvent',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
dueAt: Date | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.status,
|
||||
type: FieldMetadataType.SELECT,
|
||||
label: 'Status',
|
||||
description: 'Task status',
|
||||
icon: 'IconCheck',
|
||||
defaultValue: "'TODO'",
|
||||
options: [
|
||||
{ value: 'TODO', label: 'To do', position: 0, color: 'sky' },
|
||||
{
|
||||
value: 'IN_PROGESS',
|
||||
label: 'In progress',
|
||||
position: 1,
|
||||
color: 'purple',
|
||||
},
|
||||
{
|
||||
value: 'DONE',
|
||||
label: 'Done',
|
||||
position: 1,
|
||||
color: 'green',
|
||||
},
|
||||
],
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
status: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.taskTargets,
|
||||
label: 'Targets',
|
||||
description: 'Task targets',
|
||||
icon: 'IconCheckbox',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
inverseSideTarget: () => TaskTargetWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
taskTargets: Relation<TaskTargetWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.attachments,
|
||||
label: 'Attachments',
|
||||
description: 'Task attachments',
|
||||
icon: 'IconFileImport',
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
inverseSideTarget: () => AttachmentWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
attachments: Relation<AttachmentWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.assignee,
|
||||
label: 'Assignee',
|
||||
description: 'Task assignee',
|
||||
icon: 'IconUserCircle',
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
inverseSideTarget: () => WorkspaceMemberWorkspaceEntity,
|
||||
inverseSideFieldKey: 'assignedTasks',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
assignee: Relation<WorkspaceMemberWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('assignee')
|
||||
assigneeId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.timelineActivities,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Timeline Activities',
|
||||
description: 'Timeline Activities linked to the task.',
|
||||
icon: 'IconTimelineEvent',
|
||||
inverseSideTarget: () => TimelineActivityWorkspaceEntity,
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
timelineActivities: Relation<TimelineActivityWorkspaceEntity[]>;
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
import { ObjectRecordBaseEvent } from 'src/engine/integrations/event-emitter/types/object-record.base.event';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
import { TimelineActivityService } from 'src/modules/timeline/services/timeline-activity.service';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||
import { TimelineActivityService } from 'src/modules/timeline/services/timeline-activity.service';
|
||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@Processor(MessageQueue.entityEventsToDbQueue)
|
||||
export class UpsertTimelineActivityFromInternalEvent {
|
||||
@ -37,8 +37,8 @@ export class UpsertTimelineActivityFromInternalEvent {
|
||||
// We ignore every that is not a LinkedObject or a Business Object
|
||||
if (
|
||||
data.objectMetadata.isSystem &&
|
||||
data.objectMetadata.nameSingular !== 'activityTarget' &&
|
||||
data.objectMetadata.nameSingular !== 'activity'
|
||||
data.objectMetadata.nameSingular !== 'noteTarget' &&
|
||||
data.objectMetadata.nameSingular !== 'taskTarget'
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -21,13 +21,18 @@ export class TimelineActivityService {
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
) {}
|
||||
|
||||
private targetObjects: Record<string, string> = {
|
||||
note: 'noteTarget',
|
||||
task: 'taskTarget',
|
||||
};
|
||||
|
||||
async upsertEvent(event: ObjectRecordBaseEvent) {
|
||||
const events = await this.transformEvent(event);
|
||||
|
||||
if (!events || events.length === 0) return;
|
||||
|
||||
for (const event of events) {
|
||||
return await this.timelineActivityRepository.upsertOne(
|
||||
await this.timelineActivityRepository.upsertOne(
|
||||
event.name,
|
||||
event.properties,
|
||||
event.objectName ?? event.objectMetadata.nameSingular,
|
||||
@ -44,12 +49,21 @@ export class TimelineActivityService {
|
||||
private async transformEvent(
|
||||
event: ObjectRecordBaseEvent,
|
||||
): Promise<TransformedEvent[]> {
|
||||
if (['note', 'task'].includes(event.objectMetadata.nameSingular)) {
|
||||
const linkedObjects = await this.handleLinkedObjects(event);
|
||||
|
||||
// 2 timelines, one for the linked object and one for the task/note
|
||||
if (linkedObjects?.length > 0) return [...linkedObjects, event];
|
||||
}
|
||||
|
||||
if (
|
||||
['activity', 'messageParticipant', 'activityTarget'].includes(
|
||||
['noteTarget', 'taskTarget', 'messageParticipant'].includes(
|
||||
event.objectMetadata.nameSingular,
|
||||
)
|
||||
) {
|
||||
return await this.handleLinkedObjects(event);
|
||||
const linkedObjects = await this.handleLinkedObjects(event);
|
||||
|
||||
return linkedObjects;
|
||||
}
|
||||
|
||||
return [event];
|
||||
@ -61,10 +75,17 @@ export class TimelineActivityService {
|
||||
);
|
||||
|
||||
switch (event.objectMetadata.nameSingular) {
|
||||
case 'activityTarget':
|
||||
return this.processActivityTarget(event, dataSourceSchema);
|
||||
case 'activity':
|
||||
return this.processActivity(event, dataSourceSchema);
|
||||
case 'noteTarget':
|
||||
return this.processActivityTarget(event, dataSourceSchema, 'note');
|
||||
case 'taskTarget':
|
||||
return this.processActivityTarget(event, dataSourceSchema, 'task');
|
||||
case 'note':
|
||||
case 'task':
|
||||
return this.processActivity(
|
||||
event,
|
||||
dataSourceSchema,
|
||||
event.objectMetadata.nameSingular,
|
||||
);
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
@ -73,17 +94,18 @@ export class TimelineActivityService {
|
||||
private async processActivity(
|
||||
event: ObjectRecordBaseEvent,
|
||||
dataSourceSchema: string,
|
||||
activityType: string,
|
||||
) {
|
||||
const activityTargets =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."activityTarget"
|
||||
WHERE "activityId" = $1`,
|
||||
`SELECT * FROM ${dataSourceSchema}."${this.targetObjects[activityType]}"
|
||||
WHERE "${activityType}Id" = $1`,
|
||||
[event.recordId],
|
||||
event.workspaceId,
|
||||
);
|
||||
|
||||
const activity = await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."activity"
|
||||
`SELECT * FROM ${dataSourceSchema}."${activityType}"
|
||||
WHERE "id" = $1`,
|
||||
[event.recordId],
|
||||
event.workspaceId,
|
||||
@ -96,7 +118,10 @@ export class TimelineActivityService {
|
||||
.map((activityTarget) => {
|
||||
const targetColumn: string[] = Object.entries(activityTarget)
|
||||
.map(([columnName, columnValue]: [string, string]) => {
|
||||
if (columnName === 'activityId' || !columnName.endsWith('Id'))
|
||||
if (
|
||||
columnName === activityType + 'Id' ||
|
||||
!columnName.endsWith('Id')
|
||||
)
|
||||
return;
|
||||
if (columnValue === null) return;
|
||||
|
||||
@ -108,7 +133,7 @@ export class TimelineActivityService {
|
||||
|
||||
return {
|
||||
...event,
|
||||
name: activity[0].type.toLowerCase() + '.' + event.name.split('.')[1],
|
||||
name: 'linked-' + event.name,
|
||||
objectName: targetColumn[0].replace(/Id$/, ''),
|
||||
recordId: activityTarget[targetColumn[0]],
|
||||
linkedRecordCachedName: activity[0].title,
|
||||
@ -122,10 +147,11 @@ export class TimelineActivityService {
|
||||
private async processActivityTarget(
|
||||
event: ObjectRecordBaseEvent,
|
||||
dataSourceSchema: string,
|
||||
activityType: string,
|
||||
) {
|
||||
const activityTarget =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."activityTarget"
|
||||
`SELECT * FROM ${dataSourceSchema}."${this.targetObjects[activityType]}"
|
||||
WHERE "id" = $1`,
|
||||
[event.recordId],
|
||||
event.workspaceId,
|
||||
@ -134,7 +160,7 @@ export class TimelineActivityService {
|
||||
if (activityTarget.length === 0) return;
|
||||
|
||||
const activity = await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."activity"
|
||||
`SELECT * FROM ${dataSourceSchema}."${activityType}"
|
||||
WHERE "id" = $1`,
|
||||
[activityTarget[0].activityId],
|
||||
event.workspaceId,
|
||||
@ -143,12 +169,13 @@ export class TimelineActivityService {
|
||||
if (activity.length === 0) return;
|
||||
|
||||
const activityObjectMetadataId = event.objectMetadata.fields.find(
|
||||
(field) => field.name === 'activity',
|
||||
(field) => field.name === activityType,
|
||||
)?.toRelationMetadata?.fromObjectMetadataId;
|
||||
|
||||
const targetColumn: string[] = Object.entries(activityTarget[0])
|
||||
.map(([columnName, columnValue]: [string, string]) => {
|
||||
if (columnName === 'activityId' || !columnName.endsWith('Id')) return;
|
||||
if (columnName === activityType + 'Id' || !columnName.endsWith('Id'))
|
||||
return;
|
||||
if (columnValue === null) return;
|
||||
|
||||
return columnName;
|
||||
@ -160,7 +187,7 @@ export class TimelineActivityService {
|
||||
return [
|
||||
{
|
||||
...event,
|
||||
name: activity[0].type.toLowerCase() + '.' + event.name.split('.')[1],
|
||||
name: 'linked-' + event.name,
|
||||
properties: {},
|
||||
objectName: targetColumn[0].replace(/Id$/, ''),
|
||||
recordId: activityTarget[0][targetColumn[0]],
|
||||
|
||||
@ -17,8 +17,10 @@ import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-re
|
||||
import { TIMELINE_ACTIVITY_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 { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||
import { NoteWorkspaceEntity } from 'src/modules/note/standard-objects/note.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 { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { WorkflowWorkspaceEntity } from 'src/modules/workflow/common/standard-objects/workflow.workspace-entity';
|
||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||
|
||||
@ -153,6 +155,36 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
@WorkspaceJoinColumn('opportunity')
|
||||
opportunityId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.note,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Note',
|
||||
description: 'Event note',
|
||||
icon: 'IconTargetArrow',
|
||||
inverseSideTarget: () => NoteWorkspaceEntity,
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
note: Relation<NoteWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('note')
|
||||
noteId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.task,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
label: 'Task',
|
||||
description: 'Event task',
|
||||
icon: 'IconTargetArrow',
|
||||
inverseSideTarget: () => TaskWorkspaceEntity,
|
||||
inverseSideFieldKey: 'timelineActivities',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
task: Relation<TaskWorkspaceEntity> | null;
|
||||
|
||||
@WorkspaceJoinColumn('task')
|
||||
taskId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.workflow,
|
||||
type: RelationMetadataType.MANY_TO_ONE,
|
||||
|
||||
@ -26,6 +26,7 @@ import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/com
|
||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
import { TaskWorkspaceEntity } from 'src/modules/task/standard-objects/task.workspace-entity';
|
||||
import { AuditLogWorkspaceEntity } from 'src/modules/timeline/standard-objects/audit-log.workspace-entity';
|
||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||
|
||||
@ -146,6 +147,18 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
assignedActivities: Relation<ActivityWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.assignedTasks,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
label: 'Assigned tasks',
|
||||
description: 'Tasks assigned to the workspace member',
|
||||
icon: 'IconCheckbox',
|
||||
inverseSideTarget: () => TaskWorkspaceEntity,
|
||||
inverseSideFieldKey: 'assignee',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
})
|
||||
assignedTasks: Relation<TaskWorkspaceEntity[]>;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.favorites,
|
||||
type: RelationMetadataType.ONE_TO_MANY,
|
||||
|
||||
Reference in New Issue
Block a user