From 83b9c28e5bef0a36ed91ed2dacaf1666ce986726 Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Wed, 8 Feb 2023 16:28:21 -0700 Subject: [PATCH 1/6] Remove template search resolver --- .../src/app/app-routing.module.ts | 3 +- .../template-search.component.html | 33 +++++++++++++------ .../template-search.component.ts | 25 +++++++++----- .../template-search.resolver.ts | 20 ----------- 4 files changed, 40 insertions(+), 41 deletions(-) delete mode 100644 buildingmotif-app/src/app/template-search/template-search.resolver.ts diff --git a/buildingmotif-app/src/app/app-routing.module.ts b/buildingmotif-app/src/app/app-routing.module.ts index 50a5d8745..a0fe720d7 100644 --- a/buildingmotif-app/src/app/app-routing.module.ts +++ b/buildingmotif-app/src/app/app-routing.module.ts @@ -1,7 +1,6 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { TemplateSearchComponent } from '../app/template-search/template-search.component' -import { TemplateSearchResolver } from '../app/template-search/template-search.resolver' import { TemplateDetailComponent } from '../app/template-detail/template-detail.component' import { ModelSearchComponent } from '../app/model-search/model-search.component' import { ModelSearchResolver } from '../app/model-search/model-search.resolver' @@ -16,7 +15,7 @@ const routes: Routes = [ { path: 'templates/:id', component: TemplateDetailComponent }, { path: 'templates/:id/evaluate', component: TemplateEvaluateComponent, resolve: {TemplateEvaluateResolver}}, { path: 'models/:id', component: ModelDetailComponent, resolve: {ModelDetailResolver}}, - { path: 'templates', component: TemplateSearchComponent, resolve: {templateSearch:TemplateSearchResolver}}, + { path: 'templates', component: TemplateSearchComponent}, { path: 'models', component: ModelSearchComponent, resolve: {ModelSearchResolver}}, { path: '', redirectTo: '/templates', pathMatch: 'full' }, ]; diff --git a/buildingmotif-app/src/app/template-search/template-search.component.html b/buildingmotif-app/src/app/template-search/template-search.component.html index 7a97496b2..f2c42870c 100644 --- a/buildingmotif-app/src/app/template-search/template-search.component.html +++ b/buildingmotif-app/src/app/template-search/template-search.component.html @@ -1,6 +1,8 @@
Templates
+ +
- - - {{template.name}} - - - - - - -
\ No newline at end of file + +
+ +
+ + +
no templates
+ + +
+ + + {{template.name}} + + + + + + +
+ diff --git a/buildingmotif-app/src/app/template-search/template-search.component.ts b/buildingmotif-app/src/app/template-search/template-search.component.ts index 0a556e209..8a1794064 100644 --- a/buildingmotif-app/src/app/template-search/template-search.component.ts +++ b/buildingmotif-app/src/app/template-search/template-search.component.ts @@ -3,7 +3,6 @@ import { TemplateSearchService, Template } from './template-search.service'; import {FormControl} from '@angular/forms'; import {Observable} from 'rxjs'; import {map, startWith} from 'rxjs/operators'; -import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'app-template-search', @@ -12,24 +11,32 @@ import { ActivatedRoute } from '@angular/router'; providers: [TemplateSearchService], }) export class TemplateSearchComponent implements OnInit{ - templates: Template[] = []; + error: any = undefined; + templates: Template[] | undefined = undefined; fitlerStringControl = new FormControl(''); filteredTemplates: Observable = new Observable(); - constructor(private route: ActivatedRoute) { - this.templates = this.route.snapshot.data["templateSearch"]; + constructor(private TemplateSearchService: TemplateSearchService){} + + private _setTemplates(data: Template[]): void { + this.templates = data; + this.filteredTemplates = this.fitlerStringControl.valueChanges.pipe( + startWith(''), + map(value => this._filterTemplatesByName(value || '')), + ); } private _filterTemplatesByName(value: string): Template[] { const filterValue = value.toLowerCase(); - + if(this.templates == undefined) return []; return this.templates.filter(template => template.name.toLocaleLowerCase().includes(filterValue)) } ngOnInit() { - this.filteredTemplates = this.fitlerStringControl.valueChanges.pipe( - startWith(''), - map(value => this._filterTemplatesByName(value || '')), - ); + this.TemplateSearchService.getAllTemplates() + .subscribe({ + next: (data: Template[]) => this._setTemplates(data), // success path + error: (error) => this.error = error // error path + }); } } diff --git a/buildingmotif-app/src/app/template-search/template-search.resolver.ts b/buildingmotif-app/src/app/template-search/template-search.resolver.ts deleted file mode 100644 index cd9a2d47b..000000000 --- a/buildingmotif-app/src/app/template-search/template-search.resolver.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { Injectable } from '@angular/core'; -import { - Router, Resolve, - RouterStateSnapshot, - ActivatedRouteSnapshot -} from '@angular/router'; -import { Observable, of } from 'rxjs'; -import { TemplateSearchService, Template } from './template-search.service'; - -@Injectable({ - providedIn: 'root' -}) -export class TemplateSearchResolver implements Resolve { - - constructor(private templateSearchService: TemplateSearchService) {} - - resolve(route: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable { - return this.templateSearchService.getAllTemplates() - } -} From 533e59d5af3095424bf896befbebb3107a939fef Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Thu, 23 Feb 2023 17:30:43 -0700 Subject: [PATCH 2/6] Put template evaulation in Model details --- buildingmotif-app/src/app/app.module.ts | 4 ++ .../model-detail/model-detail.component.html | 7 +-- .../model-detail/model-detail.component.ts | 10 ++++ .../model-validate.component.html | 1 - .../model-validate.component.ts | 2 +- .../template-evaluate-form.component.html | 3 +- .../template-evaluate-result.component.css | 4 +- .../template-evaluate-result.component.html | 20 +++---- .../template-evaluate-result.component.ts | 14 +++-- .../template-evaluate.component.css | 12 ++++- .../template-evaluate.component.html | 38 ++++++++------ .../template-evaluate.component.ts | 52 ++++++++++++------- .../template-evaluate.service.ts | 8 +-- .../template-search.component.html | 2 +- .../template-search.component.ts | 10 +++- buildingmotif/api/views/template.py | 2 +- 16 files changed, 118 insertions(+), 71 deletions(-) diff --git a/buildingmotif-app/src/app/app.module.ts b/buildingmotif-app/src/app/app.module.ts index 001f42f72..ce3787937 100644 --- a/buildingmotif-app/src/app/app.module.ts +++ b/buildingmotif-app/src/app/app.module.ts @@ -36,6 +36,8 @@ import { LibraryService } from './library/library.service'; import {MatSidenavModule} from '@angular/material/sidenav'; import {MatTabsModule} from '@angular/material/tabs'; import {MatCheckboxModule} from '@angular/material/checkbox'; +import {MatDialogModule} from '@angular/material/dialog'; +import {MatStepperModule} from '@angular/material/stepper'; @NgModule({ declarations: [ @@ -76,6 +78,8 @@ import {MatCheckboxModule} from '@angular/material/checkbox'; MatSidenavModule, MatTabsModule, MatCheckboxModule, + MatDialogModule, + MatStepperModule, ], providers: [TemplateDetailService, LibraryService], bootstrap: [AppComponent] diff --git a/buildingmotif-app/src/app/model-detail/model-detail.component.html b/buildingmotif-app/src/app/model-detail/model-detail.component.html index 9074afa49..578858b3c 100644 --- a/buildingmotif-app/src/app/model-detail/model-detail.component.html +++ b/buildingmotif-app/src/app/model-detail/model-detail.component.html @@ -22,10 +22,11 @@ - Content 1 - Content 2 + + + - + diff --git a/buildingmotif-app/src/app/model-detail/model-detail.component.ts b/buildingmotif-app/src/app/model-detail/model-detail.component.ts index 4217bf957..68161a6cd 100644 --- a/buildingmotif-app/src/app/model-detail/model-detail.component.ts +++ b/buildingmotif-app/src/app/model-detail/model-detail.component.ts @@ -8,6 +8,8 @@ import { MatSnackBarHorizontalPosition, MatSnackBarVerticalPosition, } from '@angular/material/snack-bar'; +import {MatDialog} from '@angular/material/dialog'; +import {TemplateEvaluateComponent} from '../template-evaluate/template-evaluate.component' @Component({ selector: 'app-model-detail', @@ -37,6 +39,7 @@ export class ModelDetailComponent{ private route: ActivatedRoute, private ModelDetailService: ModelDetailService, private _snackBar: MatSnackBar, + public dialog: MatDialog, ) { [this.model, this.graph] = route.snapshot.data["ModelDetailResolver"]; this.graphFormControl.setValue(this.graph); @@ -63,4 +66,11 @@ export class ModelDetailComponent{ undoChangesToGraph(): void { this.graphFormControl.setValue(this.graph) } + + openEvaulateEvent(templateId: number): void { + this.dialog.open( + TemplateEvaluateComponent, + {data: {templateId, modelId: this.model.id}} + ); + } } diff --git a/buildingmotif-app/src/app/model-validate/model-validate.component.html b/buildingmotif-app/src/app/model-validate/model-validate.component.html index 5df35e89f..3fed57d4a 100644 --- a/buildingmotif-app/src/app/model-validate/model-validate.component.html +++ b/buildingmotif-app/src/app/model-validate/model-validate.component.html @@ -1,6 +1,5 @@
- {{library.name}} diff --git a/buildingmotif-app/src/app/model-validate/model-validate.component.ts b/buildingmotif-app/src/app/model-validate/model-validate.component.ts index e3e22fb13..37359e4da 100644 --- a/buildingmotif-app/src/app/model-validate/model-validate.component.ts +++ b/buildingmotif-app/src/app/model-validate/model-validate.component.ts @@ -70,7 +70,7 @@ export class ModelValidateComponent implements OnInit{ const selectedLibraries = this.libraries.filter((_, i) => this.selectedLibrariesForm.value[i]) const args = selectedLibraries.map(l => l.id); - if (!!this.modelId){ + if (this.modelId !== undefined){ this.showValidatingSpinner = true; this.modelValidateService.validateModel(this.modelId, args).subscribe( diff --git a/buildingmotif-app/src/app/template-evaluate/template-evaluate-form/template-evaluate-form.component.html b/buildingmotif-app/src/app/template-evaluate/template-evaluate-form/template-evaluate-form.component.html index 8193c6533..107e9505f 100644 --- a/buildingmotif-app/src/app/template-evaluate/template-evaluate-form/template-evaluate-form.component.html +++ b/buildingmotif-app/src/app/template-evaluate/template-evaluate-form/template-evaluate-form.component.html @@ -25,6 +25,7 @@ mat-raised-button color="primary" (click)="evaluateClicked()" - [disabled]="parametersForm.invalid">Evaluate + [disabled]="parametersForm.invalid" + matStepperNext>Evaluate
\ No newline at end of file diff --git a/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.css b/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.css index 8c5c76520..74c56145d 100644 --- a/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.css +++ b/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.css @@ -1,5 +1,7 @@ -.addToModel{ +.actions{ display: flex; gap: 1rem; + padding-top: 1rem; + justify-content:flex-end; align-items: baseline; } \ No newline at end of file diff --git a/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.html b/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.html index 66b9d88f1..52100ee3e 100644 --- a/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.html +++ b/buildingmotif-app/src/app/template-evaluate/template-evaluate-result/template-evaluate-result.component.html @@ -1,23 +1,19 @@ + + -
- - - - - {{model.name}} - - - - - +
+ + diff --git a/buildingmotif-app/src/app/template-search/template-search.component.ts b/buildingmotif-app/src/app/template-search/template-search.component.ts index 8a1794064..6491aa412 100644 --- a/buildingmotif-app/src/app/template-search/template-search.component.ts +++ b/buildingmotif-app/src/app/template-search/template-search.component.ts @@ -1,4 +1,4 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter} from '@angular/core'; import { TemplateSearchService, Template } from './template-search.service'; import {FormControl} from '@angular/forms'; import {Observable} from 'rxjs'; @@ -16,8 +16,16 @@ export class TemplateSearchComponent implements OnInit{ fitlerStringControl = new FormControl(''); filteredTemplates: Observable = new Observable(); + // When in model detail page, allow init of evaluation. + @Input() evaulateModelId: number | undefined; + @Output() openEvaulateEvent = new EventEmitter(); + constructor(private TemplateSearchService: TemplateSearchService){} + openEvaluateTemplate(template_id: number): void { + this.openEvaulateEvent.emit(template_id); + } + private _setTemplates(data: Template[]): void { this.templates = data; this.filteredTemplates = this.fitlerStringControl.valueChanges.pipe( diff --git a/buildingmotif/api/views/template.py b/buildingmotif/api/views/template.py index 903cb372d..0d2fb1e3f 100644 --- a/buildingmotif/api/views/template.py +++ b/buildingmotif/api/views/template.py @@ -70,7 +70,7 @@ def evaluate(template_id: int) -> flask.Response: }, status.HTTP_400_BAD_REQUEST # parse bindings from input JSON - bindings = get_bindings(request.get_json()) + bindings = get_bindings(request.get_json()["bindings"]) graph_or_template = template.evaluate(bindings=bindings) if isinstance(graph_or_template, Template): graph = graph_or_template.body From 549e2efaa6d27f3647669e6c6cc902a1af69235a Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Thu, 23 Feb 2023 17:59:28 -0700 Subject: [PATCH 3/6] Update evaulate template endpoint to take in model WIP --- .../template-evaluate.service.ts | 2 +- buildingmotif/api/views/template.py | 17 +++- tests/unit/api/test_template.py | 88 +++++++++++++++++-- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/buildingmotif-app/src/app/template-evaluate/template-evaluate.service.ts b/buildingmotif-app/src/app/template-evaluate/template-evaluate.service.ts index 524409119..30e1f0e0a 100644 --- a/buildingmotif-app/src/app/template-evaluate/template-evaluate.service.ts +++ b/buildingmotif-app/src/app/template-evaluate/template-evaluate.service.ts @@ -26,7 +26,7 @@ export class TemplateEvaluateService { return this.http.post( `http://localhost:5000/templates/${templateId}/evaluate`, - {modelId, bindings}, + {model_id: modelId, bindings}, {responseType: 'text'} ) .pipe( diff --git a/buildingmotif/api/views/template.py b/buildingmotif/api/views/template.py index 0d2fb1e3f..6e83120b1 100644 --- a/buildingmotif/api/views/template.py +++ b/buildingmotif/api/views/template.py @@ -8,7 +8,7 @@ from sqlalchemy.orm.exc import NoResultFound from buildingmotif.api.serializers.template import serialize -from buildingmotif.dataclasses import Template +from buildingmotif.dataclasses import Model, Template blueprint = Blueprint("templates", __name__) @@ -69,8 +69,21 @@ def evaluate(template_id: int) -> flask.Response: "message": "request content type must be json" }, status.HTTP_400_BAD_REQUEST + model_id = request.get_json().get("model_id") + if model_id is None: + return {"message": "body must contain 'model_id'"}, status.HTTP_400_BAD_REQUEST + try: + model = Model.load(model_id) + except NoResultFound: + return {"message": f"No model with id {model_id}"}, status.HTTP_404_NOT_FOUND + + bindings = request.get_json().get("bindings") + if bindings is None: + return {"message": "body must contain 'bindings'"}, status.HTTP_400_BAD_REQUEST + bindings = get_bindings(bindings) + bindings = {k: model.name + "/" + v for k, v in bindings.items()} + # parse bindings from input JSON - bindings = get_bindings(request.get_json()["bindings"]) graph_or_template = template.evaluate(bindings=bindings) if isinstance(graph_or_template, Template): graph = graph_or_template.body diff --git a/tests/unit/api/test_template.py b/tests/unit/api/test_template.py index 254ed7bb7..fa6e9cb2f 100644 --- a/tests/unit/api/test_template.py +++ b/tests/unit/api/test_template.py @@ -1,7 +1,7 @@ from flask_api import status from rdflib import Graph, Namespace -from buildingmotif.dataclasses import Library +from buildingmotif.dataclasses import Library, Model from buildingmotif.namespaces import BRICK, A BLDG = Namespace("urn:building/") @@ -78,6 +78,7 @@ def test_get_template_not_found(client): def test_evaluate(client, building_motif): + model = Model.create(name="urn:my_model") lib = Library.load(directory="tests/unit/fixtures/templates") zone = lib.get_template_by_name("zone") zone.inline_dependencies() @@ -85,12 +86,89 @@ def test_evaluate(client, building_motif): results = client.post( f"/templates/{zone.id}/evaluate", - json={"name": {"@id": BLDG["zone1"]}, "cav": {"@id": BLDG["cav1"]}}, + json={ + "model_id": model.id, + "bindings": {"name": {"@id": BLDG["zone1"]}, "cav": {"@id": BLDG["cav1"]}}, + }, ) assert results.status_code == status.HTTP_200_OK graph = Graph().parse(data=results.data, format="ttl") - assert (BLDG["cav1"], A, BRICK.CAV) in graph - assert (BLDG["zone1"], A, BRICK.HVAC_Zone) in graph - assert (BLDG["zone1"], BRICK.isFedBy, BLDG["cav1"]) in graph + assert (model.name + "/" + BLDG["cav1"], A, BRICK.CAV) in graph + assert (model.name + "/" + BLDG["zone1"], A, BRICK.HVAC_Zone) in graph + assert ( + model.name + "/" + BLDG["zone1"], + BRICK.isFedBy, + model.name + "/" + BLDG["cav1"], + ) in graph assert len(list(graph.triples((None, None, None)))) == 3 + + +def test_evaluate_bad_templated_id(client, building_motif): + model = Model.create(name="urn:my_model") + + results = client.post( + "/templates/-1/evaluate", + json={ + "model_id": model.id, + "bindings": {"name": {"@id": BLDG["zone1"]}, "cav": {"@id": BLDG["cav1"]}}, + }, + ) + + assert results.status_code == 404 + + +def test_evaluate_no_body(client, building_motif): + lib = Library.load(directory="tests/unit/fixtures/templates") + zone = lib.get_template_by_name("zone") + zone.inline_dependencies() + assert zone.parameters == {"name", "cav"} + + results = client.post(f"/templates/{zone.id}/evaluate") + + assert results.status_code == 400 + + +def test_evaluate_bad_body(client, building_motif): + model = Model.create(name="urn:my_model") + lib = Library.load(directory="tests/unit/fixtures/templates") + zone = lib.get_template_by_name("zone") + zone.inline_dependencies() + assert zone.parameters == {"name", "cav"} + + results = client.post( + f"/templates/{zone.id}/evaluate", + json={ + # no model + "bindings": {"name": {"@id": BLDG["zone1"]}, "cav": {"@id": BLDG["cav1"]}}, + }, + ) + + assert results.status_code == 400 + + results = client.post( + f"/templates/{zone.id}/evaluate", + json={ + "model_id": model.id, + # no bindings + }, + ) + + assert results.status_code == 400 + + +def test_evaluate_bad_model_id(client, building_motif): + lib = Library.load(directory="tests/unit/fixtures/templates") + zone = lib.get_template_by_name("zone") + zone.inline_dependencies() + assert zone.parameters == {"name", "cav"} + + results = client.post( + f"/templates/{zone.id}/evaluate", + json={ + "model_id": -1, + "bindings": {"name": {"@id": BLDG["zone1"]}, "cav": {"@id": BLDG["cav1"]}}, + }, + ) + + assert results.status_code == 404 From 3cf2508f900d64fdde74eb491c40fb0940c7f2b3 Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Mon, 17 Apr 2023 11:30:54 -0600 Subject: [PATCH 4/6] Add back in line lost in rebase --- .../src/app/model-validate/model-validate.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/buildingmotif-app/src/app/model-validate/model-validate.component.html b/buildingmotif-app/src/app/model-validate/model-validate.component.html index 3fed57d4a..5df35e89f 100644 --- a/buildingmotif-app/src/app/model-validate/model-validate.component.html +++ b/buildingmotif-app/src/app/model-validate/model-validate.component.html @@ -1,5 +1,6 @@
+ {{library.name}} From 6e4402cb4050e5056605ad41621445b48aa081a4 Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Mon, 17 Apr 2023 11:38:12 -0600 Subject: [PATCH 5/6] Address comments --- .../src/app/template-evaluate/template-evaluate.component.ts | 1 - buildingmotif/api/views/template.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/buildingmotif-app/src/app/template-evaluate/template-evaluate.component.ts b/buildingmotif-app/src/app/template-evaluate/template-evaluate.component.ts index 7937ea8f9..93be18bd8 100644 --- a/buildingmotif-app/src/app/template-evaluate/template-evaluate.component.ts +++ b/buildingmotif-app/src/app/template-evaluate/template-evaluate.component.ts @@ -34,7 +34,6 @@ export class TemplateEvaluateComponent implements OnInit { .subscribe({ next: (template: Template) => { this.template = template - console.log("template-evaluate: init " + this.template.id + " " + this.modelId) }, // success path error: (error) => this.error = error // error path }); diff --git a/buildingmotif/api/views/template.py b/buildingmotif/api/views/template.py index 6e83120b1..27592d6cf 100644 --- a/buildingmotif/api/views/template.py +++ b/buildingmotif/api/views/template.py @@ -81,7 +81,7 @@ def evaluate(template_id: int) -> flask.Response: if bindings is None: return {"message": "body must contain 'bindings'"}, status.HTTP_400_BAD_REQUEST bindings = get_bindings(bindings) - bindings = {k: model.name + "/" + v for k, v in bindings.items()} + bindings = {k: model.name.strip("/") + "/" + v for k, v in bindings.items()} # parse bindings from input JSON graph_or_template = template.evaluate(bindings=bindings) From 1a7ef1e3b22b154812bf5b6e8de619d20fbe8528 Mon Sep 17 00:00:00 2001 From: Hannah Eslinger Date: Mon, 17 Apr 2023 17:54:22 -0600 Subject: [PATCH 6/6] Edit strip --- buildingmotif/api/views/template.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildingmotif/api/views/template.py b/buildingmotif/api/views/template.py index 27592d6cf..86350aa83 100644 --- a/buildingmotif/api/views/template.py +++ b/buildingmotif/api/views/template.py @@ -81,7 +81,7 @@ def evaluate(template_id: int) -> flask.Response: if bindings is None: return {"message": "body must contain 'bindings'"}, status.HTTP_400_BAD_REQUEST bindings = get_bindings(bindings) - bindings = {k: model.name.strip("/") + "/" + v for k, v in bindings.items()} + bindings = {k: model.name.rstrip("/") + "/" + v for k, v in bindings.items()} # parse bindings from input JSON graph_or_template = template.evaluate(bindings=bindings)