fixed tests and feedback

This commit is contained in:
lotte
2020-03-25 18:22:20 +01:00
committed by Art Lowel
parent ef3a235178
commit e7a5daad18
29 changed files with 248 additions and 57 deletions

View File

@@ -1616,6 +1616,8 @@
"process.new.select-parameters": "Parameters", "process.new.select-parameters": "Parameters",
"process.new.cancel": "Cancel",
"process.new.submit": "Submit", "process.new.submit": "Submit",
"process.new.select-script": "Script", "process.new.select-script": "Script",
@@ -1630,6 +1632,24 @@
"process.new.parameter.string.required": "Parameter value is required", "process.new.parameter.string.required": "Parameter value is required",
"process.new.parameter.type.value": "value",
"process.new.parameter.type.file": "file",
"process.new.parameter.required.missing": "The following parameters are required but still missing:",
"process.new.notification.success.title": "Success",
"process.new.notification.success.content": "The process was successfully created",
"process.new.notification.error.title": "Error",
"process.new.notification.error.content": "An error occurred while creating this process",
"process.new.header": "Create a new process",
"process.new.title": "Create a new process",
"publication.listelement.badge": "Publication", "publication.listelement.badge": "Publication",

View File

@@ -10,10 +10,12 @@ import { HttpClient } from '@angular/common/http';
import { DefaultChangeAnalyzer } from '../default-change-analyzer.service'; import { DefaultChangeAnalyzer } from '../default-change-analyzer.service';
import { Script } from '../../../process-page/scripts/script.model'; import { Script } from '../../../process-page/scripts/script.model';
import { ProcessParameter } from '../../../process-page/processes/process-parameter.model'; import { ProcessParameter } from '../../../process-page/processes/process-parameter.model';
import { map } from 'rxjs/operators'; import { find, map, switchMap, tap } from 'rxjs/operators';
import { URLCombiner } from '../../url-combiner/url-combiner'; import { URLCombiner } from '../../url-combiner/url-combiner';
import { MultipartPostRequest, RestRequest } from '../request.models'; import { MultipartPostRequest, RestRequest } from '../request.models';
import { RequestService } from '../request.service'; import { RequestService } from '../request.service';
import { Observable } from 'rxjs';
import { RequestEntry } from '../request.reducer';
@Injectable() @Injectable()
export class ScriptDataService extends DataService<Script> { export class ScriptDataService extends DataService<Script> {
@@ -31,15 +33,18 @@ export class ScriptDataService extends DataService<Script> {
super(); super();
} }
public invoke(scriptName: string, parameters: ProcessParameter[], files: File[]) { public invoke(scriptName: string, parameters: ProcessParameter[], files: File[]): Observable<RequestEntry> {
this.getBrowseEndpoint().pipe( const requestId = this.requestService.generateRequestId();
return this.getBrowseEndpoint().pipe(
map((endpoint: string) => new URLCombiner(endpoint, scriptName, 'processes').toString()), map((endpoint: string) => new URLCombiner(endpoint, scriptName, 'processes').toString()),
map((endpoint: string) => { map((endpoint: string) => {
const body = this.getInvocationFormData(parameters, files); const body = this.getInvocationFormData(parameters, files);
return new MultipartPostRequest(this.requestService.generateRequestId(), endpoint, body) return new MultipartPostRequest(requestId, endpoint, body)
}), }),
map((request: RestRequest) => this.requestService.configure(request)) map((request: RestRequest) => this.requestService.configure(request)),
).subscribe(); switchMap(() => this.requestService.getByUUID(requestId)),
find((request: RequestEntry) => request.completed)
);
} }
private getInvocationFormData(parameters: ProcessParameter[], files: File[]): FormData { private getInvocationFormData(parameters: ProcessParameter[], files: File[]): FormData {

View File

@@ -1,14 +1,25 @@
<div class="container"> <div class="container">
<div class="row"> <div class="row">
<h2 class="col-12">
{{'process.new.header' | translate}}
</h2>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<form #form="ngForm" (ngSubmit)="submitForm(form)"> <form #form="ngForm" (ngSubmit)="submitForm(form)">
<ds-scripts-select (select)="selectedScript = $event"></ds-scripts-select> <ds-scripts-select (select)="selectedScript = $event"></ds-scripts-select>
<ds-process-parameters [script]="selectedScript" (updateParameters)="parameters = $event"></ds-process-parameters> <ds-process-parameters [script]="selectedScript" (updateParameters)="parameters = $event"></ds-process-parameters>
<button [disabled]="form.invalid" type="submit" class="btn btn-light float-right">{{ 'process.new.submit' | translate }}</button> <button [routerLink]="['/processes']" class="btn btn-light float-left">{{ 'process.new.cancel' | translate }}</button>
<button type="submit" class="btn btn-light float-right">{{ 'process.new.submit' | translate }}</button>
</form> </form>
</div> </div>
<div class="col-12 col-md-6"> <div class="col-12 col-md-6">
<ds-script-help [script]="selectedScript"></ds-script-help> <ds-script-help [script]="selectedScript"></ds-script-help>
</div> </div>
</div> </div>
<div *ngIf="missingParameters.length > 0" class="alert alert-danger validation-error">
{{'process.new.parameter.required.missing' | translate}}
<ul>
<li *ngFor="let missing of missingParameters">{{missing}}</li>
</ul>
</div>
</div> </div>

View File

@@ -9,6 +9,9 @@ import { MockTranslateLoader } from '../../shared/testing/mock-translate-loader'
import { ScriptParameter } from '../scripts/script-parameter.model'; import { ScriptParameter } from '../scripts/script-parameter.model';
import { Script } from '../scripts/script.model'; import { Script } from '../scripts/script.model';
import { ProcessParameter } from '../processes/process-parameter.model'; import { ProcessParameter } from '../processes/process-parameter.model';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service-stub';
import { of as observableOf } from 'rxjs';
describe('NewProcessComponent', () => { describe('NewProcessComponent', () => {
let component: NewProcessComponent; let component: NewProcessComponent;
@@ -26,7 +29,17 @@ describe('NewProcessComponent', () => {
Object.assign(new ProcessParameter(), { name: '-b', value: '123' }), Object.assign(new ProcessParameter(), { name: '-b', value: '123' }),
Object.assign(new ProcessParameter(), { name: '-c', value: 'value' }), Object.assign(new ProcessParameter(), { name: '-c', value: 'value' }),
]; ];
scriptService = jasmine.createSpyObj('scriptService', ['invoke']) scriptService = jasmine.createSpyObj(
'scriptService',
{
invoke: observableOf({
response:
{
isSuccessful: true
}
})
}
)
} }
beforeEach(async(() => { beforeEach(async(() => {
@@ -43,6 +56,7 @@ describe('NewProcessComponent', () => {
declarations: [NewProcessComponent], declarations: [NewProcessComponent],
providers: [ providers: [
{ provide: ScriptDataService, useValue: scriptService }, { provide: ScriptDataService, useValue: scriptService },
{ provide: NotificationsService, useValue: NotificationsServiceStub },
], ],
schemas: [NO_ERRORS_SCHEMA] schemas: [NO_ERRORS_SCHEMA]
}) })
@@ -62,7 +76,7 @@ describe('NewProcessComponent', () => {
}); });
it('should call invoke on the scriptService on submit', () => { it('should call invoke on the scriptService on submit', () => {
component.submitForm({ invalid: false }); component.submitForm({ invalid: false } as any);
expect(scriptService.invoke).toHaveBeenCalled(); expect(scriptService.invoke).toHaveBeenCalled();
}); });
}); });

View File

@@ -3,7 +3,13 @@ import { Script } from '../scripts/script.model';
import { Process } from '../processes/process.model'; import { Process } from '../processes/process.model';
import { ProcessParameter } from '../processes/process-parameter.model'; import { ProcessParameter } from '../processes/process-parameter.model';
import { ScriptDataService } from '../../core/data/processes/script-data.service'; import { ScriptDataService } from '../../core/data/processes/script-data.service';
import { NgForm } from '@angular/forms'; import { ControlContainer, NgForm } from '@angular/forms';
import { ScriptParameter } from '../scripts/script-parameter.model';
import { RequestEntry } from '../../core/data/request.reducer';
import { NotificationsService } from '../../shared/notifications/notifications.service';
import { TranslateService } from '@ngx-translate/core';
import { take } from 'rxjs/operators';
import { pipe } from 'rxjs';
/** /**
* Component to create a new script * Component to create a new script
@@ -27,14 +33,19 @@ export class NewProcessComponent implements OnInit {
/** /**
* The parameter values to use to start the process * The parameter values to use to start the process
*/ */
public parameters: ProcessParameter[]; public parameters: ProcessParameter[] = [];
/** /**
* Optional files that are used as parameter values * Optional files that are used as parameter values
*/ */
public files: File[] = []; public files: File[] = [];
constructor(private scriptService: ScriptDataService) { /**
* Contains the missing parameters on submission
*/
public missingParameters = [];
constructor(private scriptService: ScriptDataService, private notificationsService: NotificationsService, private translationService: TranslateService) {
} }
ngOnInit(): void { ngOnInit(): void {
@@ -46,7 +57,7 @@ export class NewProcessComponent implements OnInit {
* @param form * @param form
*/ */
submitForm(form: NgForm) { submitForm(form: NgForm) {
if (!this.validateForm(form)) { if (!this.validateForm(form) || this.isRequiredMissing()) {
return; return;
} }
@@ -58,6 +69,18 @@ export class NewProcessComponent implements OnInit {
} }
); );
this.scriptService.invoke(this.selectedScript.id, stringParameters, this.files) this.scriptService.invoke(this.selectedScript.id, stringParameters, this.files)
.pipe(take(1))
.subscribe((requestEntry: RequestEntry) => {
if (requestEntry.response.isSuccessful) {
const title = this.translationService.get('process.new.notification.success.title');
const content = this.translationService.get('process.new.notification.success.content');
this.notificationsService.success(title, content)
} else {
const title = this.translationService.get('process.new.notification.error.title');
const content = this.translationService.get('process.new.notification.error.content');
this.notificationsService.error(title, content)
}
})
} }
/** /**
@@ -69,7 +92,7 @@ export class NewProcessComponent implements OnInit {
if (typeof processParameter.value === 'object') { if (typeof processParameter.value === 'object') {
this.files = [...this.files, processParameter.value]; this.files = [...this.files, processParameter.value];
return processParameter.value.name; return processParameter.value.name;
} }
return processParameter.value; return processParameter.value;
} }
@@ -88,4 +111,20 @@ export class NewProcessComponent implements OnInit {
} }
return true; return true;
} }
private isRequiredMissing() {
const setParams: string[] = this.parameters
.map((param) => param.name);
const requiredParams: ScriptParameter[] = this.selectedScript.parameters.filter((param) => param.mandatory)
for (const rp of requiredParams) {
if (!setParams.includes(rp.name)) {
this.missingParameters.push(rp.name);
}
}
return this.missingParameters.length > 0;
}
}
export function controlContainerFactory(controlContainer?: ControlContainer) {
return controlContainer;
} }

View File

@@ -1,7 +1,7 @@
<div class="form-row mb-2"> <div class="form-row mb-2">
<select required id="process-parameters" <select id="process-parameters"
class="form-control col" class="form-control col"
name="script" name="parameter-{{index}}"
[(ngModel)]="selectedParameter" [(ngModel)]="selectedParameter"
#param="ngModel"> #param="ngModel">
<option [ngValue]="undefined">Add a parameter...</option> <option [ngValue]="undefined">Add a parameter...</option>
@@ -9,7 +9,7 @@
{{param.nameLong || param.name}} {{param.nameLong || param.name}}
</option> </option>
</select> </select>
<ds-parameter-value-input [parameter]="selectedScriptParameter" (updateValue)="selectedParameterValue = $event" class="d-block col"></ds-parameter-value-input> <ds-parameter-value-input [parameter]="selectedScriptParameter" (updateValue)="selectedParameterValue = $event" class="d-block col" [index]="index"></ds-parameter-value-input>
<button *ngIf="removable" class="btn btn-light col-1 remove-button" (click)="removeParameter.emit(parameterValue);"><span class="fas fa-trash"></span></button> <button *ngIf="removable" class="btn btn-light col-1 remove-button" (click)="removeParameter.emit(parameterValue);"><span class="fas fa-trash"></span></button>
<span *ngIf="!removable" class="col-1"></span> <span *ngIf="!removable" class="col-1"></span>
</div> </div>

View File

@@ -1,7 +1,9 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnInit, Output, Optional } from '@angular/core';
import { ProcessParameter } from '../../../processes/process-parameter.model'; import { ProcessParameter } from '../../../processes/process-parameter.model';
import { ScriptParameter } from '../../../scripts/script-parameter.model'; import { ScriptParameter } from '../../../scripts/script-parameter.model';
import { hasNoValue } from '../../../../shared/empty.util'; import { hasNoValue } from '../../../../shared/empty.util';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../new-process.component';
/** /**
* Component to select a single parameter for a process * Component to select a single parameter for a process
@@ -9,13 +11,19 @@ import { hasNoValue } from '../../../../shared/empty.util';
@Component({ @Component({
selector: 'ds-parameter-select', selector: 'ds-parameter-select',
templateUrl: './parameter-select.component.html', templateUrl: './parameter-select.component.html',
styleUrls: ['./parameter-select.component.scss'] styleUrls: ['./parameter-select.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class ParameterSelectComponent implements OnInit { export class ParameterSelectComponent implements OnInit {
@Input() index: number;
/** /**
* The current parameter value of the selected parameter * The current parameter value of the selected parameter
*/ */
@Input() parameterValue: ProcessParameter; @Input() parameterValue: ProcessParameter;
/** /**
* The available script parameters for the script * The available script parameters for the script
*/ */

View File

@@ -1 +1 @@
<input type="hidden" value="true"/> <input type="hidden" value="true" name="boolean-value-{{index}}" id="boolean-value-{{index}}"/>

View File

@@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Optional } from '@angular/core';
import { ValueInputComponent } from '../value-input.component'; import { ValueInputComponent } from '../value-input.component';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../../new-process.component';
/** /**
* Represents the value of a boolean parameter * Represents the value of a boolean parameter
@@ -7,7 +9,10 @@ import { ValueInputComponent } from '../value-input.component';
@Component({ @Component({
selector: 'ds-boolean-value-input', selector: 'ds-boolean-value-input',
templateUrl: './boolean-value-input.component.html', templateUrl: './boolean-value-input.component.html',
styleUrls: ['./boolean-value-input.component.scss'] styleUrls: ['./boolean-value-input.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class BooleanValueInputComponent extends ValueInputComponent<boolean> implements OnInit { export class BooleanValueInputComponent extends ValueInputComponent<boolean> implements OnInit {
ngOnInit() { ngOnInit() {

View File

@@ -1,4 +1,4 @@
<input required #string="ngModel" type="text" class="form-control" id="string-value-input" [ngModel]="value" (ngModelChange)="setValue($event)"/> <input required #string="ngModel" type="text" class="form-control" name="date-value-{{index}}" id="date-value-{{index}}" [ngModel]="value" (ngModelChange)="setValue($event)"/>
<div *ngIf="string.invalid && (string.dirty || string.touched)" <div *ngIf="string.invalid && (string.dirty || string.touched)"
class="alert alert-danger validation-error"> class="alert alert-danger validation-error">
<div *ngIf="string.errors.required"> <div *ngIf="string.errors.required">

View File

@@ -1,5 +1,7 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, Optional } from '@angular/core';
import { ValueInputComponent } from '../value-input.component'; import { ValueInputComponent } from '../value-input.component';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../../new-process.component';
/** /**
* Represents the user inputted value of a date parameter * Represents the user inputted value of a date parameter
@@ -7,7 +9,10 @@ import { ValueInputComponent } from '../value-input.component';
@Component({ @Component({
selector: 'ds-date-value-input', selector: 'ds-date-value-input',
templateUrl: './date-value-input.component.html', templateUrl: './date-value-input.component.html',
styleUrls: ['./date-value-input.component.scss'] styleUrls: ['./date-value-input.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class DateValueInputComponent extends ValueInputComponent<string> { export class DateValueInputComponent extends ValueInputComponent<string> {
/** /**

View File

@@ -1,7 +1,10 @@
<label for="file-upload" class="btn btn-light"> <label for="file-upload-{{index}}" class="d-flex align-items-center">
{{'process.new.parameter.file.upload-button' | translate}} <span class="btn btn-light">
{{'process.new.parameter.file.upload-button' | translate}}
</span>
<span class="file-name ml-1">{{fileObject?.name}}</span>
</label> </label>
<input requireFile #file="ngModel" type="file" id="file-upload" class="form-control-file d-none" [ngModel]="file" (ngModelChange)="setFile($event)"/> <input requireFile #file="ngModel" type="file" name="file-upload-{{index}}" id="file-upload-{{index}}" class="form-control-file d-none" [ngModel]="fileObject" (ngModelChange)="setFile($event)"/>
<div *ngIf="file.invalid && (file.dirty || file.touched)" <div *ngIf="file.invalid && (file.dirty || file.touched)"
class="alert alert-danger validation-error"> class="alert alert-danger validation-error">
<div *ngIf="file.errors.required"> <div *ngIf="file.errors.required">

View File

@@ -0,0 +1,6 @@
.file-name {
flex: 1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

View File

@@ -1,6 +1,6 @@
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms'; import { FormsModule, NgForm, ReactiveFormsModule } from '@angular/forms';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../../shared/mocks/mock-translate-loader'; import { MockTranslateLoader } from '../../../../../shared/mocks/mock-translate-loader';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@@ -9,7 +9,7 @@ import { NO_ERRORS_SCHEMA } from '@angular/core';
import { FileValueAccessorDirective } from '../../../../../shared/utils/file-value-accessor.directive'; import { FileValueAccessorDirective } from '../../../../../shared/utils/file-value-accessor.directive';
import { FileValidator } from '../../../../../shared/utils/require-file.validator'; import { FileValidator } from '../../../../../shared/utils/require-file.validator';
describe('FileValueInputComponent', () => { describe('FileValueInputComponent', () => {
let component: FileValueInputComponent; let component: FileValueInputComponent;
let fixture: ComponentFixture<FileValueInputComponent>; let fixture: ComponentFixture<FileValueInputComponent>;

View File

@@ -1,5 +1,7 @@
import { Component } from '@angular/core'; import { Component, Optional } from '@angular/core';
import { ValueInputComponent } from '../value-input.component'; import { ValueInputComponent } from '../value-input.component';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../../new-process.component';
/** /**
* Represents the user inputted value of a file parameter * Represents the user inputted value of a file parameter
@@ -7,15 +9,18 @@ import { ValueInputComponent } from '../value-input.component';
@Component({ @Component({
selector: 'ds-file-value-input', selector: 'ds-file-value-input',
templateUrl: './file-value-input.component.html', templateUrl: './file-value-input.component.html',
styleUrls: ['./file-value-input.component.scss'] styleUrls: ['./file-value-input.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class FileValueInputComponent extends ValueInputComponent<File> { export class FileValueInputComponent extends ValueInputComponent<File> {
/** /**
* The current value of the file * The current value of the file
*/ */
file: File; fileObject: File;
setFile(files) { setFile(files) {
this.file = files.length > 0 ? files[0] : undefined; this.fileObject = files.length > 0 ? files[0] : undefined;
this.updateValue.emit(this.file); this.updateValue.emit(this.fileObject);
} }
} }

