focalboard/webapp/src/cardFilter.ts

124 lines
4.1 KiB
TypeScript
Raw Normal View History

2020-10-15 12:22:38 -07:00
import { IPropertyTemplate } from "./blocks/board"
import { Card } from "./blocks/card"
2020-10-08 09:21:27 -07:00
import { FilterClause } from "./filterClause"
import { FilterGroup } from "./filterGroup"
import { Utils } from "./utils"
class CardFilter {
2020-10-14 17:35:15 -07:00
static applyFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[], cards: Card[]): Card[] {
2020-10-08 09:21:27 -07:00
return cards.filter(card => this.isFilterGroupMet(filterGroup, templates, card))
}
2020-10-14 17:35:15 -07:00
static isFilterGroupMet(filterGroup: FilterGroup, templates: IPropertyTemplate[], card: Card): boolean {
2020-10-08 09:21:27 -07:00
const { filters } = filterGroup
if (filterGroup.filters.length < 1) {
return true // No filters = always met
}
if (filterGroup.operation === "or") {
for (const filter of filters) {
if (FilterGroup.isAnInstanceOf(filter)) {
if (this.isFilterGroupMet(filter, templates, card)) { return true }
} else {
if (this.isClauseMet(filter, templates, card)) { return true }
}
}
return false
} else {
Utils.assert(filterGroup.operation === "and")
for (const filter of filters) {
if (FilterGroup.isAnInstanceOf(filter)) {
if (!this.isFilterGroupMet(filter, templates, card)) { return false }
} else {
if (!this.isClauseMet(filter, templates, card)) { return false }
}
}
return true
}
}
2020-10-14 17:35:15 -07:00
static isClauseMet(filter: FilterClause, templates: IPropertyTemplate[], card: Card): boolean {
2020-10-13 16:49:29 -07:00
const value = card.properties[filter.propertyId]
2020-10-08 09:21:27 -07:00
switch (filter.condition) {
case "includes": {
if (filter.values.length < 1) { break } // No values = ignore clause (always met)
return (filter.values.find(cValue => cValue === value) !== undefined)
}
case "notIncludes": {
if (filter.values.length < 1) { break } // No values = ignore clause (always met)
return (filter.values.find(cValue => cValue === value) === undefined)
}
case "isEmpty": {
return !value
}
case "isNotEmpty": {
return !!value
}
2020-10-14 17:58:11 -07:00
default: {
Utils.assertFailure(`Invalid filter condition ${filter.condition}`)
}
2020-10-08 09:21:27 -07:00
}
return true
}
2020-10-14 17:58:11 -07:00
static propertiesThatMeetFilterGroup(filterGroup: FilterGroup, templates: IPropertyTemplate[]): Record<string, string> {
2020-10-08 09:21:27 -07:00
// TODO: Handle filter groups
const filters = filterGroup.filters.filter(o => !FilterGroup.isAnInstanceOf(o))
2020-10-14 17:58:11 -07:00
if (filters.length < 1) { return {} }
2020-10-08 09:21:27 -07:00
if (filterGroup.operation === "or") {
// Just need to meet the first clause
const property = this.propertyThatMeetsFilterClause(filters[0] as FilterClause, templates)
2020-10-14 17:58:11 -07:00
const result: Record<string, string> = {}
result[property.id] = property.value
return result
2020-10-08 09:21:27 -07:00
} else {
2020-10-14 17:58:11 -07:00
const result: Record<string, string> = {}
filters.forEach(filterClause => {
const p = this.propertyThatMeetsFilterClause(filterClause as FilterClause, templates)
result[p.id] = p.value
})
return result
2020-10-08 09:21:27 -07:00
}
}
2020-10-13 16:49:29 -07:00
static propertyThatMeetsFilterClause(filterClause: FilterClause, templates: IPropertyTemplate[]): { id: string, value?: string } {
2020-10-08 09:21:27 -07:00
const template = templates.find(o => o.id === filterClause.propertyId)
switch (filterClause.condition) {
case "includes": {
if (filterClause.values.length < 1) { return { id: filterClause.propertyId } }
return { id: filterClause.propertyId, value: filterClause.values[0] }
}
case "notIncludes": {
if (filterClause.values.length < 1) { return { id: filterClause.propertyId } }
if (template.type === "select") {
const option = template.options.find(o => !filterClause.values.includes(o.value))
return { id: filterClause.propertyId, value: option.value }
} else {
// TODO: Handle non-select types
return { id: filterClause.propertyId }
}
}
case "isEmpty": {
return { id: filterClause.propertyId }
}
case "isNotEmpty": {
if (template.type === "select") {
if (template.options.length > 0) {
const option = template.options[0]
return { id: filterClause.propertyId, value: option.value }
} else {
return { id: filterClause.propertyId }
}
} else {
// TODO: Handle non-select types
return { id: filterClause.propertyId }
}
}
}
}
}
export { CardFilter }