Skip to content

Commit

Permalink
add plugin filter editor
Browse files Browse the repository at this point in the history
  • Loading branch information
infacc committed Jul 1, 2023
1 parent e32de70 commit 01bf1bb
Show file tree
Hide file tree
Showing 11 changed files with 436 additions and 49 deletions.
4 changes: 4 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ import { ChangeUiTemplateComponent } from './dialogs/change-ui-template/change-u
import { ReactiveFormsModule } from '@angular/forms';
import { TemplateDetailsComponent } from './components-small/template-details/template-details.component';
import { ExperimentWorkspaceDetailComponent } from './components/experiment-workspace-detail/experiment-workspace-detail.component';
import { PluginFilterNodeComponent } from './components-small/plugin-filter-node/plugin-filter-node.component';
import { PluginFilterEditorComponent } from './components-small/plugin-filter-editor/plugin-filter-editor.component';

@NgModule({
declarations: [
Expand Down Expand Up @@ -124,6 +126,8 @@ import { ExperimentWorkspaceDetailComponent } from './components/experiment-work
TemplateDetailsComponent,
ExperimentWorkspaceDetailComponent,
ImportExperimentComponent,
PluginFilterNodeComponent,
PluginFilterEditorComponent,
],
imports: [
BrowserModule,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<div class="filter-editor mat-elevation-z0">
<h3 class="editor-description">Filter Editor</h3>
<qhana-plugin-filter-node *ngIf="filterObject" [filterObject]="filterObject" (childChange)="updateFilter($event)"></qhana-plugin-filter-node>
<details>
<summary>Filter String Info</summary>

<i>Enter a filter string as JSON object. Filter strings have the following keys:</i>
<dl>
<dt>name</dt> <dd>Represents the name of a plugin.</dd>
<dt>tag</dt> <dd>Allows filtering elements by their assigned tags.</dd>
<dt>version</dt> <dd>Uses PEP 440 version specifier to filter elements based on specific versions or version ranges.</dd>
<dt>not</dt> <dd>Specifies a filter string to exclude certain elements.</dd>
<dt>and</dt> <dd>Includes multiple filter strings, with elements passing all conditions included in the filtered results (intersection).</dd>
<dt>or</dt> <dd>Includes multiple filter strings, with elements meeting at least one condition included in the filtered results (union).</dd>
</dl>
<mat-divider></mat-divider>
<p>Examples:</p>
<mat-form-field class="form-field">
<textarea class="filter-example" matInput disabled rows="3">
{
"name": "hello-world"
}
</textarea>
</mat-form-field>
<mat-form-field class="form-field">
<textarea class="filter-example" matInput disabled rows="3">
{
"not": { "name": "hello-world" }
}
</textarea>
</mat-form-field>
<mat-form-field class="form-field">
<textarea class="filter-example" matInput disabled rows="6">
{
"and": [
{ "tag": "data-loading" },
{ "version": ">=0.2.0" }
]
}
</textarea>
</mat-form-field>
</details>
<mat-form-field class="form-field">
<mat-label>Filter String:</mat-label>
<textarea matInput [formControl]="filterControl" cdkTextareaAutosize #autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1" cdkAutosizeMaxRows="20" (input)="updateFilterEditor()">
</textarea>
<button mat-raised-button class="copy-button" type="button" (click)="copyFilterString()">
<mat-icon>content_copy</mat-icon>
</button>
</mat-form-field>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
.form-field
width: 100%

.copy-button
position: absolute
top: 0
right: 0

.filter-editor
display: flex
flex-direction: column
gap: 1rem
padding-bottom: 0px

.editor-description
margin: 0
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PluginFilterEditorComponent } from './plugin-filter-editor.component';

describe('PluginFilterEditorComponent', () => {
let component: PluginFilterEditorComponent;
let fixture: ComponentFixture<PluginFilterEditorComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PluginFilterEditorComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(PluginFilterEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { Component, Input, OnInit } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
import { ApiLink } from 'src/app/services/api-data-types';
import { PluginRegistryBaseService } from 'src/app/services/registry.service';
import { TemplateTabApiObject } from 'src/app/services/templates.service';

@Component({
selector: 'qhana-plugin-filter-editor',
templateUrl: './plugin-filter-editor.component.html',
styleUrls: ['./plugin-filter-editor.component.sass']
})
export class PluginFilterEditorComponent implements OnInit {
@Input() tabLink: ApiLink | null = null;

filterString: string = "{}";
filterControl = new FormControl(this.filterString, [Validators.required, Validators.minLength(2)]); // TODO: Add validator for JSON

filterObject: any = null;

constructor(private registry: PluginRegistryBaseService) { }

ngOnInit(): void {
if (this.tabLink == null) {
console.warn("No tab link provided to plugin filter node component");
return;
}
this.registry.getByApiLink<TemplateTabApiObject>(this.tabLink).then(response => {
this.filterString = response?.data?.filterString ?? this.filterString;
this.filterObject = JSON.parse(this.filterString);
});
}

updateFilter(event: [number, any]) {
const [index, value] = event;
this.filterObject = value;
this.filterString = JSON.stringify(this.filterObject, null, 2);
this.filterControl.setValue(this.filterString);
}

copyFilterString() {
navigator.clipboard.writeText(this.filterString);
}

updateFilterEditor() {
const filterValue = this.filterControl.value;
if (filterValue == null) {
return;
}
if (this.filterControl.valid) {
this.filterObject = JSON.parse(filterValue);
this.filterString = filterValue;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<mat-card class="filter-node mat-elevation-z0" [ngClass]="{'filter-leaf' : type === 'name' || type === 'tag' || type === 'version'}">
<mat-card-header>
<div class="config-header-buttons" *ngIf="isFilterEmpty()">
<button mat-raised-button type="button" (click)="setFilter('and')" color="primary">
<mat-icon>add</mat-icon>
Filter Set
</button>
<button mat-raised-button type="button" (click)="setFilter('name')" color="primary">
<mat-icon>add</mat-icon>
Filter
</button>
</div>
<ng-container *ngIf="children != null">
<div class="config-header">
<mat-button-toggle-group [value]="type" (valueChange)="changeType($event)">
<mat-button-toggle value="and">AND</mat-button-toggle>
<mat-button-toggle value="or">OR</mat-button-toggle>
</mat-button-toggle-group>
<div class="config-header-buttons">
<button mat-raised-button *ngIf="depth < 2" type="button" (click)="addFilter('and')" color="primary">
<mat-icon>add</mat-icon>
Filter Set
</button>
<button mat-raised-button type="button" (click)="addFilter('name')" color="primary">
<mat-icon>add</mat-icon>
Filter
</button>
<button mat-raised-button type="button" (click)="deleteFilter()" color="warn">
<mat-icon>delete</mat-icon>
</button>
</div>
</div>
</ng-container>
</mat-card-header>
<mat-card-content>
<ng-container *ngIf="children != null">
<qhana-plugin-filter-node *ngFor="let child of children; let i = index" [filterObject]="children[i]" [index]="i"
[depth]="depth + 1" (childChange)="updateChild($event)"></qhana-plugin-filter-node>
</ng-container>
<div class="config-filter" *ngIf="value != null">
<mat-form-field class="inner-form-field">
<mat-label>Filter Type:</mat-label>
<mat-select [value]="type" (valueChange)="changeType($event)">
<mat-option value="name">Name</mat-option>
<mat-option value="tag">Tag</mat-option>
<mat-option value="version">Version</mat-option>
</mat-select>
</mat-form-field>
<mat-form-field class="form-field">
<mat-label>Filter String:</mat-label>
<textarea matInput [value]="value" (input)="updateFilterString($event)" cdkTextareaAutosize #autosize="cdkTextareaAutosize"
cdkAutosizeMinRows="1" cdkAutosizeMaxRows="15"></textarea>
</mat-form-field>
<button mat-raised-button type="button" (click)="deleteFilter()" color="warn">
<mat-icon>delete</mat-icon>
</button>
</div>
<mat-checkbox *ngIf="!isFilterEmpty()" [checked]="inverted" (change)="invertFilter()">Invert</mat-checkbox>
</mat-card-content>
</mat-card>
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
.form-field
width: 100%

.filter-node
border: 1px solid #7b7b7b

.filter-node, .filter-node > mat-card-content
display: flex
flex-direction: column
justify-content: flex-start
gap: 10px
font-size: 12px

.filter-leaf
padding: 8px 16px

::ng-deep .mat-card-header-text
margin: 0px

.config-header
width: 100%
display: flex
flex-direction: row
justify-content: space-between

.config-header-buttons
display: flex
flex-direction: row
justify-content: flex-end
gap: 10px

.config-header > mat-button-toggle-group
height: 32px

.config-header > mat-button-toggle-group > mat-button-toggle
height: 32px
font-size: 12px
display: flex
align-items: center

.config-filter
display: flex
flex-direction: row
justify-content: space-between
align-items: center
gap: 10px
font-size: 12px

::ng-deep .config-filter > mat-form-field > .mat-form-field-wrapper
margin: 0px
padding: 0px
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { PluginFilterNodeComponent } from './plugin-filter-node.component';

describe('PluginFilterNodeComponent', () => {
let component: PluginFilterNodeComponent;
let fixture: ComponentFixture<PluginFilterNodeComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PluginFilterNodeComponent ]
})
.compileComponents();

fixture = TestBed.createComponent(PluginFilterNodeComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
Loading

0 comments on commit 01bf1bb

Please sign in to comment.