View File

@@ -1,7 +1,7 @@
<div [ngSwitch]="parameter?.type"> <div [ngSwitch]="parameter?.type">
<ds-string-value-input *ngSwitchCase="parameterTypes.STRING" (updateValue)="updateValue.emit($event)"></ds-string-value-input> <ds-string-value-input *ngSwitchCase="parameterTypes.STRING" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
<ds-string-value-input *ngSwitchCase="parameterTypes.OUTPUT" (updateValue)="updateValue.emit($event)"></ds-string-value-input> <ds-string-value-input *ngSwitchCase="parameterTypes.OUTPUT" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
<ds-date-value-input *ngSwitchCase="parameterTypes.DATE" (updateValue)="updateValue.emit($event)"></ds-date-value-input> <ds-date-value-input *ngSwitchCase="parameterTypes.DATE" (updateValue)="updateValue.emit($event)" [index]="index"></ds-date-value-input>
<ds-file-value-input *ngSwitchCase="parameterTypes.FILE" (updateValue)="updateValue.emit($event)"></ds-file-value-input> <ds-file-value-input *ngSwitchCase="parameterTypes.FILE" (updateValue)="updateValue.emit($event)" [index]="index"></ds-file-value-input>
<ds-boolean-value-input *ngSwitchCase="parameterTypes.BOOLEAN" (updateValue)="updateValue.emit($event)"></ds-boolean-value-input> <ds-boolean-value-input *ngSwitchCase="parameterTypes.BOOLEAN" (updateValue)="updateValue.emit($event)" [index]="index"></ds-boolean-value-input>
</div> </div>

