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
This commit is contained in:
@ -14,6 +14,8 @@ const StyledTableWithPointerEvents = styled(StyledTable)<{
|
|||||||
& > * {
|
& > * {
|
||||||
pointer-events: ${({ isDragging }) => (isDragging ? 'none' : 'auto')};
|
pointer-events: ${({ isDragging }) => (isDragging ? 'none' : 'auto')};
|
||||||
}
|
}
|
||||||
|
min-height: ${({ isDragging }) => (isDragging ? '100%' : 'auto')};
|
||||||
|
height: ${({ isDragging }) => (isDragging ? '100%' : 'auto')};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export interface RecordTableContentProps {
|
export interface RecordTableContentProps {
|
||||||
|
|||||||
@ -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<Workspace>,
|
||||||
|
protected readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
|
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
|
private readonly searchVectorService: SearchVectorService,
|
||||||
|
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
|
||||||
|
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
|
||||||
|
) {
|
||||||
|
super(workspaceRepository, twentyORMGlobalManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
override async runOnWorkspace({
|
||||||
|
index,
|
||||||
|
total,
|
||||||
|
workspaceId,
|
||||||
|
options,
|
||||||
|
}: RunOnWorkspaceArgs): Promise<void> {
|
||||||
|
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}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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 { 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 { 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 { 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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.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 { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
|
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([Workspace], 'core'),
|
TypeOrmModule.forFeature([Workspace], 'core'),
|
||||||
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
TypeOrmModule.forFeature(
|
||||||
|
[FieldMetadataEntity, ObjectMetadataEntity],
|
||||||
|
'metadata',
|
||||||
|
),
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
|
SearchVectorModule,
|
||||||
|
WorkspaceMigrationRunnerModule,
|
||||||
|
WorkspaceMetadataVersionModule,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
|
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
|
||||||
CopyTypeormMigrationsCommand,
|
CopyTypeormMigrationsCommand,
|
||||||
BackfillWorkflowNextStepIdsCommand,
|
BackfillWorkflowNextStepIdsCommand,
|
||||||
RemoveRelationForeignKeyFieldMetadataCommand,
|
RemoveRelationForeignKeyFieldMetadataCommand,
|
||||||
|
UpgradeSearchVectorOnPersonEntityCommand,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
|
MigrateWorkflowEventListenersToAutomatedTriggersCommand,
|
||||||
RemoveRelationForeignKeyFieldMetadataCommand,
|
RemoveRelationForeignKeyFieldMetadataCommand,
|
||||||
BackfillWorkflowNextStepIdsCommand,
|
BackfillWorkflowNextStepIdsCommand,
|
||||||
CopyTypeormMigrationsCommand,
|
CopyTypeormMigrationsCommand,
|
||||||
|
UpgradeSearchVectorOnPersonEntityCommand,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class V0_53_UpgradeVersionCommandModule {}
|
export class V0_53_UpgradeVersionCommandModule {}
|
||||||
|
|||||||
@ -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 { 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 { 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 { 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 { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
|
||||||
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||||
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
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 migrateWorkflowEventListenersToAutomatedTriggersCommand: MigrateWorkflowEventListenersToAutomatedTriggersCommand,
|
||||||
protected readonly backfillWorkflowNextStepIdsCommand: BackfillWorkflowNextStepIdsCommand,
|
protected readonly backfillWorkflowNextStepIdsCommand: BackfillWorkflowNextStepIdsCommand,
|
||||||
protected readonly copyTypeormMigrationsCommand: CopyTypeormMigrationsCommand,
|
protected readonly copyTypeormMigrationsCommand: CopyTypeormMigrationsCommand,
|
||||||
|
protected readonly upgradeSearchVectorOnPersonEntityCommand: UpgradeSearchVectorOnPersonEntityCommand,
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
workspaceRepository,
|
workspaceRepository,
|
||||||
@ -114,6 +116,7 @@ export class UpgradeCommand extends UpgradeCommandRunner {
|
|||||||
this.migrateWorkflowEventListenersToAutomatedTriggersCommand,
|
this.migrateWorkflowEventListenersToAutomatedTriggersCommand,
|
||||||
this.backfillWorkflowNextStepIdsCommand,
|
this.backfillWorkflowNextStepIdsCommand,
|
||||||
this.copyTypeormMigrationsCommand,
|
this.copyTypeormMigrationsCommand,
|
||||||
|
this.upgradeSearchVectorOnPersonEntityCommand,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { isNonEmptyString } from '@sniptt/guards';
|
||||||
import { FieldMetadataType } from 'twenty-shared/types';
|
import { FieldMetadataType } from 'twenty-shared/types';
|
||||||
import { getLogoUrlFromDomainName } from 'twenty-shared/utils';
|
import { getLogoUrlFromDomainName } from 'twenty-shared/utils';
|
||||||
import { Brackets, ObjectLiteral } from 'typeorm';
|
import { Brackets, ObjectLiteral } from 'typeorm';
|
||||||
@ -96,7 +97,7 @@ export class SearchService {
|
|||||||
...(imageIdentifierField ? [imageIdentifierField] : []),
|
...(imageIdentifierField ? [imageIdentifierField] : []),
|
||||||
].map((field) => `"${field}"`);
|
].map((field) => `"${field}"`);
|
||||||
|
|
||||||
const searchQuery = searchTerms
|
const searchQuery = isNonEmptyString(searchTerms)
|
||||||
? queryBuilder
|
? queryBuilder
|
||||||
.select(fieldsToSelect)
|
.select(fieldsToSelect)
|
||||||
.addSelect(
|
.addSelect(
|
||||||
|
|||||||
@ -2,12 +2,12 @@ export const formatSearchTerms = (
|
|||||||
searchTerm: string,
|
searchTerm: string,
|
||||||
operator: 'and' | 'or' = 'and',
|
operator: 'and' | 'or' = 'and',
|
||||||
) => {
|
) => {
|
||||||
if (searchTerm === '') {
|
if (searchTerm.trim() === '') {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
const words = searchTerm.trim().split(/\s+/);
|
const words = searchTerm.trim().split(/\s+/);
|
||||||
const formattedWords = words.map((word) => {
|
const formattedWords = words.map((word) => {
|
||||||
const escapedWord = word.replace(/[\\:'&|!()]/g, '\\$&');
|
const escapedWord = word.replace(/[\\:'&|!()@<>]/g, '\\$&');
|
||||||
|
|
||||||
return `${escapedWord}:*`;
|
return `${escapedWord}:*`;
|
||||||
});
|
});
|
||||||
|
|||||||
@ -30,15 +30,8 @@ describe('getTsVectorColumnExpressionFromFields', () => {
|
|||||||
const result = getTsVectorColumnExpressionFromFields(fields);
|
const result = getTsVectorColumnExpressionFromFields(fields);
|
||||||
const expected = `
|
const expected = `
|
||||||
to_tsvector('simple', COALESCE("nameFirstName", '') || ' ' || COALESCE("nameLastName", '') || ' ' || COALESCE("jobTitle", '') || ' ' ||
|
to_tsvector('simple', COALESCE("nameFirstName", '') || ' ' || COALESCE("nameLastName", '') || ' ' || COALESCE("jobTitle", '') || ' ' ||
|
||||||
COALESCE(
|
COALESCE("emailsPrimaryEmail", '') || ' ' ||
|
||||||
replace(
|
COALESCE(SPLIT_PART("emailsPrimaryEmail", '@', 2), ''))
|
||||||
"emailsPrimaryEmail",
|
|
||||||
'@',
|
|
||||||
' '
|
|
||||||
),
|
|
||||||
''
|
|
||||||
)
|
|
||||||
)
|
|
||||||
`.trim();
|
`.trim();
|
||||||
|
|
||||||
expect(result.trim()).toBe(expected);
|
expect(result.trim()).toBe(expected);
|
||||||
|
|||||||
@ -82,15 +82,9 @@ const getColumnExpression = (
|
|||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case FieldMetadataType.EMAILS:
|
case FieldMetadataType.EMAILS:
|
||||||
return `
|
return `
|
||||||
COALESCE(
|
COALESCE(${quotedColumnName}, '') || ' ' ||
|
||||||
replace(
|
COALESCE(SPLIT_PART(${quotedColumnName}, '@', 2), '')`;
|
||||||
${quotedColumnName},
|
|
||||||
'@',
|
|
||||||
' '
|
|
||||||
),
|
|
||||||
''
|
|
||||||
)
|
|
||||||
`;
|
|
||||||
default:
|
default:
|
||||||
return `COALESCE(${quotedColumnName}, '')`;
|
return `COALESCE(${quotedColumnName}, '')`;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user