Metadata api create entity in workspace custom app (#15911)

# Introduction
Cleaner and fewer scope version of
https://github.com/twentyhq/twenty/pull/15745 ( removed sync-metadata
hack through, too ambitious migration and upgrade )

Please note that this PR won't have any interaction with the existing
sync-metadata
Which mean that the sync metadata does not update the standard entities
applicationId and universalIdentifier, and it won't we will deprecate it
on favor of a workspace migration aka twenty-standard app installation

## API Metadata
Any operation going through the api metadata nows automatically scope
the related entity to the workspace custom application instance. (
optionally passing an applicationId to allow current hacky implem of app
sync service )

We need to either ignore the tests or remove the cli status check from
the blocking status badges for a PR to be merged

## New workspace
Already handled in previous
https://github.com/twentyhq/twenty/pull/15625, when a workspace is
created it gets created a twenty standard and custom workspace instance

All his views and permissions will be prefilled to the its twenty
standard app instance with a specific universalIdentifier

## New universalIdentifier
At the contrary as before with standardIds, universalIdentifier are
unique for a given workspace
This means that createdAt field of both object company and opportunity
will have a unique universalIdentifier whereas they share the same
standardId

## FlatApplication
Introduced the flatApplication and cache. Will migrate existing
`MetadataName` to be `SyncableMetadataName` in a following PR

## What's next
Next we will describe a twenty standard app configuration as json that
will be used to generate a workspace migration that will be run instead
of the sync metadata, in a nutshell we aim to deprecated the sync
metadata
So we can standardize any entity to have a non nullable applicationId
and universalIdentifier