View File

@@ -1,6 +1,8 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output } from '@angular/core';
import { ScriptParameterType } from '../../../scripts/script-parameter-type.model'; import { ScriptParameterType } from '../../../scripts/script-parameter-type.model';
import { ScriptParameter } from '../../../scripts/script-parameter.model'; import { ScriptParameter } from '../../../scripts/script-parameter.model';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../new-process.component';
/** /**
* Component that renders the correct parameter value input based the script parameter's type * Component that renders the correct parameter value input based the script parameter's type
@@ -8,9 +10,14 @@ import { ScriptParameter } from '../../../scripts/script-parameter.model';
@Component({ @Component({
selector: 'ds-parameter-value-input', selector: 'ds-parameter-value-input',
templateUrl: './parameter-value-input.component.html', templateUrl: './parameter-value-input.component.html',
styleUrls: ['./parameter-value-input.component.scss'] styleUrls: ['./parameter-value-input.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class ParameterValueInputComponent { export class ParameterValueInputComponent {
@Input() index: number;
/** /**
* The current script parameter * The current script parameter
*/ */

View File

@@ -1,4 +1,4 @@
<input required #string="ngModel" type="text" class="form-control" id="string-value-input" [ngModel]="value" (ngModelChange)="setValue($event)"/> <input required #string="ngModel" type="text" name="string-value-{{index}}" class="form-control" id="string-value-{{index}}" [ngModel]="value" (ngModelChange)="setValue($event)"/>
<div *ngIf="string.invalid && (string.dirty || string.touched)" <div *ngIf="string.invalid && (string.dirty || string.touched)"
class="alert alert-danger validation-error"> class="alert alert-danger validation-error">
<div *ngIf="string.errors.required"> <div *ngIf="string.errors.required">

