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:
@ -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']>;
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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: [],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}),
|
}),
|
||||||
|
|||||||
@ -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
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -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],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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 ||
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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 = ({
|
||||||
|
|||||||
@ -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 = ({
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
],
|
],
|
||||||
|
|||||||
@ -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,
|
||||||
];
|
];
|
||||||
|
|||||||
@ -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:
|
||||||
|
|||||||
@ -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> {}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
|
||||||
);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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],
|
||||||
]);
|
]);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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: '',
|
||||||
@ -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;
|
||||||
@ -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()
|
||||||
@ -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',
|
||||||
@ -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);
|
||||||
@ -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 {}
|
||||||
@ -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,
|
||||||
);
|
);
|
||||||
@ -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)
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -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', () => {
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
|||||||
@ -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 {}
|
||||||
@ -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>,
|
||||||
@ -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}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -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,
|
||||||
Reference in New Issue
Block a user