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/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.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 @@
+
+
-
-
-
+
+
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..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,9 +1,8 @@
-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';
import {map, startWith} from 'rxjs/operators';
-import { ActivatedRoute } from '@angular/router';
@Component({
selector: 'app-template-search',
@@ -12,24 +11,40 @@ 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"];
- }
+ // When in model detail page, allow init of evaluation.
+ @Input() evaulateModelId: number | undefined;
+ @Output() openEvaulateEvent = new EventEmitter();
- private _filterTemplatesByName(value: string): Template[] {
- const filterValue = value.toLowerCase();
+ constructor(private TemplateSearchService: TemplateSearchService){}
- return this.templates.filter(template => template.name.toLocaleLowerCase().includes(filterValue))
+ openEvaluateTemplate(template_id: number): void {
+ this.openEvaulateEvent.emit(template_id);
}
- ngOnInit() {
+ 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.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()
- }
-}
diff --git a/buildingmotif/api/views/template.py b/buildingmotif/api/views/template.py
index 903cb372d..86350aa83 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.rstrip("/") + "/" + v for k, v in bindings.items()}
+
# parse bindings from input JSON
- bindings = get_bindings(request.get_json())
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