mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
Merge pull request #1819 from omnivore-app/feature/search-filter-with-no-as-keyword
This commit is contained in:
commit
65c244edd7
4 changed files with 98 additions and 13 deletions
|
|
@ -15,6 +15,7 @@ import {
|
|||
InFilter,
|
||||
LabelFilter,
|
||||
LabelFilterType,
|
||||
NoFilter,
|
||||
ReadFilter,
|
||||
SortBy,
|
||||
SortOrder,
|
||||
|
|
@ -114,16 +115,15 @@ const appendExcludeLabelFilter = (
|
|||
body: SearchBody,
|
||||
filters: LabelFilter[]
|
||||
): void => {
|
||||
const labels = filters.map((filter) => filter.labels).flat()
|
||||
body.query.bool.must_not.push({
|
||||
nested: {
|
||||
path: 'labels',
|
||||
query: filters.map((filter) => {
|
||||
return {
|
||||
terms: {
|
||||
'labels.name': filter.labels,
|
||||
},
|
||||
}
|
||||
}),
|
||||
query: {
|
||||
terms: {
|
||||
'labels.name': labels,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -209,6 +209,21 @@ const appendRecommendedBy = (body: SearchBody, recommendedBy: string): void => {
|
|||
})
|
||||
}
|
||||
|
||||
const appendNoFilters = (body: SearchBody, noFilters: NoFilter[]): void => {
|
||||
noFilters.forEach((filter) => {
|
||||
body.query.bool.must_not.push({
|
||||
nested: {
|
||||
path: filter.field,
|
||||
query: {
|
||||
exists: {
|
||||
field: filter.field,
|
||||
},
|
||||
},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const createPage = async (
|
||||
page: Page,
|
||||
ctx: PageContext
|
||||
|
|
@ -390,6 +405,7 @@ export const searchPages = async (
|
|||
matchFilters,
|
||||
ids,
|
||||
includeContent,
|
||||
noFilters,
|
||||
} = args
|
||||
// default order is descending
|
||||
const sortOrder = sort?.order || SortOrder.DESCENDING
|
||||
|
|
@ -485,6 +501,10 @@ export const searchPages = async (
|
|||
})
|
||||
}
|
||||
|
||||
if (noFilters) {
|
||||
appendNoFilters(body, noFilters)
|
||||
}
|
||||
|
||||
console.log('searching pages in elastic', JSON.stringify(body))
|
||||
|
||||
const response = await client.search<SearchResponse<Page>, SearchBody>({
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
HasFilter,
|
||||
InFilter,
|
||||
LabelFilter,
|
||||
NoFilter,
|
||||
ReadFilter,
|
||||
SortParams,
|
||||
} from '../utils/search'
|
||||
|
|
@ -103,7 +104,17 @@ export interface SearchBody {
|
|||
terms: {
|
||||
'labels.name': string[]
|
||||
}
|
||||
}[]
|
||||
}
|
||||
}
|
||||
}
|
||||
| {
|
||||
nested: {
|
||||
path: string
|
||||
query: {
|
||||
exists: {
|
||||
field: string
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)[]
|
||||
|
|
@ -317,4 +328,5 @@ export interface PageSearchArgs {
|
|||
ids?: string[]
|
||||
recommendedBy?: string
|
||||
includeContent?: boolean
|
||||
noFilters?: NoFilter[]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ export interface SearchFilter {
|
|||
matchFilters: FieldFilter[]
|
||||
ids: string[]
|
||||
recommendedBy?: string
|
||||
noFilters: NoFilter[]
|
||||
}
|
||||
|
||||
export enum LabelFilterType {
|
||||
|
|
@ -83,6 +84,10 @@ export interface FieldFilter {
|
|||
value: string
|
||||
}
|
||||
|
||||
export interface NoFilter {
|
||||
field: string
|
||||
}
|
||||
|
||||
const parseRecommendedBy = (str?: string): string | undefined => {
|
||||
if (str === undefined) {
|
||||
return undefined
|
||||
|
|
@ -263,6 +268,22 @@ const parseIds = (field: string, str?: string): string[] | undefined => {
|
|||
return str.split(',')
|
||||
}
|
||||
|
||||
const parseNoFilter = (str?: string): NoFilter | undefined => {
|
||||
if (str === undefined) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const strLower = str.toLowerCase()
|
||||
const accepted = ['highlight', 'label']
|
||||
if (accepted.includes(strLower)) {
|
||||
return {
|
||||
field: `${strLower}s`,
|
||||
}
|
||||
}
|
||||
|
||||
return undefined
|
||||
}
|
||||
|
||||
export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
||||
const searchQuery = query ? query.replace(/\W\s":/g, '') : undefined
|
||||
const result: SearchFilter = {
|
||||
|
|
@ -275,6 +296,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
|||
termFilters: [],
|
||||
matchFilters: [],
|
||||
ids: [],
|
||||
noFilters: [],
|
||||
}
|
||||
|
||||
if (!searchQuery) {
|
||||
|
|
@ -288,6 +310,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
|||
termFilters: [],
|
||||
matchFilters: [],
|
||||
ids: [],
|
||||
noFilters: [],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -310,6 +333,7 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
|||
'updated',
|
||||
'includes',
|
||||
'recommendedBy',
|
||||
'no',
|
||||
],
|
||||
tokenize: true,
|
||||
})
|
||||
|
|
@ -395,6 +419,11 @@ export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
|||
result.recommendedBy = parseRecommendedBy(keyword.value)
|
||||
break
|
||||
}
|
||||
case 'no': {
|
||||
const noFilter = parseNoFilter(keyword.value)
|
||||
noFilter && result.noFilters.push(noFilter)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -810,7 +810,7 @@ describe('Article API', () => {
|
|||
userId: user.id,
|
||||
pageType: PageType.Article,
|
||||
title: 'test title',
|
||||
content: '<p>search page</p>',
|
||||
content: '<p>test search api</p>',
|
||||
slug: 'test slug',
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
|
|
@ -849,7 +849,7 @@ describe('Article API', () => {
|
|||
|
||||
context('when we search for a keyword', () => {
|
||||
before(() => {
|
||||
keyword = 'search'
|
||||
keyword = 'search api'
|
||||
})
|
||||
|
||||
it('saves the term in search history', async () => {
|
||||
|
|
@ -875,7 +875,7 @@ describe('Article API', () => {
|
|||
|
||||
context('when type:highlights is not in the query', () => {
|
||||
before(() => {
|
||||
keyword = 'search'
|
||||
keyword = 'search api'
|
||||
})
|
||||
|
||||
it('should return pages in descending order', async () => {
|
||||
|
|
@ -901,7 +901,7 @@ describe('Article API', () => {
|
|||
|
||||
context('when type:highlights is in the query', () => {
|
||||
before(() => {
|
||||
keyword = 'search type:highlights'
|
||||
keyword = "'search api' type:highlights"
|
||||
})
|
||||
|
||||
it('should return highlights in descending order', async () => {
|
||||
|
|
@ -918,7 +918,7 @@ describe('Article API', () => {
|
|||
|
||||
context('when is:unread is in the query', () => {
|
||||
before(() => {
|
||||
keyword = 'search is:unread'
|
||||
keyword = "'search api' is:unread"
|
||||
})
|
||||
|
||||
it('should return unread articles in descending order', async () => {
|
||||
|
|
@ -932,6 +932,30 @@ describe('Article API', () => {
|
|||
expect(res.body.data.search.edges[4].node.id).to.eq(pages[0].id)
|
||||
})
|
||||
})
|
||||
|
||||
context('when no:label is in the query', () => {
|
||||
before(async () => {
|
||||
keyword = "'search api' no:label"
|
||||
})
|
||||
|
||||
it('returns non-labeled items in descending order', async () => {
|
||||
const res = await graphqlRequest(query, authToken).expect(200)
|
||||
|
||||
expect(res.body.data.search.pageInfo.totalCount).to.eq(5)
|
||||
})
|
||||
})
|
||||
|
||||
context('when no:highlight is in the query', () => {
|
||||
before(async () => {
|
||||
keyword = "'search api' no:highlight"
|
||||
})
|
||||
|
||||
it('returns non-highlighted items in descending order', async () => {
|
||||
const res = await graphqlRequest(query, authToken).expect(200)
|
||||
|
||||
expect(res.body.data.search.pageInfo.totalCount).to.eq(0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('TypeaheadSearch API', () => {
|
||||
|
|
|
|||
Loading…
Reference in a new issue