Skip to content

Commit

Permalink
Merge pull request #9653 from owncloud/use-kql
Browse files Browse the repository at this point in the history
[full-ci] - use KQL as default search query language
  • Loading branch information
Jan authored Sep 5, 2023
2 parents f8753de + 0723a86 commit 911fb9b
Show file tree
Hide file tree
Showing 8 changed files with 64 additions and 42 deletions.
4 changes: 2 additions & 2 deletions .drone.env
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# The version of OCIS to use in pipelines that test against OCIS
OCIS_COMMITID=9c3511d2b08de25b16be3d309d0a7710b6848747
OCIS_BRANCH=master
OCIS_COMMITID=5d1d0a68bd34e5a12abba010152c7a2228974f52
OCIS_BRANCH=enable-kql
22 changes: 22 additions & 0 deletions changelog/unreleased/enhancement-kql-search-query-language
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Enhancement: Keyword Query Language (KQL) search syntax

We've introduced [KQL](https://learn.microsoft.com/en-us/sharepoint/dev/general-development/keyword-query-language-kql-syntax-reference) as our default query language.
Previously we used our own simple language for queries which is now replaced by kql.

`sample.tx* Tags:important Tags:report Content:annual*`

becomes

`name:"sample.tx*" AND tag:important AND tag:report AND content:"annual*"`

by default KQL uses `AND` as property restriction and the query described above can also be formulated as follows

`name:"sample.tx*" tag:important tag:report content:"annual*"`

More advanced syntax like grouping combined with boolean property restriction is supported too

`(name:"sample*" name:"*txt") tag:important OR tag:report content:"annual*"`

