update globalSearch resolver (#10680)
### Context In order to deprecate search[Object] resolvers, we need to update globalSearch resolver to bring it to the same level of functionality ### Solution - Add includedObject args to search in pre-selected tables - Add record filtering ### Tested on gql api ✅ - Simple search with search term - Search with excluded objects, with included objects, with both and both with search term - Search with id filtering and all args combined - Search with deletedAt filtering and all args combined - from front, search in command menu back end part of https://github.com/twentyhq/core-team-issues/issues/495
This commit is contained in:
@ -14,6 +14,7 @@ export type Scalars = {
|
|||||||
Int: number;
|
Int: number;
|
||||||
Float: number;
|
Float: number;
|
||||||
ConnectionCursor: any;
|
ConnectionCursor: any;
|
||||||
|
Date: any;
|
||||||
DateTime: string;
|
DateTime: string;
|
||||||
JSON: any;
|
JSON: any;
|
||||||
JSONObject: any;
|
JSONObject: any;
|
||||||
@ -356,6 +357,17 @@ export type CustomDomainValidRecords = {
|
|||||||
records: Array<CustomDomainRecord>;
|
records: Array<CustomDomainRecord>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type DateFilter = {
|
||||||
|
eq?: InputMaybe<Scalars['Date']>;
|
||||||
|
gt?: InputMaybe<Scalars['Date']>;
|
||||||
|
gte?: InputMaybe<Scalars['Date']>;
|
||||||
|
in?: InputMaybe<Array<Scalars['Date']>>;
|
||||||
|
is?: InputMaybe<FilterIs>;
|
||||||
|
lt?: InputMaybe<Scalars['Date']>;
|
||||||
|
lte?: InputMaybe<Scalars['Date']>;
|
||||||
|
neq?: InputMaybe<Scalars['Date']>;
|
||||||
|
};
|
||||||
|
|
||||||
export type DeleteApprovedAccessDomainInput = {
|
export type DeleteApprovedAccessDomainInput = {
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
};
|
};
|
||||||
@ -579,6 +591,11 @@ export enum FileFolder {
|
|||||||
WorkspaceLogo = 'WorkspaceLogo'
|
WorkspaceLogo = 'WorkspaceLogo'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum FilterIs {
|
||||||
|
NotNull = 'NotNull',
|
||||||
|
Null = 'Null'
|
||||||
|
}
|
||||||
|
|
||||||
export type FindAvailableSsoidpOutput = {
|
export type FindAvailableSsoidpOutput = {
|
||||||
__typename?: 'FindAvailableSSOIDPOutput';
|
__typename?: 'FindAvailableSSOIDPOutput';
|
||||||
id: Scalars['String'];
|
id: Scalars['String'];
|
||||||
@ -631,6 +648,17 @@ export enum HealthIndicatorId {
|
|||||||
worker = 'worker'
|
worker = 'worker'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type IdFilter = {
|
||||||
|
eq?: InputMaybe<Scalars['ID']>;
|
||||||
|
gt?: InputMaybe<Scalars['ID']>;
|
||||||
|
gte?: InputMaybe<Scalars['ID']>;
|
||||||
|
in?: InputMaybe<Array<Scalars['ID']>>;
|
||||||
|
is?: InputMaybe<FilterIs>;
|
||||||
|
lt?: InputMaybe<Scalars['ID']>;
|
||||||
|
lte?: InputMaybe<Scalars['ID']>;
|
||||||
|
neq?: InputMaybe<Scalars['ID']>;
|
||||||
|
};
|
||||||
|
|
||||||
export enum IdentityProviderType {
|
export enum IdentityProviderType {
|
||||||
OIDC = 'OIDC',
|
OIDC = 'OIDC',
|
||||||
SAML = 'SAML'
|
SAML = 'SAML'
|
||||||
@ -1205,6 +1233,16 @@ export type ObjectIndexMetadatasConnection = {
|
|||||||
pageInfo: PageInfo;
|
pageInfo: PageInfo;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ObjectRecordFilterInput = {
|
||||||
|
and?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||||
|
createdAt?: InputMaybe<DateFilter>;
|
||||||
|
deletedAt?: InputMaybe<DateFilter>;
|
||||||
|
id?: InputMaybe<IdFilter>;
|
||||||
|
not?: InputMaybe<ObjectRecordFilterInput>;
|
||||||
|
or?: InputMaybe<Array<ObjectRecordFilterInput>>;
|
||||||
|
updatedAt?: InputMaybe<DateFilter>;
|
||||||
|
};
|
||||||
|
|
||||||
/** Onboarding status */
|
/** Onboarding status */
|
||||||
export enum OnboardingStatus {
|
export enum OnboardingStatus {
|
||||||
COMPLETED = 'COMPLETED',
|
COMPLETED = 'COMPLETED',
|
||||||
@ -1396,6 +1434,8 @@ export type QueryGetTimelineThreadsFromPersonIdArgs = {
|
|||||||
|
|
||||||
export type QueryGlobalSearchArgs = {
|
export type QueryGlobalSearchArgs = {
|
||||||
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>;
|
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>;
|
||||||
|
filter?: InputMaybe<ObjectRecordFilterInput>;
|
||||||
|
includedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>;
|
||||||
limit: Scalars['Int'];
|
limit: Scalars['Int'];
|
||||||
searchInput: Scalars['String'];
|
searchInput: Scalars['String'];
|
||||||
};
|
};
|
||||||
|
|||||||
@ -20,10 +20,11 @@ describe('GlobalSearchService', () => {
|
|||||||
|
|
||||||
describe('filterObjectMetadataItems', () => {
|
describe('filterObjectMetadataItems', () => {
|
||||||
it('should return searchable object metadata items', () => {
|
it('should return searchable object metadata items', () => {
|
||||||
const objectMetadataItems = service.filterObjectMetadataItems(
|
const objectMetadataItems = service.filterObjectMetadataItems({
|
||||||
mockObjectMetadataItemsWithFieldMaps,
|
objectMetadataItemWithFieldMaps: mockObjectMetadataItemsWithFieldMaps,
|
||||||
[],
|
includedObjectNameSingulars: [],
|
||||||
);
|
excludedObjectNameSingulars: [],
|
||||||
|
});
|
||||||
|
|
||||||
expect(objectMetadataItems).toEqual([
|
expect(objectMetadataItems).toEqual([
|
||||||
mockObjectMetadataItemsWithFieldMaps[0],
|
mockObjectMetadataItemsWithFieldMaps[0],
|
||||||
@ -32,16 +33,28 @@ describe('GlobalSearchService', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
it('should return searchable object metadata items without excluded ones', () => {
|
it('should return searchable object metadata items without excluded ones', () => {
|
||||||
const objectMetadataItems = service.filterObjectMetadataItems(
|
const objectMetadataItems = service.filterObjectMetadataItems({
|
||||||
mockObjectMetadataItemsWithFieldMaps,
|
objectMetadataItemWithFieldMaps: mockObjectMetadataItemsWithFieldMaps,
|
||||||
['company'],
|
includedObjectNameSingulars: [],
|
||||||
);
|
excludedObjectNameSingulars: ['company'],
|
||||||
|
});
|
||||||
|
|
||||||
expect(objectMetadataItems).toEqual([
|
expect(objectMetadataItems).toEqual([
|
||||||
mockObjectMetadataItemsWithFieldMaps[0],
|
mockObjectMetadataItemsWithFieldMaps[0],
|
||||||
mockObjectMetadataItemsWithFieldMaps[2],
|
mockObjectMetadataItemsWithFieldMaps[2],
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
it('should return searchable object metadata items with included ones only', () => {
|
||||||
|
const objectMetadataItems = service.filterObjectMetadataItems({
|
||||||
|
objectMetadataItemWithFieldMaps: mockObjectMetadataItemsWithFieldMaps,
|
||||||
|
includedObjectNameSingulars: ['company'],
|
||||||
|
excludedObjectNameSingulars: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(objectMetadataItems).toEqual([
|
||||||
|
mockObjectMetadataItemsWithFieldMaps[1],
|
||||||
|
]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getLabelIdentifierColumns', () => {
|
describe('getLabelIdentifierColumns', () => {
|
||||||
|
|||||||
@ -2,6 +2,8 @@ import { ArgsType, Field, Int } from '@nestjs/graphql';
|
|||||||
|
|
||||||
import { IsArray, IsInt, IsOptional, IsString } from 'class-validator';
|
import { IsArray, IsInt, IsOptional, IsString } from 'class-validator';
|
||||||
|
|
||||||
|
import { ObjectRecordFilterInput } from 'src/engine/core-modules/global-search/dtos/object-record-filter-input';
|
||||||
|
|
||||||
@ArgsType()
|
@ArgsType()
|
||||||
export class GlobalSearchArgs {
|
export class GlobalSearchArgs {
|
||||||
@Field(() => String)
|
@Field(() => String)
|
||||||
@ -12,6 +14,15 @@ export class GlobalSearchArgs {
|
|||||||
@IsInt()
|
@IsInt()
|
||||||
limit: number;
|
limit: number;
|
||||||
|
|
||||||
|
@IsArray()
|
||||||
|
@Field(() => [String], { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
includedObjectNameSingulars?: string[];
|
||||||
|
|
||||||
|
@IsOptional()
|
||||||
|
@Field(() => ObjectRecordFilterInput, { nullable: true })
|
||||||
|
filter?: ObjectRecordFilterInput;
|
||||||
|
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@Field(() => [String], { nullable: true })
|
@Field(() => [String], { nullable: true })
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
|
|||||||
@ -0,0 +1,116 @@
|
|||||||
|
import { Field, ID, InputType, registerEnumType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
import { IsArray, IsOptional } from 'class-validator';
|
||||||
|
|
||||||
|
import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
|
||||||
|
import { DateScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class ObjectRecordFilterInput implements Partial<ObjectRecordFilter> {
|
||||||
|
@Field(() => [ObjectRecordFilterInput], { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
and?: ObjectRecordFilterInput[];
|
||||||
|
|
||||||
|
@Field(() => ObjectRecordFilterInput, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
not?: ObjectRecordFilterInput;
|
||||||
|
|
||||||
|
@Field(() => [ObjectRecordFilterInput], { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
@IsArray()
|
||||||
|
or?: ObjectRecordFilterInput[];
|
||||||
|
|
||||||
|
@Field(() => IDFilterType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
id?: IDFilterType | null;
|
||||||
|
|
||||||
|
@Field(() => DateFilterType, { nullable: true })
|
||||||
|
createdAt?: DateFilterType | null;
|
||||||
|
|
||||||
|
@Field(() => DateFilterType, { nullable: true })
|
||||||
|
updatedAt?: DateFilterType | null;
|
||||||
|
|
||||||
|
@Field(() => DateFilterType, { nullable: true })
|
||||||
|
deletedAt?: DateFilterType | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType('IDFilter')
|
||||||
|
class IDFilterType {
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
eq?: string;
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
gt?: string;
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
gte?: string;
|
||||||
|
|
||||||
|
@Field(() => [ID], { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
in?: string[];
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
lt?: string;
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
lte?: string;
|
||||||
|
|
||||||
|
@Field(() => ID, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
neq?: string;
|
||||||
|
|
||||||
|
@Field(() => FilterIs, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
is?: FilterIs;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType('DateFilter')
|
||||||
|
class DateFilterType {
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
eq?: Date;
|
||||||
|
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
gt?: Date;
|
||||||
|
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
gte?: Date;
|
||||||
|
|
||||||
|
@Field(() => [DateScalarType], { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
in?: Date[];
|
||||||
|
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
lt?: Date;
|
||||||
|
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
lte?: Date;
|
||||||
|
|
||||||
|
@Field(() => DateScalarType, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
neq?: Date;
|
||||||
|
|
||||||
|
@Field(() => FilterIs, { nullable: true })
|
||||||
|
@IsOptional()
|
||||||
|
is?: FilterIs;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum FilterIs {
|
||||||
|
NotNull = 'NOT_NULL',
|
||||||
|
Null = 'NULL',
|
||||||
|
}
|
||||||
|
|
||||||
|
registerEnumType(FilterIs, {
|
||||||
|
name: 'FilterIs',
|
||||||
|
});
|
||||||
@ -1,11 +1,12 @@
|
|||||||
import { Module } from '@nestjs/common';
|
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 { 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 { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service';
|
||||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [WorkspaceCacheStorageModule],
|
imports: [WorkspaceCacheStorageModule, FeatureFlagModule],
|
||||||
providers: [GlobalSearchResolver, GlobalSearchService],
|
providers: [GlobalSearchResolver, GlobalSearchService],
|
||||||
})
|
})
|
||||||
export class GlobalSearchModule {}
|
export class GlobalSearchModule {}
|
||||||
|
|||||||
@ -3,6 +3,9 @@ import { Args, Query, Resolver } from '@nestjs/graphql';
|
|||||||
|
|
||||||
import chunk from 'lodash.chunk';
|
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 { 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 { GlobalSearchRecordDTO } from 'src/engine/core-modules/global-search/dtos/global-search-record-dto';
|
||||||
import {
|
import {
|
||||||
@ -27,13 +30,20 @@ export class GlobalSearchResolver {
|
|||||||
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
|
||||||
private readonly twentyORMManager: TwentyORMManager,
|
private readonly twentyORMManager: TwentyORMManager,
|
||||||
private readonly globalSearchService: GlobalSearchService,
|
private readonly globalSearchService: GlobalSearchService,
|
||||||
|
private readonly featureFlagService: FeatureFlagService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Query(() => [GlobalSearchRecordDTO])
|
@Query(() => [GlobalSearchRecordDTO])
|
||||||
async globalSearch(
|
async globalSearch(
|
||||||
@AuthWorkspace() workspace: Workspace,
|
@AuthWorkspace() workspace: Workspace,
|
||||||
@Args()
|
@Args()
|
||||||
{ searchInput, limit, excludedObjectNameSingulars }: GlobalSearchArgs,
|
{
|
||||||
|
searchInput,
|
||||||
|
limit,
|
||||||
|
filter,
|
||||||
|
includedObjectNameSingulars,
|
||||||
|
excludedObjectNameSingulars,
|
||||||
|
}: GlobalSearchArgs,
|
||||||
) {
|
) {
|
||||||
const currentCacheVersion =
|
const currentCacheVersion =
|
||||||
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);
|
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);
|
||||||
@ -58,15 +68,19 @@ export class GlobalSearchResolver {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const featureFlagMap =
|
||||||
|
await this.featureFlagService.getWorkspaceFeatureFlagsMap(workspace.id);
|
||||||
|
|
||||||
const objectMetadataItemWithFieldMaps = Object.values(
|
const objectMetadataItemWithFieldMaps = Object.values(
|
||||||
objectMetadataMaps.byId,
|
objectMetadataMaps.byId,
|
||||||
);
|
);
|
||||||
|
|
||||||
const filteredObjectMetadataItems =
|
const filteredObjectMetadataItems =
|
||||||
this.globalSearchService.filterObjectMetadataItems(
|
this.globalSearchService.filterObjectMetadataItems({
|
||||||
objectMetadataItemWithFieldMaps,
|
objectMetadataItemWithFieldMaps,
|
||||||
excludedObjectNameSingulars,
|
includedObjectNameSingulars: includedObjectNameSingulars ?? [],
|
||||||
);
|
excludedObjectNameSingulars: excludedObjectNameSingulars ?? [],
|
||||||
|
});
|
||||||
|
|
||||||
const allRecordsWithObjectMetadataItems: RecordsWithObjectMetadataItem[] =
|
const allRecordsWithObjectMetadataItems: RecordsWithObjectMetadataItem[] =
|
||||||
[];
|
[];
|
||||||
@ -83,18 +97,18 @@ export class GlobalSearchResolver {
|
|||||||
objectMetadataItem.nameSingular,
|
objectMetadataItem.nameSingular,
|
||||||
);
|
);
|
||||||
|
|
||||||
repository.createQueryBuilder();
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
records:
|
records:
|
||||||
await this.globalSearchService.buildSearchQueryAndGetRecords(
|
await this.globalSearchService.buildSearchQueryAndGetRecords({
|
||||||
repository,
|
entityManager: repository,
|
||||||
objectMetadataItem,
|
objectMetadataItem,
|
||||||
formatSearchTerms(searchInput, 'and'),
|
featureFlagMap,
|
||||||
formatSearchTerms(searchInput, 'or'),
|
searchTerms: formatSearchTerms(searchInput, 'and'),
|
||||||
|
searchTermsOr: formatSearchTerms(searchInput, 'or'),
|
||||||
limit,
|
limit,
|
||||||
),
|
filter: filter ?? ({} as ObjectRecordFilter),
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,12 +1,16 @@
|
|||||||
import { Entity } from '@microsoft/microsoft-graph-types';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import { FieldMetadataType, getLogoUrlFromDomainName } from 'twenty-shared';
|
import { FieldMetadataType, getLogoUrlFromDomainName } from 'twenty-shared';
|
||||||
import { Brackets } from 'typeorm';
|
import { Brackets, ObjectLiteral } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
import { ObjectRecord } from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
|
||||||
|
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 { 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 { 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 { 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 {
|
import {
|
||||||
GlobalSearchException,
|
GlobalSearchException,
|
||||||
GlobalSearchExceptionCode,
|
GlobalSearchExceptionCode,
|
||||||
@ -14,30 +18,70 @@ import {
|
|||||||
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item';
|
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item';
|
||||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
|
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 { 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';
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
export class GlobalSearchService {
|
export class GlobalSearchService {
|
||||||
filterObjectMetadataItems(
|
filterObjectMetadataItems({
|
||||||
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps[],
|
objectMetadataItemWithFieldMaps,
|
||||||
excludedObjectNameSingulars: string[] | undefined,
|
includedObjectNameSingulars,
|
||||||
) {
|
excludedObjectNameSingulars,
|
||||||
|
}: {
|
||||||
|
objectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps[];
|
||||||
|
includedObjectNameSingulars: string[];
|
||||||
|
excludedObjectNameSingulars: string[];
|
||||||
|
}) {
|
||||||
return objectMetadataItemWithFieldMaps.filter(
|
return objectMetadataItemWithFieldMaps.filter(
|
||||||
({ nameSingular, isSearchable }) => {
|
({ nameSingular, isSearchable }) => {
|
||||||
return (
|
if (!isSearchable) {
|
||||||
!excludedObjectNameSingulars?.includes(nameSingular) && isSearchable
|
return false;
|
||||||
);
|
}
|
||||||
|
if (excludedObjectNameSingulars.includes(nameSingular)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (includedObjectNameSingulars.length > 0) {
|
||||||
|
return includedObjectNameSingulars.includes(nameSingular);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async buildSearchQueryAndGetRecords(
|
async buildSearchQueryAndGetRecords<Entity extends ObjectLiteral>({
|
||||||
entityManager: WorkspaceRepository<Entity>,
|
entityManager,
|
||||||
objectMetadataItem: ObjectMetadataItemWithFieldMaps,
|
objectMetadataItem,
|
||||||
searchTerms: string,
|
featureFlagMap,
|
||||||
searchTermsOr: string,
|
searchTerms,
|
||||||
limit: number,
|
searchTermsOr,
|
||||||
) {
|
limit,
|
||||||
|
filter,
|
||||||
|
}: {
|
||||||
|
entityManager: WorkspaceRepository<Entity>;
|
||||||
|
objectMetadataItem: ObjectMetadataItemWithFieldMaps;
|
||||||
|
featureFlagMap: FeatureFlagMap;
|
||||||
|
searchTerms: string;
|
||||||
|
searchTermsOr: string;
|
||||||
|
limit: number;
|
||||||
|
filter: ObjectRecordFilterInput;
|
||||||
|
}) {
|
||||||
const queryBuilder = entityManager.createQueryBuilder();
|
const queryBuilder = entityManager.createQueryBuilder();
|
||||||
|
|
||||||
|
const queryParser = new GraphqlQueryParser(
|
||||||
|
objectMetadataItem.fieldsByName,
|
||||||
|
generateObjectMetadataMaps([objectMetadataItem]),
|
||||||
|
featureFlagMap,
|
||||||
|
);
|
||||||
|
|
||||||
|
queryParser.applyFilterToBuilder(
|
||||||
|
queryBuilder,
|
||||||
|
objectMetadataItem.nameSingular,
|
||||||
|
filter,
|
||||||
|
);
|
||||||
|
|
||||||
|
queryParser.applyDeletedAtToBuilder(queryBuilder, filter);
|
||||||
|
|
||||||
const imageIdentifierField =
|
const imageIdentifierField =
|
||||||
this.getImageIdentifierColumn(objectMetadataItem);
|
this.getImageIdentifierColumn(objectMetadataItem);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user