## Upgrade command
Introduced an upgrade command that will create a custom workspace
instance for any workspace that do not have one in order to align with
the new behavior when creating a new workspace
This commit is contained in:
Paul Rastoin
2025-11-23 22:35:17 +01:00
committed by GitHub
parent 4848bc03f3
commit f9ab09c404
99 changed files with 3458 additions and 730 deletions
@@ -9,6 +9,7 @@ import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
type RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
@@ -37,6 +38,7 @@ export class SeedDashboardViewCommand extends ActiveOrSuspendedWorkspacesMigrati
private readonly dataSourceRepository: Repository<DataSourceEntity>,
@InjectRepository(ViewEntity)
private readonly viewRepository: Repository<ViewEntity>,
private readonly applicationService: ApplicationService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
@@ -59,7 +61,17 @@ export class SeedDashboardViewCommand extends ActiveOrSuspendedWorkspacesMigrati
);
}
const views = [dashboardsAllView([dashboardObjectMetadata], true)];
const { twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{ workspaceId },
);
const views = [
dashboardsAllView({
objectMetadataItems: [dashboardObjectMetadata],
useCoreNaming: true,
twentyStandardFlatApplication,
}),
];
const schema = await this.dataSourceRepository.findOne({
where: {
@@ -99,7 +111,12 @@ export class SeedDashboardViewCommand extends ActiveOrSuspendedWorkspacesMigrati
await queryRunner.connect();
const createdViews = await createCoreViews(queryRunner, workspaceId, views);
const createdViews = await createCoreViews(
queryRunner,
workspaceId,
views,
twentyStandardFlatApplication,
);
await prefillWorkspaceFavorites(
createdViews.map((view) => view.id),
@@ -11,9 +11,12 @@ import { MigrateAttachmentTypeToFileCategoryCommand } from 'src/database/command
import { MigrateChannelPartialFullSyncStagesCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-channel-partial-full-sync-stages.command';
import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-regenerate-search-vectors.command';
import { SeedDashboardViewCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-seed-dashboard-view.command';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
@@ -30,10 +33,13 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
IndexMetadataEntity,
ViewEntity,
DataSourceEntity,
ApplicationEntity,
]),
WorkspaceSchemaManagerModule,
WorkspaceCacheStorageModule,
ObjectMetadataModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
ApplicationModule,
],
providers: [
MigrateChannelPartialFullSyncStagesCommand,
@@ -9,9 +9,9 @@ import {
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
@Command({
name: 'upgrade:1-11:create-twenty-standard-application',
@@ -0,0 +1,65 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Command } from 'nest-commander';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
type RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
@Command({
name: 'upgrade:1-12:create-workspace-custom-application',
description:
'Create workspace-custom application for workspaces that do not have them',
})
export class CreateWorkspaceCustomApplicationCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
constructor(
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly applicationService: ApplicationService,
) {
super(workspaceRepository, twentyORMGlobalManager);
}
override async runOnWorkspace({
workspaceId,
}: RunOnWorkspaceArgs): Promise<void> {
this.logger.log(
`Checking standard applications for workspace ${workspaceId}`,
);
const workspace = await this.workspaceRepository.findOne({
where: { id: workspaceId },
});
if (!isDefined(workspace)) {
throw new Error(`${workspaceId} workspace not found`);
}
if (isDefined(workspace.workspaceCustomApplicationId)) {
this.logger.log(
`${workspaceId} skipping custom workspace application creation as already exists`,
);
return;
}
const customWorkspaceApplication =
await this.applicationService.createWorkspaceCustomApplication({
workspaceId,
workspaceDisplayName: workspace.displayName,
});
await this.workspaceRepository.update(workspace.id, {
workspaceCustomApplicationId: customWorkspaceApplication.id,
});
this.logger.log(`Successfully create workspace custom application`);
}
}
@@ -1,18 +1,17 @@
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Command } from 'nest-commander';
import { Repository } from 'typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import {
ActiveOrSuspendedWorkspacesMigrationCommandRunner,
RunOnWorkspaceArgs,
} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
// Note: this command needs to be run after CreateWorkspaceCustomApplicationCommand
@Command({
name: 'upgrade:1-12:set-standard-application-not-uninstallable',
description: 'Set canBeUninstalled flag to false for standard applications',
@@ -22,8 +21,6 @@ export class SetStandardApplicationNotUninstallableCommand extends ActiveOrSuspe
@InjectRepository(WorkspaceEntity)
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
private readonly applicationService: ApplicationService,
) {
super(workspaceRepository, twentyORMGlobalManager);
@@ -35,36 +32,31 @@ export class SetStandardApplicationNotUninstallableCommand extends ActiveOrSuspe
}: RunOnWorkspaceArgs): Promise<void> {
try {
this.logger.log(
`Checking standard applications for workspace ${workspaceId}`,
`Checking workspace applications for workspace ${workspaceId}`,
);
const existingApplications = await this.applicationRepository.find({
where: [
const { twentyStandardFlatApplication, workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
universalIdentifier:
TWENTY_STANDARD_APPLICATION.universalIdentifier,
},
{
workspaceId,
description: 'Workspace custom application',
sourcePath: 'workspace-custom',
},
],
});
);
if (options.dryRun) {
return;
}
for (const existingApplication of existingApplications) {
for (const existingApplication of [
twentyStandardFlatApplication,
workspaceCustomFlatApplication,
]) {
await this.applicationService.update(existingApplication.id, {
canBeUninstalled: false,
});
}
this.logger.log(`Successfully updated standard applications`);
this.logger.log(`Successfully updated workspace application`);
} catch (e) {
this.logger.error(`Failed to update standard applications`, e);
this.logger.error(`Failed to update workspace application`, e);
}
}
}
@@ -1,19 +1,25 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { CreateWorkspaceCustomApplicationCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-create-workspace-custom-application.command';
import { SetStandardApplicationNotUninstallableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-set-standard-application-not-uninstallable.command';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceSchemaManagerModule } from 'src/engine/twenty-orm/workspace-schema-manager/workspace-schema-manager.module';
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, ApplicationEntity]),
TypeOrmModule.forFeature([WorkspaceEntity]),
WorkspaceSchemaManagerModule,
ApplicationModule,
],
providers: [SetStandardApplicationNotUninstallableCommand],
exports: [SetStandardApplicationNotUninstallableCommand],
providers: [
CreateWorkspaceCustomApplicationCommand,
SetStandardApplicationNotUninstallableCommand,
],
exports: [
CreateWorkspaceCustomApplicationCommand,
SetStandardApplicationNotUninstallableCommand,
],
})
export class V1_12_UpgradeVersionCommandModule {}
@@ -3,10 +3,10 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { V1_10_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-10/1-10-upgrade-version-command.module';
import { V1_11_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-11/1-11-upgrade-version-command.module';
import { V1_12_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-12/1-12-upgrade-version-command.module';
import { V1_6_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-6/1-6-upgrade-version-command.module';
import { V1_7_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-7/1-7-upgrade-version-command.module';
import { V1_8_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-8/1-8-upgrade-version-command.module';
import { V1_12_UpgradeVersionCommandModule } from 'src/database/commands/upgrade-version-command/1-12/1-12-upgrade-version-command.module';
import { UpgradeCommand } from 'src/database/commands/upgrade-version-command/upgrade.command';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceSyncMetadataModule } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.module';
@@ -18,10 +18,11 @@ import { MigrateAttachmentAuthorToCreatedByCommand } from 'src/database/commands
import { MigrateAttachmentTypeToFileCategoryCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command';
import { MigrateChannelPartialFullSyncStagesCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-migrate-channel-partial-full-sync-stages.command';
import { RegenerateSearchVectorsCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-regenerate-search-vectors.command';
import { SeedDashboardViewCommand } from 'src/database/commands/upgrade-version-command/1-10/1-10-seed-dashboard-view.command';
import { CleanOrphanedRoleTargetsCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-clean-orphaned-role-targets.command';
import { CleanOrphanedUserWorkspacesCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-clean-orphaned-user-workspaces.command';
import { CreateTwentyStandardApplicationCommand } from 'src/database/commands/upgrade-version-command/1-11/1-11-create-twenty-standard-application.command';
import { CreateWorkspaceCustomApplicationCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-create-workspace-custom-application.command';
import { SetStandardApplicationNotUninstallableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-set-standard-application-not-uninstallable.command';
import { FixLabelIdentifierPositionAndVisibilityCommand } from 'src/database/commands/upgrade-version-command/1-6/1-6-fix-label-identifier-position-and-visibility.command';
import { BackfillWorkflowManualTriggerAvailabilityCommand } from 'src/database/commands/upgrade-version-command/1-7/1-7-backfill-workflow-manual-trigger-availability.command';
import { DeduplicateUniqueFieldsCommand } from 'src/database/commands/upgrade-version-command/1-8/1-8-deduplicate-unique-fields.command';
@@ -33,7 +34,6 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
import { SyncWorkspaceMetadataCommand } from 'src/engine/workspace-manager/workspace-sync-metadata/commands/sync-workspace-metadata.command';
import { SetStandardApplicationNotUninstallableCommand } from 'src/database/commands/upgrade-version-command/1-12/1-12-set-standard-application-not-uninstallable.command';
@Command({
name: 'upgrade',
@@ -70,7 +70,6 @@ export class UpgradeCommand extends UpgradeCommandRunner {
protected readonly cleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand: CleanOrphanedKanbanAggregateOperationFieldMetadataIdCommand,
protected readonly migrateChannelPartialFullSyncStagesCommand: MigrateChannelPartialFullSyncStagesCommand,
protected readonly makeSureDashboardNamingAvailableCommand: MakeSureDashboardNamingAvailableCommand,
protected readonly seedDashboardViewCommand: SeedDashboardViewCommand,
protected readonly createViewKanbanFieldMetadataIdForeignKeyMigrationCommand: CreateViewKanbanFieldMetadataIdForeignKeyMigrationCommand,
protected readonly flushWorkspaceCacheCommand: FlushCacheCommand,
@@ -81,6 +80,8 @@ export class UpgradeCommand extends UpgradeCommandRunner {
// 1.12 Commands
protected readonlysetStandardApplicationNotUninstallableCommand: SetStandardApplicationNotUninstallableCommand,
protected readonly createTwentyStandardApplicationCommand: CreateTwentyStandardApplicationCommand,
protected readonly createWorkspaceCustomApplicationCommand: CreateWorkspaceCustomApplicationCommand,
) {
super(
workspaceRepository,
@@ -124,13 +125,12 @@ export class UpgradeCommand extends UpgradeCommandRunner {
afterSyncMetadata: [
this.migrateAttachmentAuthorToCreatedByCommand,
this.migrateAttachmentTypeToFileCategoryCommand,
this.seedDashboardViewCommand,
this.flushWorkspaceCacheCommand,
],
};
const commands_1110: VersionCommands = {
beforeSyncMetadata: [this.seedStandardApplicationsCommand],
beforeSyncMetadata: [this.createTwentyStandardApplicationCommand],
afterSyncMetadata: [
this.cleanOrphanedUserWorkspacesCommand,
this.cleanOrphanedRoleTargetsCommand,
@@ -138,7 +138,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
};
const commands_1120: VersionCommands = {
beforeSyncMetadata: [],
beforeSyncMetadata: [this.createWorkspaceCustomApplicationCommand],
afterSyncMetadata: [
this.readonlysetStandardApplicationNotUninstallableCommand,
],
@@ -0,0 +1,38 @@
import { Module } from '@nestjs/common';
import { ApplicationResolver } from 'src/engine/core-modules/application/application.resolver';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
import { CronTriggerModule } from 'src/engine/metadata-modules/cron-trigger/cron-trigger.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { DatabaseEventTriggerModule } from 'src/engine/metadata-modules/database-event-trigger/database-event-trigger.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module';
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
@Module({
imports: [
ApplicationModule,
ApplicationVariableEntityModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
ObjectMetadataModule,
FieldMetadataModule,
DataSourceModule,
ServerlessFunctionLayerModule,
ServerlessFunctionModule,
DatabaseEventTriggerModule,
CronTriggerModule,
RouteTriggerModule,
WorkspaceMigrationV2Module,
PermissionsModule,
],
providers: [ApplicationResolver, ApplicationSyncService],
exports: [ApplicationSyncService],
})
export class ApplicationSyncModule {}
@@ -284,6 +284,7 @@ export class ApplicationSyncService {
await this.fieldMetadataServiceV2.createOneField({
createFieldInput,
workspaceId,
applicationId,
});
}
}
@@ -406,6 +407,7 @@ export class ApplicationSyncService {
const createdObject = await this.objectMetadataServiceV2.createOneObject({
createObjectInput,
applicationId,
workspaceId,
});
@@ -526,18 +528,21 @@ export class ApplicationSyncService {
serverlessFunctionId: serverlessFunctionToUpdate.id,
triggersToSync: serverlessFunctionToSync.triggers || [],
workspaceId,
applicationId,
});
await this.syncCronTriggersForServerlessFunction({
serverlessFunctionId: serverlessFunctionToUpdate.id,
triggersToSync: serverlessFunctionToSync.triggers || [],
workspaceId,
applicationId,
});
await this.syncRouteTriggersForServerlessFunction({
serverlessFunctionId: serverlessFunctionToUpdate.id,
triggersToSync: serverlessFunctionToSync.triggers || [],
workspaceId,
applicationId,
});
}
@@ -567,18 +572,21 @@ export class ApplicationSyncService {
serverlessFunctionId: createdServerlessFunction.id,
triggersToSync: serverlessFunctionToCreate.triggers || [],
workspaceId,
applicationId,
});
await this.syncCronTriggersForServerlessFunction({
serverlessFunctionId: createdServerlessFunction.id,
triggersToSync: serverlessFunctionToCreate.triggers || [],
workspaceId,
applicationId,
});
await this.syncRouteTriggersForServerlessFunction({
serverlessFunctionId: createdServerlessFunction.id,
triggersToSync: serverlessFunctionToCreate.triggers || [],
workspaceId,
applicationId,
});
}
}
@@ -587,10 +595,12 @@ export class ApplicationSyncService {
serverlessFunctionId,
triggersToSync,
workspaceId,
applicationId,
}: {
serverlessFunctionId: string;
triggersToSync: ServerlessFunctionTriggerManifest[];
workspaceId: string;
applicationId: string;
}) {
const databaseEventTriggersToSync = triggersToSync.filter(
(trigger) => trigger.type === 'databaseEvent',
@@ -695,6 +705,7 @@ export class ApplicationSyncService {
await this.databaseEventTriggerV2Service.createOne(
createDatabaseEventTriggerInput,
workspaceId,
applicationId,
);
}
}
@@ -703,10 +714,12 @@ export class ApplicationSyncService {
serverlessFunctionId,
triggersToSync,
workspaceId,
applicationId,
}: {
serverlessFunctionId: string;
triggersToSync: ServerlessFunctionTriggerManifest[];
workspaceId: string;
applicationId: string;
}) {
const cronTriggersToSync = triggersToSync.filter(
(trigger) => trigger.type === 'cron',
@@ -808,6 +821,7 @@ export class ApplicationSyncService {
await this.cronTriggerV2Service.createOne(
createCronTriggerInput,
workspaceId,
applicationId,
);
}
}
@@ -816,10 +830,12 @@ export class ApplicationSyncService {
serverlessFunctionId,
triggersToSync,
workspaceId,
applicationId,
}: {
serverlessFunctionId: string;
triggersToSync: ServerlessFunctionTriggerManifest[];
workspaceId: string;
applicationId: string;
}) {
const routeTriggersToSync = triggersToSync.filter(
(trigger) => trigger.type === 'route',
@@ -922,6 +938,7 @@ export class ApplicationSyncService {
await this.routeTriggerV2Service.createOne(
createRouteTriggerInput,
workspaceId,
applicationId,
);
}
}
@@ -1,42 +1,19 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-sync.service';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationResolver } from 'src/engine/core-modules/application/application.resolver';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationVariableEntityModule } from 'src/engine/core-modules/applicationVariable/application-variable.module';
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { AgentEntity } from 'src/engine/metadata-modules/agent/agent.entity';
import { CronTriggerModule } from 'src/engine/metadata-modules/cron-trigger/cron-trigger.module';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
import { DatabaseEventTriggerModule } from 'src/engine/metadata-modules/database-event-trigger/database-event-trigger.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { RouteTriggerModule } from 'src/engine/metadata-modules/route-trigger/route-trigger.module';
import { ServerlessFunctionLayerModule } from 'src/engine/metadata-modules/serverless-function-layer/serverless-function-layer.module';
import { ServerlessFunctionModule } from 'src/engine/metadata-modules/serverless-function/serverless-function.module';
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module';
@Module({
imports: [
TypeOrmModule.forFeature([ApplicationEntity, AgentEntity, WorkspaceEntity]),
WorkspaceManyOrAllFlatEntityMapsCacheModule,
ObjectMetadataModule,
FieldMetadataModule,
DataSourceModule,
ApplicationVariableEntityModule,
ServerlessFunctionLayerModule,
ServerlessFunctionModule,
DatabaseEventTriggerModule,
CronTriggerModule,
RouteTriggerModule,
WorkspaceMigrationV2Module,
PermissionsModule,
],
exports: [ApplicationService],
providers: [ApplicationResolver, ApplicationService, ApplicationSyncService],
exports: [ApplicationService, WorkspaceFlatApplicationMapCacheService],
providers: [ApplicationService, WorkspaceFlatApplicationMapCacheService],
})
export class ApplicationModule {}
@@ -3,23 +3,86 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { type QueryRunner, Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import {
ApplicationException,
ApplicationExceptionCode,
} from 'src/engine/core-modules/application/application.exception';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
@Injectable()
export class ApplicationService {
constructor(
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceFlatApplicationMapCacheService: WorkspaceFlatApplicationMapCacheService,
@InjectRepository(WorkspaceEntity)
private readonly workspaceRepository: Repository<WorkspaceEntity>,
) {}
async findWorkspaceTwentyStandardAndCustomApplicationOrThrow({
workspaceId,
}: {
workspaceId: string;
}) {
const workspace = await this.workspaceRepository.findOne({
where: {
id: workspaceId,
},
});
if (!isDefined(workspace)) {
throw new ApplicationException(
`Could not find workspace ${workspaceId}`,
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
);
}
const flatApplicationMaps =
await this.workspaceFlatApplicationMapCacheService.getExistingOrRecomputeFlatMaps(
{
workspaceId,
},
);
const twentyStandardApplicationId =
flatApplicationMaps.idByUniversalIdentifier[
TWENTY_STANDARD_APPLICATION.universalIdentifier
];
if (!isDefined(twentyStandardApplicationId)) {
throw new ApplicationException(
`Could not find workspace twenty standard applicationId in cache ${workspaceId}`,
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
);
}
const twentyStandardFlatApplication =
flatApplicationMaps.byId[twentyStandardApplicationId];
const workspaceCustomFlatApplication =
flatApplicationMaps.byId[workspace.workspaceCustomApplicationId];
if (
!isDefined(twentyStandardFlatApplication) ||
!isDefined(workspaceCustomFlatApplication)
) {
throw new ApplicationException(
`Could not find workspace custom and standard applications ${workspace.id}`,
ApplicationExceptionCode.APPLICATION_NOT_FOUND,
);
}
return {
twentyStandardFlatApplication,
workspaceCustomFlatApplication,
};
}
async findManyApplications(
workspaceId: string,
): Promise<ApplicationEntity[]> {
@@ -82,31 +145,63 @@ export class ApplicationService {
async createTwentyStandardApplication(
{
workspaceId,
skipCacheInvalidation = false,
}: {
workspaceId: string;
skipCacheInvalidation?: boolean;
},
queryRunner?: QueryRunner,
) {
return await this.create(
const twentyStandardApplication = await this.create(
{
...TWENTY_STANDARD_APPLICATION,
serverlessFunctionLayerId: null,
workspaceId,
canBeUninstalled: false,
},
queryRunner,
);
if (!skipCacheInvalidation) {
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId,
});
}
return twentyStandardApplication;
}
async createWorkspaceCustomApplication(
{
workspaceId,
workspaceDisplayName,
}: {
workspaceId: string;
workspaceDisplayName?: string;
},
queryRunner?: QueryRunner,
) {
const applicationId = v4();
const workspaceCustomApplication = await this.create(
{
description: 'Workspace custom application',
name: `${isDefined(workspaceDisplayName) ? workspaceDisplayName : 'Workspace'}'s custom application`,
sourcePath: 'workspace-custom',
version: '1.0.0',
universalIdentifier: applicationId,
workspaceId: workspaceId,
id: applicationId,
serverlessFunctionLayerId: null,
canBeUninstalled: false,
},
queryRunner,
);
return workspaceCustomApplication;
}
async create(
data: {
universalIdentifier?: string;
name: string;
description?: string;
version?: string;
serverlessFunctionLayerId: string | null;
sourcePath: string;
workspaceId: string;
},
data: Partial<ApplicationEntity> & { workspaceId: string },
queryRunner?: QueryRunner,
): Promise<ApplicationEntity> {
const application = this.applicationRepository.create({
@@ -118,7 +213,13 @@ export class ApplicationService {
return queryRunner.manager.save(ApplicationEntity, application);
}
return this.applicationRepository.save(application);
const savedApplication = await this.applicationRepository.save(application);
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId: data.workspaceId,
});
return savedApplication;
}
async update(
@@ -127,6 +228,10 @@ export class ApplicationService {
): Promise<ApplicationEntity> {
await this.applicationRepository.update({ id }, data);
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId: data.workspaceId as string,
});
const updatedApplication = await this.findById(id);
if (!updatedApplication) {
@@ -151,8 +256,14 @@ export class ApplicationService {
workspaceId,
});
await this.flatEntityMapsCacheService.invalidateFlatEntityMaps({
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId,
});
await this.workspaceManyOrAllFlatEntityMapsCacheService.invalidateFlatEntityMaps(
{
workspaceId,
},
);
}
}
@@ -0,0 +1,9 @@
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
export const APPLICATION_ENTITY_RELATION_PROPERTIES = [
'workspace',
'agents',
'serverlessFunctions',
'objects',
'applicationVariables',
] as const satisfies (keyof ApplicationEntity)[];
@@ -8,20 +8,12 @@ export const TWENTY_STANDARD_APPLICATION = {
version: '1.0.0',
sourcePath: 'cli-sync',
sourceType: 'local',
canBeUninstalled: false,
} as const satisfies CreateApplicationInput;
export type CreateApplicationInput = Omit<
} as const satisfies Pick<
ApplicationEntity,
| 'workspaceId'
| 'serverlessFunctionLayerId'
| 'id'
| 'createdAt'
| 'updatedAt'
| 'deletedAt'
| 'workspace'
| 'agents'
| 'applicationVariables'
| 'objects'
| 'serverlessFunctions'
| 'universalIdentifier'
| 'name'
| 'description'
| 'version'
| 'sourcePath'
| 'sourceType'
>;
@@ -0,0 +1,60 @@
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { AllMetadataName } from 'twenty-shared/metadata';
import { Repository } from 'typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FlatApplicationCacheMaps } from 'src/engine/core-modules/application/types/flat-application-cache-maps.type';
import { fromApplicationEntityToFlatApplication } from 'src/engine/core-modules/application/utils/from-application-entity-to-flat-application.util';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { MetadataToFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/types/metadata-to-flat-entity-maps-key';
import { WorkspaceFlatMapCache } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import { WorkspaceFlatMapCacheService } from 'src/engine/workspace-flat-map-cache/services/workspace-flat-map-cache.service';
@Injectable()
@WorkspaceFlatMapCache(
'flatApplicationMaps' as MetadataToFlatEntityMapsKey<AllMetadataName>,
) // TODO prastoin introduce SyncableMetadata notion
export class WorkspaceFlatApplicationMapCacheService extends WorkspaceFlatMapCacheService<FlatApplicationCacheMaps> {
constructor(
@InjectCacheStorage(CacheStorageNamespace.EngineWorkspace)
cacheStorageService: CacheStorageService,
@InjectRepository(ApplicationEntity)
private readonly applicationRepository: Repository<ApplicationEntity>,
) {
super(cacheStorageService);
}
protected async computeFlatMap({
workspaceId,
}: {
workspaceId: string;
}): Promise<FlatApplicationCacheMaps> {
const applicationEntities = await this.applicationRepository.find({
where: {
workspaceId,
},
withDeleted: true,
});
const flatApplicationMaps: FlatApplicationCacheMaps = {
byId: {},
idByUniversalIdentifier: {},
};
for (const applicationEntity of applicationEntities) {
const flatApplication =
fromApplicationEntityToFlatApplication(applicationEntity);
flatApplicationMaps.byId[flatApplication.id] = flatApplication;
flatApplicationMaps.idByUniversalIdentifier[
flatApplication.universalIdentifier
] = flatApplication.id;
}
return flatApplicationMaps;
}
}
@@ -0,0 +1,4 @@
import { type APPLICATION_ENTITY_RELATION_PROPERTIES } from 'src/engine/core-modules/application/constants/application-entity-relation-properties.constant';
export type ApplicationEntityRelationProperties =
(typeof APPLICATION_ENTITY_RELATION_PROPERTIES)[number];
@@ -0,0 +1,6 @@
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
export type FlatApplicationCacheMaps = {
byId: Partial<Record<string, FlatApplication>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
};
@@ -0,0 +1,7 @@
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { type ApplicationEntityRelationProperties } from 'src/engine/core-modules/application/types/application-entity-relation-properties.type';
export type FlatApplication = Omit<
ApplicationEntity,
ApplicationEntityRelationProperties
>;
@@ -0,0 +1,13 @@
import { removePropertiesFromRecord } from 'twenty-shared/utils';
import { type ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { APPLICATION_ENTITY_RELATION_PROPERTIES } from 'src/engine/core-modules/application/constants/application-entity-relation-properties.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
export const fromApplicationEntityToFlatApplication = (
applicationEntity: ApplicationEntity,
): FlatApplication =>
removePropertiesFromRecord(
applicationEntity,
APPLICATION_ENTITY_RELATION_PROPERTIES,
);
@@ -11,6 +11,7 @@ import { v4 } from 'uuid';
import { USER_SIGNUP_EVENT_NAME } from 'src/engine/api/graphql/workspace-query-runner/constants/user-signup-event-name.constants';
import { type AppTokenEntity } from 'src/engine/core-modules/app-token/app-token.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
import {
AuthException,
AuthExceptionCode,
@@ -39,7 +40,6 @@ import { WorkspaceInvitationService } from 'src/engine/core-modules/workspace-in
import { AuthProviderEnum } from 'src/engine/core-modules/workspace/types/workspace.type';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { WorkspaceEventEmitter } from 'src/engine/workspace-event-emitter/workspace-event-emitter';
import { computeWorkspaceCustomCreateApplicationInput } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-workspace-custom-create-application-input';
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
import { isWorkEmail } from 'src/utils/is-work-email';
@@ -60,6 +60,7 @@ export class SignInUpService {
private readonly subdomainManagerService: SubdomainManagerService,
private readonly userService: UserService,
private readonly metricsService: MetricsService,
private readonly workspaceFlatApplicationMapCacheService: WorkspaceFlatApplicationMapCacheService,
private readonly applicationService: ApplicationService,
@InjectDataSource()
private readonly dataSource: DataSource,
@@ -459,26 +460,19 @@ export class SignInUpService {
isWorkEmailFound && (await isLogoUrlValid()) ? logoUrl : undefined;
const workspaceId = v4();
const workspaceCustomApplicationCreateInput =
computeWorkspaceCustomCreateApplicationInput({
workspace: {
id: workspaceId,
},
});
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const workspaceCustomApplication = await this.applicationService.create(
{
...workspaceCustomApplicationCreateInput,
serverlessFunctionLayerId: null,
},
queryRunner,
);
const workspaceCustomApplication =
await this.applicationService.createWorkspaceCustomApplication(
{
workspaceId,
},
queryRunner,
);
const workspaceToCreate = this.workspaceRepository.create({
id: workspaceId,
@@ -532,6 +526,9 @@ export class SignInUpService {
);
await queryRunner.commitTransaction();
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId,
});
return { user, workspace };
} catch (error) {
@@ -9,6 +9,7 @@ import { AiModule } from 'src/engine/core-modules/ai/ai.module';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { AppTokenModule } from 'src/engine/core-modules/app-token/app-token.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { ApplicationSyncModule } from 'src/engine/core-modules/application/application-sync.module';
import { ApprovedAccessDomainModule } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.module';
import { AuthModule } from 'src/engine/core-modules/auth/auth.module';
import { BillingWebhookModule } from 'src/engine/core-modules/billing-webhook/billing-webhook.module';
@@ -77,6 +78,7 @@ import { FileModule } from './file/file.module';
FileModule,
OpenApiModule,
ApplicationModule,
ApplicationSyncModule,
AppTokenModule,
TimelineMessagingModule,
TimelineCalendarEventModule,
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { CronTriggerCronCommand } from 'src/engine/metadata-modules/cron-trigger/crons/commands/cron-trigger.cron.command';
import { CronTriggerCronJob } from 'src/engine/metadata-modules/cron-trigger/crons/jobs/cron-trigger.cron.job';
@@ -15,6 +16,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, CronTriggerEntity]),
ApplicationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
import { CronTriggerIdInput } from 'src/engine/metadata-modules/cron-trigger/dtos/cron-trigger-id.input';
import { UpdateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/update-cron-trigger.input';
@@ -23,12 +24,25 @@ export class CronTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
cronTriggerInput: CreateCronTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -43,6 +57,8 @@ export class CronTriggerV2Service {
{
createCronTriggerInput: cronTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
},
);
@@ -6,18 +6,21 @@ import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/t
export const fromCreateCronTriggerInputToFlatCronTrigger = ({
createCronTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createCronTriggerInput: CreateCronTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatCronTrigger => {
const now = new Date();
const id = v4();
return {
id: v4(),
universalIdentifier: createCronTriggerInput.universalIdentifier ?? v4(),
id,
universalIdentifier: createCronTriggerInput.universalIdentifier ?? id,
settings: createCronTriggerInput.settings,
serverlessFunctionId: createCronTriggerInput.serverlessFunctionId,
applicationId: createCronTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
workspaceId,
createdAt: now,
updatedAt: now,
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { DatabaseEventTriggerEntity } from 'src/engine/metadata-modules/database-event-trigger/entities/database-event-trigger.entity';
import { CallDatabaseEventTriggerJobsJob } from 'src/engine/metadata-modules/database-event-trigger/jobs/call-database-event-trigger-jobs.job';
@@ -14,6 +15,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([WorkspaceEntity, DatabaseEventTriggerEntity]),
ApplicationModule,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { CreateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/create-database-event-trigger.input';
import { DatabaseEventTriggerIdInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/database-event-trigger-id.input';
import { UpdateDatabaseEventTriggerInput } from 'src/engine/metadata-modules/database-event-trigger/dtos/update-database-event-trigger.input';
@@ -23,12 +24,25 @@ export class DatabaseEventTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
databaseEventTriggerInput: CreateDatabaseEventTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -47,6 +61,8 @@ export class DatabaseEventTriggerV2Service {
fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger({
createDatabaseEventTriggerInput: databaseEventTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -6,21 +6,24 @@ import { type FlatDatabaseEventTrigger } from 'src/engine/metadata-modules/datab
export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({
createDatabaseEventTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createDatabaseEventTriggerInput: CreateDatabaseEventTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatDatabaseEventTrigger => {
const now = new Date();
const id = uuidV4();
return {
id: uuidV4(),
id,
universalIdentifier:
createDatabaseEventTriggerInput.universalIdentifier ?? uuidV4(),
createDatabaseEventTriggerInput.universalIdentifier ?? id,
settings: createDatabaseEventTriggerInput.settings,
serverlessFunctionId: createDatabaseEventTriggerInput.serverlessFunctionId,
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createDatabaseEventTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -9,6 +9,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ActorModule } from 'src/engine/core-modules/actor/actor.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module';
@@ -60,6 +61,7 @@ import { UpdateFieldInput } from './dtos/update-field.input';
DataSourceModule,
TypeORMModule,
ActorModule,
ApplicationModule,
FeatureFlagModule,
ViewModule,
ViewFieldModule,
@@ -5,6 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { isDefined } from 'twenty-shared/utils';
import { FindOneOptions, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { type CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { type DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
@@ -32,6 +33,7 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {
super(fieldMetadataRepository);
}
@@ -39,13 +41,20 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
async createOneField({
createFieldInput,
workspaceId,
applicationId,
}: {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatFieldMetadata> {
const [createdFieldMetadata] = await this.createManyFields({
workspaceId,
createFieldInputs: [createFieldInput],
applicationId,
});
if (!isDefined(createdFieldMetadata)) {
@@ -283,14 +292,27 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
async createManyFields({
createFieldInputs,
workspaceId,
applicationId,
}: {
createFieldInputs: Omit<CreateFieldInput, 'workspaceId'>[];
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatFieldMetadata[]> {
if (createFieldInputs.length === 0) {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatIndexMaps: existingFlatIndexMaps,
@@ -316,6 +338,8 @@ export class FieldMetadataServiceV2 extends TypeOrmQueryService<FieldMetadataEnt
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
createFieldInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
}),
);
}
@@ -104,7 +104,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"result": {
"flatFieldMetadatas": [
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -140,7 +140,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -178,7 +178,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -214,7 +214,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"calendarViewIds": [],
"createdAt": Any<ClockDate>,
"defaultValue": null,
@@ -254,7 +254,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
],
"indexMetadatas": [
{
"applicationId": null,
"applicationId": Any<String>,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
@@ -278,7 +278,7 @@ exports[`fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test su
"workspaceId": Any<String>,
},
{
"applicationId": null,
"applicationId": Any<String>,
"createdAt": Any<ClockDate>,
"flatIndexFieldMetadatas": [
{
@@ -42,6 +42,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should create morph relation field metadata with valid input on rocket object to pet object',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -100,6 +102,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is missing',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -118,6 +122,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when morphRelationsCreationPayload is empty array',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -137,6 +143,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has different relation types',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -169,6 +177,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has several references to same object metadata',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -201,6 +211,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
'should fail when morphRelationsCreationPayload has invalid relation payload',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -225,6 +237,8 @@ describe('fromCreateFieldInputToFlatFieldMetadatasToCreate MORPH_RELATION test s
title: 'should fail when target object metadata is not found',
context: {
input: {
workspaceCustomApplicationId:
'20202020-81ee-42da-a281-668632f32fe7',
createFieldInput: {
name: 'newField',
label: 'newFieldLabel',
@@ -25,12 +25,14 @@ import { type FlatIndexMetadata } from 'src/engine/metadata-modules/flat-index-m
export type FromCreateFieldInputToFlatObjectMetadataArgs = {
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput: rawCreateFieldInput,
workspaceId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
workspaceCustomApplicationId,
}: FromCreateFieldInputToFlatObjectMetadataArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -71,6 +73,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
createFieldInput,
workspaceId,
fieldMetadataId,
workspaceCustomApplicationId,
});
switch (createFieldInput.type) {
@@ -83,6 +86,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata: parentFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
});
}
case FieldMetadataType.RELATION: {
@@ -94,6 +98,7 @@ export const fromCreateFieldInputToFlatFieldMetadatasToCreate = async ({
type: createFieldInput.type,
},
workspaceId,
workspaceCustomApplicationId,
});
}
case FieldMetadataType.RATING: {
@@ -21,12 +21,14 @@ type FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs = {
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
createFieldInput,
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
}: FromMorphRelationCreateFieldInputToFlatFieldMetadatasArgs): Promise<
FieldInputTranspilationResult<{
flatFieldMetadatas: FlatFieldMetadata[];
@@ -90,6 +92,7 @@ export const fromMorphRelationCreateFieldInputToFlatFieldMetadatas = async ({
targetFlatObjectMetadata,
workspaceId,
morphId,
workspaceCustomApplicationId,
});
return {
@@ -21,12 +21,14 @@ type FromRelationCreateFieldInputToFlatFieldMetadataArgs = {
existingFlatObjectMetadataMaps: FlatEntityMaps<FlatObjectMetadata>;
sourceFlatObjectMetadata: FlatObjectMetadata;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
existingFlatObjectMetadataMaps,
sourceFlatObjectMetadata,
createFieldInput,
workspaceId,
workspaceCustomApplicationId,
}: FromRelationCreateFieldInputToFlatFieldMetadataArgs): Promise<
FieldInputTranspilationResult<SourceTargetMorphOrRelationFlatFieldAndFlatIndex>
> => {
@@ -67,6 +69,7 @@ export const fromRelationCreateFieldInputToFlatFieldMetadatas = async ({
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
});
return {
@@ -44,7 +44,7 @@ export const generateIndexForFlatFieldMetadata = ({
universalIdentifier: indexId,
updatedAt: createdAt,
workspaceId,
applicationId: null,
applicationId: flatFieldMetadata.applicationId,
},
flatObjectMetadata,
},
@@ -1,7 +1,7 @@
import {
FieldMetadataType,
RelationType,
RelationOnDeleteAction,
RelationType,
} from 'twenty-shared/types';
import { v4 } from 'uuid';
@@ -46,6 +46,7 @@ type GenerateMorphOrRelationFlatFieldMetadataPairArgs = {
> & { type: MorphOrRelationFieldMetadataType };
workspaceId: string;
morphId?: string | null;
workspaceCustomApplicationId: string;
};
export type SourceTargetMorphOrRelationFlatFieldAndFlatIndex = {
@@ -58,6 +59,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
sourceFlatObjectMetadata,
targetFlatObjectMetadata,
workspaceId,
workspaceCustomApplicationId,
sourceFlatObjectMetadataJoinColumnName,
morphId = null,
}: GenerateMorphOrRelationFlatFieldMetadataPairArgs): SourceTargetMorphOrRelationFlatFieldAndFlatIndex => {
@@ -78,6 +80,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput,
workspaceId,
fieldMetadataId: sourceRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
}),
morphId,
objectMetadataId: sourceFlatObjectMetadata.id,
@@ -116,6 +119,7 @@ export const generateMorphOrRelationFlatFieldMetadataPair = ({
createFieldInput: targetCreateFieldInput,
workspaceId,
fieldMetadataId: targetRelationTargetFieldMetadataId,
workspaceCustomApplicationId,
}),
type: FieldMetadataType.RELATION,
defaultValue: null,
@@ -9,11 +9,13 @@ type GetDefaultFlatFieldMetadataArgs = {
fieldMetadataId: string;
createFieldInput: Omit<CreateFieldInput, 'workspaceId'>;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const getDefaultFlatFieldMetadata = ({
createFieldInput,
fieldMetadataId,
workspaceId,
workspaceCustomApplicationId,
}: GetDefaultFlatFieldMetadataArgs) => {
const { defaultValue, settings } = extractAndSanitizeObjectStringFields(
createFieldInput,
@@ -56,7 +58,7 @@ export const getDefaultFlatFieldMetadata = ({
updatedAt: createdAt,
isUIReadOnly: createFieldInput.isUIReadOnly ?? false,
morphId: null,
applicationId: createFieldInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
viewFilterIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -17,11 +17,13 @@ type FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs
{
createObjectInput: CreateObjectInput;
workspaceId: string;
workspaceCustomApplicationId: string;
} & Pick<AllFlatEntityMaps, 'flatObjectMetadataMaps'>;
export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate =
({
createObjectInput: rawCreateObjectInput,
workspaceId,
workspaceCustomApplicationId,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
}: FromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreateArgs): {
flatObjectMetadataToCreate: FlatObjectMetadata;
@@ -48,7 +50,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
buildDefaultFlatFieldMetadatasForCustomObject({
flatObjectMetadata: {
id: objectMetadataId,
applicationId: createObjectInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
},
workspaceId,
});
@@ -81,7 +83,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
shortcut: createObjectInput.shortcut ?? null,
standardId: createObjectInput.standardId ?? null,
standardOverrides: null,
applicationId: createObjectInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
universalIdentifier: objectMetadataId,
targetTableName: 'DEPRECATED',
workspaceId,
@@ -8,11 +8,13 @@ import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-o
export type FromCreateViewFieldInputToFlatViewFieldToCreateArgs = {
createViewFieldInput: CreateViewFieldInput;
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
createViewFieldInput: rawCreateViewFieldInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreateViewFieldInputToFlatViewFieldToCreateArgs): FlatViewField => {
const { fieldMetadataId, viewId, ...createViewFieldInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -37,6 +39,6 @@ export const fromCreateViewFieldInputToFlatViewFieldToCreate = ({
size: createViewFieldInput.size ?? DEFAULT_VIEW_FIELD_SIZE,
position: createViewFieldInput.position ?? 0,
aggregateOperation: createViewFieldInput.aggregateOperation ?? null,
applicationId: createViewFieldInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -8,9 +8,11 @@ import { type CreateViewFilterInput } from 'src/engine/metadata-modules/view-fil
export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
createViewFilterInput: rawCreateViewFilterInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createViewFilterInput: CreateViewFilterInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatViewFilter => {
const { fieldMetadataId, viewId, value, ...createViewFilterInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -44,6 +46,6 @@ export const fromCreateViewFilterInputToFlatViewFilterToCreate = ({
positionInViewFilterGroup:
createViewFilterInput.positionInViewFilterGroup ?? null,
subFieldName: createViewFilterInput.subFieldName ?? null,
applicationId: createViewFilterInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -7,9 +7,11 @@ import { type CreateViewGroupInput } from 'src/engine/metadata-modules/view-grou
export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({
createViewGroupInput: rawCreateViewGroupInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createViewGroupInput: CreateViewGroupInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatViewGroup => {
const { fieldMetadataId, viewId, ...createViewGroupInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -33,6 +35,6 @@ export const fromCreateViewGroupInputToFlatViewGroupToCreate = ({
isVisible: createViewGroupInput.isVisible ?? true,
fieldValue: createViewGroupInput.fieldValue,
position: createViewGroupInput.position ?? 0,
applicationId: createViewGroupInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -11,10 +11,12 @@ export const fromCreateViewInputToFlatViewToCreate = ({
createViewInput: rawCreateViewInput,
workspaceId,
createdByUserWorkspaceId,
workspaceCustomApplicationId,
}: {
createViewInput: CreateViewInput;
workspaceId: string;
createdByUserWorkspaceId?: string;
workspaceCustomApplicationId: string;
}): FlatView => {
const { objectMetadataId, ...createViewInput } =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
@@ -52,6 +54,6 @@ export const fromCreateViewInputToFlatViewToCreate = ({
viewFieldIds: [],
viewFilterIds: [],
viewGroupIds: [],
applicationId: createViewInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -5,6 +5,7 @@ import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm';
import { fromArrayToUniqueKeyRecord, isDefined } from 'twenty-shared/utils';
import { FindManyOptions, FindOneOptions, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findManyFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-many-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -43,6 +44,7 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly applicationService: ApplicationService,
) {
super(objectMetadataRepository);
}
@@ -323,10 +325,22 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
async createOneObject({
createObjectInput,
workspaceId,
applicationId,
}: {
createObjectInput: CreateObjectInput;
workspaceId: string;
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string;
}): Promise<FlatObjectMetadata> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
@@ -354,6 +368,8 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
} = fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCreate({
createObjectInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
@@ -367,13 +383,15 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
flatEntityToUpdate: [],
});
const flatDefaultViewToCreate = await this.createDefaultFlatView({
const flatDefaultViewToCreate = await this.computeFlatViewToCreate({
objectMetadata: flatObjectMetadataToCreate,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const flatDefaultViewFieldsToCreate =
await this.createDefaultFlatViewFields({
await this.computeFlatViewFieldsToCreate({
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
objectFlatFieldMetadatas: findManyFlatEntityByIdInFlatEntityMapsOrThrow(
{
flatEntityMaps: flatFieldMetadataMapsFromTo.to,
@@ -456,10 +474,12 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
return createdFlatObjectMetadata;
}
private async createDefaultFlatView({
private async computeFlatViewToCreate({
objectMetadata,
workspaceId,
workspaceCustomApplicationId,
}: {
workspaceCustomApplicationId: string;
objectMetadata: FlatObjectMetadata;
workspaceId: string;
}) {
@@ -475,16 +495,19 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
const flatViewFromCreateInput = fromCreateViewInputToFlatViewToCreate({
createViewInput: defaultViewInput,
workspaceId,
workspaceCustomApplicationId,
});
return flatViewFromCreateInput;
}
private async createDefaultFlatViewFields({
private async computeFlatViewFieldsToCreate({
objectFlatFieldMetadatas,
viewId,
workspaceId,
workspaceCustomApplicationId,
}: {
workspaceCustomApplicationId: string;
objectFlatFieldMetadatas: FlatFieldMetadata[];
viewId: string;
workspaceId: string;
@@ -500,6 +523,7 @@ export class ObjectMetadataServiceV2 extends TypeOrmQueryService<ObjectMetadataE
size: DEFAULT_VIEW_FIELD_SIZE,
viewId: viewId,
},
workspaceCustomApplicationId,
workspaceId: workspaceId,
}),
);
@@ -9,6 +9,7 @@ import {
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
@@ -50,6 +51,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
FieldMetadataEntity,
]),
TypeOrmModule.forFeature([FeatureFlagEntity, ViewEntity]),
ApplicationModule,
DataSourceModule,
WorkspaceMigrationModule,
WorkspaceMigrationRunnerModule,
@@ -1,123 +0,0 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { type FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const buildDefaultFieldsForCustomObject = (
workspaceId: string,
): Array<Partial<FieldMetadataEntity> & { id: string }> => [
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
type: FieldMetadataType.UUID,
name: 'id',
label: 'Id',
icon: 'Icon123',
description: 'Id',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: true,
isUIReadOnly: true,
workspaceId,
defaultValue: 'uuid',
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
type: FieldMetadataType.TEXT,
name: 'name',
label: 'Name',
icon: 'IconAbc',
description: 'Name',
isNullable: false,
isActive: true,
isCustom: false,
isUIReadOnly: false,
workspaceId,
defaultValue: "''",
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
type: FieldMetadataType.DATE_TIME,
name: 'createdAt',
label: 'Creation date',
icon: 'IconCalendar',
description: 'Creation date',
isNullable: false,
isActive: true,
isCustom: false,
isUIReadOnly: false,
workspaceId,
defaultValue: 'now',
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
type: FieldMetadataType.DATE_TIME,
name: 'updatedAt',
label: 'Last update',
icon: 'IconCalendarClock',
description: 'Last time the record was changed',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: false,
workspaceId,
defaultValue: 'now',
},
{
id: v4(),
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
type: FieldMetadataType.DATE_TIME,
name: 'deletedAt',
label: 'Deleted at',
icon: 'IconCalendarClock',
description: 'Deletion date',
isNullable: true,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: true,
workspaceId,
defaultValue: null,
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
type: FieldMetadataType.ACTOR,
name: 'createdBy',
label: 'Created by',
icon: 'IconCreativeCommonsSa',
description: 'The creator of the record',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: false,
isUIReadOnly: true,
workspaceId,
defaultValue: { name: "''", source: "'MANUAL'" },
},
{
id: v4(),
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
type: FieldMetadataType.POSITION,
name: 'position',
label: 'Position',
icon: 'IconHierarchy2',
description: 'Position',
isNullable: false,
isActive: true,
isCustom: false,
isSystem: true,
isUIReadOnly: false,
workspaceId,
defaultValue: 0,
},
];
@@ -7,7 +7,6 @@ import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { createDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { getTsVectorColumnExpressionFromFields } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util';
type BuildDefaultFlatFieldMetadataForCustomObjectArgs = {
@@ -24,9 +23,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
flatObjectMetadata: { id: objectMetadataId, applicationId },
}: BuildDefaultFlatFieldMetadataForCustomObjectArgs) => {
const createdAt = new Date();
const idFieldId = v4();
const idField: FlatFieldMetadata<FieldMetadataType.UUID> = {
type: FieldMetadataType.UUID,
id: v4(),
id: idFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -34,10 +34,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: true,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.id,
]),
universalIdentifier: idFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.id,
name: 'id',
@@ -63,9 +60,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const nameFieldId = v4();
const nameField: FlatFieldMetadata<FieldMetadataType.TEXT> = {
type: FieldMetadataType.TEXT,
id: v4(),
id: nameFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -73,10 +71,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
]),
universalIdentifier: nameFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.name,
name: 'name',
@@ -102,9 +97,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const createdAtFieldId = v4();
const createdAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: createdAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -112,10 +108,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
]),
universalIdentifier: createdAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.createdAt,
name: 'createdAt',
@@ -141,9 +134,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const updatedAtFieldId = v4();
const updatedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: updatedAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -151,10 +145,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
]),
universalIdentifier: updatedAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt,
name: 'updatedAt',
@@ -180,9 +171,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const deletedAtFieldId = v4();
const deletedAtField: FlatFieldMetadata<FieldMetadataType.DATE_TIME> = {
type: FieldMetadataType.DATE_TIME,
id: v4(),
id: deletedAtFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -190,10 +182,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
]),
universalIdentifier: deletedAtFieldId,
workspaceId,
standardId: BASE_OBJECT_STANDARD_FIELD_IDS.deletedAt,
name: 'deletedAt',
@@ -219,9 +208,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const createdByFieldId = v4();
const createdByField: FlatFieldMetadata<FieldMetadataType.ACTOR> = {
type: FieldMetadataType.ACTOR,
id: v4(),
id: createdByFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -229,10 +219,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
]),
universalIdentifier: createdByFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.createdBy,
name: 'createdBy',
@@ -257,9 +244,10 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const positionFieldId = v4();
const positionField: FlatFieldMetadata<FieldMetadataType.POSITION> = {
type: FieldMetadataType.POSITION,
id: v4(),
id: positionFieldId,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
@@ -267,10 +255,7 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
]),
universalIdentifier: positionFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
name: 'position',
@@ -296,20 +281,18 @@ export const buildDefaultFlatFieldMetadatasForCustomObject = ({
applicationId: applicationId ?? null,
};
const searchVectorFieldId = v4();
const searchVectorField: FlatFieldMetadata<FieldMetadataType.TS_VECTOR> = {
type: FieldMetadataType.TS_VECTOR,
viewFieldIds: [],
viewGroupIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
id: v4(),
id: searchVectorFieldId,
isLabelSyncedWithName: false,
isUnique: false,
objectMetadataId,
universalIdentifier: createDeterministicUuid([
objectMetadataId,
CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
]),
universalIdentifier: searchVectorFieldId,
workspaceId,
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.searchVector,
name: 'searchVector',
@@ -1,7 +1,7 @@
import { type STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
import { capitalize, isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
@@ -16,15 +16,9 @@ import {
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
import { buildDescriptionForRelationFieldMetadataOnFromField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-from-field.util';
import { buildDescriptionForRelationFieldMetadataOnToField } from 'src/engine/metadata-modules/object-metadata/utils/build-description-for-relation-field-on-to-field.util';
import {
CUSTOM_OBJECT_STANDARD_FIELD_IDS,
STANDARD_OBJECT_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
import { STANDARD_OBJECT_ICONS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-icons';
import {
createDeterministicUuid,
createRelationDeterministicUuid,
} from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
import { createRelationDeterministicUuid } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/create-deterministic-uuid.util';
const generateSourceFlatFieldMetadata = ({
workspaceId,
@@ -49,17 +43,6 @@ const generateSourceFlatFieldMetadata = ({
STANDARD_OBJECT_ICONS[
targetFlatObjectMetadata.nameSingular as keyof typeof STANDARD_OBJECT_ICONS
] || 'IconBuildingSkyscraper';
const standardId =
CUSTOM_OBJECT_STANDARD_FIELD_IDS[
targetFlatObjectMetadata.namePlural as keyof typeof CUSTOM_OBJECT_STANDARD_FIELD_IDS
];
if (!isDefined(standardId)) {
throw new ObjectMetadataException(
`Standard field ID not found for target object ${targetFlatObjectMetadata.namePlural}`,
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
);
}
return {
calendarViewIds: [],
@@ -89,16 +72,13 @@ const generateSourceFlatFieldMetadata = ({
settings: {
relationType: RelationType.ONE_TO_MANY,
},
standardId,
standardId: null,
standardOverrides: null,
type: FieldMetadataType.RELATION,
universalIdentifier: createDeterministicUuid([
sourceFlatObjectMetadata.id,
standardId,
]),
universalIdentifier: sourceFieldMetadataId,
workspaceId,
morphId: null,
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
applicationId: sourceFlatObjectMetadata.applicationId,
};
};
@@ -165,11 +145,8 @@ const generateTargetFlatFieldMetadata = ({
relationTargetFieldMetadataId: sourceFlatFieldMetadata.id,
relationTargetObjectMetadataId: sourceFlatObjectMetadata.id,
standardOverrides: null,
universalIdentifier: createDeterministicUuid([
targetFlatObjectMetadata.id,
standardId,
]),
applicationId: sourceFlatObjectMetadata.applicationId ?? null,
universalIdentifier: sourceFlatFieldMetadata.relationTargetFieldMetadataId,
applicationId: sourceFlatObjectMetadata.applicationId,
};
};
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FileModule } from 'src/engine/core-modules/file/file.module';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { UserWorkspaceModule } from 'src/engine/core-modules/user-workspace/user-workspace.module';
@@ -30,6 +31,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
PermissionFlagModule,
WorkspacePermissionsCacheModule,
FileModule,
ApplicationModule,
],
providers: [RoleService, RoleResolver],
exports: [RoleService],
@@ -3,7 +3,9 @@ import { InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { v4 } from 'uuid';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
import {
@@ -29,6 +31,7 @@ export class RoleService {
private readonly roleRepository: Repository<RoleEntity>,
private readonly userRoleService: UserRoleService,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly applicationService: ApplicationService,
) {}
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
@@ -70,10 +73,18 @@ export class RoleService {
input: CreateRoleInput;
workspaceId: string;
}): Promise<RoleEntity> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
await this.validateRoleInputOrThrow({ input, workspaceId });
const id = input.id ?? v4();
const role = await this.roleRepository.save({
id: input.id,
id,
label: input.label,
description: input.description,
icon: input.icon,
@@ -88,6 +99,8 @@ export class RoleService {
canBeAssignedToApiKeys: input.canBeAssignedToApiKeys,
isEditable: true,
workspaceId,
applicationId: workspaceCustomFlatApplication.id,
universalIdentifier: id,
});
await this.workspacePermissionsCacheService.recomputeRolesPermissionsCache({
@@ -198,9 +211,13 @@ export class RoleService {
public async createMemberRole({
workspaceId,
applicationId,
}: {
applicationId: string;
workspaceId: string;
}): Promise<RoleEntity> {
const id = v4();
return this.roleRepository.save({
label: MEMBER_ROLE_LABEL,
description: 'Member role',
@@ -216,15 +233,22 @@ export class RoleService {
canBeAssignedToApiKeys: false,
isEditable: true,
workspaceId,
applicationId,
id,
universalIdentifier: id,
});
}
// Only used for dev seeding and testing
public async createGuestRole({
workspaceId,
applicationId,
}: {
workspaceId: string;
applicationId: string;
}): Promise<RoleEntity> {
const id = v4();
return this.roleRepository.save({
label: 'Guest',
description: 'Guest role',
@@ -240,6 +264,9 @@ export class RoleService {
canBeAssignedToApiKeys: false,
isEditable: false,
workspaceId,
applicationId,
id,
universalIdentifier: id,
});
}
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
import { WorkspaceDomainsModule } from 'src/engine/core-modules/domain/workspace-domains/workspace-domains.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@@ -17,6 +18,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
@Module({
imports: [
TypeOrmModule.forFeature([RouteTriggerEntity]),
ApplicationModule,
TokenModule,
WorkspaceDomainsModule,
ServerlessFunctionModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -23,12 +24,25 @@ export class RouteTriggerV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne(
routeTriggerInput: CreateRouteTriggerInput,
workspaceId: string,
/**
* @deprecated do not use call validateBuildAndRunWorkspaceMigration contextually
* when interacting with another application than workspace custom one
* */
applicationId?: string,
) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -43,6 +57,8 @@ export class RouteTriggerV2Service {
fromCreateRouteTriggerInputToFlatRouteTrigger({
createRouteTriggerInput: routeTriggerInput,
workspaceId,
workspaceCustomApplicationId:
applicationId ?? workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -6,16 +6,18 @@ import { type FlatRouteTrigger } from 'src/engine/metadata-modules/route-trigger
export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({
createRouteTriggerInput,
workspaceId,
workspaceCustomApplicationId,
}: {
createRouteTriggerInput: CreateRouteTriggerInput;
workspaceId: string;
workspaceCustomApplicationId: string;
}): FlatRouteTrigger => {
const now = new Date();
const id = uuidV4();
return {
id: uuidV4(),
universalIdentifier:
createRouteTriggerInput.universalIdentifier ?? uuidV4(),
id,
universalIdentifier: createRouteTriggerInput.universalIdentifier ?? id,
path: createRouteTriggerInput.path,
isAuthRequired: createRouteTriggerInput.isAuthRequired,
httpMethod: createRouteTriggerInput.httpMethod,
@@ -23,6 +25,6 @@ export const fromCreateRouteTriggerInputToFlatRouteTrigger = ({
workspaceId,
createdAt: now,
updatedAt: now,
applicationId: createRouteTriggerInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
};
};
@@ -3,6 +3,7 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { AuditModule } from 'src/engine/core-modules/audit/audit.module';
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
@@ -35,6 +36,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
]),
FileModule,
ThrottlerModule,
ApplicationModule,
AuditModule,
FeatureFlagModule,
PermissionsModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -23,6 +24,7 @@ export class ServerlessFunctionV2Service {
constructor(
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -34,6 +36,13 @@ export class ServerlessFunctionV2Service {
};
workspaceId: string;
}) {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const flatEntityMaps =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -49,6 +58,7 @@ export class ServerlessFunctionV2Service {
fromCreateServerlessFunctionInputToFlatServerlessFunction({
createServerlessFunctionInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -14,11 +14,13 @@ export type FromCreateServerlessFunctionInputToFlatServerlessFunctionArgs = {
serverlessFunctionLayerId: string;
};
workspaceId: string;
workspaceCustomApplicationId: string;
};
export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
createServerlessFunctionInput: rawCreateServerlessFunctionInput,
workspaceId,
workspaceCustomApplicationId,
}: FromCreateServerlessFunctionInputToFlatServerlessFunctionArgs): FlatServerlessFunction => {
const id = v4();
const currentDate = new Date();
@@ -35,13 +37,13 @@ export const fromCreateServerlessFunctionInputToFlatServerlessFunction = ({
handlerName:
rawCreateServerlessFunctionInput.handlerName ?? DEFAULT_HANDLER_NAME,
universalIdentifier:
rawCreateServerlessFunctionInput.universalIdentifier ?? v4(),
rawCreateServerlessFunctionInput.universalIdentifier ?? id,
createdAt: currentDate,
updatedAt: currentDate,
deletedAt: null,
latestVersion: null,
publishedVersions: [],
applicationId: rawCreateServerlessFunctionInput.applicationId ?? null,
applicationId: workspaceCustomApplicationId,
runtime: ServerlessFunctionRuntime.NODE22,
timeoutSeconds: rawCreateServerlessFunctionInput.timeoutSeconds ?? 300,
serverlessFunctionLayerId:
@@ -4,6 +4,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { IsNull, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -32,6 +33,7 @@ export class ViewFieldV2Service {
private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
@InjectRepository(ViewFieldEntity)
private readonly viewFieldRepository: Repository<ViewFieldEntity>,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -67,6 +69,13 @@ export class ViewFieldV2Service {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewFieldMaps: existingFlatViewFieldMaps,
flatViewMaps,
@@ -89,6 +98,7 @@ export class ViewFieldV2Service {
fromCreateViewFieldInputToFlatViewFieldToCreate({
createViewFieldInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
}),
);
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
import { ViewFieldController } from 'src/engine/metadata-modules/view-field/controllers/view-field.controller';
@@ -16,6 +17,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewFieldEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
PermissionsModule,
WorkspaceMigrationV2Module,
WorkspaceManyOrAllFlatEntityMapsCacheModule,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -22,6 +23,7 @@ export class ViewFilterV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -31,6 +33,13 @@ export class ViewFilterV2Service {
createViewFilterInput: CreateViewFilterInput;
workspaceId: string;
}): Promise<ViewFilterDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewFilterMaps: existingFlatViewFilterMaps,
flatViewMaps,
@@ -52,6 +61,7 @@ export class ViewFilterV2Service {
fromCreateViewFilterInputToFlatViewFilterToCreate({
createViewFilterInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -18,6 +19,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewFilterEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -27,6 +28,7 @@ export class ViewGroupV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -62,6 +64,13 @@ export class ViewGroupV2Service {
return [];
}
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatViewGroupMaps: existingFlatViewGroupMaps,
flatViewMaps,
@@ -82,6 +91,7 @@ export class ViewGroupV2Service {
fromCreateViewGroupInputToFlatViewGroupToCreate({
createViewGroupInput,
workspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
}),
);
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
@@ -18,6 +19,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
imports: [
TypeOrmModule.forFeature([ViewGroupEntity, ViewEntity]),
WorkspaceCacheStorageModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
WorkspaceMigrationV2Module,
@@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { isDefined } from 'twenty-shared/utils';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service';
import { computeFlatEntityMapsFromTo } from 'src/engine/metadata-modules/flat-entity/utils/compute-flat-entity-maps-from-to.util';
import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps-or-throw.util';
@@ -22,6 +23,7 @@ export class ViewV2Service {
constructor(
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly applicationService: ApplicationService,
) {}
async createOne({
@@ -33,6 +35,13 @@ export class ViewV2Service {
workspaceId: string;
createdByUserWorkspaceId?: string;
}): Promise<ViewDTO> {
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
const {
flatObjectMetadataMaps,
flatViewMaps: existingFlatViewMaps,
@@ -52,6 +61,7 @@ export class ViewV2Service {
createViewInput,
workspaceId,
createdByUserWorkspaceId,
workspaceCustomApplicationId: workspaceCustomFlatApplication.id,
});
const validateAndBuildResult =
@@ -1,6 +1,7 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { I18nModule } from 'src/engine/core-modules/i18n/i18n.module';
import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module';
@@ -32,6 +33,7 @@ import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspa
ViewGroupModule,
ViewSortModule,
I18nModule,
ApplicationModule,
FeatureFlagModule,
PermissionsModule,
UserRoleModule,
@@ -8,8 +8,6 @@ import { isDefined } from 'twenty-shared/utils';
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity.type';
import { WORKSPACE_FLAT_MAP_CACHE_KEY } from 'src/engine/workspace-flat-map-cache/decorators/workspace-flat-map-cache.decorator';
import {
WorkspaceFlatMapCacheException,
@@ -17,9 +15,7 @@ import {
} from 'src/engine/workspace-flat-map-cache/exceptions/workspace-flat-map-cache.exception';
@Injectable()
export abstract class WorkspaceFlatMapCacheService<
T extends FlatEntityMaps<FlatEntity>,
> {
export abstract class WorkspaceFlatMapCacheService<T> {
protected readonly logger = new Logger(this.constructor.name);
private readonly localCacheFlatMaps = new Map<string, T>();
@@ -4,6 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { DataSource, Repository } from 'typeorm';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service';
@@ -41,7 +42,13 @@ export class DevSeederPermissionsService {
private readonly coreDataSource: DataSource,
) {}
public async initPermissions(workspaceId: string) {
public async initPermissions({
twentyStandardApplication,
workspaceId,
}: {
workspaceId: string;
twentyStandardApplication: ApplicationEntity;
}) {
const adminRole = await this.roleRepository.findOne({
where: {
standardId: ADMIN_ROLE.standardId,
@@ -100,6 +107,7 @@ export class DevSeederPermissionsService {
const guestRole = await this.roleService.createGuestRole({
workspaceId,
applicationId: twentyStandardApplication.id,
});
await this.userRoleService.assignRoleToUserWorkspace({
@@ -135,6 +143,7 @@ export class DevSeederPermissionsService {
const memberRole = await this.roleService.createMemberRole({
workspaceId,
applicationId: twentyStandardApplication.id,
});
await this.coreDataSource
@@ -13,7 +13,6 @@ import { seedFeatureFlags } from 'src/engine/workspace-manager/dev-seeder/core/u
import { seedUserWorkspaces } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-user-workspaces.util';
import { seedUsers } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-users.util';
import { createWorkspace } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-workspace.util';
import { computeWorkspaceCustomCreateApplicationInput } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/compute-workspace-custom-create-application-input';
import { extractVersionMajorMinorPatch } from 'src/utils/version/extract-version-major-minor-patch';
type SeedCoreSchemaArgs = {
@@ -36,14 +35,6 @@ export const seedCoreSchema = async ({
const schemaName = 'core';
const createWorkspaceStaticInput = SEEDER_CREATE_WORKSPACE_INPUT[workspaceId];
const workspaceCustomApplicationCreateInput =
computeWorkspaceCustomCreateApplicationInput({
workspace: {
id: workspaceId,
displayName: createWorkspaceStaticInput.displayName,
},
});
const version = extractVersionMajorMinorPatch(appVersion);
const queryRunner = dataSource.createQueryRunner();
@@ -51,13 +42,14 @@ export const seedCoreSchema = async ({
await queryRunner.startTransaction();
try {
const customWorkspaceApplication = await applicationService.create(
{
...workspaceCustomApplicationCreateInput,
serverlessFunctionLayerId: null,
},
queryRunner,
);
const customWorkspaceApplication =
await applicationService.createWorkspaceCustomApplication(
{
workspaceId,
workspaceDisplayName: createWorkspaceStaticInput.displayName,
},
queryRunner,
);
await createWorkspace({
queryRunner,
@@ -76,6 +68,7 @@ export const seedCoreSchema = async ({
await applicationService.createTwentyStandardApplication(
{
workspaceId,
skipCacheInvalidation: true,
},
queryRunner,
);
@@ -4,6 +4,7 @@ import { InjectDataSource } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { DataSource } from 'typeorm';
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type DataSourceEntity } from 'src/engine/metadata-modules/data-source/data-source.entity';
import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input';
import { FieldMetadataServiceV2 } from 'src/engine/metadata-modules/field-metadata/services/field-metadata.service-v2';
@@ -85,9 +86,11 @@ export class DevSeederMetadataService {
dataSourceMetadata,
workspaceId,
featureFlags,
twentyStandardFlatApplication,
}: {
dataSourceMetadata: DataSourceEntity;
workspaceId: string;
twentyStandardFlatApplication: FlatApplication;
featureFlags?: Record<string, boolean>;
}) {
const config = this.workspaceConfigs[workspaceId];
@@ -98,6 +101,7 @@ export class DevSeederMetadataService {
);
}
// TODO get
for (const obj of config.objects) {
await this.seedCustomObject({
dataSourceId: dataSourceMetadata.id,
@@ -126,6 +130,7 @@ export class DevSeederMetadataService {
workspaceId,
dataSourceMetadata,
featureFlags,
twentyStandardFlatApplication,
});
}
@@ -181,10 +186,12 @@ export class DevSeederMetadataService {
workspaceId,
dataSourceMetadata,
featureFlags,
twentyStandardFlatApplication,
}: {
workspaceId: string;
dataSourceMetadata: DataSourceEntity;
featureFlags?: Record<string, boolean>;
twentyStandardFlatApplication: FlatApplication;
}): Promise<void> {
const createdObjectMetadata =
await this.objectMetadataServiceV2.findManyWithinWorkspace(workspaceId);
@@ -195,6 +202,7 @@ export class DevSeederMetadataService {
objectMetadataItems: createdObjectMetadata,
workspaceSchemaName: dataSourceMetadata.schema,
featureFlags,
twentyStandardFlatApplication,
});
}
@@ -5,6 +5,8 @@ import { isDefined } from 'twenty-shared/utils';
import { DataSource } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
import { WorkspaceFlatApplicationMapCacheService } from 'src/engine/core-modules/application/services/workspace-flat-application-map-cache.service';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
@@ -21,7 +23,6 @@ import { seedPageLayoutWidgets } from 'src/engine/workspace-manager/dev-seeder/c
import { seedPageLayouts } from 'src/engine/workspace-manager/dev-seeder/core/utils/seed-page-layouts.util';
import { DevSeederDataService } from 'src/engine/workspace-manager/dev-seeder/data/services/dev-seeder-data.service';
import { DevSeederMetadataService } from 'src/engine/workspace-manager/dev-seeder/metadata/services/dev-seeder-metadata.service';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
import { WorkspaceSyncMetadataService } from 'src/engine/workspace-manager/workspace-sync-metadata/workspace-sync-metadata.service';
@Injectable()
@@ -38,6 +39,7 @@ export class DevSeederService {
private readonly flatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService,
private readonly devSeederDataService: DevSeederDataService,
private readonly applicationService: ApplicationService,
private readonly workspaceFlatApplicationMapCacheService: WorkspaceFlatApplicationMapCacheService,
@InjectDataSource()
private readonly coreDataSource: DataSource,
) {}
@@ -59,6 +61,10 @@ export class DevSeederService {
workspaceId,
);
await this.workspaceFlatApplicationMapCacheService.invalidateCache({
workspaceId,
});
const dataSourceMetadata =
await this.dataSourceService.createDataSourceMetadata(
workspaceId,
@@ -80,6 +86,13 @@ export class DevSeederService {
);
}
const { twentyStandardFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
await this.workspaceSyncMetadataService.synchronize({
workspaceId: workspaceId,
dataSourceId: dataSourceMetadata.id,
@@ -90,13 +103,17 @@ export class DevSeederService {
dataSourceMetadata,
workspaceId,
featureFlags,
twentyStandardFlatApplication,
});
await this.devSeederMetadataService.seedRelations({
workspaceId,
});
await this.devSeederPermissionsService.initPermissions(workspaceId);
await this.devSeederPermissionsService.initPermissions({
workspaceId,
twentyStandardApplication,
});
await seedPageLayouts(this.coreDataSource, 'core', workspaceId);
await seedPageLayoutTabs(this.coreDataSource, 'core', workspaceId);
@@ -1,7 +1,7 @@
import { isString } from '@sniptt/guards';
import { type DataSource, type QueryRunner } from 'typeorm';
import { v4 } from 'uuid';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewFieldEntity } from 'src/engine/metadata-modules/view-field/entities/view-field.entity';
import { ViewFilterEntity } from 'src/engine/metadata-modules/view-filter/entities/view-filter.entity';
@@ -39,6 +39,7 @@ type PrefillCoreViewsArgs = {
objectMetadataItems: ObjectMetadataEntity[];
featureFlags?: Record<string, boolean>;
workspaceSchemaName: string;
twentyStandardFlatApplication: FlatApplication;
};
export const prefillCoreViews = async ({
@@ -46,25 +47,32 @@ export const prefillCoreViews = async ({
workspaceId,
objectMetadataItems,
workspaceSchemaName,
twentyStandardFlatApplication,
}: PrefillCoreViewsArgs): Promise<ViewEntity[]> => {
const views = [
companiesAllView(objectMetadataItems, true),
peopleAllView(objectMetadataItems, true),
opportunitiesAllView(objectMetadataItems, true),
opportunitiesByStageView(objectMetadataItems, true),
notesAllView(objectMetadataItems, true),
tasksAllView(objectMetadataItems, true),
tasksAssignedToMeView(objectMetadataItems, true),
tasksByStatusView(objectMetadataItems, true),
workflowsAllView(objectMetadataItems, true),
workflowVersionsAllView(objectMetadataItems, true),
workflowRunsAllView(objectMetadataItems, true),
dashboardsAllView(objectMetadataItems, true),
workspaceMembersAllView(objectMetadataItems, true),
messagesAllView(objectMetadataItems, true),
messageThreadsAllView(objectMetadataItems, true),
calendarEventsAllView(objectMetadataItems, true),
];
companiesAllView,
peopleAllView,
opportunitiesAllView,
opportunitiesByStageView,
notesAllView,
tasksAllView,
tasksAssignedToMeView,
tasksByStatusView,
workflowsAllView,
workflowVersionsAllView,
workflowRunsAllView,
dashboardsAllView,
workspaceMembersAllView,
messagesAllView,
messageThreadsAllView,
calendarEventsAllView,
].map((seeder) =>
seeder({
objectMetadataItems,
useCoreNaming: true,
twentyStandardFlatApplication,
}),
);
const queryRunner = coreDataSource.createQueryRunner();
@@ -73,7 +81,12 @@ export const prefillCoreViews = async ({
try {
await queryRunner.startTransaction();
const createdViews = await createCoreViews(queryRunner, workspaceId, views);
const createdViews = await createCoreViews(
queryRunner,
workspaceId,
views,
twentyStandardFlatApplication,
);
await prefillWorkspaceFavorites(
createdViews
@@ -112,13 +125,9 @@ export const createCoreViews = async (
queryRunner: QueryRunner,
workspaceId: string,
viewDefinitions: ViewDefinition[],
twentyStandardFlatApplication: FlatApplication,
): Promise<ViewEntity[]> => {
const viewDefinitionsWithId = viewDefinitions.map((viewDefinition) => ({
...viewDefinition,
id: v4(),
}));
const coreViews: Partial<ViewEntity>[] = viewDefinitionsWithId.map(
const coreViews: Partial<ViewEntity>[] = viewDefinitions.map(
({
id,
name,
@@ -131,6 +140,8 @@ export const createCoreViews = async (
openRecordIn,
kanbanAggregateOperation,
kanbanAggregateOperationFieldMetadataId,
applicationId,
universalIdentifier,
}) => ({
id,
name: isString(name) ? name : name.message || '',
@@ -150,13 +161,15 @@ export const createCoreViews = async (
workspaceId,
anyFieldFilterValue: null,
visibility: ViewVisibility.WORKSPACE,
applicationId,
universalIdentifier,
}),
);
const viewRepository = queryRunner.manager.getRepository(ViewEntity);
const createdViews = await viewRepository.save(coreViews);
for (const viewDefinition of viewDefinitionsWithId) {
for (const viewDefinition of viewDefinitions) {
if (viewDefinition.fields && viewDefinition.fields.length > 0) {
const coreViewFields: Partial<ViewFieldEntity>[] =
viewDefinition.fields.map((field) => ({
@@ -166,6 +179,8 @@ export const createCoreViews = async (
size: field.size,
viewId: viewDefinition.id,
workspaceId,
applicationId: twentyStandardFlatApplication.id,
universalIdentifier: field.universalIdentifier,
}));
const viewFieldRepository =
@@ -182,6 +197,8 @@ export const createCoreViews = async (
operand: filter.operand,
value: filter.value,
workspaceId,
applicationId: twentyStandardFlatApplication.id,
universalIdentifier: filter.universalIdentifier,
}));
const viewFilterRepository =
@@ -203,6 +220,8 @@ export const createCoreViews = async (
position: group.position,
viewId: viewDefinition.id,
workspaceId,
applicationId: twentyStandardFlatApplication.id,
universalIdentifier: group.universalIdentifier,
}));
const viewGroupRepository =
@@ -11,13 +11,18 @@ export interface ViewDefinition {
type: string;
key: string | null;
position: number;
applicationId: string;
universalIdentifier: string;
icon?: string;
isCustom?: boolean;
openRecordIn?: ViewOpenRecordInType;
kanbanFieldMetadataId?: string;
kanbanAggregateOperation?: AggregateOperations;
kanbanAggregateOperationFieldMetadataId?: string;
calendarFieldMetadataId?: string;
calendarLayout?: string;
fields?: {
universalIdentifier: string;
fieldMetadataId: string;
position: number;
isVisible: boolean;
@@ -25,12 +30,14 @@ export interface ViewDefinition {
aggregateOperation?: AggregateOperations;
}[];
filters?: {
universalIdentifier: string;
fieldMetadataId: string;
displayValue: string;
operand: ViewFilterOperand;
value: string;
}[];
groups?: {
universalIdentifier: string;
fieldMetadataId: string;
isVisible: boolean;
fieldValue: string;
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
CALENDAR_EVENT_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const calendarEventsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
export const calendarEventsAllView = ({
objectMetadataItems,
twentyStandardFlatApplication,
useCoreNaming,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const calendarEventObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.calendarEvent,
);
@@ -20,7 +29,13 @@ export const calendarEventsAllView = (
throw new Error('CalendarEvent object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Calendar Events',
objectMetadataId: calendarEventObjectMetadata.id ?? '',
type: 'calendar',
@@ -45,6 +60,9 @@ export const calendarEventsAllView = (
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.title.universalIdentifier,
},
{
fieldMetadataId:
@@ -55,6 +73,9 @@ export const calendarEventsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.startsAt.universalIdentifier,
},
{
fieldMetadataId:
@@ -65,6 +86,9 @@ export const calendarEventsAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.endsAt.universalIdentifier,
},
{
fieldMetadataId:
@@ -75,6 +99,9 @@ export const calendarEventsAllView = (
position: 3,
isVisible: true,
size: 100,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.isFullDay.universalIdentifier,
},
{
fieldMetadataId:
@@ -85,6 +112,9 @@ export const calendarEventsAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.location.universalIdentifier,
},
{
fieldMetadataId:
@@ -96,6 +126,9 @@ export const calendarEventsAllView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.conferenceLink.universalIdentifier,
},
{
fieldMetadataId:
@@ -106,6 +139,9 @@ export const calendarEventsAllView = (
position: 6,
isVisible: true,
size: 100,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.isCanceled.universalIdentifier,
},
{
fieldMetadataId:
@@ -116,6 +152,9 @@ export const calendarEventsAllView = (
position: 7,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.calendarEvent.views.allCalendarEvents.viewFields
.createdAt.universalIdentifier,
},
],
};
@@ -1,18 +1,27 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
COMPANY_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const companiesAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const companiesAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const companyObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.company,
);
@@ -21,7 +30,13 @@ export const companiesAllView = (
throw new Error('Company object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.company.views.allCompanies.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Companies',
objectMetadataId: companyObjectMetadata.id ?? '',
type: 'table',
@@ -39,6 +54,9 @@ export const companiesAllView = (
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -50,6 +68,9 @@ export const companiesAllView = (
isVisible: true,
size: 100,
aggregateOperation: AggregateOperations.COUNT,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.domainName
.universalIdentifier,
},
{
fieldMetadataId:
@@ -60,6 +81,9 @@ export const companiesAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -70,6 +94,9 @@ export const companiesAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.accountOwner
.universalIdentifier,
},
{
fieldMetadataId:
@@ -80,6 +107,9 @@ export const companiesAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.createdAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -91,6 +121,9 @@ export const companiesAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.MAX,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.employees
.universalIdentifier,
},
{
fieldMetadataId:
@@ -102,6 +135,9 @@ export const companiesAllView = (
isVisible: true,
size: 170,
aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.linkedinLink
.universalIdentifier,
},
{
fieldMetadataId:
@@ -112,6 +148,9 @@ export const companiesAllView = (
isVisible: true,
size: 170,
aggregateOperation: AggregateOperations.COUNT_NOT_EMPTY,
universalIdentifier:
STANDARD_OBJECTS.company.views.allCompanies.viewFields.address
.universalIdentifier,
},
],
};
@@ -1,53 +0,0 @@
import { msg } from '@lingui/core/macro';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
export const customAllView = (
objectMetadataItem: ObjectMetadataEntity,
useCoreNaming = false,
) => {
const nameField = objectMetadataItem.fields.find(
(field) => field.name === 'name',
);
const otherFields = objectMetadataItem.fields.filter(
(field) => field.name !== 'name',
);
if (!nameField) {
throw new Error(
`Name field not found while creating All ${objectMetadataItem.namePlural} view`,
);
}
return {
name: useCoreNaming
? msg`All {objectLabelPlural}`
: `All ${objectMetadataItem.labelPlural}`,
objectMetadataId: objectMetadataItem.id,
type: 'table',
key: 'INDEX',
position: 0,
icon: 'IconList',
isCustom: false,
kanbanFieldMetadataId: '',
filters: [],
fields: [
{
fieldMetadataId:
objectMetadataItem.fields.find((field) => field.name === 'name')
?.id ?? '',
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
},
...otherFields.map((field, index) => ({
fieldMetadataId: field.id,
position: index + 1,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
})),
],
};
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
DASHBOARD_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const dashboardsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const dashboardsAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const dashboardObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.dashboard,
);
@@ -20,7 +29,13 @@ export const dashboardsAllView = (
throw new Error('Dashboard object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.dashboard.views.allDashboards.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Dashboards',
objectMetadataId: dashboardObjectMetadata.id ?? '',
type: 'table',
@@ -39,6 +54,9 @@ export const dashboardsAllView = (
position: 0,
isVisible: true,
size: 200,
universalIdentifier:
STANDARD_OBJECTS.dashboard.views.allDashboards.viewFields.title
.universalIdentifier,
},
{
fieldMetadataId:
@@ -49,6 +67,9 @@ export const dashboardsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.dashboard.views.allDashboards.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -59,6 +80,9 @@ export const dashboardsAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.dashboard.views.allDashboards.viewFields.createdAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -69,6 +93,9 @@ export const dashboardsAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.dashboard.views.allDashboards.viewFields.updatedAt
.universalIdentifier,
},
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
MESSAGE_THREAD_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const messageThreadsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
export const messageThreadsAllView = ({
objectMetadataItems,
twentyStandardFlatApplication,
useCoreNaming,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const messageThreadObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.messageThread,
);
@@ -20,7 +29,13 @@ export const messageThreadsAllView = (
throw new Error('MessageThread object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.messageThread.views.allMessageThreads.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Message Threads',
objectMetadataId: messageThreadObjectMetadata.id ?? '',
type: 'table',
@@ -39,6 +54,9 @@ export const messageThreadsAllView = (
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
universalIdentifier:
STANDARD_OBJECTS.messageThread.views.allMessageThreads.viewFields
.messages.universalIdentifier,
},
{
fieldMetadataId:
@@ -49,6 +67,9 @@ export const messageThreadsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.messageThread.views.allMessageThreads.viewFields
.createdAt.universalIdentifier,
},
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
MESSAGE_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const messagesAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
export const messagesAllView = ({
objectMetadataItems,
twentyStandardFlatApplication,
useCoreNaming,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const messageObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.message,
);
@@ -20,7 +29,13 @@ export const messagesAllView = (
throw new Error('Message object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.message.views.allMessages.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Messages',
objectMetadataId: messageObjectMetadata.id ?? '',
type: 'table',
@@ -38,6 +53,9 @@ export const messagesAllView = (
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.subject
.universalIdentifier,
},
{
fieldMetadataId:
@@ -48,6 +66,9 @@ export const messagesAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.messageThread
.universalIdentifier,
},
{
fieldMetadataId:
@@ -59,6 +80,9 @@ export const messagesAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields
.messageParticipants.universalIdentifier,
},
{
fieldMetadataId:
@@ -69,6 +93,9 @@ export const messagesAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.receivedAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -79,6 +106,9 @@ export const messagesAllView = (
position: 4,
isVisible: true,
size: 180,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.headerMessageId
.universalIdentifier,
},
{
fieldMetadataId:
@@ -88,6 +118,9 @@ export const messagesAllView = (
position: 5,
isVisible: true,
size: 200,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.text
.universalIdentifier,
},
{
fieldMetadataId:
@@ -98,6 +131,9 @@ export const messagesAllView = (
position: 6,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.message.views.allMessages.viewFields.createdAt
.universalIdentifier,
},
],
};
@@ -1,16 +1,25 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
NOTE_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const notesAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const notesAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const noteObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.note,
);
@@ -19,7 +28,13 @@ export const notesAllView = (
throw new Error('Note object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.note.views.allNotes.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Notes',
objectMetadataId: noteObjectMetadata.id,
type: 'table',
@@ -37,6 +52,9 @@ export const notesAllView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.note.views.allNotes.viewFields.title
.universalIdentifier,
},
{
fieldMetadataId:
@@ -46,6 +64,9 @@ export const notesAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.note.views.allNotes.viewFields.noteTargets
.universalIdentifier,
},
{
fieldMetadataId:
@@ -55,6 +76,9 @@ export const notesAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.note.views.allNotes.viewFields.bodyV2
.universalIdentifier,
},
{
fieldMetadataId:
@@ -64,6 +88,9 @@ export const notesAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.note.views.allNotes.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -74,18 +101,19 @@ export const notesAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.note.views.allNotes.viewFields.createdAt
.universalIdentifier,
},
/*
TODO: Add later, since we don't have real-time it probably doesn't work well?
{
fieldMetadataId:
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[
BASE_OBJECT_STANDARD_FIELD_IDS.updatedAt
objectMetadataMap[STANDARD_OBJECT_IDS.note].fields[.updatedAt
],
position: 0,
isVisible: true,
size: 210,
},
size: 210},
*/
],
};
@@ -1,14 +1,23 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const opportunitiesAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const opportunitiesAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const opportunityObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.opportunity,
);
@@ -17,7 +26,13 @@ export const opportunitiesAllView = (
throw new Error('Opportunity object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.opportunity.views.allOpportunities.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Opportunities',
objectMetadataId: opportunityObjectMetadata.id,
type: 'table',
@@ -35,6 +50,9 @@ export const opportunitiesAllView = (
position: 0,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -46,6 +64,9 @@ export const opportunitiesAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.AVG,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields.amount
.universalIdentifier,
},
{
fieldMetadataId:
@@ -56,6 +77,9 @@ export const opportunitiesAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields
.createdBy.universalIdentifier,
},
{
fieldMetadataId:
@@ -67,6 +91,9 @@ export const opportunitiesAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.MIN,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields
.closeDate.universalIdentifier,
},
{
fieldMetadataId:
@@ -77,6 +104,9 @@ export const opportunitiesAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields.company
.universalIdentifier,
},
{
fieldMetadataId:
@@ -88,6 +118,9 @@ export const opportunitiesAllView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.allOpportunities.viewFields
.pointOfContact.universalIdentifier,
},
],
};
@@ -1,14 +1,23 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { OPPORTUNITY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const opportunitiesByStageView = (
objectMetadataItems: ObjectMetadataEntity[],
export const opportunitiesByStageView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const opportunityObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.opportunity,
);
@@ -17,7 +26,13 @@ export const opportunitiesByStageView = (
throw new Error('Opportunity object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.opportunity.views.byStage.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`By Stage` : 'By Stage',
objectMetadataId: opportunityObjectMetadata.id,
type: 'kanban',
@@ -43,6 +58,9 @@ export const opportunitiesByStageView = (
position: 0,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -53,6 +71,9 @@ export const opportunitiesByStageView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.amount
.universalIdentifier,
},
{
fieldMetadataId:
@@ -63,6 +84,9 @@ export const opportunitiesByStageView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -73,6 +97,9 @@ export const opportunitiesByStageView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.closeDate
.universalIdentifier,
},
{
fieldMetadataId:
@@ -83,6 +110,9 @@ export const opportunitiesByStageView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.company
.universalIdentifier,
},
{
fieldMetadataId:
@@ -94,6 +124,9 @@ export const opportunitiesByStageView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewFields.pointOfContact
.universalIdentifier,
},
],
groups: [
@@ -106,6 +139,9 @@ export const opportunitiesByStageView = (
isVisible: true,
fieldValue: 'NEW',
position: 0,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewGroups!.new
.universalIdentifier,
},
{
fieldMetadataId:
@@ -116,6 +152,9 @@ export const opportunitiesByStageView = (
isVisible: true,
fieldValue: 'SCREENING',
position: 1,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewGroups!.screening
.universalIdentifier,
},
{
fieldMetadataId:
@@ -126,6 +165,9 @@ export const opportunitiesByStageView = (
isVisible: true,
fieldValue: 'MEETING',
position: 2,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewGroups!.meeting
.universalIdentifier,
},
{
fieldMetadataId:
@@ -136,6 +178,9 @@ export const opportunitiesByStageView = (
isVisible: true,
fieldValue: 'PROPOSAL',
position: 3,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewGroups!.proposal
.universalIdentifier,
},
{
fieldMetadataId:
@@ -146,6 +191,9 @@ export const opportunitiesByStageView = (
isVisible: true,
fieldValue: 'CUSTOMER',
position: 4,
universalIdentifier:
STANDARD_OBJECTS.opportunity.views.byStage.viewGroups!.customer
.universalIdentifier,
},
],
};
@@ -1,150 +0,0 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { type 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';
export const opportunitiesTableByStageView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
const opportunityObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.opportunity,
);
if (!opportunityObjectMetadata) {
throw new Error('Opportunity object metadata not found');
}
return {
name: useCoreNaming ? msg`By Stage` : 'By Stage',
objectMetadataId: opportunityObjectMetadata.id,
type: 'table',
key: null,
position: 1,
icon: 'IconList',
kanbanFieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) => field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
filters: [],
fields: [
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) => field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.name,
)?.id ?? '',
position: 0,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.amount,
)?.id ?? '',
position: 1,
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.AVG,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.createdBy,
)?.id ?? '',
position: 2,
isVisible: true,
size: 150,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.closeDate,
)?.id ?? '',
position: 3,
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.MAX,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.company,
)?.id ?? '',
position: 4,
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.COUNT,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId ===
OPPORTUNITY_STANDARD_FIELD_IDS.pointOfContact,
)?.id ?? '',
position: 5,
isVisible: true,
size: 150,
},
],
groups: [
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
isVisible: true,
fieldValue: 'NEW',
position: 0,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
isVisible: true,
fieldValue: 'SCREENING',
position: 1,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
isVisible: true,
fieldValue: 'MEETING',
position: 2,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
isVisible: true,
fieldValue: 'PROPOSAL',
position: 3,
},
{
fieldMetadataId:
opportunityObjectMetadata.fields.find(
(field) =>
field.standardId === OPPORTUNITY_STANDARD_FIELD_IDS.stage,
)?.id ?? '',
isVisible: true,
fieldValue: 'CUSTOMER',
position: 4,
},
],
};
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { AggregateOperations } from 'src/engine/api/graphql/graphql-query-runner/constants/aggregate-operations.constant';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
PERSON_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const peopleAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const peopleAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const personObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.person,
);
@@ -20,7 +29,13 @@ export const peopleAllView = (
throw new Error('Person object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.person.views.allPeople.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All People',
objectMetadataId: personObjectMetadata.id,
type: 'table',
@@ -38,6 +53,9 @@ export const peopleAllView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -48,6 +66,9 @@ export const peopleAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.COUNT_UNIQUE_VALUES,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.emails
.universalIdentifier,
},
{
fieldMetadataId:
@@ -57,6 +78,9 @@ export const peopleAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -66,6 +90,9 @@ export const peopleAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.company
.universalIdentifier,
},
{
fieldMetadataId:
@@ -76,6 +103,9 @@ export const peopleAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.PERCENTAGE_EMPTY,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.phones
.universalIdentifier,
},
{
fieldMetadataId:
@@ -87,6 +117,9 @@ export const peopleAllView = (
isVisible: true,
size: 150,
aggregateOperation: AggregateOperations.MIN,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.createdAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -96,6 +129,9 @@ export const peopleAllView = (
position: 6,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.city
.universalIdentifier,
},
{
fieldMetadataId:
@@ -105,6 +141,9 @@ export const peopleAllView = (
position: 7,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.jobTitle
.universalIdentifier,
},
{
fieldMetadataId:
@@ -115,6 +154,9 @@ export const peopleAllView = (
position: 8,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.linkedinLink
.universalIdentifier,
},
{
fieldMetadataId:
@@ -124,6 +166,9 @@ export const peopleAllView = (
position: 9,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.person.views.allPeople.viewFields.xLink
.universalIdentifier,
},
],
};
@@ -1,16 +1,25 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
TASK_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const tasksAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const tasksAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const taskObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.task,
);
@@ -19,7 +28,13 @@ export const tasksAllView = (
throw new Error('Task object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.task.views.allTasks.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Tasks',
objectMetadataId: taskObjectMetadata.id,
type: 'table',
@@ -35,8 +50,7 @@ export const tasksAllView = (
],
displayValue: 'Task',
operand: 'is',
value: '["TASK"]',
},
value: '["TASK"]'},
],*/,
fields: [
{
@@ -47,6 +61,9 @@ export const tasksAllView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.title
.universalIdentifier,
},
{
fieldMetadataId:
@@ -56,6 +73,9 @@ export const tasksAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.status
.universalIdentifier,
},
{
fieldMetadataId:
@@ -65,6 +85,9 @@ export const tasksAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.taskTargets
.universalIdentifier,
},
{
fieldMetadataId:
@@ -74,6 +97,9 @@ export const tasksAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -83,6 +109,9 @@ export const tasksAllView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.dueAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -92,6 +121,9 @@ export const tasksAllView = (
position: 6,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.assignee
.universalIdentifier,
},
{
fieldMetadataId:
@@ -101,6 +133,9 @@ export const tasksAllView = (
position: 7,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.bodyV2
.universalIdentifier,
},
{
fieldMetadataId:
@@ -111,18 +146,19 @@ export const tasksAllView = (
position: 8,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.allTasks.viewFields.createdAt
.universalIdentifier,
},
/*
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
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[.updatedAt
],
position: 0,
isVisible: true,
size: 210,
},
size: 210},
*/
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { ViewFilterOperand } from 'twenty-shared/types';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { ViewFilterOperand } from 'twenty-shared/types';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
TASK_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const tasksAssignedToMeView = (
objectMetadataItems: ObjectMetadataEntity[],
export const tasksAssignedToMeView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const taskObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.task,
);
@@ -20,7 +29,13 @@ export const tasksAssignedToMeView = (
throw new Error('Task object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.task.views.assignedToMe.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`Assigned to Me` : 'Assigned to Me',
objectMetadataId: taskObjectMetadata.id,
type: 'table',
@@ -40,6 +55,9 @@ export const tasksAssignedToMeView = (
isCurrentWorkspaceMemberSelected: true,
selectedRecordIds: [],
}),
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFilters!.assigneeIsMe
.universalIdentifier,
},
],
fields: [
@@ -51,6 +69,9 @@ export const tasksAssignedToMeView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.title
.universalIdentifier,
},
/*{
fieldMetadataId:
@@ -59,8 +80,7 @@ export const tasksAssignedToMeView = (
],
position: 2,
isVisible: true,
size: 150,
},*/
size: 150},*/
{
fieldMetadataId:
taskObjectMetadata.fields.find(
@@ -69,6 +89,9 @@ export const tasksAssignedToMeView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.taskTargets
.universalIdentifier,
},
{
fieldMetadataId:
@@ -78,6 +101,9 @@ export const tasksAssignedToMeView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -87,6 +113,9 @@ export const tasksAssignedToMeView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.dueAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -96,6 +125,9 @@ export const tasksAssignedToMeView = (
position: 6,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.assignee
.universalIdentifier,
},
{
fieldMetadataId:
@@ -105,6 +137,9 @@ export const tasksAssignedToMeView = (
position: 7,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.bodyV2
.universalIdentifier,
},
{
fieldMetadataId:
@@ -115,6 +150,9 @@ export const tasksAssignedToMeView = (
position: 8,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewFields.createdAt
.universalIdentifier,
},
],
groups: [
@@ -126,6 +164,9 @@ export const tasksAssignedToMeView = (
isVisible: true,
fieldValue: 'TODO',
position: 0,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewGroups!.todo
.universalIdentifier,
},
{
fieldMetadataId:
@@ -135,6 +176,9 @@ export const tasksAssignedToMeView = (
isVisible: true,
fieldValue: 'IN_PROGRESS',
position: 1,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewGroups!.inProgress
.universalIdentifier,
},
{
fieldMetadataId:
@@ -144,6 +188,9 @@ export const tasksAssignedToMeView = (
isVisible: true,
fieldValue: 'DONE',
position: 2,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewGroups!.done
.universalIdentifier,
},
{
fieldMetadataId:
@@ -153,6 +200,9 @@ export const tasksAssignedToMeView = (
isVisible: true,
fieldValue: '',
position: 3,
universalIdentifier:
STANDARD_OBJECTS.task.views.assignedToMe.viewGroups!.empty
.universalIdentifier,
},
],
};
@@ -1,16 +1,25 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
TASK_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const tasksByStatusView = (
objectMetadataItems: ObjectMetadataEntity[],
export const tasksByStatusView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const taskObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.task,
);
@@ -19,7 +28,13 @@ export const tasksByStatusView = (
throw new Error('Task object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.task.views.byStatus.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`By Status` : 'By Status',
objectMetadataId: taskObjectMetadata.id,
type: 'kanban',
@@ -38,8 +53,7 @@ export const tasksByStatusView = (
],
displayValue: 'Task',
operand: 'is',
value: '["TASK"]',
},
value: '["TASK"]'},
],*/,
fields: [
{
@@ -50,6 +64,9 @@ export const tasksByStatusView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewFields.title
.universalIdentifier,
},
{
fieldMetadataId:
@@ -59,6 +76,9 @@ export const tasksByStatusView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewFields.status
.universalIdentifier,
},
{
fieldMetadataId:
@@ -68,6 +88,9 @@ export const tasksByStatusView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewFields.dueAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -77,6 +100,9 @@ export const tasksByStatusView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewFields.assignee
.universalIdentifier,
},
{
fieldMetadataId:
@@ -87,18 +113,19 @@ export const tasksByStatusView = (
position: 6,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewFields.createdAt
.universalIdentifier,
},
/*
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
objectMetadataMap[STANDARD_OBJECT_IDS.task].fields[.updatedAt
],
position: 0,
isVisible: true,
size: 210,
},
size: 210},
*/
],
groups: [
@@ -110,6 +137,9 @@ export const tasksByStatusView = (
isVisible: true,
fieldValue: 'TODO',
position: 0,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewGroups!.todo
.universalIdentifier,
},
{
fieldMetadataId:
@@ -119,6 +149,9 @@ export const tasksByStatusView = (
isVisible: true,
fieldValue: 'IN_PROGRESS',
position: 1,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewGroups!.inProgress
.universalIdentifier,
},
{
fieldMetadataId:
@@ -128,6 +161,9 @@ export const tasksByStatusView = (
isVisible: true,
fieldValue: 'DONE',
position: 2,
universalIdentifier:
STANDARD_OBJECTS.task.views.byStatus.viewGroups!.done
.universalIdentifier,
},
],
};
@@ -1,14 +1,23 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { WORKFLOW_RUN_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const workflowRunsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const workflowRunsAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const workflowRunObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.workflowRun,
);
@@ -17,7 +26,13 @@ export const workflowRunsAllView = (
throw new Error('Workflow run object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Workflow Runs',
objectMetadataId: workflowRunObjectMetadata.id,
type: 'table',
@@ -37,6 +52,9 @@ export const workflowRunsAllView = (
position: 0,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -47,6 +65,9 @@ export const workflowRunsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields.workflow
.universalIdentifier,
},
{
fieldMetadataId:
@@ -57,6 +78,9 @@ export const workflowRunsAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields.status
.universalIdentifier,
},
{
fieldMetadataId:
@@ -67,6 +91,9 @@ export const workflowRunsAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields
.startedAt.universalIdentifier,
},
{
fieldMetadataId:
@@ -77,6 +104,9 @@ export const workflowRunsAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields
.createdBy.universalIdentifier,
},
{
fieldMetadataId:
@@ -88,6 +118,9 @@ export const workflowRunsAllView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowRun.views.allWorkflowRuns.viewFields
.workflowVersion.universalIdentifier,
},
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
WORKFLOW_VERSION_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const workflowVersionsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const workflowVersionsAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const workflowVersionObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.workflowVersion,
);
@@ -20,7 +29,14 @@ export const workflowVersionsAllView = (
throw new Error('Workflow version object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions
.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming
? msg`All {objectLabelPlural}`
: 'All Workflow Versions',
@@ -42,6 +58,9 @@ export const workflowVersionsAllView = (
position: 0,
isVisible: true,
size: 210,
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions.viewFields
.name.universalIdentifier,
},
{
fieldMetadataId:
@@ -52,6 +71,9 @@ export const workflowVersionsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions.viewFields
.workflow.universalIdentifier,
},
{
fieldMetadataId:
@@ -62,6 +84,9 @@ export const workflowVersionsAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions.viewFields
.status.universalIdentifier,
},
{
fieldMetadataId:
@@ -72,6 +97,9 @@ export const workflowVersionsAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions.viewFields
.updatedAt.universalIdentifier,
},
{
fieldMetadataId:
@@ -82,6 +110,9 @@ export const workflowVersionsAllView = (
position: 4,
isVisible: false,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.views.allWorkflowVersions.viewFields
.runs.universalIdentifier,
},
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { ViewOpenRecordInType } from 'src/engine/metadata-modules/view/types/view-open-record-in-type.type';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
WORKFLOW_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const workflowsAllView = (
objectMetadataItems: ObjectMetadataEntity[],
export const workflowsAllView = ({
objectMetadataItems,
useCoreNaming = false,
) => {
twentyStandardFlatApplication,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const workflowObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.workflow,
);
@@ -20,7 +29,13 @@ export const workflowsAllView = (
throw new Error('Workflow object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.workflow.views.allWorkflows.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming ? msg`All {objectLabelPlural}` : 'All Workflows',
objectMetadataId: workflowObjectMetadata.id,
type: 'table',
@@ -39,6 +54,9 @@ export const workflowsAllView = (
position: 0,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.name
.universalIdentifier,
},
{
fieldMetadataId:
@@ -49,6 +67,9 @@ export const workflowsAllView = (
position: 1,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.statuses
.universalIdentifier,
},
{
fieldMetadataId:
@@ -59,6 +80,9 @@ export const workflowsAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.updatedAt
.universalIdentifier,
},
{
fieldMetadataId:
@@ -69,6 +93,9 @@ export const workflowsAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.createdBy
.universalIdentifier,
},
{
fieldMetadataId:
@@ -79,6 +106,9 @@ export const workflowsAllView = (
position: 4,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.versions
.universalIdentifier,
},
{
fieldMetadataId:
@@ -88,6 +118,9 @@ export const workflowsAllView = (
position: 5,
isVisible: false,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workflow.views.allWorkflows.viewFields.runs
.universalIdentifier,
},
],
};
@@ -1,17 +1,26 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
import { v4 } from 'uuid';
import { STANDARD_OBJECTS } from 'src/engine/core-modules/application/constants/standard-object.constant';
import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { type ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { type ViewDefinition } from 'src/engine/workspace-manager/standard-objects-prefill-data/types/view-definition.interface';
import { DEFAULT_VIEW_FIELD_SIZE } from 'src/engine/workspace-manager/standard-objects-prefill-data/views/constants/DEFAULT_VIEW_FIELD_SIZE';
import {
BASE_OBJECT_STANDARD_FIELD_IDS,
WORKSPACE_MEMBER_STANDARD_FIELD_IDS,
} from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
export const workspaceMembersAllView = (
objectMetadataItems: ObjectMetadataEntity[],
useCoreNaming = false,
) => {
export const workspaceMembersAllView = ({
objectMetadataItems,
twentyStandardFlatApplication,
useCoreNaming,
}: {
objectMetadataItems: ObjectMetadataEntity[];
useCoreNaming?: boolean;
twentyStandardFlatApplication: FlatApplication;
}): ViewDefinition => {
const workspaceMemberObjectMetadata = objectMetadataItems.find(
(object) => object.standardId === STANDARD_OBJECT_IDS.workspaceMember,
);
@@ -20,7 +29,14 @@ export const workspaceMembersAllView = (
throw new Error('WorkspaceMember object metadata not found');
}
const viewUniversalIdentifier =
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers
.universalIdentifier;
return {
id: v4(),
universalIdentifier: viewUniversalIdentifier,
applicationId: twentyStandardFlatApplication.id,
name: useCoreNaming
? msg`All {objectLabelPlural}`
: 'All Workspace Members',
@@ -41,6 +57,9 @@ export const workspaceMembersAllView = (
position: 0,
isVisible: true,
size: DEFAULT_VIEW_FIELD_SIZE,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.name.universalIdentifier,
},
{
fieldMetadataId:
@@ -52,6 +71,9 @@ export const workspaceMembersAllView = (
position: 1,
isVisible: true,
size: 180,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.userEmail.universalIdentifier,
},
{
fieldMetadataId:
@@ -63,6 +85,9 @@ export const workspaceMembersAllView = (
position: 2,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.avatarUrl.universalIdentifier,
},
{
fieldMetadataId:
@@ -74,6 +99,9 @@ export const workspaceMembersAllView = (
position: 3,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.colorScheme.universalIdentifier,
},
{
fieldMetadataId:
@@ -84,6 +112,9 @@ export const workspaceMembersAllView = (
position: 4,
isVisible: true,
size: 120,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.locale.universalIdentifier,
},
{
fieldMetadataId:
@@ -94,6 +125,9 @@ export const workspaceMembersAllView = (
position: 5,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.timeZone.universalIdentifier,
},
{
fieldMetadataId:
@@ -105,6 +139,9 @@ export const workspaceMembersAllView = (
position: 6,
isVisible: true,
size: 120,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.dateFormat.universalIdentifier,
},
{
fieldMetadataId:
@@ -116,6 +153,9 @@ export const workspaceMembersAllView = (
position: 7,
isVisible: true,
size: 120,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.timeFormat.universalIdentifier,
},
{
fieldMetadataId:
@@ -126,6 +166,9 @@ export const workspaceMembersAllView = (
position: 8,
isVisible: true,
size: 150,
universalIdentifier:
STANDARD_OBJECTS.workspaceMember.views.allWorkspaceMembers.viewFields
.createdAt.universalIdentifier,
},
],
};
@@ -4,6 +4,7 @@ import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
import { DataSource, Repository } from 'typeorm';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
@@ -79,9 +80,10 @@ export class WorkspaceManagerService {
const featureFlags =
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspaceId);
await this.applicationService.createTwentyStandardApplication({
workspaceId,
});
const twentyStandardApplication =
await this.applicationService.createTwentyStandardApplication({
workspaceId,
});
// TODO later replace by twenty-standard installation aka workspaceMigration run
await this.workspaceSyncMetadataService.synchronize({
@@ -96,7 +98,18 @@ export class WorkspaceManagerService {
`Metadata creation took ${dataSourceMetadataCreationEnd - dataSourceMetadataCreationStart}ms`,
);
await this.setupDefaultRoles(workspaceId, userId);
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
workspaceId,
},
);
await this.setupDefaultRoles({
workspaceId,
userId,
workspaceCustomFlatApplication,
});
const prefillStandardObjectsStart = performance.now();
@@ -104,6 +117,7 @@ export class WorkspaceManagerService {
dataSourceMetadata,
workspaceId,
featureFlags,
twentyStandardApplication,
);
const prefillStandardObjectsEnd = performance.now();
@@ -117,6 +131,7 @@ export class WorkspaceManagerService {
dataSourceMetadata: DataSourceEntity,
workspaceId: string,
featureFlags: Record<string, boolean>,
twentyStandardFlatApplication: FlatApplication,
) {
const createdObjectMetadata =
await this.objectMetadataServiceV2.findManyWithinWorkspace(workspaceId);
@@ -128,6 +143,7 @@ export class WorkspaceManagerService {
);
await prefillCoreViews({
twentyStandardFlatApplication,
coreDataSource: this.coreDataSource,
workspaceId,
objectMetadataItems: createdObjectMetadata,
@@ -155,10 +171,15 @@ export class WorkspaceManagerService {
await this.workspaceDataSourceService.deleteWorkspaceDBSchema(workspaceId);
}
private async setupDefaultRoles(
workspaceId: string,
userId: string,
): Promise<void> {
private async setupDefaultRoles({
userId,
workspaceId,
workspaceCustomFlatApplication,
}: {
workspaceId: string;
userId: string;
workspaceCustomFlatApplication: FlatApplication;
}): Promise<void> {
const adminRole = await this.roleRepository.findOne({
where: {
standardId: ADMIN_ROLE.standardId,
@@ -180,6 +201,7 @@ export class WorkspaceManagerService {
const memberRole = await this.roleService.createMemberRole({
workspaceId,
applicationId: workspaceCustomFlatApplication.id,
});
await this.workspaceRepository.update(workspaceId, {
@@ -18,7 +18,14 @@ type AgentComparatorResult = {
type WorkspaceAgentComparatorArgs = FromTo<FlatAgent[], 'FlatAgents'>;
const agentPropertiesToIgnore = ['id', 'createdAt', 'updatedAt', 'workspaceId'];
const agentPropertiesToIgnore = [
'id',
'createdAt',
'updatedAt',
'workspaceId',
'universalIdentifier',
'applicationId',
];
@Injectable()
export class WorkspaceAgentComparator {
@@ -29,6 +29,8 @@ const commonFieldPropertiesToIgnore = [
'relationTargetObjectMetadataId',
'relationTargetFieldMetadata',
'relationTargetObjectMetadata',
'universalIdentifier',
'applicationId',
];
const fieldPropertiesToStringify = ['defaultValue'] as const;
@@ -10,7 +10,13 @@ import {
import { type IndexMetadataEntity } from 'src/engine/metadata-modules/index-metadata/index-metadata.entity';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
const propertiesToIgnore = ['createdAt', 'updatedAt', 'indexFieldMetadatas'];
const propertiesToIgnore = [
'createdAt',
'updatedAt',
'indexFieldMetadatas',
'universalIdentifier',
'applicationId',
];
@Injectable()
export class WorkspaceIndexComparator {
@@ -20,6 +20,8 @@ const objectPropertiesToIgnore = [
'imageIdentifierFieldMetadataId',
'isActive',
'fields',
'universalIdentifier',
'applicationId',
];
@Injectable()
@@ -37,6 +37,8 @@ const rolePropertiesToIgnore = [
'permissionFlags',
'objectPermissions',
'fieldPermissions',
'universalIdentifier',
'applicationId',
];
@Injectable()
@@ -157,33 +157,6 @@ export const CONNECTED_ACCOUNT_STANDARD_FIELD_IDS = {
connectionParameters: '20202020-a1b2-46be-814f-6228af16c481',
} as const;
export const EVENT_STANDARD_FIELD_IDS = {
properties: '20202020-f142-4b04-b91b-6a2b4af3bf10',
workspaceMember: '20202020-af23-4479-9a30-868edc474b35',
person: '20202020-c414-45b9-a60a-ac27aa96229e',
company: '20202020-04ad-4221-a744-7a8278a5ce20',
opportunity: '20202020-7664-4a35-a3df-580d389fd5f0',
custom: '20202020-4a71-41b0-9f83-9cdcca3f8b14',
} as const;
export const AUDIT_LOGS_STANDARD_FIELD_IDS = {
name: '20202020-2462-4b9d-b5d9-745febb3b095',
properties: '20202020-5d36-470e-8fad-d56ea3ab2fd0',
context: '20202020-b9d1-4058-9a75-7469cab5ca8c',
objectName: '20202020-76ba-4c47-b7e5-96034005d00a',
objectMetadataId: '20202020-127b-409d-9864-0ec44aa9ed98',
recordId: '20202020-c578-4acf-bf94-eb53b035cea2',
workspaceMember: '20202020-6e96-4300-b3f5-67a707147385',
} as const;
export const BEHAVIORAL_EVENT_STANDARD_FIELD_IDS = {
name: '20202020-2462-4b9d-b5d9-745febb3b095',
properties: '20202020-5d36-470e-8fad-d56ea3ab2fd0',
context: '20202020-bd62-4b5b-8385-6caeed8f8078',
objectName: '20202020-a744-406c-a2e1-9d83d74f4341',
recordId: '20202020-6d8b-4ca5-9869-f882cb335673',
} as const;
export const TIMELINE_ACTIVITY_STANDARD_FIELD_IDS = {
happensAt: '20202020-9526-4993-b339-c4318c4d39f0',
type: '20202020-5e7b-4ccd-8b8a-86b94b474134',
@@ -537,7 +510,6 @@ export const WORKSPACE_MEMBER_STANDARD_FIELD_IDS = {
calendarStartDay: '20202020-92d0-1d7f-a126-25ededa6b142',
numberFormat: '20202020-7f40-4e7f-b126-11c0eda6b141',
} as const;
export const CUSTOM_OBJECT_STANDARD_FIELD_IDS = {
name: '20202020-ba07-4ffd-ba63-009491f5749c',
position: '20202020-c2bd-4e16-bb9a-c8b0411bf49d',
@@ -568,7 +540,6 @@ export const STANDARD_OBJECT_FIELD_IDS = {
activity: ACTIVITY_STANDARD_FIELD_IDS,
attachment: ATTACHMENT_STANDARD_FIELD_IDS,
blocklist: BLOCKLIST_STANDARD_FIELD_IDS,
behavioralEvent: BEHAVIORAL_EVENT_STANDARD_FIELD_IDS,
calendarChannelEventAssociation:
CALENDAR_CHANNEL_EVENT_ASSOCIATION_STANDARD_FIELD_IDS,
calendarChannel: CALENDAR_CHANNEL_STANDARD_FIELD_IDS,
@@ -579,7 +550,6 @@ export const STANDARD_OBJECT_FIELD_IDS = {
connectedAccount: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS,
dashboard: DASHBOARD_STANDARD_FIELD_IDS,
favorite: FAVORITE_STANDARD_FIELD_IDS,
auditLog: AUDIT_LOGS_STANDARD_FIELD_IDS,
messageChannelMessageAssociation:
MESSAGE_CHANNEL_MESSAGE_ASSOCIATION_STANDARD_FIELD_IDS,
messageChannel: MESSAGE_CHANNEL_STANDARD_FIELD_IDS,
@@ -1,5 +1,4 @@
import { type FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
export interface WorkspaceSyncContext {
workspaceId: string;
dataSourceId: string;
@@ -1,27 +0,0 @@
import { isDefined } from 'twenty-shared/utils';
import { v4 } from 'uuid';
import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
import { type CreateApplicationInput } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
export const computeWorkspaceCustomCreateApplicationInput = ({
workspace,
applicationId = v4(),
}: {
applicationId?: string;
workspace: Pick<WorkspaceEntity, 'id' | 'displayName'>;
}) =>
({
description: 'Workspace custom application',
name: `${isDefined(workspace.displayName) ? `${workspace.displayName}'s ` : ''}custom application`,
sourcePath: 'workspace-custom',
sourceType: 'local',
version: '1.0.0',
universalIdentifier: applicationId,
workspaceId: workspace.id,
id: applicationId,
canBeUninstalled: false,
}) as const satisfies CreateApplicationInput & {
workspaceId: string;
id: string;
};
@@ -10,7 +10,7 @@ import { jestExpectToBeDefined } from 'test/utils/jest-expect-to-be-defined.util
import { isDefined } from 'twenty-shared/utils';
import { WorkspaceActivationStatus } from 'twenty-shared/workspace';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/twenty-standard-applications';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/core-modules/application/constants/twenty-standard-applications';
describe('Successful user and workspace creation', () => {
let createdUserAccessToken: string | undefined;