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:
+19
-2
@@ -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),
|
||||
|
||||
+6
@@ -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,
|
||||
|
||||
+1
-1
@@ -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',
|
||||
|
||||
+65
@@ -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`);
|
||||
}
|
||||
}
|
||||
+15
-23
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+13
-7
@@ -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 {}
|
||||
|
||||
+1
-1
@@ -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';
|
||||
|
||||
+6
-6
@@ -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,
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user