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:
Félix Malfait
2024-07-31 15:36:11 +02:00
committed by GitHub
parent defcee2a02
commit 80c0fc7ff1
239 changed files with 18418 additions and 8671 deletions

View File

@ -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,

View File

@ -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();
}
}
}
}
}

View File

@ -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);
}
}

View File

@ -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,
],
})

View File

@ -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}`,
);
}
}

View File

@ -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 {}

View File

@ -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();

View File

@ -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 = {

View File

@ -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);

View File

@ -30,6 +30,7 @@ export const mapFieldMetadataToGraphqlQuery = (
FieldMetadataType.MULTI_SELECT,
FieldMetadataType.POSITION,
FieldMetadataType.RAW_JSON,
FieldMetadataType.RICH_TEXT,
].includes(fieldType);
if (fieldIsSimpleValue) {

View File

@ -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': {

View File

@ -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' };

View File

@ -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()

View File

@ -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')

View File

@ -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 =

View File

@ -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],
};

View File

@ -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:

View File

@ -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 }],

View File

@ -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)
);
});
}
/**

View File

@ -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',

View File

@ -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();
};

View File

@ -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,
},
];
};

View File

@ -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,
},
];
};

View File

@ -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,
},
];
};

View File

@ -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();
}
}
};

View File

@ -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,
},
*/
],
};
};

View File

@ -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,
},
],
};
};

View File

@ -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,
},
*/
],
};
};

View File

@ -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,
},
],
};
};

View File

@ -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,
},
],
};
};

View File

@ -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,
},
],
};
};

View File

@ -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,
},
*/
],
};
};

View File

@ -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,
},
*/
],
};
};

View File

@ -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[],
);

View File

@ -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 {

View File

@ -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',

View File

@ -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',

View File

@ -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,
];
}

View File

@ -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,
];

View File

@ -144,6 +144,7 @@ export class WorkspaceSyncMetadataService {
await queryRunner.commitTransaction();
// Execute migrations
this.logger.log('Executing pending migrations');
await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations(
context.workspaceId,
);

View File

@ -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,

View File

@ -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,

View File

@ -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>;
}

View File

@ -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[]>;
}

View File

@ -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,

View File

@ -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,

View File

@ -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>;
}

View File

@ -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[]>;
}

View File

@ -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;
}

View File

@ -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]],

View File

@ -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,

View File

@ -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,