View File

@@ -1,6 +1,6 @@
import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing'; import { async, ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms'; import { FormsModule, NgForm } from '@angular/forms';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core'; import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { MockTranslateLoader } from '../../../../../shared/mocks/mock-translate-loader'; import { MockTranslateLoader } from '../../../../../shared/mocks/mock-translate-loader';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
@@ -20,7 +20,9 @@ describe('StringValueInputComponent', () => {
useClass: MockTranslateLoader useClass: MockTranslateLoader
} }
})], })],
declarations: [StringValueInputComponent] declarations: [StringValueInputComponent],
providers: [
]
}) })
.compileComponents(); .compileComponents();
})); }));
@@ -40,7 +42,7 @@ describe('StringValueInputComponent', () => {
expect(validationError).toBeFalsy(); expect(validationError).toBeFalsy();
}); });
it('should show a validation error if the input field was touched but left empty', fakeAsync(() => { it('should show a validation error if the input field was touched but left empty', fakeAsync(() => {
component.value = ''; component.value = '';
fixture.detectChanges(); fixture.detectChanges();
tick(); tick();

View File

@@ -1,5 +1,7 @@
import { Component } from '@angular/core'; import { Component, Optional } from '@angular/core';
import { ValueInputComponent } from '../value-input.component'; import { ValueInputComponent } from '../value-input.component';
import { ControlContainer, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../../../new-process.component';
/** /**
* Represents the user inputted value of a string parameter * Represents the user inputted value of a string parameter
@@ -7,7 +9,10 @@ import { ValueInputComponent } from '../value-input.component';
@Component({ @Component({
selector: 'ds-string-value-input', selector: 'ds-string-value-input',
templateUrl: './string-value-input.component.html', templateUrl: './string-value-input.component.html',
styleUrls: ['./string-value-input.component.scss'] styleUrls: ['./string-value-input.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class StringValueInputComponent extends ValueInputComponent<string> { export class StringValueInputComponent extends ValueInputComponent<string> {
/** /**

View File

@@ -1,9 +1,10 @@
import { EventEmitter, Output } from '@angular/core'; import { EventEmitter, Input, Output } from '@angular/core';
/** /**
* Abstract class that represents value input components * Abstract class that represents value input components
*/ */
export abstract class ValueInputComponent<T> { export abstract class ValueInputComponent<T> {
@Input() index: number;
/** /**
* Used by the subclasses to emit the value when it's updated * Used by the subclasses to emit the value when it's updated
*/ */

View File

@@ -5,6 +5,7 @@
[parameters]="script.parameters" [parameters]="script.parameters"
[parameterValue]="value" [parameterValue]="value"
[removable]="!last" [removable]="!last"
[index]="i"
(removeParameter)="removeParameter(i)" (removeParameter)="removeParameter(i)"
(changeParameter)="updateParameter($event, i)"></ds-parameter-select> (changeParameter)="updateParameter($event, i)"></ds-parameter-select>
</div> </div>

View File

@@ -1,7 +1,10 @@
import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { Component, EventEmitter, Input, OnChanges, Optional, Output, SimpleChanges } from '@angular/core';
import { Script } from '../../scripts/script.model'; import { Script } from '../../scripts/script.model';
import { ProcessParameter } from '../../processes/process-parameter.model'; import { ProcessParameter } from '../../processes/process-parameter.model';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { ControlContainer, NgForm } from '@angular/forms';
import { ScriptParameter } from '../../scripts/script-parameter.model';
import { controlContainerFactory } from '../new-process.component';
/** /**
* Component that represents the selected list of parameters for a script * Component that represents the selected list of parameters for a script
@@ -9,7 +12,10 @@ import { hasValue } from '../../../shared/empty.util';
@Component({ @Component({
selector: 'ds-process-parameters', selector: 'ds-process-parameters',
templateUrl: './process-parameters.component.html', templateUrl: './process-parameters.component.html',
styleUrls: ['./process-parameters.component.scss'] styleUrls: ['./process-parameters.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class ProcessParametersComponent implements OnChanges { export class ProcessParametersComponent implements OnChanges {
/** /**
@@ -42,7 +48,7 @@ export class ProcessParametersComponent implements OnChanges {
*/ */
initParameters() { initParameters() {
this.parameterValues = []; this.parameterValues = [];
this.addParameter(); this.initializeParameter();
} }
/** /**
@@ -67,6 +73,20 @@ export class ProcessParametersComponent implements OnChanges {
this.parameterValues = this.parameterValues.filter((value, i) => i !== index); this.parameterValues = this.parameterValues.filter((value, i) => i !== index);
} }
/**
* Initializes parameter values based on the selected script
*/
initializeParameter() {
if (hasValue(this.script)) {
this.parameterValues = this.script.parameters
.filter((param) => param.mandatory)
.map(
(parameter: ScriptParameter) => Object.assign(new ProcessParameter(), { name: parameter.name })
);
}
this.addParameter();
}
/** /**
* Adds an empty parameter value to the end of the list * Adds an empty parameter value to the end of the list
*/ */

View File

@@ -3,7 +3,16 @@
<table class="table-borderless mt-3 text-secondary"> <table class="table-borderless mt-3 text-secondary">
<tr *ngFor="let param of script?.parameters"> <tr *ngFor="let param of script?.parameters">
<td>{{param.name}} {{param.nameLong}} {{param.type !== 'boolean' ? '<' + param.type + '>' : ''}}</td> <td class="align-top text-nowrap">{{param.name}} {{param.nameLong}} <ng-container *ngTemplateOutlet="type; context: param"></ng-container></td>
<td>{{param.description}}</td> <td>{{param.description}}</td>
</tr> </tr>
</table> </table>
<ng-template #type let-type="type">
<ng-container [ngSwitch]="type">
<span *ngSwitchCase="parameterTypes.DATE"><{{'process.new.parameter.type.value' | translate}}></span>
<span *ngSwitchCase="parameterTypes.STRING"><{{'process.new.parameter.type.value' | translate}}></span>
<span *ngSwitchCase="parameterTypes.OUTPUT"><{{'process.new.parameter.type.value' | translate}}></span>
<span *ngSwitchCase="parameterTypes.FILE"><{{'process.new.parameter.type.file' | translate}}></span>
</ng-container>
</ng-template>

View File

@@ -5,6 +5,10 @@ import { ScriptParameter } from '../../scripts/script-parameter.model';
import { Script } from '../../scripts/script.model'; import { Script } from '../../scripts/script.model';
import { ScriptParameterType } from '../../scripts/script-parameter-type.model'; import { ScriptParameterType } from '../../scripts/script-parameter-type.model';
import { By } from '@angular/platform-browser'; import { By } from '@angular/platform-browser';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
import { FormsModule } from '@angular/forms';
import { MockTranslateLoader } from '../../../shared/testing/mock-translate-loader';
describe('ScriptHelpComponent', () => { describe('ScriptHelpComponent', () => {
let component: ScriptHelpComponent; let component: ScriptHelpComponent;
@@ -25,7 +29,16 @@ describe('ScriptHelpComponent', () => {
beforeEach(async(() => { beforeEach(async(() => {
init(); init();
TestBed.configureTestingModule({ TestBed.configureTestingModule({
declarations: [ ScriptHelpComponent ] imports: [
FormsModule,
TranslateModule.forRoot({
loader: {
provide: TranslateLoader,
useClass: MockTranslateLoader
}
})],
declarations: [ ScriptHelpComponent ],
schemas: [NO_ERRORS_SCHEMA]
}) })
.compileComponents(); .compileComponents();
})); }));

View File

@@ -1,5 +1,6 @@
import { Component, Input } from '@angular/core'; import { Component, Input } from '@angular/core';
import { Script } from '../../scripts/script.model'; import { Script } from '../../scripts/script.model';
import { ScriptParameterType } from '../../scripts/script-parameter-type.model';
/** /**
* Components that represents a help section for the script use and parameters * Components that represents a help section for the script use and parameters
@@ -14,4 +15,9 @@ export class ScriptHelpComponent {
* The current script to show the help information for * The current script to show the help information for
*/ */
@Input() script: Script; @Input() script: Script;
/**
* The available script parameter types
*/
parameterTypes = ScriptParameterType;
} }

View File

@@ -1,5 +1,5 @@
<div class="form-group" *ngIf="scripts$ | async"> <div class="form-group" *ngIf="scripts$ | async">
<label for="process-script"><label>{{'process.new.select-script' | translate}}</label></label> <label for="process-script">{{'process.new.select-script' | translate}}</label>
<select required id="process-script" <select required id="process-script"
class="form-control" class="form-control"
name="script" name="script"

View File

@@ -1,4 +1,4 @@
import { Component, EventEmitter, OnDestroy, OnInit, Output } from '@angular/core'; import { Component, EventEmitter, OnDestroy, OnInit, Optional, Output } from '@angular/core';
import { ScriptDataService } from '../../../core/data/processes/script-data.service'; import { ScriptDataService } from '../../../core/data/processes/script-data.service';
import { Script } from '../../scripts/script.model'; import { Script } from '../../scripts/script.model';
import { Observable, Subscription } from 'rxjs'; import { Observable, Subscription } from 'rxjs';
@@ -7,6 +7,8 @@ import { PaginatedList } from '../../../core/data/paginated-list';
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators'; import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
import { ActivatedRoute, Params, Router } from '@angular/router'; import { ActivatedRoute, Params, Router } from '@angular/router';
import { hasValue } from '../../../shared/empty.util'; import { hasValue } from '../../../shared/empty.util';
import { ControlContainer, FormControl, NgForm } from '@angular/forms';
import { controlContainerFactory } from '../new-process.component';
const SCRIPT_QUERY_PARAMETER = 'script'; const SCRIPT_QUERY_PARAMETER = 'script';
@@ -16,7 +18,10 @@ const SCRIPT_QUERY_PARAMETER = 'script';
@Component({ @Component({
selector: 'ds-scripts-select', selector: 'ds-scripts-select',
templateUrl: './scripts-select.component.html', templateUrl: './scripts-select.component.html',
styleUrls: ['./scripts-select.component.scss'] styleUrls: ['./scripts-select.component.scss'],
viewProviders: [ { provide: ControlContainer,
useFactory: controlContainerFactory,
deps: [[new Optional(), NgForm]] } ]
}) })
export class ScriptsSelectComponent implements OnInit, OnDestroy { export class ScriptsSelectComponent implements OnInit, OnDestroy {
@Output() select: EventEmitter<Script> = new EventEmitter<Script>(); @Output() select: EventEmitter<Script> = new EventEmitter<Script>();

View File

@@ -8,6 +8,7 @@ import { NewProcessComponent } from './new/new-process.component';
{ {
path: 'new', path: 'new',
component: NewProcessComponent, component: NewProcessComponent,
data: { title: 'process.new.title' }
}, },
]) ])
] ]