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

@ -707,16 +707,6 @@ export type GetServerlessFunctionSourceCodeInput = {
version?: Scalars['String']['input']; version?: Scalars['String']['input'];
}; };
export type GlobalSearchRecord = {
__typename?: 'GlobalSearchRecord';
imageUrl?: Maybe<Scalars['String']['output']>;
label: Scalars['String']['output'];
objectSingularName: Scalars['String']['output'];
recordId: Scalars['String']['output'];
tsRank: Scalars['Float']['output'];
tsRankCD: Scalars['Float']['output'];
};
export enum HealthIndicatorId { export enum HealthIndicatorId {
app = 'app', app = 'app',
connectedAccount = 'connectedAccount', connectedAccount = 'connectedAccount',
@ -934,6 +924,7 @@ export type Mutation = {
updateOneField: Field; updateOneField: Field;
updateOneObject: Object; updateOneObject: Object;
updateOneRemoteServer: RemoteServer; updateOneRemoteServer: RemoteServer;
updateOneRole: Role;
updateOneServerlessFunction: ServerlessFunction; updateOneServerlessFunction: ServerlessFunction;
updatePasswordViaResetToken: InvalidatePassword; updatePasswordViaResetToken: InvalidatePassword;
updateWorkflowVersionStep: WorkflowAction; updateWorkflowVersionStep: WorkflowAction;
@ -1481,13 +1472,13 @@ export type Query = {
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal; getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal; getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
globalSearch: Array<GlobalSearchRecord>;
index: Index; index: Index;
indexMetadatas: IndexConnection; indexMetadatas: IndexConnection;
object: Object; object: Object;
objects: ObjectConnection; objects: ObjectConnection;
plans: Array<BillingPlanOutput>; plans: Array<BillingPlanOutput>;
relationMetadata: RelationMetadataConnection; relationMetadata: RelationMetadataConnection;
search: Array<SearchRecord>;
validatePasswordResetToken: ValidatePasswordResetToken; validatePasswordResetToken: ValidatePasswordResetToken;
}; };
@ -1593,15 +1584,6 @@ export type QueryGetTimelineThreadsFromPersonIdArgs = {
}; };
export type QueryGlobalSearchArgs = {
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']['input']>>;
filter?: InputMaybe<ObjectRecordFilterInput>;
includedObjectNameSingulars?: InputMaybe<Array<Scalars['String']['input']>>;
limit: Scalars['Int']['input'];
searchInput: Scalars['String']['input'];
};
export type QueryIndexArgs = { export type QueryIndexArgs = {
id: Scalars['UUID']['input']; id: Scalars['UUID']['input'];
}; };
@ -1629,6 +1611,15 @@ export type QueryRelationMetadataArgs = {
}; };
export type QuerySearchArgs = {
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']['input']>>;
filter?: InputMaybe<ObjectRecordFilterInput>;
includedObjectNameSingulars?: InputMaybe<Array<Scalars['String']['input']>>;
limit: Scalars['Int']['input'];
searchInput: Scalars['String']['input'];
};
export type QueryValidatePasswordResetTokenArgs = { export type QueryValidatePasswordResetTokenArgs = {
passwordResetToken: Scalars['String']['input']; passwordResetToken: Scalars['String']['input'];
}; };
@ -1827,6 +1818,16 @@ export enum SsoIdentityProviderStatus {
Inactive = 'Inactive' Inactive = 'Inactive'
} }
export type SearchRecord = {
__typename?: 'SearchRecord';
imageUrl?: Maybe<Scalars['String']['output']>;
label: Scalars['String']['output'];
objectNameSingular: Scalars['String']['output'];
recordId: Scalars['String']['output'];
tsRank: Scalars['Float']['output'];
tsRankCD: Scalars['Float']['output'];
};
export type SendInvitationsOutput = { export type SendInvitationsOutput = {
__typename?: 'SendInvitationsOutput'; __typename?: 'SendInvitationsOutput';
errors: Array<Scalars['String']['output']>; errors: Array<Scalars['String']['output']>;

View File

@ -643,16 +643,6 @@ export type GetServerlessFunctionSourceCodeInput = {
version?: Scalars['String']; version?: Scalars['String'];
}; };
export type GlobalSearchRecord = {
__typename?: 'GlobalSearchRecord';
imageUrl?: Maybe<Scalars['String']>;
label: Scalars['String'];
objectSingularName: Scalars['String'];
recordId: Scalars['String'];
tsRank: Scalars['Float'];
tsRankCD: Scalars['Float'];
};
export enum HealthIndicatorId { export enum HealthIndicatorId {
app = 'app', app = 'app',
connectedAccount = 'connectedAccount', connectedAccount = 'connectedAccount',
@ -1368,12 +1358,12 @@ export type Query = {
getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal; getTimelineCalendarEventsFromPersonId: TimelineCalendarEventsWithTotal;
getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal; getTimelineThreadsFromCompanyId: TimelineThreadsWithTotal;
getTimelineThreadsFromPersonId: TimelineThreadsWithTotal; getTimelineThreadsFromPersonId: TimelineThreadsWithTotal;
globalSearch: Array<GlobalSearchRecord>;
index: Index; index: Index;
indexMetadatas: IndexConnection; indexMetadatas: IndexConnection;
object: Object; object: Object;
objects: ObjectConnection; objects: ObjectConnection;
plans: Array<BillingPlanOutput>; plans: Array<BillingPlanOutput>;
search: Array<SearchRecord>;
validatePasswordResetToken: ValidatePasswordResetToken; validatePasswordResetToken: ValidatePasswordResetToken;
}; };
@ -1453,7 +1443,7 @@ export type QueryGetTimelineThreadsFromPersonIdArgs = {
}; };
export type QueryGlobalSearchArgs = { export type QuerySearchArgs = {
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>; excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>;
filter?: InputMaybe<ObjectRecordFilterInput>; filter?: InputMaybe<ObjectRecordFilterInput>;
includedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>; includedObjectNameSingulars?: InputMaybe<Array<Scalars['String']>>;
@ -1646,6 +1636,16 @@ export enum SsoIdentityProviderStatus {
Inactive = 'Inactive' Inactive = 'Inactive'
} }
export type SearchRecord = {
__typename?: 'SearchRecord';
imageUrl?: Maybe<Scalars['String']>;
label: Scalars['String'];
objectNameSingular: Scalars['String'];
recordId: Scalars['String'];
tsRank: Scalars['Float'];
tsRankCD: Scalars['Float'];
};
export type SendInvitationsOutput = { export type SendInvitationsOutput = {
__typename?: 'SendInvitationsOutput'; __typename?: 'SendInvitationsOutput';
errors: Array<Scalars['String']>; errors: Array<Scalars['String']>;
@ -2456,7 +2456,7 @@ export type GetClientConfigQueryVariables = Exact<{ [key: string]: never; }>;
export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } }; export type GetClientConfigQuery = { __typename?: 'Query', clientConfig: { __typename?: 'ClientConfig', signInPrefilled: boolean, isMultiWorkspaceEnabled: boolean, isEmailVerificationRequired: boolean, defaultSubdomain?: string | null, frontDomain: string, debugMode: boolean, analyticsEnabled: boolean, isAttachmentPreviewEnabled: boolean, chromeExtensionId?: string | null, canManageFeatureFlags: boolean, isMicrosoftMessagingEnabled: boolean, isMicrosoftCalendarEnabled: boolean, isGoogleMessagingEnabled: boolean, isGoogleCalendarEnabled: boolean, billing: { __typename?: 'Billing', isBillingEnabled: boolean, billingUrl?: string | null, trialPeriods: Array<{ __typename?: 'BillingTrialPeriodDTO', duration: number, isCreditCardRequired: boolean }> }, authProviders: { __typename?: 'AuthProviders', google: boolean, password: boolean, microsoft: boolean, sso: Array<{ __typename?: 'SSOIdentityProvider', id: string, name: string, type: IdentityProviderType, status: SsoIdentityProviderStatus, issuer: string }> }, support: { __typename?: 'Support', supportDriver: string, supportFrontChatId?: string | null }, sentry: { __typename?: 'Sentry', dsn?: string | null, environment?: string | null, release?: string | null }, captcha: { __typename?: 'Captcha', provider?: CaptchaDriverType | null, siteKey?: string | null }, api: { __typename?: 'ApiConfig', mutationMaximumAffectedRecords: number }, publicFeatureFlags: Array<{ __typename?: 'PublicFeatureFlag', key: FeatureFlagKey, metadata: { __typename?: 'PublicFeatureFlagMetadata', label: string, description: string, imagePath: string } }> } };
export type GlobalSearchQueryVariables = Exact<{ export type SearchQueryVariables = Exact<{
searchInput: Scalars['String']; searchInput: Scalars['String'];
limit: Scalars['Int']; limit: Scalars['Int'];
excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']> | Scalars['String']>; excludedObjectNameSingulars?: InputMaybe<Array<Scalars['String']> | Scalars['String']>;
@ -2465,7 +2465,7 @@ export type GlobalSearchQueryVariables = Exact<{
}>; }>;
export type GlobalSearchQuery = { __typename?: 'Query', globalSearch: Array<{ __typename?: 'GlobalSearchRecord', recordId: string, objectSingularName: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number }> }; export type SearchQuery = { __typename?: 'Query', search: Array<{ __typename?: 'SearchRecord', recordId: string, objectNameSingular: string, label: string, imageUrl?: string | null, tsRankCD: number, tsRank: number }> };
export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>; export type SkipSyncEmailOnboardingStepMutationVariables = Exact<{ [key: string]: never; }>;
@ -4145,9 +4145,9 @@ export function useGetClientConfigLazyQuery(baseOptions?: Apollo.LazyQueryHookOp
export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>; export type GetClientConfigQueryHookResult = ReturnType<typeof useGetClientConfigQuery>;
export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>; export type GetClientConfigLazyQueryHookResult = ReturnType<typeof useGetClientConfigLazyQuery>;
export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>; export type GetClientConfigQueryResult = Apollo.QueryResult<GetClientConfigQuery, GetClientConfigQueryVariables>;
export const GlobalSearchDocument = gql` export const SearchDocument = gql`
query GlobalSearch($searchInput: String!, $limit: Int!, $excludedObjectNameSingulars: [String!], $includedObjectNameSingulars: [String!], $filter: ObjectRecordFilterInput) { query Search($searchInput: String!, $limit: Int!, $excludedObjectNameSingulars: [String!], $includedObjectNameSingulars: [String!], $filter: ObjectRecordFilterInput) {
globalSearch( search(
searchInput: $searchInput searchInput: $searchInput
limit: $limit limit: $limit
excludedObjectNameSingulars: $excludedObjectNameSingulars excludedObjectNameSingulars: $excludedObjectNameSingulars
@ -4155,7 +4155,7 @@ export const GlobalSearchDocument = gql`
filter: $filter filter: $filter
) { ) {
recordId recordId
objectSingularName objectNameSingular
label label
imageUrl imageUrl
tsRankCD tsRankCD
@ -4165,16 +4165,16 @@ export const GlobalSearchDocument = gql`
`; `;
/** /**
* __useGlobalSearchQuery__ * __useSearchQuery__
* *
* To run a query within a React component, call `useGlobalSearchQuery` and pass it any options that fit your needs. * To run a query within a React component, call `useSearchQuery` and pass it any options that fit your needs.
* When your component renders, `useGlobalSearchQuery` returns an object from Apollo Client that contains loading, error, and data properties * When your component renders, `useSearchQuery` returns an object from Apollo Client that contains loading, error, and data properties
* you can use to render your UI. * you can use to render your UI.
* *
* @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options; * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
* *
* @example * @example
* const { data, loading, error } = useGlobalSearchQuery({ * const { data, loading, error } = useSearchQuery({
* variables: { * variables: {
* searchInput: // value for 'searchInput' * searchInput: // value for 'searchInput'
* limit: // value for 'limit' * limit: // value for 'limit'
@ -4184,17 +4184,17 @@ export const GlobalSearchDocument = gql`
* }, * },
* }); * });
*/ */
export function useGlobalSearchQuery(baseOptions: Apollo.QueryHookOptions<GlobalSearchQuery, GlobalSearchQueryVariables>) { export function useSearchQuery(baseOptions: Apollo.QueryHookOptions<SearchQuery, SearchQueryVariables>) {
const options = {...defaultOptions, ...baseOptions} const options = {...defaultOptions, ...baseOptions}
return Apollo.useQuery<GlobalSearchQuery, GlobalSearchQueryVariables>(GlobalSearchDocument, options); return Apollo.useQuery<SearchQuery, SearchQueryVariables>(SearchDocument, options);
} }
export function useGlobalSearchLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<GlobalSearchQuery, GlobalSearchQueryVariables>) { export function useSearchLazyQuery(baseOptions?: Apollo.LazyQueryHookOptions<SearchQuery, SearchQueryVariables>) {
const options = {...defaultOptions, ...baseOptions} const options = {...defaultOptions, ...baseOptions}
return Apollo.useLazyQuery<GlobalSearchQuery, GlobalSearchQueryVariables>(GlobalSearchDocument, options); return Apollo.useLazyQuery<SearchQuery, SearchQueryVariables>(SearchDocument, options);
} }
export type GlobalSearchQueryHookResult = ReturnType<typeof useGlobalSearchQuery>; export type SearchQueryHookResult = ReturnType<typeof useSearchQuery>;
export type GlobalSearchLazyQueryHookResult = ReturnType<typeof useGlobalSearchLazyQuery>; export type SearchLazyQueryHookResult = ReturnType<typeof useSearchLazyQuery>;
export type GlobalSearchQueryResult = Apollo.QueryResult<GlobalSearchQuery, GlobalSearchQueryVariables>; export type SearchQueryResult = Apollo.QueryResult<SearchQuery, SearchQueryVariables>;
export const SkipSyncEmailOnboardingStepDocument = gql` export const SkipSyncEmailOnboardingStepDocument = gql`
mutation SkipSyncEmailOnboardingStep { mutation SkipSyncEmailOnboardingStep {
skipSyncEmailOnboardingStep { skipSyncEmailOnboardingStep {

View File

@ -164,10 +164,10 @@ export const NoResultsSearchFallback: Story = {
parameters: { parameters: {
msw: { msw: {
handlers: [ handlers: [
graphql.query('GlobalSearch', () => { graphql.query('Search', () => {
return HttpResponse.json({ return HttpResponse.json({
data: { data: {
globalSearch: [], search: [],
}, },
}); });
}), }),

View File

@ -1,14 +1,14 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
export const globalSearch = gql` export const search = gql`
query GlobalSearch( query Search(
$searchInput: String! $searchInput: String!
$limit: Int! $limit: Int!
$excludedObjectNameSingulars: [String!] $excludedObjectNameSingulars: [String!]
$includedObjectNameSingulars: [String!] $includedObjectNameSingulars: [String!]
$filter: ObjectRecordFilterInput $filter: ObjectRecordFilterInput
) { ) {
globalSearch( search(
searchInput: $searchInput searchInput: $searchInput
limit: $limit limit: $limit
excludedObjectNameSingulars: $excludedObjectNameSingulars excludedObjectNameSingulars: $excludedObjectNameSingulars
@ -16,7 +16,7 @@ export const globalSearch = gql`
filter: $filter filter: $filter
) { ) {
recordId recordId
objectSingularName objectNameSingular
label label
imageUrl imageUrl
tsRankCD tsRankCD

View File

@ -5,17 +5,17 @@ import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSi
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { capitalize } from 'twenty-shared/utils';
import { Avatar } from 'twenty-ui'; import { Avatar } from 'twenty-ui';
import { useDebounce } from 'use-debounce'; import { useDebounce } from 'use-debounce';
import { useGlobalSearchQuery } from '~/generated/graphql'; import { useSearchQuery } from '~/generated/graphql';
import { capitalize } from 'twenty-shared/utils';
export const useCommandMenuSearchRecords = () => { export const useCommandMenuSearchRecords = () => {
const commandMenuSearch = useRecoilValue(commandMenuSearchState); const commandMenuSearch = useRecoilValue(commandMenuSearchState);
const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300); const [deferredCommandMenuSearch] = useDebounce(commandMenuSearch, 300);
const { data: globalSearchData, loading } = useGlobalSearchQuery({ const { data: searchData, loading } = useSearchQuery({
variables: { variables: {
searchInput: deferredCommandMenuSearch ?? '', searchInput: deferredCommandMenuSearch ?? '',
limit: MAX_SEARCH_RESULTS, limit: MAX_SEARCH_RESULTS,
@ -26,17 +26,17 @@ export const useCommandMenuSearchRecords = () => {
const { openRecordInCommandMenu } = useOpenRecordInCommandMenu(); const { openRecordInCommandMenu } = useOpenRecordInCommandMenu();
const commands = useMemo(() => { const commands = useMemo(() => {
return (globalSearchData?.globalSearch ?? []).map((searchRecord) => { return (searchData?.search ?? []).map((searchRecord) => {
const command = { const command = {
id: searchRecord.recordId, id: searchRecord.recordId,
label: searchRecord.label, label: searchRecord.label,
description: capitalize(searchRecord.objectSingularName), description: capitalize(searchRecord.objectNameSingular),
to: `object/${searchRecord.objectSingularName}/${searchRecord.recordId}`, to: `object/${searchRecord.objectNameSingular}/${searchRecord.recordId}`,
shouldCloseCommandMenuOnClick: true, shouldCloseCommandMenuOnClick: true,
Icon: () => ( Icon: () => (
<Avatar <Avatar
type={ type={
searchRecord.objectSingularName === 'company' searchRecord.objectNameSingular === 'company'
? 'squared' ? 'squared'
: 'rounded' : 'rounded'
} }
@ -48,14 +48,14 @@ export const useCommandMenuSearchRecords = () => {
}; };
if ( if (
[CoreObjectNameSingular.Task, CoreObjectNameSingular.Note].includes( [CoreObjectNameSingular.Task, CoreObjectNameSingular.Note].includes(
searchRecord.objectSingularName as CoreObjectNameSingular, searchRecord.objectNameSingular as CoreObjectNameSingular,
) )
) { ) {
return { return {
...command, ...command,
to: '', to: '',
onCommandClick: () => { onCommandClick: () => {
searchRecord.objectSingularName === 'task' searchRecord.objectNameSingular === 'task'
? openRecordInCommandMenu({ ? openRecordInCommandMenu({
recordId: searchRecord.recordId, recordId: searchRecord.recordId,
objectNameSingular: CoreObjectNameSingular.Task, objectNameSingular: CoreObjectNameSingular.Task,
@ -69,7 +69,7 @@ export const useCommandMenuSearchRecords = () => {
} }
return command; return command;
}); });
}, [globalSearchData, openRecordInCommandMenu]); }, [searchData, openRecordInCommandMenu]);
return { return {
loading, loading,

View File

@ -1,23 +1,23 @@
import { getAvatarType } from '@/object-metadata/utils/getAvatarType'; import { getAvatarType } from '@/object-metadata/utils/getAvatarType';
import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage'; import { getBasePathToShowPage } from '@/object-metadata/utils/getBasePathToShowPage';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
import { GlobalSearchRecord } from '~/generated/graphql'; import { SearchRecord } from '~/generated/graphql';
export const formatGlobalSearchRecordAsSingleRecordPickerRecord = ( export const formatSearchRecordAsSingleRecordPickerRecord = (
searchRecord: GlobalSearchRecord, searchRecord: SearchRecord,
): SingleRecordPickerRecord => { ): SingleRecordPickerRecord => {
return { return {
id: searchRecord.recordId, id: searchRecord.recordId,
name: searchRecord.label, name: searchRecord.label,
avatarUrl: searchRecord.imageUrl ?? undefined, avatarUrl: searchRecord.imageUrl ?? undefined,
avatarType: getAvatarType(searchRecord.objectSingularName), avatarType: getAvatarType(searchRecord.objectNameSingular),
linkToShowPage: linkToShowPage:
getBasePathToShowPage({ getBasePathToShowPage({
objectNameSingular: searchRecord.objectSingularName, objectNameSingular: searchRecord.objectNameSingular,
}) + searchRecord.recordId, }) + searchRecord.recordId,
record: { record: {
id: searchRecord.recordId, id: searchRecord.recordId,
__typename: searchRecord.objectSingularName, __typename: searchRecord.objectNameSingular,
}, },
}; };
}; };

View File

@ -7,12 +7,9 @@ import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
import { WatchQueryFetchPolicy } from '@apollo/client'; import { WatchQueryFetchPolicy } from '@apollo/client';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import {
ObjectRecordFilterInput,
useGlobalSearchQuery,
} from '~/generated/graphql';
import { logError } from '~/utils/logError';
import { isDefined } from 'twenty-shared/utils'; import { isDefined } from 'twenty-shared/utils';
import { ObjectRecordFilterInput, useSearchQuery } from '~/generated/graphql';
import { logError } from '~/utils/logError';
export type UseSearchRecordsParams = ObjectMetadataItemIdentifier & { export type UseSearchRecordsParams = ObjectMetadataItemIdentifier & {
limit?: number; limit?: number;
@ -38,7 +35,7 @@ export const useObjectRecordSearchRecords = ({
const { enqueueSnackBar } = useSnackBar(); const { enqueueSnackBar } = useSnackBar();
const { data, loading, error, previousData } = useGlobalSearchQuery({ const { data, loading, error, previousData } = useSearchQuery({
skip: skip:
skip || skip ||
!objectMetadataItem || !objectMetadataItem ||
@ -53,11 +50,11 @@ export const useObjectRecordSearchRecords = ({
fetchPolicy: fetchPolicy, fetchPolicy: fetchPolicy,
onError: (error) => { onError: (error) => {
logError( logError(
`useGlobalSearchRecords for "${objectMetadataItem.namePlural}" error : ` + `useSearchRecords for "${objectMetadataItem.namePlural}" error : ` +
error, error,
); );
enqueueSnackBar( enqueueSnackBar(
`Error during useGlobalSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`, `Error during useSearchRecords for "${objectMetadataItem.namePlural}", ${error.message}`,
{ {
variant: SnackBarVariant.Error, variant: SnackBarVariant.Error,
}, },
@ -68,7 +65,7 @@ export const useObjectRecordSearchRecords = ({
const effectiveData = loading ? previousData : data; const effectiveData = loading ? previousData : data;
const searchRecords = useMemo( const searchRecords = useMemo(
() => effectiveData?.globalSearch || [], () => effectiveData?.search || [],
[effectiveData], [effectiveData],
); );

View File

@ -12,7 +12,7 @@ import { SelectableItem } from '@/ui/layout/selectable-list/components/Selectabl
import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList'; import { useSelectableList } from '@/ui/layout/selectable-list/hooks/useSelectableList';
import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow'; import { useAvailableComponentInstanceIdOrThrow } from '@/ui/utilities/state/component-state/hooks/useAvailableComponentInstanceIdOrThrow';
import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2'; import { useRecoilComponentFamilyValueV2 } from '@/ui/utilities/state/component-state/hooks/useRecoilComponentFamilyValueV2';
import { GlobalSearchRecord } from '~/generated-metadata/graphql'; import { SearchRecord } from '~/generated-metadata/graphql';
export const StyledSelectableItem = styled(SelectableItem)` export const StyledSelectableItem = styled(SelectableItem)`
height: 100%; height: 100%;
@ -20,7 +20,7 @@ export const StyledSelectableItem = styled(SelectableItem)`
`; `;
type MultipleRecordPickerMenuItemContentProps = { type MultipleRecordPickerMenuItemContentProps = {
searchRecord: GlobalSearchRecord; searchRecord: SearchRecord;
objectMetadataItem: ObjectMetadataItem; objectMetadataItem: ObjectMetadataItem;
onChange: (morphItem: RecordPickerPickableMorphItem) => void; onChange: (morphItem: RecordPickerPickableMorphItem) => void;
}; };

View File

@ -1,5 +1,5 @@
import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults'; import { MAX_SEARCH_RESULTS } from '@/command-menu/constants/MaxSearchResults';
import { globalSearch } from '@/command-menu/graphql/queries/globalSearch'; import { search } from '@/command-menu/graphql/queries/search';
import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem'; import { ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
import { usePerformCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/usePerformCombinedFindManyRecords'; import { usePerformCombinedFindManyRecords } from '@/object-record/multiple-objects/hooks/usePerformCombinedFindManyRecords';
import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState'; import { multipleRecordPickerPickableMorphItemsComponentState } from '@/object-record/record-picker/multiple-record-picker/states/multipleRecordPickerPickableMorphItemsComponentState';
@ -10,8 +10,8 @@ import { RecordPickerPickableMorphItem } from '@/object-record/record-picker/typ
import { ApolloClient, useApolloClient } from '@apollo/client'; import { ApolloClient, useApolloClient } from '@apollo/client';
import { isNonEmptyArray } from '@sniptt/guards'; import { isNonEmptyArray } from '@sniptt/guards';
import { useRecoilCallback } from 'recoil'; import { useRecoilCallback } from 'recoil';
import { GlobalSearchRecord } from '~/generated-metadata/graphql';
import { capitalize, isDefined } from 'twenty-shared/utils'; import { capitalize, isDefined } from 'twenty-shared/utils';
import { SearchRecord } from '~/generated-metadata/graphql';
export const useMultipleRecordPickerPerformSearch = () => { export const useMultipleRecordPickerPerformSearch = () => {
const client = useApolloClient(); const client = useApolloClient();
@ -117,23 +117,23 @@ export const useMultipleRecordPickerPerformSearch = () => {
const morphItems = [ const morphItems = [
...updatedPickedMorphItems, ...updatedPickedMorphItems,
...searchRecordsFilteredOnPickedRecordsWithoutDuplicates.map( ...searchRecordsFilteredOnPickedRecordsWithoutDuplicates.map(
({ recordId, objectSingularName }) => ({ ({ recordId, objectNameSingular }) => ({
isMatchingSearchFilter: true, isMatchingSearchFilter: true,
isSelected: true, isSelected: true,
objectMetadataId: searchableObjectMetadataItems.find( objectMetadataId: searchableObjectMetadataItems.find(
(objectMetadata) => (objectMetadata) =>
objectMetadata.nameSingular === objectSingularName, objectMetadata.nameSingular === objectNameSingular,
)?.id, )?.id,
recordId, recordId,
}), }),
), ),
...searchRecordsExcludingPickedRecordsWithoutDuplicates.map( ...searchRecordsExcludingPickedRecordsWithoutDuplicates.map(
({ recordId, objectSingularName }) => ({ ({ recordId, objectNameSingular }) => ({
isMatchingSearchFilter: true, isMatchingSearchFilter: true,
isSelected: false, isSelected: false,
objectMetadataId: searchableObjectMetadataItems.find( objectMetadataId: searchableObjectMetadataItems.find(
(objectMetadata) => (objectMetadata) =>
objectMetadata.nameSingular === objectSingularName, objectMetadata.nameSingular === objectNameSingular,
)?.id, )?.id,
recordId, recordId,
}), }),
@ -168,8 +168,8 @@ export const useMultipleRecordPickerPerformSearch = () => {
.map(({ nameSingular }) => { .map(({ nameSingular }) => {
const recordIdsForMetadataItem = searchRecords const recordIdsForMetadataItem = searchRecords
.filter( .filter(
({ objectSingularName }) => ({ objectNameSingular }) =>
objectSingularName === nameSingular, objectNameSingular === nameSingular,
) )
.map(({ recordId }) => recordId); .map(({ recordId }) => recordId);
@ -251,14 +251,14 @@ const performSearchQueries = async ({
searchFilter: string; searchFilter: string;
searchableObjectMetadataItems: ObjectMetadataItem[]; searchableObjectMetadataItems: ObjectMetadataItem[];
pickedRecordIds: string[]; pickedRecordIds: string[];
}): Promise<[GlobalSearchRecord[], GlobalSearchRecord[]]> => { }): Promise<[SearchRecord[], SearchRecord[]]> => {
if (searchableObjectMetadataItems.length === 0) { if (searchableObjectMetadataItems.length === 0) {
return [[], []]; return [[], []];
} }
const searchRecords = async (filter: any) => { const searchRecords = async (filter: any) => {
const { data } = await client.query({ const { data } = await client.query({
query: globalSearch, query: search,
variables: { variables: {
searchInput: searchFilter, searchInput: searchFilter,
includedObjectNameSingulars: searchableObjectMetadataItems.map( includedObjectNameSingulars: searchableObjectMetadataItems.map(
@ -268,7 +268,7 @@ const performSearchQueries = async ({
limit: MAX_SEARCH_RESULTS, limit: MAX_SEARCH_RESULTS,
}, },
}); });
return data.globalSearch; return data.search;
}; };
const searchRecordsExcludingPickedRecords = await searchRecords( const searchRecordsExcludingPickedRecords = await searchRecords(

View File

@ -1,11 +1,11 @@
import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext'; import { MultipleRecordPickerComponentInstanceContext } from '@/object-record/record-picker/multiple-record-picker/states/contexts/MultipleRecordPickerComponentInstanceContext';
import { ObjectRecord } from '@/object-record/types/ObjectRecord'; import { ObjectRecord } from '@/object-record/types/ObjectRecord';
import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2'; import { createComponentFamilyStateV2 } from '@/ui/utilities/state/component-state/utils/createComponentFamilyStateV2';
import { GlobalSearchRecord } from '~/generated-metadata/graphql'; import { SearchRecord } from '~/generated-metadata/graphql';
export const searchRecordStoreComponentFamilyState = export const searchRecordStoreComponentFamilyState =
createComponentFamilyStateV2< createComponentFamilyStateV2<
(GlobalSearchRecord & { record?: ObjectRecord }) | undefined, (SearchRecord & { record?: ObjectRecord }) | undefined,
string string
>({ >({
key: 'searchRecordStoreComponentFamilyState', key: 'searchRecordStoreComponentFamilyState',

View File

@ -1,4 +1,4 @@
import { formatGlobalSearchRecordAsSingleRecordPickerRecord } from '@/object-metadata/utils/formatGlobalSearchRecordAsSingleRecordPickerRecord'; import { formatSearchRecordAsSingleRecordPickerRecord } from '@/object-metadata/utils/formatSearchRecordAsSingleRecordPickerRecord';
import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit'; import { DEFAULT_SEARCH_REQUEST_LIMIT } from '@/object-record/constants/DefaultSearchRequestLimit';
import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords'; import { useObjectRecordSearchRecords } from '@/object-record/hooks/useObjectRecordSearchRecords';
import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord'; import { SingleRecordPickerRecord } from '@/object-record/record-picker/single-record-picker/types/SingleRecordPickerRecord';
@ -57,13 +57,13 @@ export const useFilteredSearchRecordQuery = ({
return { return {
selectedRecords: selectedRecords selectedRecords: selectedRecords
.map(formatGlobalSearchRecordAsSingleRecordPickerRecord) .map(formatSearchRecordAsSingleRecordPickerRecord)
.filter(isDefined), .filter(isDefined),
filteredSelectedRecords: filteredSelectedRecords filteredSelectedRecords: filteredSelectedRecords
.map(formatGlobalSearchRecordAsSingleRecordPickerRecord) .map(formatSearchRecordAsSingleRecordPickerRecord)
.filter(isDefined), .filter(isDefined),
recordsToSelect: recordsToSelect recordsToSelect: recordsToSelect
.map(formatGlobalSearchRecordAsSingleRecordPickerRecord) .map(formatSearchRecordAsSingleRecordPickerRecord)
.filter(isDefined), .filter(isDefined),
loading: loading:
recordsToSelectLoading || recordsToSelectLoading ||

View File

@ -22,8 +22,8 @@ import {
TooltipDelay, TooltipDelay,
} from 'twenty-ui'; } from 'twenty-ui';
import { import {
GlobalSearchRecord,
Role, Role,
SearchRecord,
WorkspaceMember, WorkspaceMember,
} from '~/generated-metadata/graphql'; } from '~/generated-metadata/graphql';
import { import {
@ -134,7 +134,7 @@ export const RoleAssignment = ({ role }: RoleAssignmentProps) => {
}; };
const handleSelectWorkspaceMember = ( const handleSelectWorkspaceMember = (
workspaceMemberSearchRecord: GlobalSearchRecord, workspaceMemberSearchRecord: SearchRecord,
) => { ) => {
const existingRole = workspaceMemberRoleMap.get( const existingRole = workspaceMemberRoleMap.get(
workspaceMemberSearchRecord.recordId, workspaceMemberSearchRecord.recordId,

View File

@ -6,11 +6,11 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput'; import { DropdownMenuSearchInput } from '@/ui/layout/dropdown/components/DropdownMenuSearchInput';
import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator'; import { DropdownMenuSeparator } from '@/ui/layout/dropdown/components/DropdownMenuSeparator';
import { ChangeEvent, useState } from 'react'; import { ChangeEvent, useState } from 'react';
import { GlobalSearchRecord } from '~/generated-metadata/graphql'; import { SearchRecord } from '~/generated-metadata/graphql';
type RoleAssignmentWorkspaceMemberPickerDropdownProps = { type RoleAssignmentWorkspaceMemberPickerDropdownProps = {
excludedWorkspaceMemberIds: string[]; excludedWorkspaceMemberIds: string[];
onSelect: (workspaceMemberSearchRecord: GlobalSearchRecord) => void; onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
}; };
export const RoleAssignmentWorkspaceMemberPickerDropdown = ({ export const RoleAssignmentWorkspaceMemberPickerDropdown = ({

View File

@ -1,12 +1,12 @@
import { t } from '@lingui/core/macro'; import { t } from '@lingui/core/macro';
import { MenuItem, MenuItemAvatar } from 'twenty-ui'; import { MenuItem, MenuItemAvatar } from 'twenty-ui';
import { GlobalSearchRecord } from '~/generated-metadata/graphql'; import { SearchRecord } from '~/generated-metadata/graphql';
type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = { type RoleAssignmentWorkspaceMemberPickerDropdownContentProps = {
loading: boolean; loading: boolean;
searchFilter: string; searchFilter: string;
filteredWorkspaceMembers: GlobalSearchRecord[]; filteredWorkspaceMembers: SearchRecord[];
onSelect: (workspaceMemberSearchRecord: GlobalSearchRecord) => void; onSelect: (workspaceMemberSearchRecord: SearchRecord) => void;
}; };
export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({ export const RoleAssignmentWorkspaceMemberPickerDropdownContent = ({

View File

@ -182,41 +182,41 @@ export const graphqlMocks = {
}, },
}); });
}), }),
graphql.query('GlobalSearch', () => { graphql.query('Search', () => {
return HttpResponse.json({ return HttpResponse.json({
data: { data: {
globalSearch: [ search: [
{ {
__typename: 'GlobalSearchRecordDTO', __typename: 'SearchRecordDTO',
recordId: '20202020-2d40-4e49-8df4-9c6a049191de', recordId: '20202020-2d40-4e49-8df4-9c6a049191de',
objectSingularName: 'person', objectNameSingular: 'person',
label: 'Louis Duss', label: 'Louis Duss',
imageUrl: '', imageUrl: '',
tsRankCD: 0.2, tsRankCD: 0.2,
tsRank: 0.12158542, tsRank: 0.12158542,
}, },
{ {
__typename: 'GlobalSearchRecordDTO', __typename: 'SearchRecordDTO',
recordId: '20202020-3ec3-4fe3-8997-b76aa0bfa408', recordId: '20202020-3ec3-4fe3-8997-b76aa0bfa408',
objectSingularName: 'company', objectNameSingular: 'company',
label: 'Linkedin', label: 'Linkedin',
imageUrl: 'https://twenty-icons.com/linkedin.com', imageUrl: 'https://twenty-icons.com/linkedin.com',
tsRankCD: 0.2, tsRankCD: 0.2,
tsRank: 0.12158542, tsRank: 0.12158542,
}, },
{ {
__typename: 'GlobalSearchRecordDTO', __typename: 'SearchRecordDTO',
recordId: '20202020-3f74-492d-a101-2a70f50a1645', recordId: '20202020-3f74-492d-a101-2a70f50a1645',
objectSingularName: 'company', objectNameSingular: 'company',
label: 'Libeo', label: 'Libeo',
imageUrl: 'https://twenty-icons.com/libeo.io', imageUrl: 'https://twenty-icons.com/libeo.io',
tsRankCD: 0.2, tsRankCD: 0.2,
tsRank: 0.12158542, tsRank: 0.12158542,
}, },
{ {
__typename: 'GlobalSearchRecordDTO', __typename: 'SearchRecordDTO',
recordId: '20202020-ac73-4797-824e-87a1f5aea9e0', recordId: '20202020-ac73-4797-824e-87a1f5aea9e0',
objectSingularName: 'person', objectNameSingular: 'person',
label: 'Sylvie Palmer', label: 'Sylvie Palmer',
imageUrl: '', imageUrl: '',
tsRankCD: 0.1, tsRankCD: 0.1,
@ -226,45 +226,6 @@ export const graphqlMocks = {
}, },
}); });
}), }),
graphql.query('CombinedSearchRecords', () => {
return HttpResponse.json({
data: {
searchOpportunities: {
edges: [],
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
searchCompanies: {
edges: companiesMock.slice(0, 3).map((company) => ({
node: company,
cursor: null,
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
searchPeople: {
edges: peopleMock.slice(0, 3).map((person) => ({
node: person,
cursor: null,
})),
pageInfo: {
hasNextPage: false,
hasPreviousPage: false,
startCursor: null,
endCursor: null,
},
},
},
});
}),
graphql.query('FindManyViews', ({ variables }) => { graphql.query('FindManyViews', ({ variables }) => {
const objectMetadataId = variables.filter?.objectMetadataId?.eq; const objectMetadataId = variables.filter?.objectMetadataId?.eq;
const viewType = variables.filter?.type?.eq; const viewType = variables.filter?.type?.eq;

View File

@ -10,7 +10,7 @@ import {
import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { SearchService } from 'src/engine/metadata-modules/search/search.service'; 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 { 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 { 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 { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
@ -30,7 +30,7 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveOrSus
protected readonly featureFlagRepository: Repository<FeatureFlag>, protected readonly featureFlagRepository: Repository<FeatureFlag>,
@InjectRepository(ObjectMetadataEntity, 'metadata') @InjectRepository(ObjectMetadataEntity, 'metadata')
protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, protected readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
private readonly searchService: SearchService, private readonly searchVectorService: SearchVectorService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
) { ) {
@ -57,7 +57,7 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveOrSus
}); });
if (!options.dryRun) { if (!options.dryRun) {
await this.searchService.updateSearchVector( await this.searchVectorService.updateSearchVector(
noteObjectMetadata.id, noteObjectMetadata.id,
SEARCH_FIELDS_FOR_NOTES, SEARCH_FIELDS_FOR_NOTES,
workspaceId, workspaceId,
@ -74,7 +74,7 @@ export class MigrateSearchVectorOnNoteAndTaskEntitiesCommand extends ActiveOrSus
}); });
if (!options.dryRun) { if (!options.dryRun) {
await this.searchService.updateSearchVector( await this.searchVectorService.updateSearchVector(
taskObjectMetadata.id, taskObjectMetadata.id,
SEARCH_FIELDS_FOR_TASKS, SEARCH_FIELDS_FOR_TASKS,
workspaceId, workspaceId,

View File

@ -10,7 +10,7 @@ import { FeatureFlag } from 'src/engine/core-modules/feature-flag/feature-flag.e
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 { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; 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 { 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'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
@ -23,7 +23,7 @@ import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/wor
'metadata', 'metadata',
), ),
WorkspaceDataSourceModule, WorkspaceDataSourceModule,
SearchModule, SearchVectorModule,
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule, WorkspaceMetadataVersionModule,
], ],

View File

@ -14,7 +14,6 @@ import { GraphqlQueryFindManyResolverService } from 'src/engine/api/graphql/grap
import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service'; import { GraphqlQueryFindOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-find-one-resolver.service';
import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service'; import { GraphqlQueryRestoreManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-many-resolver.service';
import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service'; import { GraphqlQueryRestoreOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-restore-one-resolver.service';
import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service';
import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service'; import { GraphqlQueryUpdateManyResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-many-resolver.service';
import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service'; import { GraphqlQueryUpdateOneResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-update-one-resolver.service';
import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service'; import { ApiEventEmitterService } from 'src/engine/api/graphql/graphql-query-runner/services/api-event-emitter.service';
@ -35,7 +34,6 @@ const graphqlQueryResolvers = [
GraphqlQueryFindOneResolverService, GraphqlQueryFindOneResolverService,
GraphqlQueryRestoreManyResolverService, GraphqlQueryRestoreManyResolverService,
GraphqlQueryRestoreOneResolverService, GraphqlQueryRestoreOneResolverService,
GraphqlQuerySearchResolverService,
GraphqlQueryUpdateManyResolverService, GraphqlQueryUpdateManyResolverService,
GraphqlQueryUpdateOneResolverService, GraphqlQueryUpdateOneResolverService,
]; ];

View File

@ -245,7 +245,6 @@ export abstract class GraphqlQueryBaseResolverService<
case RESOLVER_METHOD_NAMES.FIND_MANY: case RESOLVER_METHOD_NAMES.FIND_MANY:
case RESOLVER_METHOD_NAMES.FIND_ONE: case RESOLVER_METHOD_NAMES.FIND_ONE:
case RESOLVER_METHOD_NAMES.FIND_DUPLICATES: case RESOLVER_METHOD_NAMES.FIND_DUPLICATES:
case RESOLVER_METHOD_NAMES.SEARCH:
return PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS; return PermissionsOnAllObjectRecords.READ_ALL_OBJECT_RECORDS;
case RESOLVER_METHOD_NAMES.CREATE_MANY: case RESOLVER_METHOD_NAMES.CREATE_MANY:
case RESOLVER_METHOD_NAMES.CREATE_ONE: case RESOLVER_METHOD_NAMES.CREATE_ONE:

View File

@ -1,158 +0,0 @@
import { Injectable } from '@nestjs/common';
import { Brackets } from 'typeorm';
import { isDefined } from 'twenty-shared/utils';
import {
GraphqlQueryBaseResolverService,
GraphqlQueryResolverExecutionArgs,
} from 'src/engine/api/graphql/graphql-query-runner/interfaces/base-resolver-service';
import {
ObjectRecord,
ObjectRecordFilter,
OrderByDirection,
} from 'src/engine/api/graphql/workspace-query-builder/interfaces/object-record.interface';
import { IConnection } from 'src/engine/api/graphql/workspace-query-runner/interfaces/connection.interface';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { SearchResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { QUERY_MAX_RECORDS } from 'src/engine/api/graphql/graphql-query-runner/constants/query-max-records.constant';
import { ObjectRecordsToGraphqlConnectionHelper } from 'src/engine/api/graphql/graphql-query-runner/helpers/object-records-to-graphql-connection.helper';
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
import { formatSearchTerms } from 'src/engine/core-modules/global-search/utils/format-search-terms';
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/constants/search-vector-field.constants';
import { formatResult } from 'src/engine/twenty-orm/utils/format-result.util';
@Injectable()
export class GraphqlQuerySearchResolverService extends GraphqlQueryBaseResolverService<
SearchResolverArgs,
IConnection<ObjectRecord>
> {
async resolve(
executionArgs: GraphqlQueryResolverExecutionArgs<SearchResolverArgs>,
featureFlagsMap: Record<FeatureFlagKey, boolean>,
): Promise<IConnection<ObjectRecord>> {
const { authContext, objectMetadataMaps, objectMetadataItemWithFieldMaps } =
executionArgs.options;
const typeORMObjectRecordsParser =
new ObjectRecordsToGraphqlConnectionHelper(
objectMetadataMaps,
featureFlagsMap,
);
if (!isDefined(executionArgs.args.searchInput)) {
return typeORMObjectRecordsParser.createConnection({
objectRecords: [],
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: 0,
totalCount: 0,
order: [{ id: OrderByDirection.AscNullsFirst }],
hasNextPage: false,
hasPreviousPage: false,
});
}
const searchTerms = formatSearchTerms(
executionArgs.args.searchInput,
'and',
);
const searchTermsOr = formatSearchTerms(
executionArgs.args.searchInput,
'or',
);
const limit = executionArgs.args?.limit ?? QUERY_MAX_RECORDS;
const queryBuilder = executionArgs.repository.createQueryBuilder(
objectMetadataItemWithFieldMaps.nameSingular,
);
executionArgs.graphqlQueryParser.applyFilterToBuilder(
queryBuilder,
objectMetadataItemWithFieldMaps.nameSingular,
executionArgs.args.filter ?? ({} as ObjectRecordFilter),
);
const countQueryBuilder = queryBuilder.clone();
const resultsQueryBuilder =
searchTerms !== ''
? queryBuilder
.andWhere(
new Brackets((qb) => {
qb.where(
`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTerms)`,
{ searchTerms },
).orWhere(
`"${SEARCH_VECTOR_FIELD.name}" @@ to_tsquery('simple', :searchTermsOr)`,
{ searchTermsOr },
);
}),
)
.orderBy(
`ts_rank_cd("${SEARCH_VECTOR_FIELD.name}", to_tsquery(:searchTerms))`,
'DESC',
)
.addOrderBy(
`ts_rank("${SEARCH_VECTOR_FIELD.name}", to_tsquery(:searchTermsOr))`,
'DESC',
)
.setParameter('searchTerms', searchTerms)
.setParameter('searchTermsOr', searchTermsOr)
.take(limit)
: queryBuilder
.andWhere(
new Brackets((qb) => {
qb.where(`"${SEARCH_VECTOR_FIELD.name}" IS NOT NULL`);
}),
)
.take(limit);
const resultsWithTsVector =
(await resultsQueryBuilder.getMany()) as ObjectRecord[];
const objectRecords = formatResult<ObjectRecord[]>(
resultsWithTsVector,
objectMetadataItemWithFieldMaps,
objectMetadataMaps,
);
const totalCount = isDefined(
executionArgs.graphqlQuerySelectedFieldsResult.aggregate.totalCount,
)
? await countQueryBuilder.getCount()
: 0;
const order = undefined;
if (executionArgs.graphqlQuerySelectedFieldsResult.relations) {
await this.processNestedRelationsHelper.processNestedRelations({
objectMetadataMaps,
parentObjectMetadataItem: objectMetadataItemWithFieldMaps,
parentObjectRecords: objectRecords,
relations: executionArgs.graphqlQuerySelectedFieldsResult.relations,
aggregate: executionArgs.graphqlQuerySelectedFieldsResult.aggregate,
limit,
authContext,
dataSource: executionArgs.dataSource,
isNewRelationEnabled:
featureFlagsMap[FeatureFlagKey.IsNewRelationEnabled],
});
}
return typeORMObjectRecordsParser.createConnection({
objectRecords: objectRecords ?? [],
objectName: objectMetadataItemWithFieldMaps.nameSingular,
take: limit,
totalCount,
order,
hasNextPage: false,
hasPreviousPage: false,
});
}
async validate(
_args: SearchResolverArgs,
_options: WorkspaceQueryRunnerOptions,
): Promise<void> {}
}

View File

@ -9,7 +9,6 @@ import {
FindManyResolverArgs, FindManyResolverArgs,
FindOneResolverArgs, FindOneResolverArgs,
RestoreManyResolverArgs, RestoreManyResolverArgs,
SearchResolverArgs,
UpdateManyResolverArgs, UpdateManyResolverArgs,
UpdateOneResolverArgs, UpdateOneResolverArgs,
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
@ -43,6 +42,4 @@ export type WorkspacePreQueryHookPayload<T> = T extends 'createMany'
? DestroyManyResolverArgs ? DestroyManyResolverArgs
: T extends 'destroyOne' : T extends 'destroyOne'
? DestroyOneResolverArgs ? DestroyOneResolverArgs
: T extends 'search' : never;
? SearchResolverArgs
: never;

View File

@ -2,7 +2,6 @@ export const RESOLVER_METHOD_NAMES = {
FIND_MANY: 'findMany', FIND_MANY: 'findMany',
FIND_ONE: 'findOne', FIND_ONE: 'findOne',
FIND_DUPLICATES: 'findDuplicates', FIND_DUPLICATES: 'findDuplicates',
SEARCH: 'search',
CREATE_MANY: 'createMany', CREATE_MANY: 'createMany',
CREATE_ONE: 'createOne', CREATE_ONE: 'createOne',
UPDATE_MANY: 'updateMany', UPDATE_MANY: 'updateMany',

View File

@ -2,7 +2,6 @@ import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-res
import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory';
import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory';
import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory'; import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory';
import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory';
import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory';
import { CreateManyResolverFactory } from './create-many-resolver.factory'; import { CreateManyResolverFactory } from './create-many-resolver.factory';
@ -28,7 +27,6 @@ export const workspaceResolverBuilderFactories = [
DestroyManyResolverFactory, DestroyManyResolverFactory,
RestoreOneResolverFactory, RestoreOneResolverFactory,
RestoreManyResolverFactory, RestoreManyResolverFactory,
SearchResolverFactory,
]; ];
export const workspaceResolverBuilderMethodNames = { export const workspaceResolverBuilderMethodNames = {
@ -36,7 +34,6 @@ export const workspaceResolverBuilderMethodNames = {
FindManyResolverFactory.methodName, FindManyResolverFactory.methodName,
FindOneResolverFactory.methodName, FindOneResolverFactory.methodName,
FindDuplicatesResolverFactory.methodName, FindDuplicatesResolverFactory.methodName,
SearchResolverFactory.methodName,
], ],
mutations: [ mutations: [
CreateManyResolverFactory.methodName, CreateManyResolverFactory.methodName,

View File

@ -1,43 +0,0 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceQueryRunnerOptions } from 'src/engine/api/graphql/workspace-query-runner/interfaces/query-runner-option.interface';
import { WorkspaceResolverBuilderFactoryInterface } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolver-builder-factory.interface';
import {
Resolver,
SearchResolverArgs,
} from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
import { WorkspaceSchemaBuilderContext } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/workspace-schema-builder-context.interface';
import { GraphqlQuerySearchResolverService } from 'src/engine/api/graphql/graphql-query-runner/resolvers/graphql-query-search-resolver.service';
import { RESOLVER_METHOD_NAMES } from 'src/engine/api/graphql/workspace-resolver-builder/constants/resolver-method-names';
@Injectable()
export class SearchResolverFactory
implements WorkspaceResolverBuilderFactoryInterface
{
public static methodName = RESOLVER_METHOD_NAMES.SEARCH;
constructor(
private readonly graphqlQueryRunnerService: GraphqlQuerySearchResolverService,
) {}
create(context: WorkspaceSchemaBuilderContext): Resolver<SearchResolverArgs> {
const internalContext = context;
return async (_source, args, _context, info) => {
const options: WorkspaceQueryRunnerOptions = {
authContext: internalContext.authContext,
info,
objectMetadataMaps: internalContext.objectMetadataMaps,
objectMetadataItemWithFieldMaps:
internalContext.objectMetadataItemWithFieldMaps,
};
return await this.graphqlQueryRunnerService.execute(
args,
options,
SearchResolverFactory.methodName,
);
};
}
}

View File

@ -48,14 +48,6 @@ export interface FindDuplicatesResolverArgs<
data?: Data[]; data?: Data[];
} }
export interface SearchResolverArgs<
Filter extends ObjectRecordFilter = ObjectRecordFilter,
> {
searchInput?: string;
filter?: Filter;
limit?: number;
}
export interface CreateOneResolverArgs< export interface CreateOneResolverArgs<
Data extends Partial<ObjectRecord> = Partial<ObjectRecord>, Data extends Partial<ObjectRecord> = Partial<ObjectRecord>,
> { > {
@ -135,6 +127,5 @@ export type ResolverArgs =
| FindOneResolverArgs | FindOneResolverArgs
| RestoreManyResolverArgs | RestoreManyResolverArgs
| RestoreOneResolverArgs | RestoreOneResolverArgs
| SearchResolverArgs
| UpdateManyResolverArgs | UpdateManyResolverArgs
| UpdateOneResolverArgs; | UpdateOneResolverArgs;

View File

@ -6,7 +6,6 @@ import { WorkspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/work
import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface';
import { FindDuplicatesResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory'; import { FindDuplicatesResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/find-duplicates-resolver.factory';
import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory';
@Injectable() @Injectable()
export class WorkspaceResolverBuilderService { export class WorkspaceResolverBuilderService {
@ -19,8 +18,6 @@ export class WorkspaceResolverBuilderService {
switch (methodName) { switch (methodName) {
case FindDuplicatesResolverFactory.methodName: case FindDuplicatesResolverFactory.methodName:
return isDefined(objectMetadata.duplicateCriteria); return isDefined(objectMetadata.duplicateCriteria);
case SearchResolverFactory.methodName:
return objectMetadata.isSearchable;
default: default:
return true; return true;
} }

View File

@ -7,7 +7,6 @@ import { DestroyManyResolverFactory } from 'src/engine/api/graphql/workspace-res
import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory'; import { DestroyOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/destroy-one-resolver.factory';
import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory'; import { RestoreManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-many-resolver.factory';
import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory'; import { RestoreOneResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/restore-one-resolver.factory';
import { SearchResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/search-resolver-factory';
import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory'; import { UpdateManyResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/factories/update-many-resolver.factory';
import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service'; import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver-builder.service';
import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type';
@ -45,7 +44,6 @@ export class WorkspaceResolverFactory {
private readonly restoreOneResolverFactory: RestoreOneResolverFactory, private readonly restoreOneResolverFactory: RestoreOneResolverFactory,
private readonly restoreManyResolverFactory: RestoreManyResolverFactory, private readonly restoreManyResolverFactory: RestoreManyResolverFactory,
private readonly destroyManyResolverFactory: DestroyManyResolverFactory, private readonly destroyManyResolverFactory: DestroyManyResolverFactory,
private readonly searchResolverFactory: SearchResolverFactory,
private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService, private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService,
) {} ) {}
@ -69,7 +67,6 @@ export class WorkspaceResolverFactory {
['findOne', this.findOneResolverFactory], ['findOne', this.findOneResolverFactory],
['restoreMany', this.restoreManyResolverFactory], ['restoreMany', this.restoreManyResolverFactory],
['restoreOne', this.restoreOneResolverFactory], ['restoreOne', this.restoreOneResolverFactory],
['search', this.searchResolverFactory],
['updateMany', this.updateManyResolverFactory], ['updateMany', this.updateManyResolverFactory],
['updateOne', this.updateOneResolverFactory], ['updateOne', this.updateOneResolverFactory],
]); ]);

View File

@ -138,7 +138,6 @@ export class RootTypeFactory {
switch (methodName) { switch (methodName) {
case 'findMany': case 'findMany':
case 'findDuplicates': case 'findDuplicates':
case 'search':
return ObjectTypeDefinitionKind.Connection; return ObjectTypeDefinitionKind.Connection;
default: default:
return ObjectTypeDefinitionKind.Plain; return ObjectTypeDefinitionKind.Plain;

View File

@ -144,21 +144,6 @@ export const getResolverArgs = (
isNullable: false, isNullable: false,
}, },
}; };
case 'search':
return {
searchInput: {
type: GraphQLString,
isNullable: true,
},
limit: {
type: GraphQLInt,
isNullable: true,
},
filter: {
kind: InputTypeDefinitionKind.Filter,
isNullable: true,
},
};
default: default:
throw new Error(`Unknown resolver type: ${type}`); throw new Error(`Unknown resolver type: ${type}`);
} }

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 { 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 { 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 { 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 { HealthModule } from 'src/engine/core-modules/health/health.module';
import { LabModule } from 'src/engine/core-modules/lab/lab.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'; 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 { PostgresCredentialsModule } from 'src/engine/core-modules/postgres-credentials/postgres-credentials.module';
import { RedisClientModule } from 'src/engine/core-modules/redis-client/redis-client.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 { 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 { serverlessModuleFactory } from 'src/engine/core-modules/serverless/serverless-module.factory';
import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module'; import { ServerlessModule } from 'src/engine/core-modules/serverless/serverless.module';
import { WorkspaceSSOModule } from 'src/engine/core-modules/sso/sso.module'; import { WorkspaceSSOModule } from 'src/engine/core-modules/sso/sso.module';
@ -121,7 +121,7 @@ import { FileModule } from './file/file.module';
useFactory: serverlessModuleFactory, useFactory: serverlessModuleFactory,
inject: [EnvironmentService, FileStorageService], inject: [EnvironmentService, FileStorageService],
}), }),
GlobalSearchModule, SearchModule,
], ],
exports: [ exports: [
AnalyticsModule, AnalyticsModule,

View File

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

View File

@ -2,10 +2,10 @@ 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'; import { ObjectRecordFilterInput } from 'src/engine/core-modules/search/dtos/object-record-filter-input';
@ArgsType() @ArgsType()
export class GlobalSearchArgs { export class SearchArgs {
@Field(() => String) @Field(() => String)
@IsString() @IsString()
searchInput: string; searchInput: string;

View File

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

View File

@ -1,12 +1,12 @@
import { CustomException } from 'src/utils/custom-exception'; import { CustomException } from 'src/utils/custom-exception';
export class GlobalSearchException extends CustomException { export class SearchException extends CustomException {
constructor(message: string, code: GlobalSearchExceptionCode) { constructor(message: string, code: SearchExceptionCode) {
super(message, code); super(message, code);
} }
} }
export enum GlobalSearchExceptionCode { export enum SearchExceptionCode {
METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND', METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND',
LABEL_IDENTIFIER_FIELD_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_NOT_FOUND', LABEL_IDENTIFIER_FIELD_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_NOT_FOUND',
OBJECT_METADATA_MAP_NOT_FOUND = 'OBJECT_METADATA_MAP_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 { 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 { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util';
import { SearchException } from 'src/engine/core-modules/search/exceptions/search.exception';
@Catch(GlobalSearchException) @Catch(SearchException)
export class GlobalSearchApiExceptionFilter implements ExceptionFilter { export class SearchApiExceptionFilter implements ExceptionFilter {
constructor() {} constructor() {}
catch(exception: GlobalSearchException) { catch(exception: SearchException) {
switch (exception.code) { switch (exception.code) {
default: default:
throw new InternalServerError(exception.message); throw new InternalServerError(exception.message);

View File

@ -1,12 +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 { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
import { GlobalSearchResolver } from 'src/engine/core-modules/global-search/global-search.resolver'; import { SearchResolver } from 'src/engine/core-modules/search/search.resolver';
import { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service'; import { SearchService } from 'src/engine/core-modules/search/services/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, FeatureFlagModule], 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 { 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 { 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 { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args';
import { GlobalSearchRecordDTO } from 'src/engine/core-modules/global-search/dtos/global-search-record-dto'; import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
import { import {
GlobalSearchException, SearchException,
GlobalSearchExceptionCode, SearchExceptionCode,
} from 'src/engine/core-modules/global-search/exceptions/global-search.exception'; } from 'src/engine/core-modules/search/exceptions/search.exception';
import { GlobalSearchApiExceptionFilter } from 'src/engine/core-modules/global-search/filters/global-search-api-exception.filter'; import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter';
import { GlobalSearchService } from 'src/engine/core-modules/global-search/services/global-search.service'; import { SearchService } from 'src/engine/core-modules/search/services/search.service';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item'; import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/search/types/records-with-object-metadata-item';
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';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; 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; const OBJECT_METADATA_ITEMS_CHUNK_SIZE = 5;
@Resolver(() => [GlobalSearchRecordDTO]) @Resolver(() => [SearchRecordDTO])
@UseFilters(GlobalSearchApiExceptionFilter) @UseFilters(SearchApiExceptionFilter)
export class GlobalSearchResolver { export class SearchResolver {
constructor( constructor(
private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, private readonly workspaceCacheStorageService: WorkspaceCacheStorageService,
private readonly twentyORMManager: TwentyORMManager, private readonly twentyORMManager: TwentyORMManager,
private readonly globalSearchService: GlobalSearchService, private readonly searchService: SearchService,
private readonly featureFlagService: FeatureFlagService, private readonly featureFlagService: FeatureFlagService,
) {} ) {}
@Query(() => [GlobalSearchRecordDTO]) @Query(() => [SearchRecordDTO])
async globalSearch( async search(
@AuthWorkspace() workspace: Workspace, @AuthWorkspace() workspace: Workspace,
@Args() @Args()
{ {
@ -43,15 +43,15 @@ export class GlobalSearchResolver {
filter, filter,
includedObjectNameSingulars, includedObjectNameSingulars,
excludedObjectNameSingulars, excludedObjectNameSingulars,
}: GlobalSearchArgs, }: SearchArgs,
) { ) {
const currentCacheVersion = const currentCacheVersion =
await this.workspaceCacheStorageService.getMetadataVersion(workspace.id); await this.workspaceCacheStorageService.getMetadataVersion(workspace.id);
if (currentCacheVersion === undefined) { if (currentCacheVersion === undefined) {
throw new GlobalSearchException( throw new SearchException(
'Metadata cache version not found', '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) { if (!objectMetadataMaps) {
throw new GlobalSearchException( throw new SearchException(
`Object metadata map not found for workspace ${workspace.id} and metadata version ${currentCacheVersion}`, `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 = const filteredObjectMetadataItems =
this.globalSearchService.filterObjectMetadataItems({ this.searchService.filterObjectMetadataItems({
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
includedObjectNameSingulars: includedObjectNameSingulars ?? [], includedObjectNameSingulars: includedObjectNameSingulars ?? [],
excludedObjectNameSingulars: excludedObjectNameSingulars ?? [], excludedObjectNameSingulars: excludedObjectNameSingulars ?? [],
@ -99,16 +99,15 @@ export class GlobalSearchResolver {
return { return {
objectMetadataItem, objectMetadataItem,
records: records: await this.searchService.buildSearchQueryAndGetRecords({
await this.globalSearchService.buildSearchQueryAndGetRecords({ entityManager: repository,
entityManager: repository, objectMetadataItem,
objectMetadataItem, featureFlagMap,
featureFlagMap, searchTerms: formatSearchTerms(searchInput, 'and'),
searchTerms: formatSearchTerms(searchInput, 'and'), searchTermsOr: formatSearchTerms(searchInput, 'or'),
searchTermsOr: formatSearchTerms(searchInput, 'or'), limit,
limit, filter: filter ?? ({} as ObjectRecordFilter),
filter: filter ?? ({} as ObjectRecordFilter), }),
}),
}; };
}), }),
); );
@ -116,7 +115,7 @@ export class GlobalSearchResolver {
allRecordsWithObjectMetadataItems.push(...recordsWithObjectMetadataItems); allRecordsWithObjectMetadataItems.push(...recordsWithObjectMetadataItems);
} }
return this.globalSearchService.computeSearchObjectResults( return this.searchService.computeSearchObjectResults(
allRecordsWithObjectMetadataItems, allRecordsWithObjectMetadataItems,
limit, 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 { 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 { 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/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/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/search/dtos/object-record-filter-input';
import { ObjectRecordFilterInput } from 'src/engine/core-modules/global-search/dtos/object-record-filter-input'; import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
import { import {
GlobalSearchException, SearchException,
GlobalSearchExceptionCode, SearchExceptionCode,
} from 'src/engine/core-modules/global-search/exceptions/global-search.exception'; } from 'src/engine/core-modules/search/exceptions/search.exception';
import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/global-search/types/records-with-object-metadata-item'; 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 { 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 { 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() @Injectable()
export class GlobalSearchService { export class SearchService {
filterObjectMetadataItems({ filterObjectMetadataItems({
objectMetadataItemWithFieldMaps, objectMetadataItemWithFieldMaps,
includedObjectNameSingulars, includedObjectNameSingulars,
@ -143,9 +143,9 @@ export class GlobalSearchService {
objectMetadataItem: ObjectMetadataItemWithFieldMaps, objectMetadataItem: ObjectMetadataItemWithFieldMaps,
) { ) {
if (!objectMetadataItem.labelIdentifierFieldMetadataId) { if (!objectMetadataItem.labelIdentifierFieldMetadataId) {
throw new GlobalSearchException( throw new SearchException(
'Label identifier field not found', '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 records.map((record) => {
return { return {
recordId: record.id, recordId: record.id,
objectSingularName: objectMetadataItem.nameSingular, objectNameSingular: objectMetadataItem.nameSingular,
label: this.getLabelIdentifierValue(record, objectMetadataItem), label: this.getLabelIdentifierValue(record, objectMetadataItem),
imageUrl: this.getImageIdentifierValue(record, objectMetadataItem), imageUrl: this.getImageIdentifierValue(record, objectMetadataItem),
tsRankCD: record.tsRankCD, tsRankCD: record.tsRankCD,
@ -230,9 +230,7 @@ export class GlobalSearchService {
return this.sortSearchObjectResults(searchRecords).slice(0, limit); return this.sortSearchObjectResults(searchRecords).slice(0, limit);
} }
sortSearchObjectResults( sortSearchObjectResults(searchObjectResultsWithRank: SearchRecordDTO[]) {
searchObjectResultsWithRank: GlobalSearchRecordDTO[],
) {
return searchObjectResultsWithRank.sort((a, b) => { return searchObjectResultsWithRank.sort((a, b) => {
if (a.tsRankCD !== b.tsRankCD) { if (a.tsRankCD !== b.tsRankCD) {
return b.tsRankCD - a.tsRankCD; return b.tsRankCD - a.tsRankCD;
@ -243,8 +241,8 @@ export class GlobalSearchService {
} }
return ( return (
(STANDARD_OBJECTS_BY_PRIORITY_RANK[b.objectSingularName] || 0) - (STANDARD_OBJECTS_BY_PRIORITY_RANK[b.objectNameSingular] || 0) -
(STANDARD_OBJECTS_BY_PRIORITY_RANK[a.objectSingularName] || 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', () => { describe('formatSearchTerms', () => {
it('should format the search terms', () => { it('should format the search terms', () => {

View File

@ -27,7 +27,7 @@ import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permi
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter'; import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module'; import { RemoteTableRelationsModule } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.module';
import { SearchModule } from 'src/engine/metadata-modules/search/search.module'; 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 { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
@ -54,7 +54,7 @@ import { UpdateObjectPayload } from './dtos/update-object.input';
WorkspaceMigrationRunnerModule, WorkspaceMigrationRunnerModule,
WorkspaceMetadataVersionModule, WorkspaceMetadataVersionModule,
RemoteTableRelationsModule, RemoteTableRelationsModule,
SearchModule, SearchVectorModule,
IndexMetadataModule, IndexMetadataModule,
FeatureFlagModule, FeatureFlagModule,
PermissionsModule, PermissionsModule,

View File

@ -31,7 +31,7 @@ import {
validateObjectMetadataInputNamesOrThrow, validateObjectMetadataInputNamesOrThrow,
} from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util'; } from 'src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util';
import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service';
import { SearchService } from 'src/engine/metadata-modules/search/search.service'; import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util'; import { validateNameAndLabelAreSyncOrThrow } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service';
import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util';
@ -56,7 +56,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
private readonly dataSourceService: DataSourceService, private readonly dataSourceService: DataSourceService,
private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService, private readonly workspaceMigrationRunnerService: WorkspaceMigrationRunnerService,
private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService, private readonly workspaceMetadataVersionService: WorkspaceMetadataVersionService,
private readonly searchService: SearchService, private readonly searchVectorService: SearchVectorService,
private readonly objectMetadataRelationService: ObjectMetadataRelationService, private readonly objectMetadataRelationService: ObjectMetadataRelationService,
private readonly objectMetadataMigrationService: ObjectMetadataMigrationService, private readonly objectMetadataMigrationService: ObjectMetadataMigrationService,
private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService, private readonly objectMetadataRelatedRecordsService: ObjectMetadataRelatedRecordsService,
@ -187,7 +187,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
createdRelatedObjectMetadataCollection, createdRelatedObjectMetadataCollection,
); );
await this.searchService.createSearchVectorFieldForObject( await this.searchVectorService.createSearchVectorFieldForObject(
objectMetadataInput, objectMetadataInput,
createdObjectMetadata, createdObjectMetadata,
); );
@ -292,7 +292,7 @@ export class ObjectMetadataService extends TypeOrmQueryService<ObjectMetadataEnt
}); });
if (isSearchableFieldType(labelIdentifierFieldMetadata.type)) { if (isSearchableFieldType(labelIdentifierFieldMetadata.type)) {
await this.searchService.updateSearchVector( await this.searchVectorService.updateSearchVector(
input.id, input.id,
[ [
{ {

View File

@ -5,7 +5,7 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm';
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 { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module'; import { IndexMetadataModule } from 'src/engine/metadata-modules/index-metadata/index-metadata.module';
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
import { SearchService } from 'src/engine/metadata-modules/search/search.service'; import { SearchVectorService } from 'src/engine/metadata-modules/search-vector/search-vector.service';
import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module';
@Module({ @Module({
@ -17,7 +17,7 @@ import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-
IndexMetadataModule, IndexMetadataModule,
WorkspaceMigrationModule, WorkspaceMigrationModule,
], ],
providers: [SearchService], providers: [SearchVectorService],
exports: [SearchService], exports: [SearchVectorService],
}) })
export class SearchModule {} export class SearchVectorModule {}

View File

@ -31,7 +31,7 @@ import {
import { SearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util'; import { SearchableFieldType } from 'src/engine/workspace-manager/workspace-sync-metadata/utils/is-searchable-field.util';
@Injectable() @Injectable()
export class SearchService { export class SearchVectorService {
constructor( constructor(
@InjectRepository(ObjectMetadataEntity, 'metadata') @InjectRepository(ObjectMetadataEntity, 'metadata')
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>, private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,

View File

@ -41,8 +41,6 @@ export const getResolverName = (
case 'restoreMany': case 'restoreMany':
return `restore${pascalCase(objectMetadata.namePlural)}`; return `restore${pascalCase(objectMetadata.namePlural)}`;
case 'search':
return `search${pascalCase(objectMetadata.namePlural)}`;
default: default:
throw new Error(`Unknown resolver type: ${type}`); throw new Error(`Unknown resolver type: ${type}`);
} }

View File

@ -4,12 +4,12 @@ import { OBJECT_MODEL_COMMON_FIELDS } from 'test/integration/constants/object-mo
import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants'; import { PERSON_GQL_FIELDS } from 'test/integration/constants/person-gql-fields.constants';
import { destroyManyOperationFactory } from 'test/integration/graphql/utils/destroy-many-operation-factory.util'; import { destroyManyOperationFactory } from 'test/integration/graphql/utils/destroy-many-operation-factory.util';
import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util'; import { destroyOneOperationFactory } from 'test/integration/graphql/utils/destroy-one-operation-factory.util';
import {
globalSearchFactory,
GlobalSearchFactoryParams,
} from 'test/integration/graphql/utils/global-search-factory.util';
import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util'; import { makeGraphqlAPIRequest } from 'test/integration/graphql/utils/make-graphql-api-request.util';
import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils'; import { performCreateManyOperation } from 'test/integration/graphql/utils/perform-create-many-operation.utils';
import {
SearchFactoryParams,
searchFactory,
} from 'test/integration/graphql/utils/search-factory.util';
import { import {
LISTING_NAME_PLURAL, LISTING_NAME_PLURAL,
LISTING_NAME_SINGULAR, LISTING_NAME_SINGULAR,
@ -19,9 +19,9 @@ import { deleteOneObjectMetadataItem } from 'test/integration/metadata/suites/ob
import { findManyObjectsMetadataItems } from 'test/integration/metadata/suites/object-metadata/utils/find-many-objects-metadata-items.util'; import { findManyObjectsMetadataItems } from 'test/integration/metadata/suites/object-metadata/utils/find-many-objects-metadata-items.util';
import { EachTestingContext } from 'twenty-shared/testing'; import { EachTestingContext } from 'twenty-shared/testing';
import { GlobalSearchRecordDTO } from 'src/engine/core-modules/global-search/dtos/global-search-record-dto'; import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto';
describe('GlobalSearchResolver', () => { describe('SearchResolver', () => {
let listingObjectMetadataId: { objectMetadataId: string }; let listingObjectMetadataId: { objectMetadataId: string };
const [firstPerson, secondPerson, thirdPerson] = [ const [firstPerson, secondPerson, thirdPerson] = [
{ id: randomUUID(), name: { firstName: 'searchInput1' } }, { id: randomUUID(), name: { firstName: 'searchInput1' } },
@ -40,13 +40,8 @@ describe('GlobalSearchResolver', () => {
{ id: randomUUID(), name: 'searchInput2' }, { id: randomUUID(), name: 'searchInput2' },
]; ];
const hasSearchRecord = ( const hasSearchRecord = (search: SearchRecordDTO[], recordId: string) => {
globalSearch: GlobalSearchRecordDTO[], return search.some((item: SearchRecordDTO) => item.recordId === recordId);
recordId: string,
) => {
return globalSearch.some(
(item: GlobalSearchRecordDTO) => item.recordId === recordId,
);
}; };
beforeAll(async () => { beforeAll(async () => {
@ -127,7 +122,7 @@ describe('GlobalSearchResolver', () => {
}); });
const testsUseCases: EachTestingContext<{ const testsUseCases: EachTestingContext<{
input: GlobalSearchFactoryParams; input: SearchFactoryParams;
eval: { eval: {
definedRecordIds: string[]; definedRecordIds: string[];
undefinedRecordIds: string[]; undefinedRecordIds: string[];
@ -202,24 +197,24 @@ describe('GlobalSearchResolver', () => {
]; ];
it.each(testsUseCases)('$title', async ({ context }) => { it.each(testsUseCases)('$title', async ({ context }) => {
const graphqlOperation = globalSearchFactory(context.input); const graphqlOperation = searchFactory(context.input);
const response = await makeGraphqlAPIRequest(graphqlOperation); const response = await makeGraphqlAPIRequest(graphqlOperation);
expect(response.body.data).toBeDefined(); expect(response.body.data).toBeDefined();
expect(response.body.data.globalSearch).toBeDefined(); expect(response.body.data.search).toBeDefined();
const globalSearch = response.body.data.globalSearch; const search = response.body.data.search;
context.eval.definedRecordIds.length > 0 context.eval.definedRecordIds.length > 0
? expect(globalSearch).not.toHaveLength(0) ? expect(search).not.toHaveLength(0)
: expect(globalSearch).toHaveLength(0); : expect(search).toHaveLength(0);
context.eval.definedRecordIds.forEach((recordId) => { context.eval.definedRecordIds.forEach((recordId) => {
expect(hasSearchRecord(globalSearch, recordId)).toBeTruthy(); expect(hasSearchRecord(search, recordId)).toBeTruthy();
}); });
context.eval.undefinedRecordIds.forEach((recordId) => { context.eval.undefinedRecordIds.forEach((recordId) => {
expect(hasSearchRecord(globalSearch, recordId)).toBeFalsy(); expect(hasSearchRecord(search, recordId)).toBeFalsy();
}); });
}); });
}); });

View File

@ -1,29 +1,29 @@
import gql from 'graphql-tag'; import gql from 'graphql-tag';
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';
export type GlobalSearchFactoryParams = { export type SearchFactoryParams = {
searchInput: string; searchInput: string;
excludedObjectNameSingulars?: string[]; excludedObjectNameSingulars?: string[];
includedObjectNameSingulars?: string[]; includedObjectNameSingulars?: string[];
filter?: ObjectRecordFilterInput; filter?: ObjectRecordFilterInput;
}; };
export const globalSearchFactory = ({ export const searchFactory = ({
searchInput, searchInput,
excludedObjectNameSingulars, excludedObjectNameSingulars,
includedObjectNameSingulars, includedObjectNameSingulars,
filter, filter,
}: GlobalSearchFactoryParams) => ({ }: SearchFactoryParams) => ({
query: gql` query: gql`
query GlobalSearch( query Search(
$searchInput: String! $searchInput: String!
$limit: Int! $limit: Int!
$excludedObjectNameSingulars: [String!] $excludedObjectNameSingulars: [String!]
$includedObjectNameSingulars: [String!] $includedObjectNameSingulars: [String!]
$filter: ObjectRecordFilterInput $filter: ObjectRecordFilterInput
) { ) {
globalSearch( search(
searchInput: $searchInput searchInput: $searchInput
limit: $limit limit: $limit
excludedObjectNameSingulars: $excludedObjectNameSingulars excludedObjectNameSingulars: $excludedObjectNameSingulars
@ -31,7 +31,7 @@ export const globalSearchFactory = ({
filter: $filter filter: $filter
) { ) {
recordId recordId
objectSingularName objectNameSingular
label label
imageUrl imageUrl
tsRankCD tsRankCD
@ -41,7 +41,7 @@ export const globalSearchFactory = ({
`, `,
variables: { variables: {
searchInput, searchInput,
limit: 30, limit: 50,
excludedObjectNameSingulars, excludedObjectNameSingulars,
includedObjectNameSingulars, includedObjectNameSingulars,
filter, filter,