https://github.com/owncloud/web/pull/9653
https://github.com/owncloud/web/issues/9636
https://github.com/owncloud/web/issues/9646
33 changes: 19 additions & 14 deletions packages/web-app-files/src/components/Search/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -242,24 +242,29 @@ export default defineComponent({
}
const buildSearchTerm = (manuallyUpdateFilterChip = false) => {
let term = ''
if (unref(searchTerm)) {
term = `+Name:*${unref(searchTerm)}*`
let query = ''
const add = (k: string, v: string) => {
query = (query + ` ${k}:${v}`).trimStart()
}
const fullTextQuery = queryItemAsString(unref(fullTextParam))
if (fullTextQuery) {
term = `+Content:"${unref(searchTerm)}"`
const humanSearchTerm = unref(searchTerm)
const isContentOnlySearch = queryItemAsString(unref(fullTextParam)) == 'true'
if (isContentOnlySearch && !!humanSearchTerm) {
add('content', `"${humanSearchTerm}"`)
} else if (!!humanSearchTerm) {
add('name', `"*${humanSearchTerm}*"`)
}
if (unref(scopeQuery) && unref(doUseScope) === 'true') {
term += ` scope:${unref(scopeQuery)}`
const humanScopeQuery = unref(scopeQuery)
const isScopedSearch = unref(doUseScope) === 'true'
if (isScopedSearch && humanScopeQuery) {
add('scope', `${humanScopeQuery}`)
}
const tagsQuery = queryItemAsString(unref(tagParam))
if (tagsQuery) {
tagsQuery.split('+')?.forEach((tag) => {
term += ` +Tags:"${unref(tag)}"`
})
const humanTagsParams = queryItemAsString(unref(tagParam))
if (humanTagsParams) {
add('tag', `"${humanTagsParams}"`)
if (manuallyUpdateFilterChip && unref(tagFilter)) {
/**
Expand All @@ -272,7 +277,7 @@ export default defineComponent({
}
}
return term.trimStart()
return query
}
const breadcrumbs = computed(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ describe('List component', () => {
const searchTerm = 'term'
const { wrapper } = getWrapper({ searchTerm })
const appBar = wrapper.findComponent<any>('app-bar-stub')
expect(appBar.props('breadcrumbs')[0].text).toContain(searchTerm)
expect(appBar.props('breadcrumbs')[0].text).toEqual(`Search results for "${searchTerm}"`)
})
})
describe('filter', () => {
Expand All @@ -75,13 +75,13 @@ describe('List component', () => {
const searchTerm = 'term'
const tagFilterQuery = 'tag1'
const { wrapper } = getWrapper({
availableTags: ['tag1'],
availableTags: [tagFilterQuery],
searchTerm,
tagFilterQuery
})
await wrapper.vm.loadAvailableTagsTask.last
expect(wrapper.emitted('search')[0][0]).toEqual(
`+Name:*${searchTerm}* +Tags:"${tagFilterQuery}"`
`name:"*${searchTerm}*" tag:"${tagFilterQuery}"`
)
})
})
Expand All @@ -98,7 +98,7 @@ describe('List component', () => {
fullTextSearchEnabled: true
})
await wrapper.vm.loadAvailableTagsTask.last
expect(wrapper.emitted('search')[0][0]).toEqual(`+Content:"${searchTerm}"`)
expect(wrapper.emitted('search')[0][0]).toEqual(`content:"${searchTerm}"`)
})
})
})
Expand Down
30 changes: 13 additions & 17 deletions packages/web-app-search/src/portals/SearchBar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@ import { eventBus } from 'web-pkg/src/services/eventBus'
import { computed, defineComponent, GlobalComponents, inject, Ref, ref, unref, watch } from 'vue'
import { SearchLocationFilterConstants } from 'web-pkg/src/composables'
import { SearchBarFilter } from 'web-pkg/src/components'
import { SHARE_JAIL_ID } from 'web-client/src/helpers'
export default defineComponent({
name: 'SearchBar',
Expand All @@ -128,7 +127,7 @@ export default defineComponent({
const isMobileWidth = inject<Ref<boolean>>('isMobileWidth')
const scopeQueryValue = useRouteQuery('scope')
const shareId = useRouteQuery('shareId')
const locationFilterId = ref(SearchLocationFilterConstants.inHere)
const locationFilterId = ref(SearchLocationFilterConstants.everywhere)
const optionsDropRef = ref(null)
const activePreviewIndex = ref(null)
const term = ref('')
Expand Down Expand Up @@ -166,41 +165,36 @@ export default defineComponent({
return unref(providerStore)?.availableProviders
})
const buildLocationScopeId = () => {
const currentFolder = store.getters['Files/currentFolder']
const path = currentFolder.path === '/' ? '' : currentFolder.path
if (isShareRoute()) {
return `${SHARE_JAIL_ID}$${SHARE_JAIL_ID}!${shareId.value}${path}`
}
const spaceId = currentFolder.fileId.split('!')[0]
return `${spaceId}${path}`
}
const search = async () => {
searchResults.value = []
if (!unref(term)) {
return
}
let searchTerm = unref(term)
const terms = [`name:"*${unref(term)}*"`]
if (
unref(currentFolderAvailable) &&
unref(locationFilterId) === SearchLocationFilterConstants.inHere
) {
const currentFolder = store.getters['Files/currentFolder']
let scope
if (currentFolder?.fileId) {
scope = buildLocationScopeId()
scope = currentFolder?.fileId
} else {
scope = unref(scopeQueryValue)
}
searchTerm = `${unref(term)} scope:${scope}`
terms.push(`scope:${scope}`)
}
loading.value = true
for (const availableProvider of unref(availableProviders)) {
if (availableProvider.previewSearch?.available) {
searchResults.value.push({
providerId: availableProvider.id,
result: await availableProvider.previewSearch.search(unref(searchTerm))
result: await availableProvider.previewSearch.search(terms.join(' '))
})
}
}
Expand All @@ -215,9 +209,10 @@ export default defineComponent({
if (unref(activePreviewIndex) === null) {
const currentQuery = unref(router.currentRoute).query
const currentFolder = store.getters['Files/currentFolder']
let scope
if (unref(currentFolderAvailable) && currentFolder?.fileId) {
scope = buildLocationScopeId()
scope = currentFolder?.fileId
} else {
scope = unref(scopeQueryValue)
}
Expand Down Expand Up @@ -583,6 +578,7 @@ export default defineComponent({
&.active {
background-color: var(--oc-color-background-highlight);
}
&.disabled {
background-color: var(--oc-color-background-muted);
pointer-events: none;
Expand Down
6 changes: 3 additions & 3 deletions packages/web-pkg/src/components/SearchBarFilter.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export default defineComponent({
emits: ['update:modelValue'],
setup(props, { emit }) {
const { $gettext } = useGettext()
const useSopeQueryValue = useRouteQuery('useScope')
const useScopeQueryValue = useRouteQuery('useScope')
const currentSelection = ref<LocationOption>()
const userSelection = ref<LocationOption>()
Expand All @@ -80,8 +80,8 @@ export default defineComponent({
watch(
() => props.currentFolderAvailable,
() => {
if (unref(useSopeQueryValue)) {
const useScope = unref(useSopeQueryValue).toString() === 'true'
if (unref(useScopeQueryValue)) {
const useScope = unref(useScopeQueryValue).toString() === 'true'
if (useScope) {
currentSelection.value = unref(locationOptions).find(
({ id }) => id === SearchLocationFilterConstants.inHere
Expand Down
1 change: 0 additions & 1 deletion packages/web-pkg/src/composables/search/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
export abstract class SearchLocationFilterConstants {
static readonly defaultModeName: string = 'everywhere'
static readonly everywhere: string = 'everywhere'
static readonly inHere: string = 'in-here'
}
2 changes: 1 addition & 1 deletion pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 911fb9b

Please sign in to comment.