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 }
|