Skip to content

Commit

Permalink
Reworking Boolean Query Configuration Options (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
silvanheller authored Apr 6, 2022
1 parent 52fb14c commit ebaa5d8
Show file tree
Hide file tree
Showing 9 changed files with 252 additions and 252 deletions.
6 changes: 3 additions & 3 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {filter, map} from 'rxjs/operators';
import {MatBottomSheet} from '@angular/material/bottom-sheet';
import {HistoryComponent} from './results/history.component';
import {DistinctElementLookupService} from './core/lookup/distinct-element-lookup.service';
import {ValueType} from './query/containers/bool/bool-attribute';
import {InputType} from './query/containers/bool/bool-attribute';
import {SettingsComponent} from './settings/settings.component';
import {NotificationService} from './core/basics/notification.service';
import {AppConfig} from './app.config';
Expand Down Expand Up @@ -70,8 +70,8 @@ export class AppComponent implements OnInit, AfterViewInit {
public initLookup(config: Config, distinctLookupService: DistinctElementLookupService) {
if (config._config.query.options.boolean) {
config._config.query.boolean.forEach(v => {
const type = <number><unknown>ValueType[v[1]];
if (type === ValueType.DYNAMICOPTIONS.valueOf()) {
const type = <number><unknown>InputType[v[1]];
if (type === InputType.DYNAMICOPTIONS.valueOf()) {
const table: string = v[3];
const column: string = v[4];
distinctLookupService.getDistinct(table, column).subscribe()
Expand Down
73 changes: 49 additions & 24 deletions src/app/query/containers/bool/bool-attribute.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Options} from '@angular-slider/ngx-slider';
import {BooleanQueryValueType} from '../../../shared/model/config/boolean-query-types';

export enum BoolOperator {
EQ = '=',
Expand All @@ -17,44 +18,52 @@ export enum BoolOperator {
BETWEEN = 'BETWEEN',
}

export enum ValueType {
OPTIONS = 0,
DATE = 1,
NUMERIC = 2,
TEXT = 3,
RANGE = 4,
DYNAMICOPTIONS = 5,
export enum InputType {
OPTIONS,
DATE,
NUMERIC,
TEXT,
RANGE,
DYNAMICOPTIONS,
}

export class BoolAttribute {

public readonly displayName: string;
public readonly operators: BoolOperator[];
public readonly valueType: ValueType;
public readonly _options: string[];
public readonly inputType: InputType;
public readonly options: any[];
public readonly range: [number, number];
public readonly sliderOptions: Options;
public minValue: number;
public maxValue: number;
public readonly featureName: string;
public readonly valueType?: BooleanQueryValueType;

/**
* Default Operators are available per ValueType
* The Enum[value].valueOf() syntax is the best way to ensure that both the numeric index as well as the string get matched in the switch-case
*/
public static getDefaultOperatorsByValueType(type: ValueType): BoolOperator[] {
switch (type) {
case ValueType.DATE:
public static getDefaultOperatorsByValueType(type: InputType): BoolOperator[] {
let t = -1;
if (typeof type == 'string') {
t = Number(InputType[type])
} else {
t = type.valueOf()
}
switch (t) {
case InputType.DATE:
return [BoolOperator.EQ, BoolOperator.NEQ, BoolOperator.BETWEEN,
BoolOperator.LEQ, BoolOperator.GEQ, BoolOperator.GREATER, BoolOperator.LESS];
case ValueType.NUMERIC:
case InputType.NUMERIC:
return [BoolOperator.NEQ, BoolOperator.EQ,
BoolOperator.GEQ, BoolOperator.LEQ, BoolOperator.GREATER, BoolOperator.LESS];
case ValueType.OPTIONS:
case ValueType.DYNAMICOPTIONS:
case InputType.OPTIONS:
case InputType.DYNAMICOPTIONS:
return [BoolOperator.EQ, BoolOperator.NEQ];
case ValueType.RANGE:
case InputType.RANGE:
return [BoolOperator.BETWEEN];
case ValueType.TEXT:
case InputType.TEXT:
return [BoolOperator.LIKE, BoolOperator.EQ];
default:
console.error('type ' + type + ' not known');
Expand Down Expand Up @@ -100,29 +109,45 @@ export class BoolAttribute {
return '';
}

public get options(): string[] {
return this._options;
private static parse(value: string, type?: BooleanQueryValueType): any {
if(type){
let t = -1;
if (typeof type == 'string') {
t = Number(BooleanQueryValueType[type])
} else {
t = type
}
switch (t.valueOf()) {
case BooleanQueryValueType.number.valueOf():
return Number(value)
case BooleanQueryValueType.string.valueOf():
return value
}
}
return value
}

/**
* @param displayName how the attribute should be displayed
* @param featureName how the feature is named in cineast
* @param operators if no operator is specified, operators are chosen based on the defaults provided per ValueType
* @param valueType type of attribute
* @param inputType type of attribute, determines selection mechanism - e.g. range, text etc.
* @param options for the Options ValueType, a list of strings can be provided which will be displayed in a dropdown
* @param range for the Between ValueType, two numbers can be provided. A slider will enable to user to set the desired range.
* @param type the type the input value should have (string / number)
*/
constructor(displayName: string, featureName: string, valueType: ValueType, operators?: BoolOperator[], options?: string[], range?: [number, number]) {
constructor(displayName: string, featureName: string, inputType: InputType, operators?: BoolOperator[], options?: string[], range?: [number, number], type?: BooleanQueryValueType) {
this.displayName = displayName;
this.featureName = featureName;
this.valueType = valueType;
this.inputType = inputType;
this.valueType = type
if (operators) {
this.operators = operators;
} else {
this.operators = BoolAttribute.getDefaultOperatorsByValueType(valueType)
this.operators = BoolAttribute.getDefaultOperatorsByValueType(inputType)
}
if (options) {
this._options = options;
this.options = options.map(o => BoolAttribute.parse(o, this.valueType));
}
if (range) {
this.range = range;
Expand Down
43 changes: 12 additions & 31 deletions src/app/query/containers/bool/bool-query-term.component.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import {Component, ComponentFactoryResolver, Injectable, Input, OnInit} from '@angular/core';
import {BoolQueryTerm} from '../../../shared/model/queries/bool-query-term.model';
import {BoolAttribute, ValueType} from './bool-attribute';
import {BoolAttribute, InputType} from './bool-attribute';
import {BehaviorSubject} from 'rxjs';
import {BoolTerm} from './individual/bool-term';
import {DistinctElementLookupService} from '../../../core/lookup/distinct-element-lookup.service';
import {first, map} from 'rxjs/operators';
import {AppConfig} from '../../../app.config';
import {MiscService} from '../../../../../openapi/cineast';
import {BooleanQueryOption} from '../../../shared/model/config/boolean-query-option.model';

@Component({
selector: 'app-qt-bool',
templateUrl: 'bool-query-term.component.html',
styleUrls: ['bool-query-term.component.css']
})
@Injectable()
export class BoolQueryTermComponent implements OnInit {

// TODO add logic to store multiple queries with a combination.
Expand All @@ -23,9 +23,7 @@ export class BoolQueryTermComponent implements OnInit {
@Input()
boolTerm: BoolQueryTerm;

possibleAttributes: BehaviorSubject<BoolAttribute[]> = new BehaviorSubject(
[new BoolAttribute('debug-attribute', 'features.debug', ValueType.TEXT)]
);
possibleAttributes: BehaviorSubject<BoolAttribute[]> = new BehaviorSubject(null);

public ngOnInit() {
/* only add an empty term if there are none currently present*/
Expand All @@ -43,33 +41,16 @@ export class BoolQueryTermComponent implements OnInit {
_configService.configAsObservable.subscribe(c => {
const next = [];
c._config.query.boolean.forEach(v => {
const type = <number><unknown>ValueType[v[1]];
const displayName = v[0];
const feature: string = v[2];
switch (type) {
case ValueType.DATE.valueOf():
case ValueType.NUMERIC.valueOf():
case ValueType.TEXT.valueOf():
next.push(new BoolAttribute(displayName, feature, ValueType[<string>v[1]]));
break;
case ValueType.OPTIONS.valueOf():
next.push(new BoolAttribute(displayName, feature, ValueType[<string>v[1]], null, v.slice(3, v.length), null));
break;
case ValueType.RANGE.valueOf():
next.push(new BoolAttribute(displayName, feature, ValueType[<string>v[1]], null, null, [v[3], v[4]]));
break;
case ValueType.DYNAMICOPTIONS.valueOf():
const table: string = v[3];
const column: string = v[4];
_booleanService.findDistinctElementsByColumn()
_distinctLookupService.getDistinct(table, column).pipe(first(), map(list => list.sort())).forEach(el => {
next.push(new BoolAttribute(displayName, feature, ValueType[<string>v[1]], null, el, null));
this.possibleAttributes.next(next);
});
break;
default:
console.error(`no type ${type} found, ${ValueType.TEXT.valueOf()}`)
const option = v as BooleanQueryOption
if(BooleanQueryOption.getInputTypeValue(option.input)== InputType.DYNAMICOPTIONS){
_booleanService.findDistinctElementsByColumn()
_distinctLookupService.getDistinct(option.table, option.col).pipe(first(), map(list => list.sort())).forEach(el => {
next.push(new BoolAttribute(option.display, option.table+'.'+option.col, BooleanQueryOption.getInputTypeValue(option.input), option.operators, el, option.range, option.type))
this.possibleAttributes.next(next);
});
return
}
next.push(new BoolAttribute(option.display, option.table+'.'+option.col, BooleanQueryOption.getInputTypeValue(option.input), option.operators, option.options, option.range, option.type))
});
this.possibleAttributes.next(next);
})
Expand Down
32 changes: 15 additions & 17 deletions src/app/query/containers/bool/individual/bool-term.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<div [style.display]="'flex'" [style.padding-left]="'10px'" [style.text-align]="'left'" [style.width]="'100%'"
class="options">
<!-- Attribute Dropdown -->
<mat-select [(ngModel)]="attribute" class="options" required>
<mat-select [ngModel]="_attribute | async" (ngModelChange)="_attribute.next($event)" class="options" required>
<mat-option *ngFor="let attr of possibleAttributes | async"
[value]="attr">{{attr.displayName}}</mat-option>
</mat-select>
Expand All @@ -14,45 +14,43 @@
</div>

<!-- If the current attribute is set, populate operator & value fields -->
<ng-container *ngIf="(currentAttributeObservable | async) as currentAttrAsync" class="options">
<ng-container *ngIf="(_attribute | async) as currentAttrAsync" class="options">

<!-- Operator dropdown-->
<mat-select [(ngModel)]="operatorValue" class="options" required>
<mat-select [ngModel]="_operator | async" (ngModelChange)="_operator.next($event)" class="options" required>
<mat-option *ngFor="let operator of currentAttrAsync.operators" [value]="operator" class="options"
style="width:100%">
{{operator.toString()}}
</mat-option>
</mat-select>

<!-- Freetext Field for Numbers & Text -->
<mat-form-field *ngIf="currentAttrAsync.valueType.valueOf() === 2 || currentAttrAsync.valueType.valueOf() === 3" class="options">
<input [(ngModel)]="inputValue" matInput placeholder="Search text">
<mat-form-field *ngIf="currentAttrAsync.inputType.valueOf() === 2 || currentAttrAsync.inputType.valueOf() === 3" class="options">
<input [ngModel]="_value | async"
(ngModelChange)="_value.next($event)" matInput placeholder="Search text">
</mat-form-field>

<!-- Dropdown for Options -->
<mat-select *ngIf="currentAttrAsync.valueType.valueOf() === 0 || currentAttrAsync.valueType.valueOf() === 5;" [(ngModel)]="inputValue" class="options" required>
<mat-select *ngIf="currentAttrAsync.inputType.valueOf() === 0 || currentAttrAsync.inputType.valueOf() === 5;" [ngModel]="_value | async"
(ngModelChange)="_value.next($event)" class="options" required>
<mat-option *ngFor="let option of currentAttrAsync.options" [value]="option">
{{option}}
</mat-option>
</mat-select>

<!-- Date Selector for Date (not yet implemented) -->
<mat-form-field *ngIf="currentAttrAsync.valueType.valueOf()==1" class="options textinput">
<input [(ngModel)]="inputValue" matInput placeholder="This will be a date selector later">
<mat-form-field *ngIf="currentAttrAsync.inputType.valueOf()===1" class="options textinput">
<input [ngModel]="_value | async"
(ngModelChange)="_value.next($event)" matInput placeholder="This will be a date selector later">
</mat-form-field>

<!-- Slider for Range -->
<div *ngIf="currentAttrAsync.valueType.valueOf()==4" class="options">
<ngx-slider [(highValue)]="maxValue" [(value)]="minValue"
[options]="currentAttrAsync.sliderOptions"></ngx-slider>
<div *ngIf="currentAttrAsync.inputType.valueOf()===4" class="options">
<ngx-slider [highValue]="_max | async"
(highValueChange)="_max.next($event)" [value]="_min | async"
(valueChange)="_min.next($event)" [options]="currentAttrAsync.sliderOptions"></ngx-slider>
</div>

<!-- Traditional Slider
<mat-slider class="options" *ngIf="currentAttrAsync.valueType.valueOf()==4"
thumbLabel [displayWith]="formatLabel" tickInterval="auto" min="currentAttrAsync.range[0]" max="currentAttrAsync.range[1]" [(ngModel)]="inputValue">
</mat-slider>
-->

</ng-container>
</div>
<mat-divider></mat-divider>
Expand Down
Loading

0 comments on commit ebaa5d8

Please sign in to comment.