clean searchResolvers in server (#11114)

Introduces break in change

- remove search... resolvers
- rename globalSearch to search
- rename searchRecord.objectSingularName > objectNameSingular
closes https://github.com/twentyhq/core-team-issues/issues/643
This commit is contained in:
Etienne
2025-03-24 13:42:51 +01:00
committed by GitHub
parent 6e7d2db58f
commit 1c5f3ef5fa
52 changed files with 236 additions and 529 deletions

View File

@ -22,7 +22,6 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { fileStorageModuleFactory } from 'src/engine/core-modules/file-storage/file-storage.module-factory';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import { GlobalSearchModule } from 'src/engine/core-modules/global-search/global-search.module';
import { HealthModule } from 'src/engine/core-modules/health/health.module';
import { LabModule } from 'src/engine/core-modules/lab/lab.module';
import { LLMChatModelModule } from 'src/engine/core-modules/llm-chat-model/llm-chat-model.module';
@ -38,6 +37,7 @@ import { OpenApiModule } from 'src/engine/core-modules/open-api/open-api.module'
import { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module';
import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.module';
import { RedisClientService } from 'src/engine/core-modules/redis-client/redis-client.service';
import { SearchModule } from 'src/engine/core-modules/search/search.module';
import { serverlessModuleFactory } from 'src/engine/core-modules/serverless/serverless-module.factory';
import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module';
import { WorkspaceSSOModule } from 'src/engine/core-modules/sso/sso.module';
@ -121,7 +121,7 @@ import { FileModule } from './file/file.module';
useFactory: serverlessModuleFactory,
inject: [EnvironmentService, FileStorageService],
}),
GlobalSearchModule,
SearchModule,
],
exports: [
AnalyticsModule,

View File

@ -1,17 +1,17 @@
import { Test, TestingModule } from '@nestjs/testing';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/global-search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
import { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service';
import { mockObjectMetadataItemsWithFieldMaps } from 'src/engine/core-modules/search/__mocks__/mockObjectMetadataItemsWithFieldMaps';
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
describe('GlobalSearchService', () => {
let service: GlobalSearchService;
describe('SearchService', () => {
let service: SearchService;
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [GlobalSearchService],
providers: [SearchService],
}).compile();
service = module.get<GlobalSearchService>(GlobalSearchService);
service = module.get<SearchService>(SearchService);
});
it('should be defined', () => {
@ -103,7 +103,7 @@ describe('GlobalSearchService', () => {
it('should sort the search object results by tsRankCD', () => {
const objectResults = [
{
objectSingularName: 'person',
objectNameSingular: 'person',
tsRankCD: 2,
tsRank: 1,
recordId: '',
@ -111,7 +111,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'company',
objectNameSingular: 'company',
tsRankCD: 1,
tsRank: 1,
recordId: '',
@ -119,7 +119,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'regular-custom-object',
objectNameSingular: 'regular-custom-object',
tsRankCD: 3,
tsRank: 1,
recordId: '',
@ -138,7 +138,7 @@ describe('GlobalSearchService', () => {
it('should sort the search object results by tsRank, if tsRankCD is the same', () => {
const objectResults = [
{
objectSingularName: 'person',
objectNameSingular: 'person',
tsRankCD: 1,
tsRank: 1,
recordId: '',
@ -146,7 +146,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'company',
objectNameSingular: 'company',
tsRankCD: 1,
tsRank: 2,
recordId: '',
@ -154,7 +154,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'regular-custom-object',
objectNameSingular: 'regular-custom-object',
tsRankCD: 1,
tsRank: 3,
recordId: '',
@ -173,7 +173,7 @@ describe('GlobalSearchService', () => {
it('should sort the search object results by priority rank, if tsRankCD and tsRank are the same', () => {
const objectResults = [
{
objectSingularName: 'company',
objectNameSingular: 'company',
tsRankCD: 1,
tsRank: 1,
recordId: '',
@ -181,7 +181,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'person',
objectNameSingular: 'person',
tsRankCD: 1,
tsRank: 1,
recordId: '',
@ -189,7 +189,7 @@ describe('GlobalSearchService', () => {
imageUrl: '',
},
{
objectSingularName: 'regular-custom-object',
objectNameSingular: 'regular-custom-object',
tsRankCD: 1,
tsRank: 1,
recordId: '',

View File

@ -2,10 +2,10 @@ import { ArgsType, Field, Int } from '@nestjs/graphql';
import { IsArray, IsInt, IsOptional, IsString } from 'class-validator';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/global-search/dtos/object-record-filter-input';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/search/dtos/object-record-filter-input';
@ArgsType()
export class GlobalSearchArgs {
export class SearchArgs {
@Field(() => String)
@IsString()
searchInput: string;

View File

@ -2,8 +2,8 @@ import { Field, ObjectType } from '@nestjs/graphql';
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';
@ObjectType('GlobalSearchRecord')
export class GlobalSearchRecordDTO {
@ObjectType('SearchRecord')
export class SearchRecordDTO {
@Field(() => String)
@IsString()
@IsNotEmpty()
@ -12,7 +12,7 @@ export class GlobalSearchRecordDTO {
@Field(() => String)
@IsString()
@IsNotEmpty()
objectSingularName: string;
objectNameSingular: string;
@Field(() => String)
@IsString()

View File

@ -1,12 +1,12 @@
import { CustomException } from 'src/utils/custom-exception';
export class GlobalSearchException extends CustomException {
constructor(message: string, code: GlobalSearchExceptionCode) {
export class SearchException extends CustomException {
constructor(message: string, code: SearchExceptionCode) {
super(message, code);
}
}
export enum GlobalSearchExceptionCode {
export enum SearchExceptionCode {
METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND',
LABEL_IDENTIFIER_FIELD_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_NOT_FOUND',
OBJECT_METADATA_MAP_NOT_FOUND = 'OBJECT_METADATA_MAP_NOT_FOUND',

View File

@ -1,13 +1,13 @@
import { Catch, ExceptionFilter } from '@nestjs/common';
import { GlobalSearchException } from 'src/engine/core-modules/global-search/exceptions/global-search.exception';
import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { SearchException } from 'src/engine/core-modules/search/exceptions/search.exception';
@Catch(GlobalSearchException)
export class GlobalSearchApiExceptionFilter implements ExceptionFilter {
@Catch(SearchException)
export class SearchApiExceptionFilter implements ExceptionFilter {
constructor() {}
catch(exception: GlobalSearchException) {
catch(exception: SearchException) {
switch (exception.code) {
default:
throw new InternalServerError(exception.message);

View File

@ -1,12 +1,12 @@
import { Module } from '@nestjs/common';
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { GlobalSearchResolver } from 'src/engine/core-modules/global-search/global-search.resolver';
import { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service';
import { SearchResolver } from 'src/engine/core-modules/search/search.resolver';
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
@Module({
imports: [WorkspaceCacheStorageModule, FeatureFlagModule],
providers: [GlobalSearchResolver, GlobalSearchService],
providers: [SearchResolver, SearchService],
})
export class GlobalSearchModule {}
export class SearchModule {}

View File

@ -6,16 +6,16 @@ import chunk from 'lodash.chunk';
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
import { GlobalSearchArgs } from 'src/engine/core-modules/global-search/dtos/global-search-args';
import { GlobalSearchRecordDTO } from 'src/engine/core-modules/global-search/dtos/global-search-record-dto';
import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
import {
GlobalSearchException,
GlobalSearchExceptionCode,
} from 'src/engine/core-modules/global-search/exceptions/global-search.exception';
import { GlobalSearchApiExceptionFilter } from 'src/engine/core-modules/global-search/filters/global-search-api-exception.filter';
import { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item';
import { formatSearchTerms } from 'src/engine/core-modules/global-search/utils/format-search-terms';
SearchException,
SearchExceptionCode,
} from 'src/engine/core-modules/search/exceptions/search.exception';
import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter';
import { SearchService } from 'src/engine/core-modules/search/services/search.service';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/search/types/records-with-object-metadata-item';
import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-search-terms';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
@ -23,18 +23,18 @@ import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage
const OBJECT_METADATA_ITEMS_CHUNK_SIZE = 5;
@Resolver(() => [GlobalSearchRecordDTO])
@UseFilters(GlobalSearchApiExceptionFilter)
export class GlobalSearchResolver {
@Resolver(() => [SearchRecordDTO])
@UseFilters(SearchApiExceptionFilter)
export class SearchResolver {
constructor(
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly twentyORMManager: TwentyORMManager,
private readonly globalSearchService: GlobalSearchService,
private readonly searchService: SearchService,
private readonly featureFlagService: FeatureFlagService,
) {}
@Query(() => [GlobalSearchRecordDTO])
async globalSearch(
@Query(() => [SearchRecordDTO])
async search(
@AuthWorkspace() workspace: Workspace,
@Args()
{
@ -43,15 +43,15 @@ export class GlobalSearchResolver {
filter,
includedObjectNameSingulars,
excludedObjectNameSingulars,
}: GlobalSearchArgs,
}: SearchArgs,
) {
const currentCacheVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);
if (currentCacheVersion === undefined) {
throw new GlobalSearchException(
throw new SearchException(
'Metadata cache version not found',
GlobalSearchExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,
SearchExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND,
);
}
@ -62,9 +62,9 @@ export class GlobalSearchResolver {
);
if (!objectMetadataMaps) {
throw new GlobalSearchException(
throw new SearchException(
`Object metadata map not found for workspace ${workspace.id} and metadata version ${currentCacheVersion}`,
GlobalSearchExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
SearchExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND,
);
}
@ -76,7 +76,7 @@ export class GlobalSearchResolver {
);
const filteredObjectMetadataItems =
this.globalSearchService.filterObjectMetadataItems({
this.searchService.filterObjectMetadataItems({
objectMetadataItemWithFieldMaps,
includedObjectNameSingulars: includedObjectNameSingulars ?? [],
excludedObjectNameSingulars: excludedObjectNameSingulars ?? [],
@ -99,16 +99,15 @@ export class GlobalSearchResolver {
return {
objectMetadataItem,
records:
await this.globalSearchService.buildSearchQueryAndGetRecords({
entityManager: repository,
objectMetadataItem,
featureFlagMap,
searchTerms: formatSearchTerms(searchInput, 'and'),
searchTermsOr: formatSearchTerms(searchInput, 'or'),
limit,
filter: filter ?? ({} as ObjectRecordFilter),
}),
records: await this.searchService.buildSearchQueryAndGetRecords({
entityManager: repository,
objectMetadataItem,
featureFlagMap,
searchTerms: formatSearchTerms(searchInput, 'and'),
searchTermsOr: formatSearchTerms(searchInput, 'or'),
limit,
filter: filter ?? ({} as ObjectRecordFilter),
}),
};
}),
);
@ -116,7 +115,7 @@ export class GlobalSearchResolver {
allRecordsWithObjectMetadataItems.push(...recordsWithObjectMetadataItems);
}
return this.globalSearchService.computeSearchObjectResults(
return this.searchService.computeSearchObjectResults(
allRecordsWithObjectMetadataItems,
limit,
);

View File

@ -8,22 +8,22 @@ import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/int
import { FeatureFlagMap } from 'src/engine/core-modules/feature-flag/interfaces/feature-flag-map.interface';
import { GraphqlQueryParser } from 'src/engine/api/graphql/graphql-query-runner/graphql-query-parsers/graphql-query.parser';
import { RESULTS_LIMIT_BY_OBJECT_WITHOUT_SEARCH_TERMS } from 'src/engine/core-modules/global-search/constants/results-limit-by-object-without-search-terms';
import { STANDARD_OBJECTS_BY_PRIORITY_RANK } from 'src/engine/core-modules/global-search/constants/standard-objects-by-priority-rank';
import { GlobalSearchRecordDTO } from 'src/engine/core-modules/global-search/dtos/global-search-record-dto';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/global-search/dtos/object-record-filter-input';
import { RESULTS_LIMIT_BY_OBJECT_WITHOUT_SEARCH_TERMS } from 'src/engine/core-modules/search/constants/results-limit-by-object-without-search-terms';
import { STANDARD_OBJECTS_BY_PRIORITY_RANK } from 'src/engine/core-modules/search/constants/standard-objects-by-priority-rank';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/search/dtos/object-record-filter-input';
import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
import {
GlobalSearchException,
GlobalSearchExceptionCode,
} from 'src/engine/core-modules/global-search/exceptions/global-search.exception';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item';
SearchException,
SearchExceptionCode,
} from 'src/engine/core-modules/search/exceptions/search.exception';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/search/types/records-with-object-metadata-item';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util';
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
@Injectable()
export class GlobalSearchService {
export class SearchService {
filterObjectMetadataItems({
objectMetadataItemWithFieldMaps,
includedObjectNameSingulars,
@ -143,9 +143,9 @@ export class GlobalSearchService {
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
) {
if (!objectMetadataItem.labelIdentifierFieldMetadataId) {
throw new GlobalSearchException(
throw new SearchException(
'Label identifier field not found',
GlobalSearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND,
SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND,
);
}
@ -217,7 +217,7 @@ export class GlobalSearchService {
return records.map((record) => {
return {
recordId: record.id,
objectSingularName: objectMetadataItem.nameSingular,
objectNameSingular: objectMetadataItem.nameSingular,
label: this.getLabelIdentifierValue(record, objectMetadataItem),
imageUrl: this.getImageIdentifierValue(record, objectMetadataItem),
tsRankCD: record.tsRankCD,
@ -230,9 +230,7 @@ export class GlobalSearchService {
return this.sortSearchObjectResults(searchRecords).slice(0, limit);
}
sortSearchObjectResults(
searchObjectResultsWithRank: GlobalSearchRecordDTO[],
) {
sortSearchObjectResults(searchObjectResultsWithRank: SearchRecordDTO[]) {
return searchObjectResultsWithRank.sort((a, b) => {
if (a.tsRankCD !== b.tsRankCD) {
return b.tsRankCD - a.tsRankCD;
@ -243,8 +241,8 @@ export class GlobalSearchService {
}
return (
(STANDARD_OBJECTS_BY_PRIORITY_RANK[b.objectSingularName] || 0) -
(STANDARD_OBJECTS_BY_PRIORITY_RANK[a.objectSingularName] || 0)
(STANDARD_OBJECTS_BY_PRIORITY_RANK[b.objectNameSingular] || 0) -
(STANDARD_OBJECTS_BY_PRIORITY_RANK[a.objectNameSingular] || 0)
);
});
}

View File

@ -1,4 +1,4 @@
import { formatSearchTerms } from 'src/engine/core-modules/global-search/utils/format-search-terms';
import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-search-terms';
describe('formatSearchTerms', () => {
it('should format the search terms', () => {