Allow scoping searches to site:

This commit is contained in:
Hongbo Wu 2023-03-14 18:26:45 +08:00
parent 30fb1af9fa
commit e5f1f25a4d
4 changed files with 82 additions and 46 deletions

View file

@ -1,13 +1,6 @@
import {
ArticleSavingRequestStatus,
Page,
PageContext,
PageSearchArgs,
PageType,
ParamSet,
SearchBody,
SearchResponse,
} from './types'
import { ResponseError } from '@elastic/elasticsearch/lib/errors'
import { EntityType } from '../datalayer/pubsub'
import { BulkActionType } from '../generated/graphql'
import {
DateFilter,
FieldFilter,
@ -21,9 +14,16 @@ import {
SortOrder,
} from '../utils/search'
import { client, INDEX_ALIAS } from './index'
import { EntityType } from '../datalayer/pubsub'
import { ResponseError } from '@elastic/elasticsearch/lib/errors'
import { BulkActionType } from '../generated/graphql'
import {
ArticleSavingRequestStatus,
Page,
PageContext,
PageSearchArgs,
PageType,
ParamSet,
SearchBody,
SearchResponse,
} from './types'
const appendQuery = (body: SearchBody, query: string): void => {
body.query.bool.should.push({
@ -224,6 +224,17 @@ const appendNoFilters = (body: SearchBody, noFilters: NoFilter[]): void => {
})
}
const appendSiteNameFilter = (body: SearchBody, siteName: string): void => {
body.query.bool.should.push({
multi_match: {
query: siteName,
fields: ['siteName', 'url'],
analyzer: 'simple',
},
})
body.query.bool.minimum_should_match = 1
}
export const createPage = async (
page: Page,
ctx: PageContext
@ -404,6 +415,7 @@ export const searchPages = async (
ids,
includeContent,
noFilters,
siteName,
} = args
// default order is descending
const sortOrder = sort?.order || SortOrder.DESCENDING
@ -499,11 +511,10 @@ export const searchPages = async (
})
}
if (noFilters) {
appendNoFilters(body, noFilters)
}
noFilters && appendNoFilters(body, noFilters)
siteName && appendSiteNameFilter(body, siteName)
console.log('searching pages in elastic', JSON.stringify(body))
console.debug('searching pages in elastic', JSON.stringify(body))
const response = await client.search<SearchResponse<Page>, SearchBody>({
index: INDEX_ALIAS,
@ -523,6 +534,10 @@ export const searchPages = async (
response.body.hits.total.value,
]
} catch (e) {
if (e instanceof ResponseError) {
console.error('failed to search pages in elastic', e.meta.body.error)
return undefined
}
console.error('failed to search pages in elastic', e)
return undefined
}

View file

@ -1,6 +1,6 @@
// Define the type of the body for the Search request
import { PickTuple } from '../util'
import { PubsubClient } from '../datalayer/pubsub'
import { PickTuple } from '../util'
import {
DateFilter,
FieldFilter,
@ -80,13 +80,15 @@ export interface SearchBody {
multi_match: {
query: string
fields: string[]
operator: 'and' | 'or'
type:
operator?: 'and' | 'or'
type?:
| 'best_fields'
| 'most_fields'
| 'cross_fields'
| 'phrase'
| 'phrase_prefix'
| 'bool_prefix'
analyzer?: string
}
}[]
minimum_should_match?: number
@ -331,4 +333,5 @@ export interface PageSearchArgs {
recommendedBy?: string
includeContent?: boolean
noFilters?: NoFilter[]
siteName?: string
}

View file

@ -37,6 +37,7 @@ export interface SearchFilter {
ids: string[]
recommendedBy?: string
noFilters: NoFilter[]
siteName?: string
}
export enum LabelFilterType {
@ -335,6 +336,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
'recommendedBy',
'no',
'mode',
'site',
],
tokenize: true,
})
@ -428,6 +430,9 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
case 'mode':
// mode is ignored and used only by the frontend
break
case 'site':
result.siteName = keyword.value
break
}
}
}

View file

@ -1,31 +1,10 @@
import { createTestUser, deleteTestUser } from '../db'
import {
createTestElasticPage,
generateFakeUuid,
graphqlRequest,
request,
} from '../util'
import * as chai from 'chai'
import { expect } from 'chai'
import 'mocha'
import { User } from '../../src/entity/user'
import chaiString from 'chai-string'
import {
BulkActionType,
SyncUpdatedItemEdge,
UpdateReason,
UploadFileStatus,
} from '../../src/generated/graphql'
import {
ArticleSavingRequestStatus,
Highlight,
Page,
PageContext,
PageType,
} from '../../src/elastic/types'
import { UploadFile } from '../../src/entity/upload_file'
import 'mocha'
import { createPubSubClient } from '../../src/datalayer/pubsub'
import { getRepository } from '../../src/entity/utils'
import { refreshIndex } from '../../src/elastic'
import { addHighlightToPage } from '../../src/elastic/highlights'
import {
createPage,
deletePage,
@ -33,9 +12,30 @@ import {
getPageById,
updatePage,
} from '../../src/elastic/pages'
import { addHighlightToPage } from '../../src/elastic/highlights'
import { refreshIndex } from '../../src/elastic'
import {
ArticleSavingRequestStatus,
Highlight,
Page,
PageContext,
PageType,
} from '../../src/elastic/types'
import { SearchHistory } from '../../src/entity/search_history'
import { UploadFile } from '../../src/entity/upload_file'
import { User } from '../../src/entity/user'
import { getRepository } from '../../src/entity/utils'
import {
BulkActionType,
SyncUpdatedItemEdge,
UpdateReason,
UploadFileStatus,
} from '../../src/generated/graphql'
import { createTestUser, deleteTestUser } from '../db'
import {
createTestElasticPage,
generateFakeUuid,
graphqlRequest,
request,
} from '../util'
chai.use(chaiString)
@ -847,6 +847,7 @@ describe('Article API', () => {
url: url,
savedAt: new Date(),
state: ArticleSavingRequestStatus.Succeeded,
siteName: 'Example',
}
page.id = (await createPage(page, ctx))!
pages.push(page)
@ -984,6 +985,18 @@ describe('Article API', () => {
expect(res.body.data.search.pageInfo.totalCount).to.eq(0)
})
})
context('when site:${site_name} is in the query', () => {
before(async () => {
keyword = "'search api' site:example"
})
it('returns items from the site', async () => {
const res = await graphqlRequest(query, authToken).expect(200)
expect(res.body.data.search.pageInfo.totalCount).to.eq(5)
})
})
})
describe('TypeaheadSearch API', () => {