From ca6e979ead258abb3361f0ef21a4e428d219a550 Mon Sep 17 00:00:00 2001 From: Etienne <45695613+etiennejouan@users.noreply.github.com> Date: Mon, 12 May 2025 10:59:10 +0200 Subject: [PATCH] fixes on search (#11955) In this PR - enable search by email - search with ' ' (spaces) string and special characters do not throw entry error closes https://github.com/twentyhq/twenty/issues/11447 & https://github.com/twentyhq/core-team-issues/issues/860 --- .../components/RecordTableContent.tsx | 2 + ...-search-vector-on-person-entity.command.ts | 76 +++++++++++++++++++ .../0-53-upgrade-version-command.module.ts | 15 +++- .../upgrade.command.ts | 3 + .../search/services/search.service.ts | 3 +- .../search/utils/format-search-terms.ts | 4 +- ...ts-vectors-column-expression.utils.spec.ts | 11 +-- .../get-ts-vector-column-expression.util.ts | 12 +-- 8 files changed, 104 insertions(+), 22 deletions(-) create mode 100644 packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command.ts diff --git a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx index 4d96d0537..00884c19b 100644 --- a/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx +++ b/packages/twenty-front/src/modules/object-record/record-table/components/RecordTableContent.tsx @@ -14,6 +14,8 @@ const StyledTableWithPointerEvents = styled(StyledTable)<{ & > * { pointer-events: ${({ isDragging }) => (isDragging ? 'none' : 'auto')}; } + min-height: ${({ isDragging }) => (isDragging ? '100%' : 'auto')}; + height: ${({ isDragging }) => (isDragging ? '100%' : 'auto')}; `; export interface RecordTableContentProps { diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command.ts new file mode 100644 index 000000000..bf385c600 --- /dev/null +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command.ts @@ -0,0 +1,76 @@ +import { InjectRepository } from '@nestjs/typeorm'; + +import { Command } from 'nest-commander'; +import { Repository } from 'typeorm'; + +import { + ActiveOrSuspendedWorkspacesMigrationCommandRunner, + RunOnWorkspaceArgs, +} from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner'; +import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service'; +import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; +import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; +import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service'; +import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids'; +import { SEARCH_FIELDS_FOR_PERSON } from 'src/modules/person/standard-objects/person.workspace-entity'; + +@Command({ + name: 'upgrade:0-53:upgrade-search-vector-on-person-entity', + description: 'Upgrade search vector on person entity', +}) +export class UpgradeSearchVectorOnPersonEntityCommand extends ActiveOrSuspendedWorkspacesMigrationCommandRunner { + constructor( + @InjectRepository(Workspace, 'core') + protected readonly workspaceRepository: Repository, + protected readonly twentyORMGlobalManager: TwentyORMGlobalManager, + @InjectRepository(ObjectMetadataEntity, 'metadata') + protected readonly objectMetadataRepository: Repository, + private readonly searchVectorService: SearchVectorService, + private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, + private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, + ) { + super(workspaceRepository, twentyORMGlobalManager); + } + + override async runOnWorkspace({ + index, + total, + workspaceId, + options, + }: RunOnWorkspaceArgs): Promise { + this.logger.log( + `Running command for workspace ${workspaceId} ${index + 1}/${total}`, + ); + + const personObjectMetadata = + await this.objectMetadataRepository.findOneOrFail({ + select: ['id'], + where: { + workspaceId, + nameSingular: STANDARD_OBJECT_IDS.person, + }, + }); + + if (!options.dryRun) { + await this.searchVectorService.updateSearchVector( + personObjectMetadata.id, + SEARCH_FIELDS_FOR_PERSON, + workspaceId, + ); + + await this.workspaceMigrationRunnerService.executeMigrationFromPendingMigrations( + workspaceId, + ); + + await this.workspaceMetadataVersionService.incrementMetadataVersion( + workspaceId, + ); + } + + this.logger.log( + `Migrated search vector on person entity for workspace ${workspaceId}`, + ); + } +} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-version-command.module.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-version-command.module.ts index 8e34c992f..5e9fcc172 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-version-command.module.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/0-53/0-53-upgrade-version-command.module.ts @@ -5,27 +5,40 @@ import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrad import { CopyTypeormMigrationsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-copy-typeorm-migrations.command'; import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command'; import { RemoveRelationForeignKeyFieldMetadataCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-remove-relation-foreign-key-field-metadata.command'; +import { UpgradeSearchVectorOnPersonEntityCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { SearchVectorModule } from 'src/engine/metadata-modules/search-vector/search-vector.module'; +import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module'; +import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; @Module({ imports: [ TypeOrmModule.forFeature([Workspace], 'core'), - TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'), + TypeOrmModule.forFeature( + [FieldMetadataEntity, ObjectMetadataEntity], + 'metadata', + ), WorkspaceDataSourceModule, + SearchVectorModule, + WorkspaceMigrationRunnerModule, + WorkspaceMetadataVersionModule, ], providers: [ MigrateWorkflowEventListenersToAutomatedTriggersCommand, CopyTypeormMigrationsCommand, BackfillWorkflowNextStepIdsCommand, RemoveRelationForeignKeyFieldMetadataCommand, + UpgradeSearchVectorOnPersonEntityCommand, ], exports: [ MigrateWorkflowEventListenersToAutomatedTriggersCommand, RemoveRelationForeignKeyFieldMetadataCommand, BackfillWorkflowNextStepIdsCommand, CopyTypeormMigrationsCommand, + UpgradeSearchVectorOnPersonEntityCommand, ], }) export class V0_53_UpgradeVersionCommandModule {} diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts index 46999b504..274c3e482 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/upgrade.command.ts @@ -21,6 +21,7 @@ import { UpgradeDateAndDateTimeFieldsSettingsJsonCommand } from 'src/database/co import { BackfillWorkflowNextStepIdsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-backfill-workflow-next-step-ids.command'; import { CopyTypeormMigrationsCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-copy-typeorm-migrations.command'; import { MigrateWorkflowEventListenersToAutomatedTriggersCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-migrate-workflow-event-listeners-to-automated-triggers.command'; +import { UpgradeSearchVectorOnPersonEntityCommand } from 'src/database/commands/upgrade-version-command/0-53/0-53-upgrade-search-vector-on-person-entity.command'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager'; @@ -62,6 +63,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { protected readonly migrateWorkflowEventListenersToAutomatedTriggersCommand: MigrateWorkflowEventListenersToAutomatedTriggersCommand, protected readonly backfillWorkflowNextStepIdsCommand: BackfillWorkflowNextStepIdsCommand, protected readonly copyTypeormMigrationsCommand: CopyTypeormMigrationsCommand, + protected readonly upgradeSearchVectorOnPersonEntityCommand: UpgradeSearchVectorOnPersonEntityCommand, ) { super( workspaceRepository, @@ -114,6 +116,7 @@ export class UpgradeCommand extends UpgradeCommandRunner { this.migrateWorkflowEventListenersToAutomatedTriggersCommand, this.backfillWorkflowNextStepIdsCommand, this.copyTypeormMigrationsCommand, + this.upgradeSearchVectorOnPersonEntityCommand, ], }; diff --git a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts index 9c696b1dd..3d16fa054 100644 --- a/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts +++ b/packages/twenty-server/src/engine/core-modules/search/services/search.service.ts @@ -1,5 +1,6 @@ import { Injectable } from '@nestjs/common'; +import { isNonEmptyString } from '@sniptt/guards'; import { FieldMetadataType } from 'twenty-shared/types'; import { getLogoUrlFromDomainName } from 'twenty-shared/utils'; import { Brackets, ObjectLiteral } from 'typeorm'; @@ -96,7 +97,7 @@ export class SearchService { ...(imageIdentifierField ? [imageIdentifierField] : []), ].map((field) => `"${field}"`); - const searchQuery = searchTerms + const searchQuery = isNonEmptyString(searchTerms) ? queryBuilder .select(fieldsToSelect) .addSelect( diff --git a/packages/twenty-server/src/engine/core-modules/search/utils/format-search-terms.ts b/packages/twenty-server/src/engine/core-modules/search/utils/format-search-terms.ts index af57b7126..3e6b89a54 100644 --- a/packages/twenty-server/src/engine/core-modules/search/utils/format-search-terms.ts +++ b/packages/twenty-server/src/engine/core-modules/search/utils/format-search-terms.ts @@ -2,12 +2,12 @@ export const formatSearchTerms = ( searchTerm: string, operator: 'and' | 'or' = 'and', ) => { - if (searchTerm === '') { + if (searchTerm.trim() === '') { return ''; } const words = searchTerm.trim().split(/\s+/); const formattedWords = words.map((word) => { - const escapedWord = word.replace(/[\\:'&|!()]/g, '\\$&'); + const escapedWord = word.replace(/[\\:'&|!()@<>]/g, '\\$&'); return `${escapedWord}:*`; }); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts index 60e8f58d3..1cb2ed009 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/__tests__/get-ts-vectors-column-expression.utils.spec.ts @@ -30,15 +30,8 @@ describe('getTsVectorColumnExpressionFromFields', () => { const result = getTsVectorColumnExpressionFromFields(fields); const expected = ` to_tsvector('simple', COALESCE("nameFirstName", '') || ' ' || COALESCE("nameLastName", '') || ' ' || COALESCE("jobTitle", '') || ' ' || - COALESCE( - replace( - "emailsPrimaryEmail", - '@', - ' ' - ), - '' - ) - ) + COALESCE("emailsPrimaryEmail", '') || ' ' || + COALESCE(SPLIT_PART("emailsPrimaryEmail", '@', 2), '')) `.trim(); expect(result.trim()).toBe(expected); diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts index e7ff205ad..ea6e68938 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/utils/get-ts-vector-column-expression.util.ts @@ -82,15 +82,9 @@ const getColumnExpression = ( switch (fieldType) { case FieldMetadataType.EMAILS: return ` - COALESCE( - replace( - ${quotedColumnName}, - '@', - ' ' - ), - '' - ) - `; + COALESCE(${quotedColumnName}, '') || ' ' || + COALESCE(SPLIT_PART(${quotedColumnName}, '@', 2), '')`; + default: return `COALESCE(${quotedColumnName}, '')`; }