feat: make workflow objects searchable (#18906)
## Summary - Adds `isSearchable: true` to the workflow standard object definition so new workspaces get searchable workflows automatically - Adds a `upgrade:1-20:make-workflow-searchable` migration command that flips the `isSearchable` flag on the `objectMetadata` row for existing workspaces, with proper cache invalidation and metadata version increment - Registers the command in the 1-20 upgrade module and the `upgrade.command.ts` orchestrator The `searchVector` stored generated column already exists on the workflow table, so no data backfill is needed — this is purely a metadata flag change that makes the search service include workflows in results. ## Test plan - [x] `--dry-run` logs what it would do without making changes - [x] Actual run updates both workspaces and invalidates caches - [x] Idempotent: re-running skips already-searchable workspaces - [x] Typecheck passes - [x] Lint passes on changed files Made with [Cursor](https://cursor.com)
This commit is contained in:
+87
@@ -0,0 +1,87 @@
|
||||
import { InjectDataSource, InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { DataSource, Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
import { RunOnWorkspaceArgs } from 'src/database/commands/command-runners/workspaces-migration.command-runner';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service';
|
||||
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
|
||||
import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager';
|
||||
import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service';
|
||||
import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service';
|
||||
|
||||
@Command({
|
||||
name: 'upgrade:1-20:make-workflow-searchable',
|
||||
description: 'Set isSearchable to true on the workflow object metadata',
|
||||
})
|
||||
export class MakeWorkflowSearchableCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner {
|
||||
constructor(
|
||||
@InjectRepository(WorkspaceEntity)
|
||||
protected readonly workspaceRepository: Repository<WorkspaceEntity>,
|
||||
@InjectDataSource()
|
||||
private readonly coreDataSource: DataSource,
|
||||
protected readonly twentyORMGlobalManager: GlobalWorkspaceOrmManager,
|
||||
protected readonly dataSourceService: DataSourceService,
|
||||
private readonly workspaceCacheService: WorkspaceCacheService,
|
||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||
) {
|
||||
super(workspaceRepository, twentyORMGlobalManager, dataSourceService);
|
||||
}
|
||||
|
||||
override async runOnWorkspace({
|
||||
workspaceId,
|
||||
options,
|
||||
}: RunOnWorkspaceArgs): Promise<void> {
|
||||
const isDryRun = options.dryRun ?? false;
|
||||
|
||||
if (isDryRun) {
|
||||
this.logger.log(
|
||||
`[DRY RUN] Would set isSearchable=true on workflow object for workspace ${workspaceId}. Skipping.`,
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
const queryRunner = this.coreDataSource.createQueryRunner();
|
||||
|
||||
await queryRunner.connect();
|
||||
|
||||
try {
|
||||
const result = await queryRunner.query(
|
||||
`UPDATE core."objectMetadata"
|
||||
SET "isSearchable" = true
|
||||
WHERE "workspaceId" = $1
|
||||
AND "nameSingular" = 'workflow'
|
||||
AND "isSearchable" = false`,
|
||||
[workspaceId],
|
||||
);
|
||||
|
||||
const updatedCount = result?.[1] ?? 0;
|
||||
|
||||
if (updatedCount > 0) {
|
||||
this.logger.log(
|
||||
`Set isSearchable=true on workflow object for workspace ${workspaceId}`,
|
||||
);
|
||||
|
||||
await this.workspaceCacheService.invalidateAndRecompute(workspaceId, [
|
||||
'flatObjectMetadataMaps',
|
||||
]);
|
||||
|
||||
await this.workspaceMetadataVersionService.incrementMetadataVersion(
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.workspaceCacheStorageService.flush(workspaceId);
|
||||
} else {
|
||||
this.logger.log(
|
||||
`Workflow already searchable or not found for workspace ${workspaceId}, skipping`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
await queryRunner.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -12,6 +12,7 @@ import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/u
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-permission-flag-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
|
||||
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
|
||||
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
|
||||
import { SeedCliApplicationRegistrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-seed-cli-application-registration.command';
|
||||
@@ -76,6 +77,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
UpdateStandardIndexViewNamesCommand,
|
||||
MakeWorkflowSearchableCommand,
|
||||
],
|
||||
exports: [
|
||||
IdentifyPermissionFlagMetadataCommand,
|
||||
@@ -93,6 +95,7 @@ import { WorkflowCommonModule } from 'src/modules/workflow/common/workflow-commo
|
||||
MigrateRichTextToTextCommand,
|
||||
MigrateMessagingInfrastructureToMetadataCommand,
|
||||
UpdateStandardIndexViewNamesCommand,
|
||||
MakeWorkflowSearchableCommand,
|
||||
],
|
||||
})
|
||||
export class V1_20_UpgradeVersionCommandModule {}
|
||||
|
||||
+3
@@ -43,6 +43,7 @@ import { IdentifyObjectPermissionMetadataCommand } from 'src/database/commands/u
|
||||
import { IdentifyPermissionFlagMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-identify-permission-flag-metadata.command';
|
||||
import { MakeObjectPermissionUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-object-permission-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakePermissionFlagUniversalIdentifierAndApplicationIdNotNullableMigrationCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-permission-flag-universal-identifier-and-application-id-not-nullable-migration.command';
|
||||
import { MakeWorkflowSearchableCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-make-workflow-searchable.command';
|
||||
import { MigrateMessagingInfrastructureToMetadataCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-messaging-infrastructure-to-metadata.command';
|
||||
import { MigrateRichTextToTextCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-migrate-rich-text-to-text.command';
|
||||
import { GenerateApplicationSdkClientsCommand } from 'src/database/commands/upgrade-version-command/1-20/1-20-generate-application-sdk-clients.command';
|
||||
@@ -113,6 +114,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
protected readonly backfillFieldWidgetsCommand: BackfillFieldWidgetsCommand,
|
||||
protected readonly backfillSelectFieldOptionIdsCommand: BackfillSelectFieldOptionIdsCommand,
|
||||
protected readonly updateStandardIndexViewNamesCommand: UpdateStandardIndexViewNamesCommand,
|
||||
protected readonly makeWorkflowSearchableCommand: MakeWorkflowSearchableCommand,
|
||||
) {
|
||||
super(
|
||||
workspaceRepository,
|
||||
@@ -176,6 +178,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
||||
this.backfillFieldWidgetsCommand,
|
||||
this.backfillSelectFieldOptionIdsCommand,
|
||||
this.updateStandardIndexViewNamesCommand,
|
||||
this.makeWorkflowSearchableCommand,
|
||||
this.generateApplicationSdkClientsCommand,
|
||||
];
|
||||
|
||||
|
||||
+1
@@ -761,6 +761,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
|
||||
labelPlural: i18nLabel(msg`Workflows`),
|
||||
description: i18nLabel(msg`A workflow`),
|
||||
icon: 'IconSettingsAutomation',
|
||||
isSearchable: true,
|
||||
shortcut: 'W',
|
||||
labelIdentifierFieldMetadataName: 'name',
|
||||
},
|
||||
|
||||
+1
@@ -173,6 +173,7 @@ describe('SearchResolver', () => {
|
||||
await deleteAllRecords('noteTarget');
|
||||
await deleteAllRecords('taskTarget');
|
||||
await deleteAllRecords('dashboard');
|
||||
await deleteAllRecords('workflow');
|
||||
await deleteAllRecords('_pet');
|
||||
await deleteAllRecords('_surveyResult');
|
||||
await deleteAllRecords('_rocket');
|
||||
|
||||
Reference in New Issue
Block a user