Commit a24f57b9 authored by Simone Vuotto's avatar Simone Vuotto

Merge branch 'master' of github.com:SimoV8/ReqV-webapp

parents 957bd1e4 f5c6ca1c
......@@ -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;
}
......@@ -30,6 +30,8 @@ import {AuthGuard} from './auth.guard';
// Http Interceptor
import { JwtInterceptor } from './jwt.interceptor.js';
import { PatternWizardComponent } from './pattern-wizard/pattern-wizard.component';
import { ExpressionWizardComponent } from './pattern-wizard/expression-wizard/expression-wizard.component';
@NgModule({
......@@ -43,6 +45,8 @@ import { JwtInterceptor } from './jwt.interceptor.js';
RequirementDetailsComponent,
RequirementsTabComponent,
TasksTabComponent,
PatternWizardComponent,
ExpressionWizardComponent,
],
imports: [
BrowserModule,
......
......@@ -4,8 +4,9 @@
<i class='fa fa-spinner fa-spin' *ngIf="translateLoading"></i>
&nbsp;Translate
</button>
<button type="button" class="btn btn-primary pull-right" [disabled]="!taskIsRunning" (click)="consistencyCheck()">
<i class="fa fa-play" aria-hidden="true"></i>
<button type="button" class="btn btn-primary pull-right" [disabled]="taskIsRunning" (click)="consistencyCheck()">
<i class="fa fa-play" aria-hidden="true" *ngIf="!validateLoading"></i>
<i class='fa fa-spinner fa-spin' *ngIf="validateLoading"></i>
&nbsp;Validate
</button>
</div>
......@@ -26,9 +27,12 @@
<div class="card-text">
<div>
<label >Logs:</label>
<textarea class="form-control" readonly="readonly">{{task.log}}</textarea>
<textarea class="form-control" readonly="readonly" rows="5">{{task.log}}</textarea>
</div>
<div *ngIf="task.id === tasks[0].id && task.fail" style="padding-top: 10px">
<a href="javascript:void(0);" (click)="computeMUC();">Click here</a> if you want to search for the minimum unsatisfiable core of
requirements (Warning: it may take a long time).
</div>
</div>
</div>
</div>
......
......@@ -15,6 +15,7 @@ export class TasksTabComponent implements OnInit {
tasks: Task[];
translateLoading = false;
validateLoading = false;
taskIsRunning = false;
constructor(private taskService: TaskService,
......@@ -28,7 +29,7 @@ export class TasksTabComponent implements OnInit {
this.taskService.getTasks(this.projectId).subscribe(
tasks => {
this.tasks = tasks.map(t => new Task(t));
this.taskIsRunning = this.tasks.every(t => !t.running);
this.taskIsRunning = !this.tasks.every(t => !t.running);
this.tasks.forEach(task => {
if (task.running) {
this.checkRunningTask(task);
......@@ -55,28 +56,57 @@ export class TasksTabComponent implements OnInit {
consistencyCheck() {
this.taskIsRunning = true;
this.validateLoading = true;
this.taskService.performConsistencyCheck(this.projectId).subscribe(
task => {
this.tasks.unshift(new Task(task));
this.checkRunningTask(task);
this.validateLoading = false;
},
error => {
this.alertService.error(error.message);
this.taskIsRunning = false;
this.validateLoading = false;
}
);
}
checkRunningTask(task: Task) {
checkRunningTask(task: Task, timeout = 2500) {
setTimeout(() => {
this.taskService.getTask(this.projectId, task.id).subscribe(
t => {
task = new Task(t);
response => {
task = new Task(response);
const index = this.tasks.findIndex( t => t.id === task.id);
this.tasks[index] = task;
if (task.running) {
this.checkRunningTask(task);
this.checkRunningTask(task, timeout);
} else {
const index = this.tasks.findIndex( x => x.id === task.id);
this.tasks[index] = task;
this.taskIsRunning = false;
}
},
error => {
console.log(error);
this.taskIsRunning = false;
}
);
}, 2500);
}, timeout);
}
computeMUC() {
this.taskIsRunning = true;
this.validateLoading = true;
this.taskService.performComputeMuc(this.projectId).subscribe(
task => {
this.tasks.unshift(new Task(task));
this.checkRunningTask(task, 5000);
this.validateLoading = false;
},
error => {
this.alertService.error(error.message);
this.taskIsRunning = false;
this.validateLoading = false;
}
);
}
}
......@@ -20,9 +20,6 @@
<p class="card-text">{{ project.description }}</p>
<p class="card-text"><b>Type:</b> {{ project.type.name }}</p>
</div>
<div class="card-footer text-right">
<a href="#" class="card-link text-primary">Edit</a>
</div>
</div>
</div>
</div>
......
......@@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders, HttpResponse } from '@angular/common/http';
import { Observable, of } from 'rxjs';
import { ProjectType, Project } from '../models/project';
import { catchError } from 'rxjs/operators';
import { catchError, tap } from 'rxjs/operators';
const httpOptions = {
headers: new HttpHeaders({ 'Content-Type': 'application/json' })
......@@ -15,13 +15,15 @@ export class ProjectService {
private projectsUrl = 'api/projects';
private projectTypesUrl = 'api/projects/types';
private projects: Project[];
constructor(private http: HttpClient) { }
/** GET project types */
getProjectTypes(): Observable<ProjectType[]> {
return this.http.get<ProjectType[]>(this.projectTypesUrl, httpOptions)
.pipe(
catchError(this.handleError('getProjectsType', []))
catchError(this.handleError('getProjectsType', [])),
);
}
......@@ -29,12 +31,21 @@ export class ProjectService {
getProjects(): Observable<Project[]> {
return this.http.get<Project[]>(this.projectsUrl, httpOptions)
.pipe(
catchError(this.handleError('getProjects', []))
catchError(this.handleError('getProjects', [])),
tap(projects => this.projects = projects),
);
}
/** GET project */
getProject(id): Observable<Project> {
if (this.projects != null) {
const project = this.projects.find(prj => prj.id = id);
if (project) {
return of(project);
}
}
return this.http.get<Project>(this.projectsUrl + '/' + id, httpOptions)
.pipe(
catchError(this.handleError('getProjects', null))
......
......@@ -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 {
......
<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);
});
}
}
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