fix(filters): label highlighting and autocomplete fields now work with in operator
continuous-integration/drone/push Build was killed Details

Previously, when creating a filter query with the 'in' operator and multiple values, autocompletion and highlighting was not available. This change now implements a split for each value, seperated by a comma.
This commit is contained in:
kolaente 2024-03-11 15:41:06 +01:00
parent 0529f30e77
commit dbfe162cd2
Signed by: konrad
GPG Key ID: F40E70337AB24C9B
5 changed files with 39 additions and 17 deletions

View File

@ -46,7 +46,7 @@ The available operators for filtering include:
* `<`: Less than
* `<=`: Less than or equal to
* `like`: Matches a pattern (using wildcard `%`)
* `in`: Matches any value in a list
* `in`: Matches any value in a comma-seperated list of values
To combine multiple conditions, you can use the following logical operators:

View File

@ -16,7 +16,7 @@ import {
AVAILABLE_FILTER_FIELDS,
FILTER_JOIN_OPERATOR,
FILTER_OPERATORS,
FILTER_OPERATORS_REGEX, LABEL_FIELDS,
FILTER_OPERATORS_REGEX, LABEL_FIELDS, getFilterFieldRegexPattern,
} from '@/helpers/filters'
const {
@ -104,15 +104,25 @@ const highlightedFilterQuery = computed(() => {
})
LABEL_FIELDS
.forEach(f => {
const pattern = new RegExp(f + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*([\'"]?)([^\'"\\s]+\\1?)?', 'ig')
highlighted = highlighted.replaceAll(pattern, (match, token, start, value) => {
const pattern = getFilterFieldRegexPattern(f)
highlighted = highlighted.replaceAll(pattern, (match, prefix, operator, space, value) => {
if (typeof value === 'undefined') {
value = ''
}
let labelTitles = [value]
if(operator === 'in' || operator === '?=') {
labelTitles = value.split(',').map(v => v.trim())
}
const label = labelStore.getLabelsByExactTitles([value])[0] || undefined
const labelsHtml: string[] = []
labelTitles.forEach(t => {
const label = labelStore.getLabelByExactTitle(t) || undefined
labelsHtml.push(`<span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? t}</span>`)
})
return `${f} ${token} <span class="filter-query__label_value" style="background-color: ${label?.hexColor}; color: ${label?.textColor}">${label?.title ?? value}<span>`
return `${f} ${operator} ${labelsHtml.join(', ')}`
})
})
FILTER_OPERATORS
@ -184,26 +194,31 @@ function handleFieldInput() {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const [matched, prefix, operator, space, keyword] = match
if (keyword) {
let search = keyword
if(operator === 'in' || operator === '?=') {
const keywords = keyword.split(',')
search = keywords[keywords.length - 1].trim()
}
if (matched.startsWith('label')) {
autocompleteResultType.value = 'labels'
autocompleteResults.value = labelStore.filterLabelsByQuery([], keyword)
autocompleteResults.value = labelStore.filterLabelsByQuery([], search)
}
if (matched.startsWith('assignee')) {
autocompleteResultType.value = 'assignees'
if (projectId) {
projectUserService.getAll({projectId}, {s: keyword})
projectUserService.getAll({projectId}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
} else {
userService.getAll({}, {s: keyword})
userService.getAll({}, {s: search})
.then(users => autocompleteResults.value = users.length > 1 ? users : [])
}
}
if (!projectId && matched.startsWith('project')) {
autocompleteResultType.value = 'projects'
autocompleteResults.value = projectStore.searchProject(keyword)
autocompleteResults.value = projectStore.searchProject(search)
}
autocompleteMatchText.value = keyword
autocompleteMatchPosition.value = prefix.length - 1
autocompleteMatchPosition.value = prefix.length - 1 + keyword.replace(search, '').length
}
}
})

View File

@ -57,7 +57,7 @@ export const FILTER_JOIN_OPERATOR = [
export const FILTER_OPERATORS_REGEX = '(&lt;|&gt;|&lt;=|&gt;=|=|!=|in)'
function getFieldPattern(field: string): RegExp {
export function getFilterFieldRegexPattern(field: string): RegExp {
return new RegExp('(' + field + '\\s*' + FILTER_OPERATORS_REGEX + '\\s*)([\'"]?)([^\'"&|()]+\\1?)?', 'ig')
}
@ -73,7 +73,7 @@ export function transformFilterStringForApi(
// Transform labels to ids
LABEL_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@ -96,7 +96,7 @@ export function transformFilterStringForApi(
})
// Transform projects to ids
PROJECT_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@ -143,7 +143,7 @@ export function transformFilterStringFromApi(
// Transform labels to their titles
LABEL_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {
@ -167,7 +167,7 @@ export function transformFilterStringFromApi(
// Transform projects to ids
PROJECT_FIELDS.forEach(field => {
const pattern = getFieldPattern(field)
const pattern = getFilterFieldRegexPattern(field)
let match: RegExpExecArray | null
while ((match = pattern.exec(filter)) !== null) {

View File

@ -446,7 +446,7 @@
"lessThan": "Less than",
"lessThanOrEqual": "Less than or equal to",
"like": "Matches a pattern (using wildcard %)",
"in": "Matches any value in a list"
"in": "Matches any value in a comma-seperated list of values"
},
"logicalOperators": {
"intro": "To combine multiple conditions, you can use the following logical operators:",

View File

@ -57,6 +57,12 @@ export const useLabelStore = defineStore('label', () => {
.values(labels.value)
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
})
const getLabelByExactTitle = computed(() => {
return (labelTitle: string) => Object
.values(labels.value)
.find(l => l.title.toLowerCase() === labelTitle.toLowerCase())
})
function setIsLoading(newIsLoading: boolean) {
@ -145,6 +151,7 @@ export const useLabelStore = defineStore('label', () => {
getLabelById,
filterLabelsByQuery,
getLabelsByExactTitles,
getLabelByExactTitle,
setLabels,
setLabel,