Rudimentary nesting / expressions
This commit is contained in:
parent
96546af95f
commit
9102ddb362
@ -3,37 +3,26 @@
|
|||||||
<i-bs name="{{icon}}"></i-bs>
|
<i-bs name="{{icon}}"></i-bs>
|
||||||
<div class="d-none d-sm-inline"> {{title}}</div>
|
<div class="d-none d-sm-inline"> {{title}}</div>
|
||||||
</button>
|
</button>
|
||||||
<div class="dropdown-menu shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
<div class="dropdown-menu px-3 shadow" ngbDropdownMenu attr.aria-labelledby="dropdown_{{name}}">
|
||||||
<button type="button" class="btn btn-sm btn-outline-primary ms-3" (click)="addQuery()" [disabled]="disabled">
|
<div class="btn-group mb-2 w-100">
|
||||||
<i-bs name="plus"></i-bs> Add query
|
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addAtom()" [disabled]="disabled">
|
||||||
</button>
|
<i-bs name="plus"></i-bs> Add query
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn btn-sm btn-outline-primary" (click)="addExpression()" [disabled]="disabled">
|
||||||
|
<i-bs name="plus"></i-bs> Add expression
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<div class="list-group list-group-flush">
|
<div class="list-group list-group-flush">
|
||||||
@for (query of selectionModel.queries; track query; let i = $index) {
|
@for (query of selectionModel.queries; track query; let i = $index) {
|
||||||
<div class="list-group-item d-flex">
|
<div class="list-group-item px-0 d-flex">
|
||||||
<div class="input-group input-group-sm flex-shrink-1 flex-nowrap">
|
@switch (query.type) {
|
||||||
<ng-select
|
@case (CustomFieldQueryComponentType.Atom) {
|
||||||
class="w-50"
|
<ng-container *ngTemplateOutlet="queryAtom; context: { query: query }"></ng-container>
|
||||||
[items]="customFields"
|
|
||||||
[(ngModel)]="query.field"
|
|
||||||
[disabled]="disabled"
|
|
||||||
bindLabel="name"
|
|
||||||
bindValue="id"
|
|
||||||
></ng-select>
|
|
||||||
<select class="w-25 form-control" [(ngModel)]="query.operator" [disabled]="disabled">
|
|
||||||
<option *ngFor="let operator of getOperatorsForField(query.field)" [ngValue]="operator">{{operator}}</option>
|
|
||||||
</select>
|
|
||||||
@switch (query.operator) {
|
|
||||||
@case ('exists') {
|
|
||||||
<select class="w-25 form-control" [(ngModel)]="query.value" [disabled]="disabled">
|
|
||||||
<option value="true" i18n>true</option>
|
|
||||||
<option value="false" i18n>false</option>
|
|
||||||
</select>
|
|
||||||
}
|
|
||||||
@default {
|
|
||||||
<input class="w-25 form-control" type="text" [(ngModel)]="query.value" [disabled]="disabled">
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</div>
|
@case (CustomFieldQueryComponentType.Expression) {
|
||||||
|
<ng-container *ngTemplateOutlet="queryExpression; context: { query: query }"></ng-container>
|
||||||
|
}
|
||||||
|
}
|
||||||
<button class="btn btn-link btn-sm pe-0" type="button" (click)="removeQuery(i)" [disabled]="disabled">
|
<button class="btn btn-link btn-sm pe-0" type="button" (click)="removeQuery(i)" [disabled]="disabled">
|
||||||
<i-bs name="x"></i-bs>
|
<i-bs name="x"></i-bs>
|
||||||
</button>
|
</button>
|
||||||
@ -42,3 +31,60 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<ng-template #queryAtom let-query="query">
|
||||||
|
<div class="input-group input-group-sm flex-shrink-1 flex-nowrap">
|
||||||
|
<ng-select
|
||||||
|
class="w-50"
|
||||||
|
[items]="customFields"
|
||||||
|
[(ngModel)]="query.field"
|
||||||
|
[disabled]="disabled"
|
||||||
|
bindLabel="name"
|
||||||
|
bindValue="id"
|
||||||
|
></ng-select>
|
||||||
|
<select class="w-25 form-control" [(ngModel)]="query.operator" [disabled]="disabled">
|
||||||
|
<option *ngFor="let operator of getOperatorsForField(query.field)" [ngValue]="operator">{{operator}}</option>
|
||||||
|
</select>
|
||||||
|
@switch (query.operator) {
|
||||||
|
@case ('exists') {
|
||||||
|
<select class="w-25 form-control" [(ngModel)]="query.value" [disabled]="disabled">
|
||||||
|
<option value="true" i18n>true</option>
|
||||||
|
<option value="false" i18n>false</option>
|
||||||
|
</select>
|
||||||
|
}
|
||||||
|
@default {
|
||||||
|
<input class="w-25 form-control" type="text" [(ngModel)]="query.value" [disabled]="disabled">
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
|
||||||
|
<ng-template #queryExpression let-query="query">
|
||||||
|
<div class="d-flex flex-column">
|
||||||
|
<div class="btn-group btn-group-xs flex-fill" role="group">
|
||||||
|
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorAnd_{{query.field}}" name="logicalOperatorAnd_{{query.field}}" value="AND">
|
||||||
|
<label class="btn btn-outline-primary" for="logicalOperatorAnd_{{query.field}}" i18n>And</label>
|
||||||
|
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorOr_{{query.field}}" name="logicalOperatorOr_{{query.field}}" value="OR">
|
||||||
|
<label class="btn btn-outline-primary" for="logicalOperatorOr_{{query.field}}" i18n>Or</label>
|
||||||
|
<input [(ngModel)]="query.operator" type="radio" class="btn-check" id="logicalOperatorNot_{{query.field}}" name="logicalOperatorNot_{{query.field}}" value="NOT">
|
||||||
|
<label class="btn btn-outline-primary" for="logicalOperatorNot_{{query.field}}" i18n>Not</label>
|
||||||
|
</div>
|
||||||
|
<div class="border border-1 border-primary rounded p-2 mt-2">
|
||||||
|
@for (subquery of query.value; track subquery; let i = $index) {
|
||||||
|
<div class="list-group-item px-0 d-flex">
|
||||||
|
@switch (subquery.type) {
|
||||||
|
@case (CustomFieldQueryComponentType.Atom) {
|
||||||
|
<ng-container *ngTemplateOutlet="queryAtom; context: { query: subquery }"></ng-container>
|
||||||
|
}
|
||||||
|
@case (CustomFieldQueryComponentType.Expression) {
|
||||||
|
<ng-container *ngTemplateOutlet="queryExpression; context: { query: subquery }"></ng-container>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
<button class="btn btn-link btn-sm pe-0" type="button" (click)="removeQuery(i)" [disabled]="disabled">
|
||||||
|
<i-bs name="x"></i-bs>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -3,19 +3,29 @@ import { Subject, first, takeUntil } from 'rxjs'
|
|||||||
import { CustomField } from 'src/app/data/custom-field'
|
import { CustomField } from 'src/app/data/custom-field'
|
||||||
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
import { CustomFieldsService } from 'src/app/services/rest/custom-fields.service'
|
||||||
|
|
||||||
export class CustomFieldQuery {
|
export enum CustomFieldQueryLogicalOperator {
|
||||||
public changed = new Subject<CustomFieldQuery>()
|
And = 'AND',
|
||||||
|
Or = 'OR',
|
||||||
|
Not = 'NOT',
|
||||||
|
}
|
||||||
|
|
||||||
private _field: string
|
export enum CustomFieldQueryComponentType {
|
||||||
set field(value: string) {
|
Atom = 'Atom',
|
||||||
this._field = value
|
Expression = 'Expression',
|
||||||
this.changed.next(this)
|
}
|
||||||
}
|
|
||||||
get field(): string {
|
export class CustomFieldQueryComponent {
|
||||||
return this._field
|
public readonly type: CustomFieldQueryComponentType
|
||||||
|
public changed: Subject<CustomFieldQueryComponent>
|
||||||
|
|
||||||
|
constructor(type: CustomFieldQueryComponentType) {
|
||||||
|
this.type = type
|
||||||
|
this.changed = new Subject<CustomFieldQueryComponent>()
|
||||||
}
|
}
|
||||||
|
|
||||||
private _operator: string
|
public serialize() {}
|
||||||
|
|
||||||
|
protected _operator: string
|
||||||
set operator(value: string) {
|
set operator(value: string) {
|
||||||
this._operator = value
|
this._operator = value
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
@ -24,31 +34,75 @@ export class CustomFieldQuery {
|
|||||||
return this._operator
|
return this._operator
|
||||||
}
|
}
|
||||||
|
|
||||||
private _value: string
|
protected _value: string | CustomFieldQueryAtom[] | CustomFieldQueryExpression
|
||||||
set value(value: string) {
|
set value(
|
||||||
|
value: string | CustomFieldQueryAtom[] | CustomFieldQueryExpression
|
||||||
|
) {
|
||||||
this._value = value
|
this._value = value
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
}
|
}
|
||||||
get value(): string {
|
get value(): string | CustomFieldQueryAtom[] | CustomFieldQueryExpression {
|
||||||
return this._value
|
return this._value
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomFieldQueryAtom extends CustomFieldQueryComponent {
|
||||||
|
protected _field: string
|
||||||
|
set field(value: string) {
|
||||||
|
this._field = value
|
||||||
|
this.changed.next(this)
|
||||||
|
}
|
||||||
|
get field(): string {
|
||||||
|
return this._field
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(queryArray: [string, string, string] = [null, null, null]) {
|
||||||
|
super(CustomFieldQueryComponentType.Atom)
|
||||||
|
;[this._field, this._operator, this._value] = queryArray
|
||||||
|
}
|
||||||
|
|
||||||
|
public serialize() {
|
||||||
|
return [this._field, this._operator, this._value]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CustomFieldQueryExpression extends CustomFieldQueryComponent {
|
||||||
constructor(
|
constructor(
|
||||||
field: string = null,
|
expressionArray: [
|
||||||
operator: string = null,
|
CustomFieldQueryLogicalOperator,
|
||||||
value: string = null
|
(
|
||||||
|
| [string, string, string][]
|
||||||
|
| [CustomFieldQueryLogicalOperator, [string, string, string][]]
|
||||||
|
),
|
||||||
|
] = [CustomFieldQueryLogicalOperator.And, null]
|
||||||
) {
|
) {
|
||||||
this.field = field
|
super(CustomFieldQueryComponentType.Expression)
|
||||||
this.operator = operator
|
;[this._operator] = expressionArray
|
||||||
this.value = value
|
let values = expressionArray[1]
|
||||||
|
if (!values) {
|
||||||
|
this._value = []
|
||||||
|
} else if (values?.length > 0 && values[0] instanceof Array) {
|
||||||
|
this._value = values.map((value) => new CustomFieldQueryAtom(value))
|
||||||
|
} else {
|
||||||
|
this._value = new CustomFieldQueryExpression(values as any)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public serialize() {
|
||||||
|
let value
|
||||||
|
if (this._value instanceof Array) {
|
||||||
|
value = this._value.map((atom) => atom.serialize())
|
||||||
|
} else {
|
||||||
|
value = value.serialize()
|
||||||
|
}
|
||||||
|
return [this._operator, value]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CustomFieldQueriesModel {
|
export class CustomFieldQueriesModel {
|
||||||
// matchingModel: MatchingModel
|
public queries: Array<CustomFieldQueryAtom | CustomFieldQueryExpression> = []
|
||||||
queries: CustomFieldQuery[] = []
|
|
||||||
|
|
||||||
changed = new Subject<CustomFieldQueriesModel>()
|
public readonly changed = new Subject<CustomFieldQueriesModel>()
|
||||||
|
|
||||||
public clear(fireEvent = true) {
|
public clear(fireEvent = true) {
|
||||||
this.queries = []
|
this.queries = []
|
||||||
@ -57,8 +111,14 @@ export class CustomFieldQueriesModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public addQuery(query: CustomFieldQuery = new CustomFieldQuery()) {
|
public addQuery(query: CustomFieldQueryAtom = new CustomFieldQueryAtom()) {
|
||||||
this.queries.push(query)
|
if (this.queries.length > 0) {
|
||||||
|
if (this.queries[0].type === CustomFieldQueryComponentType.Expression) {
|
||||||
|
;(this.queries[0].value as Array<any>).push(query)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.queries.push(query)
|
||||||
|
}
|
||||||
query.changed.subscribe(() => {
|
query.changed.subscribe(() => {
|
||||||
if (query.field && query.operator && query.value) {
|
if (query.field && query.operator && query.value) {
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
@ -71,6 +131,21 @@ export class CustomFieldQueriesModel {
|
|||||||
query.changed.complete()
|
query.changed.complete()
|
||||||
this.changed.next(this)
|
this.changed.next(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addExpression(
|
||||||
|
expression: CustomFieldQueryExpression = new CustomFieldQueryExpression()
|
||||||
|
) {
|
||||||
|
if (this.queries.length > 0) {
|
||||||
|
if (this.queries[0].type === CustomFieldQueryComponentType.Atom) {
|
||||||
|
expression.value = this.queries as CustomFieldQueryAtom[]
|
||||||
|
this.queries = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.queries.push(expression)
|
||||||
|
expression.changed.subscribe(() => {
|
||||||
|
this.changed.next(this)
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
@ -79,6 +154,8 @@ export class CustomFieldQueriesModel {
|
|||||||
styleUrls: ['./custom-fields-lookup-dropdown.component.scss'],
|
styleUrls: ['./custom-fields-lookup-dropdown.component.scss'],
|
||||||
})
|
})
|
||||||
export class CustomFieldsLookupDropdownComponent {
|
export class CustomFieldsLookupDropdownComponent {
|
||||||
|
public CustomFieldQueryComponentType = CustomFieldQueryComponentType
|
||||||
|
|
||||||
@Input()
|
@Input()
|
||||||
title: string
|
title: string
|
||||||
|
|
||||||
@ -143,10 +220,14 @@ export class CustomFieldsLookupDropdownComponent {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
public addQuery() {
|
public addAtom() {
|
||||||
this.selectionModel.addQuery()
|
this.selectionModel.addQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public addExpression() {
|
||||||
|
this.selectionModel.addExpression()
|
||||||
|
}
|
||||||
|
|
||||||
public removeQuery(index: number) {
|
public removeQuery(index: number) {
|
||||||
this.selectionModel.removeQuery(index)
|
this.selectionModel.removeQuery(index)
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,8 @@ import { CustomField } from 'src/app/data/custom-field'
|
|||||||
import { SearchService } from 'src/app/services/rest/search.service'
|
import { SearchService } from 'src/app/services/rest/search.service'
|
||||||
import {
|
import {
|
||||||
CustomFieldQueriesModel,
|
CustomFieldQueriesModel,
|
||||||
CustomFieldQuery,
|
CustomFieldQueryAtom,
|
||||||
|
CustomFieldQueryExpression,
|
||||||
} from '../../common/custom-fields-lookup-dropdown/custom-fields-lookup-dropdown.component'
|
} from '../../common/custom-fields-lookup-dropdown/custom-fields-lookup-dropdown.component'
|
||||||
|
|
||||||
const TEXT_FILTER_TARGET_TITLE = 'title'
|
const TEXT_FILTER_TARGET_TITLE = 'title'
|
||||||
@ -523,11 +524,24 @@ export class FilterEditorComponent
|
|||||||
)
|
)
|
||||||
break
|
break
|
||||||
case FILTER_CUSTOM_FIELDS_LOOKUP:
|
case FILTER_CUSTOM_FIELDS_LOOKUP:
|
||||||
// TODO: fully implement
|
try {
|
||||||
const query = JSON.parse(rule.value)
|
const query = JSON.parse(rule.value)
|
||||||
this.customFieldQueriesModel.addQuery(
|
if (Array.isArray(query)) {
|
||||||
new CustomFieldQuery(query[0], query[1], query[2])
|
if (query.length === 2) {
|
||||||
)
|
// expression
|
||||||
|
this.customFieldQueriesModel.addExpression(
|
||||||
|
new CustomFieldQueryExpression(query as any)
|
||||||
|
)
|
||||||
|
} else if (query.length === 3) {
|
||||||
|
// atom
|
||||||
|
this.customFieldQueriesModel.addQuery(
|
||||||
|
new CustomFieldQueryAtom(query as any)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// TODO: handle error?
|
||||||
|
}
|
||||||
break
|
break
|
||||||
case FILTER_HAS_CUSTOM_FIELDS_ALL:
|
case FILTER_HAS_CUSTOM_FIELDS_ALL:
|
||||||
console.log('FILTER_HAS_CUSTOM_FIELDS_ALL', rule.value)
|
console.log('FILTER_HAS_CUSTOM_FIELDS_ALL', rule.value)
|
||||||
@ -752,8 +766,8 @@ export class FilterEditorComponent
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
let queries = this.customFieldQueriesModel.queries
|
let queries = this.customFieldQueriesModel.queries
|
||||||
.filter((query) => query.field && query.operator)
|
.filter((query) => query.value && query.operator)
|
||||||
.map((query) => [query.field, query.operator, query.value])
|
.map((query) => query.serialize())
|
||||||
console.log(
|
console.log(
|
||||||
'this.customFieldQueriesModel.queries',
|
'this.customFieldQueriesModel.queries',
|
||||||
this.customFieldQueriesModel.queries
|
this.customFieldQueriesModel.queries
|
||||||
@ -762,10 +776,7 @@ export class FilterEditorComponent
|
|||||||
if (queries.length > 0) {
|
if (queries.length > 0) {
|
||||||
filterRules.push({
|
filterRules.push({
|
||||||
rule_type: FILTER_CUSTOM_FIELDS_LOOKUP,
|
rule_type: FILTER_CUSTOM_FIELDS_LOOKUP,
|
||||||
value:
|
value: JSON.stringify(queries[0]),
|
||||||
queries.length === 1
|
|
||||||
? JSON.stringify(queries[0])
|
|
||||||
: JSON.stringify(queries),
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// TODO: fully implement custom fields
|
// TODO: fully implement custom fields
|
||||||
|
Loading…
x
Reference in New Issue
Block a user