Commit a651f523 authored by Simone Vuotto's avatar Simone Vuotto

Add pattern creation wizard

parent 8b447131
......@@ -4,12 +4,14 @@ import {LoginComponent} from './login/login.component';
import {ProjectsComponent} from './projects/projects.component';
import {AuthGuard} from './auth.guard';
import { ProjectDetailsComponent } from './project-details/project-details.component';
import { PatternWizardComponent } from './pattern-wizard/pattern-wizard.component';
const routes: Routes = [
{ path: '', component: LoginComponent },
{ path: 'projects', component: ProjectsComponent, canActivate: [AuthGuard] },
{ path: 'projects/:projectId', component: ProjectDetailsComponent, canActivate: [AuthGuard]},
{ path: 'projects/:projectId/addReq', component: PatternWizardComponent, canActivate: [AuthGuard]},
// otherwise redirect to home
{ path: '**', redirectTo: '' }
......
......@@ -2,7 +2,4 @@
padding: 4em 2em;
}
.hline {
border-bottom: 1px solid #373a3c;
margin-bottom: 0.5em;
}
<div class="pl-2" style="border-left: 1px solid #eeeeee; height: 100%;">
<p>{{item.text}}</p>
<div>
<fieldset *ngFor="let expr of exprs; let i = index; trackBy: customTrackBy">
<label class="form-control-label" for="exprInput">Expr {{i + 1}}</label>
<input class="form-control" id="exprInput" [(ngModel)]="exprs[i]" (keyup)="update()">
</fieldset>
</div>
</div>
import { Component, Input, OnInit, Output } from '@angular/core';
import { Item } from '../item';
import { EventEmitter } from '@angular/core';
@Component({
selector: 'app-expression-wizard',
templateUrl: './expression-wizard.component.html',
styleUrls: ['./expression-wizard.component.css']
})
export class ExpressionWizardComponent implements OnInit {
_item: Item;
@Output() itemChanged = new EventEmitter<Item>();
exprs: string[];
constructor() { }
ngOnInit() {
}
get item(): Item {
return this._item;
}
@Input()
set item(item: Item) {
this.exprs = new Array(item.expressions).map((value, index) => 'exp' + index);
this._item = item;
this.update();
console.log(this.exprs);
}
update() {
// this.exprs[index] = event.target.value;
this.item.formatText(this.exprs);
this.itemChanged.emit(this._item);
}
customTrackBy(index: number, obj: any): any {
return index;
}
}
export class Item {
name: string;
expressions: number;
template: string;
description: string;
text: string;
public constructor(name: string, expressions: number, template: string, description: string) {
this.name = name;
this.expressions = expressions;
this.template = template;
this.description = description;
this.text = template;
}
formatText(expr: string[]) {
this.text = this.template.replace(/{(\d+)}/g, function(match, number) {
return typeof expr[number] !== 'undefined' ? expr[number] : match;
});
return this.text;
}
}
<h1>New Requirement</h1>
<ol class="breadcrumb mb-4">
<li class="breadcrumb-item"><a href="#">Projects</a></li>
<li class="breadcrumb-item"><a routerLink="/projects/{{project.id}}">{{ project.name }}</a></li>
<li class="breadcrumb-item active">New Requirement</li>
</ol>
<div class="">
<div class="form-group row pb-2">
<label class="col-form-label col-sm-2 text-right" for="reqText">Req ID</label>
<div class="col-sm-10">
<input class="form-control w-auto" type="text" id="reqId" (change)="updateReqId($event)">
</div>
</div>
<div class="form-group row">
<label class="col-form-label col-sm-2 text-right" for="reqText">Requirement</label>
<div class="col-sm-10 pr-5">
<input class="form-control w-100" type="text" id="reqText" readonly="" data-cip-id="readOnlyInput"
value="{{req}}">
</div>
</div>
</div>
<div class="row equal">
<div class="col-md-6 pl-3 pr-1">
<div class="card bg-secondary h-100">
<div class="card-body">
<h4 class="card-title">Scope
<small><i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="The scope defines when the body of the pattern should hold."></i></small>
</h4>
<div class="row">
<div class="col-md-4 pr-0">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle w-100" type="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{selectedScope.name}}
</button>
<ul dropdownMenu class="dropdown-menu" role="menu">
<li *ngFor="let scope of scopes" (click)="selectScope(scope)"
class="dropdown-item">{{scope.name}}
</li>
</ul>
</div>
<div class="mt-2">
<small class="text-justify "><p class="text-muted">{{selectedScope.description}}</p></small>
</div>
</div>
<div class="col-md-8">
<app-expression-wizard [item]="selectedScope" (itemChanged)="updateRequirement($event)"></app-expression-wizard>
</div>
</div>
</div>
</div>
</div>
<div class="col-md-6 pl-1 pr-3">
<div class="card bg-secondary h-100">
<div class="card-body">
<h4 class="card-title">Pattern
<small><i class="fa fa-info-circle" data-toggle="tooltip" data-placement="right"
title="The main part of the specification."></i></small>
</h4>
<div class="row">
<div class="col-md-4 pr-0">
<div class="dropdown">
<button class="btn btn-secondary dropdown-toggle w-100" type="button"
data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
{{selectedPattern.name}}
</button>
<ul dropdownMenu class="dropdown-menu" role="menu">
<li *ngFor="let pattern of patterns" (click)="selectPattern(pattern)"
class="dropdown-item">{{pattern.name}}
</li>
</ul>
</div>
<div class="mt-2">
<small class="text-justify"><p class="text-muted">{{selectedPattern.description}}</p></small>
</div>
</div>
<div class="col-md-8">
<app-expression-wizard [item]="selectedPattern" (itemChanged)="updateRequirement($event)"></app-expression-wizard>
</div>
</div>
</div>
</div>
</div>
</div>
<div class="pt-3 pr-1">
<button class="btn btn-primary float-right" (click)="createRequirement()">Create</button>
</div>
import { Component, OnInit } from '@angular/core';
import { Item } from './item';
import { ProjectService } from '../services/project.service';
import { ActivatedRoute, Router } from '@angular/router';
import { Project } from '../models/project';
import { RequirementService } from '../services/requirement.service';
import { Requirement } from '../models/requirement';
@Component({
selector: 'app-pattern-wizard',
templateUrl: './pattern-wizard.component.html',
styleUrls: ['./pattern-wizard.component.css']
})
export class PatternWizardComponent implements OnInit {
scopes: Array<Item> = [
new Item('Globally', 0, 'Globally', 'The pattern must always hold'),
new Item('Before R', 1, 'Before {0}', 'The pattern must holds before the event R happens'),
new Item('After Q', 1, 'After {0}', ''),
new Item('Between Q and R', 2, 'Between {0} and {1}', 'The pattern must holds between events Q and R'),
new Item('After Q until R', 2, 'After {0} until {1}', 'The patter must holds after event Q and until event R')
];
patterns: Item[] = [
new Item('Universality', 1, 'it is always the case that {0} holds',
'This pattern describe a portion of a system\'s execution which contains only states that have a desired property.'),
new Item( 'Absence', 1, ' it is never the case that {0} holds',
'This pattern describes a portion of a system\'s execution that is free of certain events or states.'),
new Item('Existence', 1, '{0} eventually holds',
'This pattern describes a portion of a system\'s execution that contains an instance of certain events or states. '),
new Item('Invariance', 2, 'it is always the case that if {0} holds, then {1} holds as well',
'This pattern describes an invariance relation between two events or states.'),
new Item('Precedence', 2, 'it is always the case that if {0} holds, then {1} previously held',
'This pattern describes relationships between a pair of events/states P and S where the occurrence of P' +
' is a necessary pre-condition for an occurrence of S.'),
new Item('PrecedenceChain12', 3,
'it is always the case that if {0} holds and is succeeded by {1}, then {2} previously held',
'This pattern describes relationships between an event/state as a necessary pre-condition for an occurrence' +
' of a chain of events/states'),
new Item('PrecedenceChain21', 3,
'it is always the case that if {0} holds, then {1} previously held and was preceded by {2}',
'This pattern describes relationships between a chain of events/states as a necessary pre-condition for ' +
'an occurrence of a event/state'),
new Item('Response', 2, 'it is always the case that if {0} holds, then {1} eventually holds',
'This pattern describes cause-effect relationships between a pair of events/states. An occurrence of the' +
' first must be followed by an occurrence of the second.'),
new Item('ResponseChain12', 3, 'it is always the case that if {0} holds, then {1} eventually holds and is succeeded by {2}',
'This pattern describes cause-effect relationships between events/states. An occurrence of the first ' +
'must be followed by a chain of events/states.'),
new Item('ResponseChain21', 3, 'it is always the case that if {0} holds and is succeeded by {1}, then {2} eventually holds after {1}',
'This pattern describes cause-effect relationships between events/states. An occurrence of a chain of ' +
'events/states must be followed by an occurrence of the latter state/event.'),
];
selectedScope: Item = null;
selectedPattern: Item = null;
reqId: string = null;
req = '';
project = new Project(null, null, null, null);
constructor(private route: ActivatedRoute,
private router: Router,
private projectService: ProjectService,
private requirementService: RequirementService) { }
ngOnInit() {
this.selectedScope = this.scopes[0];
this.selectedPattern = this.patterns[0];
this.getProject();
}
getProject() {
const projectId = +this.route.snapshot.paramMap.get('projectId');
this.projectService.getProject(projectId).subscribe(project => this.project = project);
}
selectScope(selected: Item) {
this.selectedScope = selected;
}
selectPattern(selected: Item) {
this.selectedPattern = selected;
}
updateReqId(event) {
this.reqId = event.target.value;
this.updateRequirement(null);
}
updateRequirement(item) {
let reqIdLabel = '';
if (this.reqId != null && this.reqId.length > 0) {
reqIdLabel = '[ReqId=' + this.reqId + '] ';
}
this.req = reqIdLabel + this.selectedScope.text + ', ' + this.selectedPattern.text + '.';
}
createRequirement() {
const requirement = new Requirement();
requirement.text = this.req;
requirement.project = this.project.id;
requirement.disabled = false;
this.requirementService.createRequirement(requirement).subscribe(req => {
this.router.navigateByUrl('/projects/' + this.project.id);
});
}
}
......@@ -20,6 +20,10 @@ export class RequirementService {
return this.http.get<Requirement[]>(this.requirementsUrl, {params: params});
}
createRequirement(req: Requirement): Observable<Requirement> {
return this.http.post<Requirement>(this.requirementsUrl, req);
}
updateRequirement(req: Requirement): Observable<Requirement> {
return this.http.put<Requirement>(this.requirementsUrl, req);
}
......
......@@ -8,6 +8,10 @@
opacity: .5;
}
.hline {
border-bottom: 1px solid #373a3c;
margin-bottom: 0.5em;
}
.row-green {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment