projects/web-mev/src/app/features/analysis/components/operation/operation.component.ts
Operation Component used for displaying the input parameters of an operation
| changeDetection | ChangeDetectionStrategy.Default |
| selector | mev-operation |
| styleUrls | ./operation.component.scss |
| templateUrl | ./operation.component.html |
Properties |
Methods |
Inputs |
Outputs |
Accessors |
constructor(formBuilder: FormBuilder, apiService: AnalysesService, metadataService: MetadataService, dialog: MatDialog)
|
|||||||||||||||
|
Parameters :
|
| operation | |
Type : Operation
|
|
| workspace$ | |
Type : Observable<Workspace>
|
|
| workspaceId | |
Type : string
|
|
| executedOperationId | |
Type : EventEmitter<any>
|
|
| createForm | ||||
createForm(data)
|
||||
|
Configure the control setting for different types of input operation parameters The supported types are ObservationSet, FeatureSet, DataResource, BoundedFloat, Integer, Positive Integer, String, OptionString
Parameters :
Returns :
void
|
| loadData |
loadData()
|
|
Load operation data before creating form controls
Returns :
void
|
| ngOnChanges |
ngOnChanges()
|
|
Returns :
void
|
| onSubmit |
onSubmit()
|
|
Returns :
void
|
| showPlots |
showPlots()
|
|
Function is triggered when the user clicks the Show button to visualize normalization results
Returns :
void
|
| startAnalysis |
startAnalysis()
|
|
Function is triggered when the user clicks the Run button to start analysis
Returns :
void
|
| analysesForm |
Type : FormGroup
|
| booleanFields |
Type : []
|
Default value : []
|
| boxPlotData |
| Public dialog |
Type : MatDialog
|
| featureFields |
Type : []
|
Default value : []
|
| intFields |
Type : []
|
Default value : []
|
| multipleResourceFields |
Type : []
|
Default value : []
|
| multipleResourcesDropdownSettings |
Type : object
|
Default value : {}
|
| numFields |
Type : []
|
Default value : []
|
| observationFields |
Type : []
|
Default value : []
|
| optionFields |
Type : []
|
Default value : []
|
| resourceFields |
Type : []
|
Default value : []
|
| submitted |
Default value : false
|
| textFields |
Type : []
|
Default value : []
|
| f |
getf()
|
|
Convenience getter for easy access to form fields |
import {
Component,
ChangeDetectionStrategy,
Input,
EventEmitter,
Output,
OnChanges
} from '@angular/core';
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
import { AnalysesService } from '../../services/analysis.service';
import { Workspace } from '@app/features/workspace-manager/models/workspace';
import { Observable } from 'rxjs';
import { Operation } from '../../models/operation';
import { MetadataService } from '@app/core/metadata/metadata.service';
import { AnalysisPlottingResultComponent } from '../analysis-plotting-result/analysis-plotting-result.component';
import { MatDialog } from '@angular/material/dialog';
/**
* Operation Component
* used for displaying the input parameters of an operation
*/
@Component({
selector: 'mev-operation',
templateUrl: './operation.component.html',
styleUrls: ['./operation.component.scss'],
changeDetection: ChangeDetectionStrategy.Default
})
export class OperationComponent implements OnChanges {
analysesForm: FormGroup;
submitted = false;
multipleResourcesDropdownSettings = {};
@Input() workspaceId: string;
@Input() workspace$: Observable<Workspace>;
@Input() operation: Operation;
@Output() executedOperationId: EventEmitter<any> = new EventEmitter<any>();
// default settings for analyses fields
numFields = [];
intFields = [];
resourceFields = [];
multipleResourceFields = [];
textFields = [];
optionFields = [];
booleanFields = [];
observationFields = [];
featureFields = [];
boxPlotData;
constructor(
private formBuilder: FormBuilder,
private apiService: AnalysesService,
private metadataService: MetadataService,
public dialog: MatDialog
) {
this.multipleResourcesDropdownSettings = {
text: '',
selectAllText: 'Select All',
unSelectAllText: 'Unselect All',
classes: 'resource-dropdown'
};
}
ngOnChanges(): void {
this.loadData();
}
/**
* Configure the control setting for different types of input operation parameters
* The supported types are ObservationSet, FeatureSet, DataResource, BoundedFloat, Integer,
* Positive Integer, String, OptionString
*/
createForm(data) {
const inputs = data.inputs;
const controlsConfig = {};
this.numFields = [];
this.intFields = [];
this.resourceFields = [];
this.multipleResourceFields = [];
this.textFields = [];
this.booleanFields = [];
this.optionFields = [];
this.observationFields = [];
this.featureFields = [];
for (const key in inputs) {
if (inputs.hasOwnProperty(key)) {
const input = inputs[key];
const field_type = input.spec.attribute_type;
switch (field_type) {
case 'ObservationSet': {
const observationField = {
key: key,
name: input.name,
desc: input.description,
required: input.required,
sets: []
};
const availableObsSets = this.metadataService.getCustomObservationSets();
observationField.sets = availableObsSets.map(set => {
const newSet = set.elements.map(elem => {
const o = { id: elem.id };
return o;
});
return { ...set, elements: newSet };
});
this.observationFields.push(observationField);
const configObservationSetsField = [
'',
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configObservationSetsField;
break;
}
case 'FeatureSet': {
const featureField = {
key: key,
name: input.name,
desc: input.description,
required: input.required,
sets: []
};
const availableFeatSets = this.metadataService.getCustomFeatureSets();
featureField.sets = availableFeatSets.map(set => {
const newSet = set.elements.map(elem => {
const o = { id: elem.id };
return o;
});
return { ...set, elements: newSet };
});
this.featureFields.push(featureField);
const configFeatureSetsField = [
'',
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configFeatureSetsField;
break;
}
case 'DataResource': {
const resourceField = {
key: key,
name: input.name,
resource_types: input.spec.resource_types,
desc: input.description,
required: input.required,
files: [],
selectedFiles: []
};
this.apiService
.getAvailableResourcesByParam(
input.spec.resource_types,
this.workspaceId
)
.subscribe(data => {
resourceField.files = data;
if (input.spec.many === false) {
this.resourceFields.push(resourceField);
} else {
this.multipleResourceFields.push(resourceField);
}
});
const configResourceField = [
'',
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configResourceField;
break;
}
case 'BoundedFloat': {
const numField = {
key: key,
name: input.name,
min: input.spec.min,
max: input.spec.max,
desc: input.description,
required: input.required
};
this.numFields.push(numField);
const configNumField = [
input.spec.default_value,
[
...(input.required ? [Validators.required] : []),
Validators.min(input.spec.min),
Validators.max(input.spec.max),
Validators.pattern(/^-?\d*(\.\d+)?$/)
]
];
controlsConfig[key] = configNumField;
break;
}
case 'Integer': {
const intField = {
key: key,
name: input.name,
min: 0,
desc: input.description,
required: input.required
};
this.intFields.push(intField);
const configIntField = [
input.spec.default_value,
[
...(input.required ? [Validators.required] : []),
Validators.min(input.spec.min),
Validators.pattern(/^[0-9]\d*$/)
]
];
controlsConfig[key] = configIntField;
break;
}
case 'PositiveInteger': {
const posIntField = {
key: key,
name: input.name,
min: 1,
desc: input.description,
required: input.required
};
this.intFields.push(posIntField);
const configPosIntField = [
input.spec.default_value,
[
...(input.required ? [Validators.required] : []),
Validators.min(input.spec.min),
Validators.pattern(/^[1-9]\d*$/)
]
];
controlsConfig[key] = configPosIntField;
break;
}
case 'String': {
const textField = {
key: key,
name: input.name,
desc: input.description,
required: input.required
};
this.textFields.push(textField);
const configTextField = [
input.spec.default_value,
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configTextField;
break;
}
case 'OptionString': {
const optionField = {
key: key,
name: input.name,
desc: input.description,
required: input.required,
options: input.spec.options,
selectedOptions: []
};
this.optionFields.push(optionField);
const configOptionField = [
input.spec.default_value,
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configOptionField;
break;
}
case 'Boolean': {
const booleanField = {
key: key,
name: input.name,
desc: input.description,
required: input.required
};
this.booleanFields.push(booleanField);
const configBooleanField = [
input.spec.default_value,
[...(input.required ? [Validators.required] : [])]
];
controlsConfig[key] = configBooleanField;
break;
}
default: {
break;
}
}
}
}
controlsConfig['job_name'] = [
'',
[...(this.operation.mode !== 'client' ? [Validators.required] : [])]
];
this.analysesForm = this.formBuilder.group(controlsConfig);
}
/**
* Load operation data before creating form controls
*/
loadData() {
if (this.operation.mode === 'client') {
this.createForm(this.operation);
} else {
this.apiService.getOperation(this.operation.id).subscribe(data => {
this.createForm(data);
});
}
}
onSubmit() {
this.submitted = true;
}
/**
* Function is used to convert strings to floats for numeric values
* before sending the data to execute the operation
*/
convertToFloatObj(obj) {
const res = {};
for (const key in obj) {
res[key] =
isNaN(obj[key]) || typeof obj[key] === 'boolean'
? obj[key]
: parseFloat(obj[key]);
}
return res;
}
/**
* Function is triggered when the user clicks the Run button
* to start analysis
*/
startAnalysis() {
const inputs = this.convertToFloatObj(this.analysesForm.value);
this.apiService
.executeOperation(this.operation.id, this.workspaceId, inputs)
.subscribe(data => {
this.executedOperationId.emit(data.executed_operation_id);
});
}
/**
* Function is triggered when the user clicks the Show button
* to visualize normalization results
*/
showPlots() {
const inputs = this.convertToFloatObj(this.analysesForm.value);
this.boxPlotData = { ...inputs };
this.dialog.open(AnalysisPlottingResultComponent, {
data: {
boxPlotData: { ...inputs }
}
});
}
/**
* Convenience getter for easy access to form fields
*/
get f() {
return this.analysesForm.controls;
}
}
<h3>{{ operation.name }}</h3>
<p>{{ operation.description }} </p>
<form *ngIf="analysesForm" [formGroup]="analysesForm" (ngSubmit)="onSubmit()">
<!-- OPERATION NAME -->
<ng-container *ngIf="operation.mode !== 'client'">
<div class="label">
Operation name:
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="Type operation name" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<input matInput formControlName="job_name" name="job_name">
</mat-form-field>
</ng-container>
<!-- SINGLE SELECT LISTS FOR RESOURCES -->
<div *ngFor="let resourceField of resourceFields">
<div class="label">
{{ resourceField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ resourceField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<mat-select [formControlName]="resourceField.key">
<mat-option *ngFor="let file of resourceField.files" [value]="file.id">
{{ file.name }}
</mat-option>
</mat-select>
<div *ngIf="submitted && f[resourceField.key].errors">
<mat-error *ngIf="f[resourceField.key].errors.required">
This field is required
</mat-error>
</div>
</mat-form-field>
</div>
<!-- MULTIPLE SELECT LISTS FOR RESOURCES -->
<div *ngFor="let multipleResourceField of multipleResourceFields">
<div class="form-control form-control--multiselect">
<div class="label">
{{ multipleResourceField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ multipleResourceField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<angular2-multiselect [formControlName]="multipleResourceField.key" [data]="multipleResourceField.files"
[(ngModel)]="multipleResourceField.selectedFiles" [settings]="multipleResourcesDropdownSettings">
<c-item>
<ng-template let-item="item">
<label>{{ item.name }} Type: {{ item.readable_resource_type || 'N/A' }}</label>
</ng-template>
</c-item>
<c-badge>
<ng-template let-item="item">
<label>{{ item.name }}</label>
</ng-template>
</c-badge>
</angular2-multiselect>
</div>
</div>
<!-- NUMERIC FIELDS -->
<div *ngFor="let numField of numFields">
<div class="label">
{{ numField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ numField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<input matInput [formControlName]="numField.key" [name]="numField.key">
<div *ngIf="submitted && f[numField.key].errors">
<mat-error *ngIf="f[numField.key].errors.required">
This field is required
</mat-error>
<mat-error *ngIf="f[numField.key].errors.min || f[numField.key].errors.max || f[numField.key].errors.pattern">
The value should be a number between {{ numField.min }} and {{ numField.max }}.
</mat-error>
</div>
</mat-form-field>
</div>
<!-- INTEGER FIELDS -->
<div *ngFor="let intField of intFields">
<div class="label">
{{ intField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ intField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<input matInput [formControlName]="intField.key" [name]="intField.key">
<div *ngIf="f[intField.key].value && f[intField.key].errors">
<mat-error *ngIf="f[intField.key].errors.required">
This field is required
</mat-error>
<mat-error *ngIf="f[intField.key].errors.min || f[intField.key].errors.max || f[intField.key].errors.pattern">
The value should be an integer greater than or equal to {{ intField.min }}
</mat-error>
</div>
</mat-form-field>
</div>
<!-- TEXT FIELDS -->
<div *ngFor="let textField of textFields">
<div class="label">
{{ textField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ textField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<input matInput [formControlName]="textField.key" [name]="textField.key">
<div *ngIf="submitted && f[textField.key].errors">
<mat-error *ngIf="f[textField.key].errors.required">
This field is required
</mat-error>
</div>
</mat-form-field>
</div>
<!-- SINGLE SELECT LISTS FOR OBSERVATION SETS -->
<div *ngFor="let observationField of observationFields">
<div class="label">
{{ observationField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ observationField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<mat-select [formControlName]="observationField.key">
<mat-option *ngFor="let set of observationField.sets" [value]="set">
{{ set.name }}
</mat-option>
</mat-select>
<div *ngIf="submitted && f[observationField.key].errors">
<mat-error *ngIf="f[observationField.key].errors.required">
This field is required
</mat-error>
</div>
</mat-form-field>
</div>
<!-- SINGLE SELECT LISTS FOR FEATURE SETS -->
<div *ngFor="let featureField of featureFields">
<div class="label">
{{ featureField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ featureField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<mat-select [formControlName]="featureField.key">
<mat-option *ngFor="let set of featureField.sets" [value]="set">
{{ set.name }}
</mat-option>
</mat-select>
<div *ngIf="submitted && f[featureField.key].errors">
<mat-error *ngIf="f[featureField.key].errors.required">
This field is required
</mat-error>
</div>
</mat-form-field>
</div>
<!-- SINGLE SELECT LISTS FOR OPERATION OPTIONS -->
<div *ngFor="let optionField of optionFields">
<div class="label">
{{ optionField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ optionField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</div>
<mat-form-field class="form-control" color="accent">
<mat-select [formControlName]="optionField.key">
<mat-option *ngFor="let option of optionField.options" [value]="option">
{{ option }}
</mat-option>
</mat-select>
<div *ngIf="submitted && f[optionField.key].errors">
<mat-error *ngIf="f[optionField.key].errors.required">
This field is required
</mat-error>
</div>
</mat-form-field>
</div>
<!-- CHECKBOXES FOR BOOLEAN FIELDS -->
<ng-container *ngFor="let booleanField of booleanFields">
<div class="form-control" color="accent">
<mat-checkbox [formControlName]="booleanField.key" [checked]="booleanField.checked">
</mat-checkbox>
<span class="label">
{{ booleanField.name }}
<mat-icon class="analyses-header__icon" matTooltipClass="tooltip" fontSet="material-icons-outlined"
matTooltip="{{ booleanField.desc}}" aria-label="Info tooltip about the field">info
</mat-icon>
</span>
</div>
</ng-container>
<div *ngIf="operation.mode !== 'client'; else plotContainer">
<button mat-raised-button color="accent" type="submit" [disabled]="!analysesForm.valid"
(click)="startAnalysis()">Run
</button>
</div>
<ng-template #plotContainer>
<button mat-raised-button color="accent" [disabled]="!analysesForm.valid" (click)="showPlots()">Show plots
</button>
</ng-template>
</form>
./operation.component.scss
.label {
padding-right: 10px;
font-weight: bolder;
}
.analyses-header__icon {
font-size: 14px;
}
.form-control {
width: 500px;
padding-bottom: 50px;
}
mat-checkbox {
padding-right: 10px;
}