mirror of
https://github.com/DSpace/dspace-angular.git
synced 2025-10-07 10:04:11 +00:00
added create-from functionality
This commit is contained in:
@@ -1,38 +1,42 @@
|
||||
<div class="container" *ngVar="(processRD$ | async)?.payload as process">
|
||||
<h2>{{'process.detail.title' | translate:{ id: process?.processId, name: process?.scriptName } }}</h2>
|
||||
|
||||
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
|
||||
<div>{{ process?.scriptName }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<ds-process-detail-field *ngIf="process?.parameters && process?.parameters?.length > 0" id="process-arguments" [title]="'process.detail.arguments'">
|
||||
<div *ngFor="let argument of process?.parameters">{{ argument?.name }} {{ argument?.value }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<div *ngVar="(filesRD$ | async)?.payload?.page as files">
|
||||
<ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files" [title]="'process.detail.output-files'">
|
||||
<div *ngFor="let file of files">
|
||||
<span><a [href]="file?._links?.content?.href">{{getFileName(file)}}</a></span>
|
||||
<span>({{file?.sizeBytes | dsFileSize}})</span>
|
||||
</div>
|
||||
<div class="d-flex">
|
||||
<h2 class="flex-grow-1">{{'process.detail.title' | translate:{id: process?.processId, name: process?.scriptName} }}</h2>
|
||||
<div>
|
||||
<a class="btn btn-light" [routerLink]="'/processes/new'" [queryParams]="{id: process?.processId}">{{'process.detail.create' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
<ds-process-detail-field id="process-name" [title]="'process.detail.script'">
|
||||
<div>{{ process?.scriptName }}</div>
|
||||
</ds-process-detail-field>
|
||||
</div>
|
||||
|
||||
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
|
||||
<div>{{ process.startTime }}</div>
|
||||
</ds-process-detail-field>
|
||||
<ds-process-detail-field *ngIf="process?.parameters && process?.parameters?.length > 0" id="process-arguments" [title]="'process.detail.arguments'">
|
||||
<div *ngFor="let argument of process?.parameters">{{ argument?.name }} {{ argument?.value }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
|
||||
<div>{{ process.endTime }}</div>
|
||||
</ds-process-detail-field>
|
||||
<div *ngVar="(filesRD$ | async)?.payload?.page as files">
|
||||
<ds-process-detail-field *ngIf="files && files?.length > 0" id="process-files" [title]="'process.detail.output-files'">
|
||||
<div *ngFor="let file of files">
|
||||
<span><a [href]="file?._links?.content?.href">{{getFileName(file)}}</a></span>
|
||||
<span>({{file?.sizeBytes | dsFileSize}})</span>
|
||||
</div>
|
||||
</ds-process-detail-field>
|
||||
</div>
|
||||
|
||||
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
|
||||
<div>{{ process.processStatus }}</div>
|
||||
</ds-process-detail-field>
|
||||
<ds-process-detail-field *ngIf="process && process.startTime" id="process-start-time" [title]="'process.detail.start-time' | translate">
|
||||
<div>{{ process.startTime }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<!--<ds-process-detail-field id="process-output" [title]="'process.detail.output'">-->
|
||||
<ds-process-detail-field *ngIf="process && process.endTime" id="process-end-time" [title]="'process.detail.end-time' | translate">
|
||||
<div>{{ process.endTime }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<ds-process-detail-field *ngIf="process && process.processStatus" id="process-status" [title]="'process.detail.status' | translate">
|
||||
<div>{{ process.processStatus }}</div>
|
||||
</ds-process-detail-field>
|
||||
|
||||
<!--<ds-process-detail-field id="process-output" [title]="'process.detail.output'">-->
|
||||
<!--<pre class="font-weight-bold text-secondary bg-light p-3">{{'process.detail.output.alert' | translate}}</pre>-->
|
||||
<!--</ds-process-detail-field>-->
|
||||
<!--</ds-process-detail-field>-->
|
||||
|
||||
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
||||
<a class="btn btn-light mt-3" [routerLink]="'/processes'">{{'process.detail.back' | translate}}</a>
|
||||
</div>
|
||||
|
25
src/app/process-page/form/process-form.component.html
Normal file
25
src/app/process-page/form/process-form.component.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h2 class="col-12">
|
||||
{{headerKey | translate}}
|
||||
</h2>
|
||||
<div class="col-12 col-md-6">
|
||||
<form #form="ngForm" (ngSubmit)="submitForm(form)">
|
||||
<ds-scripts-select [script]="selectedScript" (select)="selectedScript = $event; parameters = undefined"></ds-scripts-select>
|
||||
<ds-process-parameters [initialParams]="parameters" [script]="selectedScript" (updateParameters)="parameters = $event"></ds-process-parameters>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<ds-script-help [script]="selectedScript"></ds-script-help>
|
||||
</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>
|
86
src/app/process-page/form/process-form.component.spec.ts
Normal file
86
src/app/process-page/form/process-form.component.spec.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { TranslateLoader, TranslateModule } from '@ngx-translate/core';
|
||||
import { NO_ERRORS_SCHEMA } from '@angular/core';
|
||||
import { ProcessFormComponent } from './process-form.component';
|
||||
import { ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||
import { ScriptParameter } from '../scripts/script-parameter.model';
|
||||
import { Script } from '../scripts/script.model';
|
||||
import { ProcessParameter } from '../processes/process-parameter.model';
|
||||
import { NotificationsService } from '../../shared/notifications/notifications.service';
|
||||
import { of as observableOf } from 'rxjs';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
describe('NewProcessComponent', () => {
|
||||
let component: ProcessFormComponent;
|
||||
let fixture: ComponentFixture<ProcessFormComponent>;
|
||||
let scriptService;
|
||||
let parameterValues;
|
||||
let script;
|
||||
|
||||
function init() {
|
||||
const param1 = new ScriptParameter();
|
||||
const param2 = new ScriptParameter();
|
||||
script = Object.assign(new Script(), { parameters: [param1, param2] });
|
||||
parameterValues = [
|
||||
Object.assign(new ProcessParameter(), { name: '-a', value: 'bla' }),
|
||||
Object.assign(new ProcessParameter(), { name: '-b', value: '123' }),
|
||||
Object.assign(new ProcessParameter(), { name: '-c', value: 'value' }),
|
||||
];
|
||||
scriptService = jasmine.createSpyObj(
|
||||
'scriptService',
|
||||
{
|
||||
invoke: observableOf({
|
||||
response:
|
||||
{
|
||||
isSuccessful: true
|
||||
}
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
beforeEach(async(() => {
|
||||
init();
|
||||
TestBed.configureTestingModule({
|
||||
imports: [
|
||||
FormsModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {
|
||||
provide: TranslateLoader,
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
})],
|
||||
declarations: [ProcessFormComponent],
|
||||
providers: [
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: NotificationsService, useClass: NotificationsServiceStub },
|
||||
{ provide: RequestService, useValue: {} },
|
||||
{ provide: Router, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
.compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(ProcessFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.parameters = parameterValues;
|
||||
component.selectedScript = script;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call invoke on the scriptService on submit', () => {
|
||||
component.submitForm({ invalid: false } as any);
|
||||
expect(scriptService.invoke).toHaveBeenCalled();
|
||||
});
|
||||
});
|
146
src/app/process-page/form/process-form.component.ts
Normal file
146
src/app/process-page/form/process-form.component.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { Script } from '../scripts/script.model';
|
||||
import { Process } from '../processes/process.model';
|
||||
import { ProcessParameter } from '../processes/process-parameter.model';
|
||||
import { ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||
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 { RequestService } from '../../core/data/request.service';
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
/**
|
||||
* Component to create a new script
|
||||
*/
|
||||
@Component({
|
||||
selector: 'ds-process-form',
|
||||
templateUrl: './process-form.component.html',
|
||||
styleUrls: ['./process-form.component.scss'],
|
||||
})
|
||||
export class ProcessFormComponent implements OnInit {
|
||||
/**
|
||||
* The currently selected script
|
||||
*/
|
||||
@Input() public selectedScript: Script = undefined;
|
||||
|
||||
/**
|
||||
* The process to create
|
||||
*/
|
||||
@Input() public process: Process = undefined;
|
||||
|
||||
/**
|
||||
* The parameter values to use to start the process
|
||||
*/
|
||||
@Input() public parameters: ProcessParameter[];
|
||||
|
||||
/**
|
||||
* Optional files that are used as parameter values
|
||||
*/
|
||||
public files: File[] = [];
|
||||
|
||||
@Input() public headerKey: string;
|
||||
|
||||
/**
|
||||
* Contains the missing parameters on submission
|
||||
*/
|
||||
public missingParameters = [];
|
||||
|
||||
constructor(
|
||||
private scriptService: ScriptDataService,
|
||||
private notificationsService: NotificationsService,
|
||||
private translationService: TranslateService,
|
||||
private requestService: RequestService,
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.process = new Process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form, sets the parameters to correct values and invokes the script with the correct parameters
|
||||
* @param form
|
||||
*/
|
||||
submitForm(form: NgForm) {
|
||||
if (!this.validateForm(form) || this.isRequiredMissing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const stringParameters: ProcessParameter[] = this.parameters.map((parameter: ProcessParameter) => {
|
||||
return {
|
||||
name: parameter.name,
|
||||
value: this.checkValue(parameter)
|
||||
};
|
||||
}
|
||||
);
|
||||
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)
|
||||
this.sendBack();
|
||||
} 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the parameter values are files
|
||||
* Replaces file parameters by strings and stores the files in a separate list
|
||||
* @param processParameter The parameter value to check
|
||||
*/
|
||||
private checkValue(processParameter: ProcessParameter): string {
|
||||
if (typeof processParameter.value === 'object') {
|
||||
this.files = [...this.files, processParameter.value];
|
||||
return processParameter.value.name;
|
||||
}
|
||||
return processParameter.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form
|
||||
* Returns false if the form is invalid
|
||||
* Returns true if the form is valid
|
||||
* @param form The NgForm object to validate
|
||||
*/
|
||||
private validateForm(form: NgForm) {
|
||||
if (form.invalid) {
|
||||
Object.keys(form.controls).forEach((key) => {
|
||||
form.controls[key].markAsDirty();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private sendBack() {
|
||||
this.requestService.removeByHrefSubstring('/processes');
|
||||
/* should subscribe on the previous method to know the action is finished and then navigate,
|
||||
will fix this when the removeByHrefSubstring changes are merged */
|
||||
this.router.navigateByUrl('/processes');
|
||||
}
|
||||
}
|
||||
|
||||
export function controlContainerFactory(controlContainer?: ControlContainer) {
|
||||
return controlContainer;
|
||||
}
|
@@ -9,7 +9,7 @@
|
||||
{{param.nameLong || param.name}}
|
||||
</option>
|
||||
</select>
|
||||
<ds-parameter-value-input [parameter]="selectedScriptParameter" (updateValue)="selectedParameterValue = $event" class="d-block col" [index]="index"></ds-parameter-value-input>
|
||||
<ds-parameter-value-input [initialValue]="parameterValue.value" [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>
|
||||
<span *ngIf="!removable" class="col-1"></span>
|
||||
</div>
|
@@ -3,7 +3,7 @@ import { ProcessParameter } from '../../../processes/process-parameter.model';
|
||||
import { ScriptParameter } from '../../../scripts/script-parameter.model';
|
||||
import { hasNoValue } from '../../../../shared/empty.util';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../new-process.component';
|
||||
import { controlContainerFactory } from '../../process-form.component';
|
||||
|
||||
/**
|
||||
* Component to select a single parameter for a process
|
||||
@@ -12,17 +12,19 @@ import { controlContainerFactory } from '../../new-process.component';
|
||||
selector: 'ds-parameter-select',
|
||||
templateUrl: './parameter-select.component.html',
|
||||
styleUrls: ['./parameter-select.component.scss'],
|
||||
viewProviders: [ { provide: ControlContainer,
|
||||
viewProviders: [{
|
||||
provide: ControlContainer,
|
||||
useFactory: controlContainerFactory,
|
||||
deps: [[new Optional(), NgForm]] } ]
|
||||
deps: [[new Optional(), NgForm]]
|
||||
}]
|
||||
})
|
||||
export class ParameterSelectComponent implements OnInit {
|
||||
export class ParameterSelectComponent {
|
||||
@Input() index: number;
|
||||
|
||||
/**
|
||||
* The current parameter value of the selected parameter
|
||||
*/
|
||||
@Input() parameterValue: ProcessParameter;
|
||||
@Input() parameterValue: ProcessParameter = new ProcessParameter();
|
||||
|
||||
/**
|
||||
* The available script parameters for the script
|
||||
@@ -44,12 +46,6 @@ export class ParameterSelectComponent implements OnInit {
|
||||
*/
|
||||
@Output() changeParameter: EventEmitter<ProcessParameter> = new EventEmitter<ProcessParameter>();
|
||||
|
||||
ngOnInit(): void {
|
||||
if (hasNoValue(this.parameterValue)) {
|
||||
this.parameterValue = new ProcessParameter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the script parameter based on the currently selected name
|
||||
*/
|
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, Optional } from '@angular/core';
|
||||
import { ValueInputComponent } from '../value-input.component';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../../new-process.component';
|
||||
import { controlContainerFactory } from '../../../process-form.component';
|
||||
|
||||
/**
|
||||
* Represents the value of a boolean parameter
|
@@ -1,7 +1,7 @@
|
||||
import { Component, OnInit, Optional } from '@angular/core';
|
||||
import { Component, OnInit, Optional, Input } from '@angular/core';
|
||||
import { ValueInputComponent } from '../value-input.component';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../../new-process.component';
|
||||
import { controlContainerFactory } from '../../../process-form.component';
|
||||
|
||||
/**
|
||||
* Represents the user inputted value of a date parameter
|
||||
@@ -19,6 +19,11 @@ export class DateValueInputComponent extends ValueInputComponent<string> {
|
||||
* The current value of the date string
|
||||
*/
|
||||
value: string;
|
||||
@Input() initialValue;
|
||||
|
||||
ngOnInit() {
|
||||
this.value = this.initialValue;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.value = value;
|
@@ -1,7 +1,7 @@
|
||||
import { Component, Optional } from '@angular/core';
|
||||
import { ValueInputComponent } from '../value-input.component';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../../new-process.component';
|
||||
import { controlContainerFactory } from '../../../process-form.component';
|
||||
|
||||
/**
|
||||
* Represents the user inputted value of a file parameter
|
@@ -0,0 +1,7 @@
|
||||
<div [ngSwitch]="parameter?.type">
|
||||
<ds-string-value-input *ngSwitchCase="parameterTypes.STRING" [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
|
||||
<ds-string-value-input *ngSwitchCase="parameterTypes.OUTPUT" [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-string-value-input>
|
||||
<ds-date-value-input *ngSwitchCase="parameterTypes.DATE" [initialValue]="initialValue" (updateValue)="updateValue.emit($event)" [index]="index"></ds-date-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)" [index]="index"></ds-boolean-value-input>
|
||||
</div>
|
@@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnChanges, OnInit, Optional, Output } f
|
||||
import { ScriptParameterType } from '../../../scripts/script-parameter-type.model';
|
||||
import { ScriptParameter } from '../../../scripts/script-parameter.model';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../new-process.component';
|
||||
import { controlContainerFactory } from '../../process-form.component';
|
||||
|
||||
/**
|
||||
* Component that renders the correct parameter value input based the script parameter's type
|
||||
@@ -23,6 +23,8 @@ export class ParameterValueInputComponent {
|
||||
*/
|
||||
@Input() parameter: ScriptParameter;
|
||||
|
||||
|
||||
@Input() initialValue: any;
|
||||
/**
|
||||
* Emits the value of the input when its updated
|
||||
*/
|
@@ -1,7 +1,7 @@
|
||||
import { Component, Optional } from '@angular/core';
|
||||
import { Component, Optional, Input } from '@angular/core';
|
||||
import { ValueInputComponent } from '../value-input.component';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../../../new-process.component';
|
||||
import { controlContainerFactory } from '../../../process-form.component';
|
||||
|
||||
/**
|
||||
* Represents the user inputted value of a string parameter
|
||||
@@ -19,6 +19,11 @@ export class StringValueInputComponent extends ValueInputComponent<string> {
|
||||
* The current value of the string
|
||||
*/
|
||||
value: string;
|
||||
@Input() initialValue;
|
||||
|
||||
ngOnInit() {
|
||||
this.value = this.initialValue;
|
||||
}
|
||||
|
||||
setValue(value) {
|
||||
this.value = value;
|
@@ -4,7 +4,7 @@ import { ProcessParameter } from '../../processes/process-parameter.model';
|
||||
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';
|
||||
import { controlContainerFactory } from '../process-form.component';
|
||||
|
||||
/**
|
||||
* Component that represents the selected list of parameters for a script
|
||||
@@ -13,15 +13,18 @@ import { controlContainerFactory } from '../new-process.component';
|
||||
selector: 'ds-process-parameters',
|
||||
templateUrl: './process-parameters.component.html',
|
||||
styleUrls: ['./process-parameters.component.scss'],
|
||||
viewProviders: [ { provide: ControlContainer,
|
||||
viewProviders: [{
|
||||
provide: ControlContainer,
|
||||
useFactory: controlContainerFactory,
|
||||
deps: [[new Optional(), NgForm]] } ]
|
||||
deps: [[new Optional(), NgForm]]
|
||||
}]
|
||||
})
|
||||
export class ProcessParametersComponent implements OnChanges {
|
||||
/**
|
||||
* The currently selected script
|
||||
*/
|
||||
@Input() script: Script;
|
||||
@Input() initialParams: ProcessParameter[];
|
||||
/**
|
||||
* Emits the parameter values when they're updated
|
||||
*/
|
||||
@@ -32,6 +35,12 @@ export class ProcessParametersComponent implements OnChanges {
|
||||
*/
|
||||
parameterValues: ProcessParameter[];
|
||||
|
||||
ngOnInit() {
|
||||
if (hasValue(this.initialParams)) {
|
||||
this.parameterValues = this.initialParams;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure the parameters are reset when the script changes
|
||||
* @param changes
|
||||
@@ -47,8 +56,12 @@ export class ProcessParametersComponent implements OnChanges {
|
||||
* Initializes the first parameter value
|
||||
*/
|
||||
initParameters() {
|
||||
this.parameterValues = [];
|
||||
this.initializeParameter();
|
||||
if (hasValue(this.initialParams)) {
|
||||
this.parameterValues = this.initialParams;
|
||||
} else {
|
||||
this.parameterValues = [];
|
||||
this.initializeParameter();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
@@ -1,14 +1,14 @@
|
||||
import { Component, EventEmitter, OnDestroy, OnInit, Optional, Output } from '@angular/core';
|
||||
import { Component, EventEmitter, Input, OnDestroy, OnInit, Optional, Output } from '@angular/core';
|
||||
import { ScriptDataService } from '../../../core/data/processes/script-data.service';
|
||||
import { Script } from '../../scripts/script.model';
|
||||
import { Observable, Subscription } from 'rxjs';
|
||||
import { distinctUntilChanged, filter, map, switchMap, take } from 'rxjs/operators';
|
||||
import { getRemoteDataPayload, getSucceededRemoteData } from '../../../core/shared/operators';
|
||||
import { PaginatedList } from '../../../core/data/paginated-list';
|
||||
import { distinctUntilChanged, map, switchMap, take } from 'rxjs/operators';
|
||||
import { ActivatedRoute, Params, Router } from '@angular/router';
|
||||
import { hasValue } from '../../../shared/empty.util';
|
||||
import { ControlContainer, FormControl, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../new-process.component';
|
||||
import { hasNoValue, hasValue } from '../../../shared/empty.util';
|
||||
import { ControlContainer, NgForm } from '@angular/forms';
|
||||
import { controlContainerFactory } from '../process-form.component';
|
||||
|
||||
const SCRIPT_QUERY_PARAMETER = 'script';
|
||||
|
||||
@@ -50,6 +50,7 @@ export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
||||
|
||||
this.routeSub = this.route.queryParams
|
||||
.pipe(
|
||||
filter((params: Params) => hasNoValue(params.id)),
|
||||
map((params: Params) => params[SCRIPT_QUERY_PARAMETER]),
|
||||
distinctUntilChanged(),
|
||||
switchMap((id: string) =>
|
||||
@@ -82,11 +83,15 @@ export class ScriptsSelectComponent implements OnInit, OnDestroy {
|
||||
this.router.navigate([],
|
||||
{
|
||||
queryParams: { [SCRIPT_QUERY_PARAMETER]: value },
|
||||
queryParamsHandling: 'merge'
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@Input()
|
||||
set script(value: Script) {
|
||||
this._selectedScript = value;
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
if (hasValue(this.routeSub)) {
|
||||
this.routeSub.unsubscribe();
|
@@ -1,25 +1,6 @@
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h2 class="col-12">
|
||||
{{'process.new.header' | translate}}
|
||||
</h2>
|
||||
<div class="col-12 col-md-6">
|
||||
<form #form="ngForm" (ngSubmit)="submitForm(form)">
|
||||
<ds-scripts-select (select)="selectedScript = $event"></ds-scripts-select>
|
||||
<ds-process-parameters [script]="selectedScript" (updateParameters)="parameters = $event"></ds-process-parameters>
|
||||
<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>
|
||||
</div>
|
||||
<div class="col-12 col-md-6">
|
||||
<ds-script-help [script]="selectedScript"></ds-script-help>
|
||||
</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>
|
||||
<ng-container *ngIf="fromExisting$ && (fromExisting$ | async)">
|
||||
<ds-process-form *ngVar="fromExisting$ | async as process" headerKey="process.new.header" [selectedScript]="script$ | async" [parameters]="process.parameters"></ds-process-form>
|
||||
</ng-container>
|
||||
<ng-container *ngIf="!fromExisting$ || !(fromExisting$ | async)">
|
||||
<ds-process-form headerKey="process.new.header"></ds-process-form>
|
||||
</ng-container>
|
||||
|
@@ -13,7 +13,10 @@ import { of as observableOf } from 'rxjs';
|
||||
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
|
||||
import { TranslateLoaderMock } from '../../shared/mocks/translate-loader.mock';
|
||||
import { RequestService } from '../../core/data/request.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { LinkService } from '../../core/cache/builders/link.service';
|
||||
import { VarDirective } from '../../shared/utils/var.directive';
|
||||
import { ProcessDataService } from '../../core/data/processes/process-data.service';
|
||||
|
||||
describe('NewProcessComponent', () => {
|
||||
let component: NewProcessComponent;
|
||||
@@ -55,12 +58,14 @@ describe('NewProcessComponent', () => {
|
||||
useClass: TranslateLoaderMock
|
||||
}
|
||||
})],
|
||||
declarations: [NewProcessComponent],
|
||||
declarations: [NewProcessComponent, VarDirective],
|
||||
providers: [
|
||||
{ provide: ScriptDataService, useValue: scriptService },
|
||||
{ provide: NotificationsService, useClass: NotificationsServiceStub },
|
||||
{ provide: RequestService, useValue: {} },
|
||||
{ provide: Router, useValue: {} },
|
||||
{ provide: ActivatedRoute, useValue: { snapshot: { queryParams: {} } } },
|
||||
{ provide: LinkService, useValue: {} },
|
||||
{ provide: ProcessDataService, useValue: {} },
|
||||
],
|
||||
schemas: [NO_ERRORS_SCHEMA]
|
||||
})
|
||||
@@ -70,17 +75,10 @@ describe('NewProcessComponent', () => {
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(NewProcessComponent);
|
||||
component = fixture.componentInstance;
|
||||
component.parameters = parameterValues;
|
||||
component.selectedScript = script;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should call invoke on the scriptService on submit', () => {
|
||||
component.submitForm({ invalid: false } as any);
|
||||
expect(scriptService.invoke).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
@@ -1,16 +1,13 @@
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
import { Script } from '../scripts/script.model';
|
||||
import { Process } from '../processes/process.model';
|
||||
import { ProcessParameter } from '../processes/process-parameter.model';
|
||||
import { ScriptDataService } from '../../core/data/processes/script-data.service';
|
||||
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 { RequestService } from '../../core/data/request.service';
|
||||
import { Router } from '@angular/router';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { ProcessDataService } from '../../core/data/processes/process-data.service';
|
||||
import { getFirstSucceededRemoteDataPayload } from '../../core/shared/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
import { LinkService } from '../../core/cache/builders/link.service';
|
||||
import { followLink } from '../../shared/utils/follow-link-config.model';
|
||||
import { Script } from '../scripts/script.model';
|
||||
|
||||
/**
|
||||
* Component to create a new script
|
||||
@@ -21,124 +18,23 @@ import { Router } from '@angular/router';
|
||||
styleUrls: ['./new-process.component.scss'],
|
||||
})
|
||||
export class NewProcessComponent implements OnInit {
|
||||
/**
|
||||
* The currently selected script
|
||||
*/
|
||||
public selectedScript: Script;
|
||||
fromExisting$?: Observable<Process>;
|
||||
script$?: Observable<Script>;
|
||||
|
||||
/**
|
||||
* The process to create
|
||||
*/
|
||||
public process: Process;
|
||||
constructor(private route: ActivatedRoute, private processService: ProcessDataService, private linkService: LinkService) {
|
||||
|
||||
/**
|
||||
* The parameter values to use to start the process
|
||||
*/
|
||||
public parameters: ProcessParameter[] = [];
|
||||
|
||||
/**
|
||||
* Optional files that are used as parameter values
|
||||
*/
|
||||
public files: File[] = [];
|
||||
|
||||
/**
|
||||
* Contains the missing parameters on submission
|
||||
*/
|
||||
public missingParameters = [];
|
||||
|
||||
constructor(
|
||||
private scriptService: ScriptDataService,
|
||||
private notificationsService: NotificationsService,
|
||||
private translationService: TranslateService,
|
||||
private requestService: RequestService,
|
||||
private router: Router) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.process = new Process();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form, sets the parameters to correct values and invokes the script with the correct parameters
|
||||
* @param form
|
||||
*/
|
||||
submitForm(form: NgForm) {
|
||||
if (!this.validateForm(form) || this.isRequiredMissing()) {
|
||||
return;
|
||||
ngOnInit() {
|
||||
const id = this.route.snapshot.queryParams.id;
|
||||
if (id) {
|
||||
this.fromExisting$ = this.processService.findById(id).pipe(getFirstSucceededRemoteDataPayload());
|
||||
this.script$ = this.fromExisting$.pipe(
|
||||
map((process: Process) => this.linkService.resolveLink<Process>(process, followLink('script'))),
|
||||
switchMap((process: Process) => process.script),
|
||||
getFirstSucceededRemoteDataPayload()
|
||||
);
|
||||
}
|
||||
|
||||
const stringParameters: ProcessParameter[] = this.parameters.map((parameter: ProcessParameter) => {
|
||||
return {
|
||||
name: parameter.name,
|
||||
value: this.checkValue(parameter)
|
||||
};
|
||||
}
|
||||
);
|
||||
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)
|
||||
this.sendBack();
|
||||
} 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the parameter values are files
|
||||
* Replaces file parameters by strings and stores the files in a separate list
|
||||
* @param processParameter The parameter value to check
|
||||
*/
|
||||
private checkValue(processParameter: ProcessParameter): string {
|
||||
if (typeof processParameter.value === 'object') {
|
||||
this.files = [...this.files, processParameter.value];
|
||||
return processParameter.value.name;
|
||||
}
|
||||
return processParameter.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the form
|
||||
* Returns false if the form is invalid
|
||||
* Returns true if the form is valid
|
||||
* @param form The NgForm object to validate
|
||||
*/
|
||||
private validateForm(form: NgForm) {
|
||||
if (form.invalid) {
|
||||
Object.keys(form.controls).forEach((key) => {
|
||||
form.controls[key].markAsDirty();
|
||||
});
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
private sendBack() {
|
||||
this.requestService.removeByHrefSubstring('/processes');
|
||||
/* should subscribe on the previous method to know the action is finished and then navigate,
|
||||
will fix this when the removeByHrefSubstring changes are merged */
|
||||
this.router.navigateByUrl('/processes');
|
||||
}
|
||||
}
|
||||
|
||||
export function controlContainerFactory(controlContainer?: ControlContainer) {
|
||||
return controlContainer;
|
||||
}
|
||||
|
@@ -1,7 +0,0 @@
|
||||
<div [ngSwitch]="parameter?.type">
|
||||
<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)" [index]="index"></ds-string-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)" [index]="index"></ds-file-value-input>
|
||||
<ds-boolean-value-input *ngSwitchCase="parameterTypes.BOOLEAN" (updateValue)="updateValue.emit($event)" [index]="index"></ds-boolean-value-input>
|
||||
</div>
|
@@ -2,20 +2,21 @@ import { NgModule } from '@angular/core';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { ProcessPageRoutingModule } from './process-page-routing.module';
|
||||
import { NewProcessComponent } from './new/new-process.component';
|
||||
import { ScriptsSelectComponent } from './new/scripts-select/scripts-select.component';
|
||||
import { ScriptHelpComponent } from './new/script-help/script-help.component';
|
||||
import { ParameterSelectComponent } from './new/process-parameters/parameter-select/parameter-select.component';
|
||||
import { ProcessParametersComponent } from './new/process-parameters/process-parameters.component';
|
||||
import { StringValueInputComponent } from './new/process-parameters/parameter-value-input/string-value-input/string-value-input.component';
|
||||
import { ParameterValueInputComponent } from './new/process-parameters/parameter-value-input/parameter-value-input.component';
|
||||
import { FileValueInputComponent } from './new/process-parameters/parameter-value-input/file-value-input/file-value-input.component';
|
||||
import { BooleanValueInputComponent } from './new/process-parameters/parameter-value-input/boolean-value-input/boolean-value-input.component';
|
||||
import { DateValueInputComponent } from './new/process-parameters/parameter-value-input/date-value-input/date-value-input.component';
|
||||
import { ScriptsSelectComponent } from './form/scripts-select/scripts-select.component';
|
||||
import { ScriptHelpComponent } from './form/script-help/script-help.component';
|
||||
import { ParameterSelectComponent } from './form/process-parameters/parameter-select/parameter-select.component';
|
||||
import { ProcessParametersComponent } from './form/process-parameters/process-parameters.component';
|
||||
import { StringValueInputComponent } from './form/process-parameters/parameter-value-input/string-value-input/string-value-input.component';
|
||||
import { ParameterValueInputComponent } from './form/process-parameters/parameter-value-input/parameter-value-input.component';
|
||||
import { FileValueInputComponent } from './form/process-parameters/parameter-value-input/file-value-input/file-value-input.component';
|
||||
import { BooleanValueInputComponent } from './form/process-parameters/parameter-value-input/boolean-value-input/boolean-value-input.component';
|
||||
import { DateValueInputComponent } from './form/process-parameters/parameter-value-input/date-value-input/date-value-input.component';
|
||||
import { ProcessOverviewComponent } from './overview/process-overview.component';
|
||||
import { ProcessDetailComponent } from './detail/process-detail.component';
|
||||
import { ProcessDetailFieldComponent } from './detail/process-detail-field/process-detail-field.component';
|
||||
import { ProcessBreadcrumbsService } from './process-breadcrumbs.service';
|
||||
import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver';
|
||||
import { ProcessFormComponent } from './form/process-form.component';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
@@ -35,7 +36,8 @@ import { ProcessBreadcrumbResolver } from './process-breadcrumb.resolver';
|
||||
DateValueInputComponent,
|
||||
ProcessOverviewComponent,
|
||||
ProcessDetailComponent,
|
||||
ProcessDetailFieldComponent
|
||||
ProcessDetailFieldComponent,
|
||||
ProcessFormComponent
|
||||
],
|
||||
providers: [
|
||||
ProcessBreadcrumbResolver,
|
||||
|
@@ -13,6 +13,7 @@ import { RemoteData } from '../../core/data/remote-data';
|
||||
import { Collection } from '../../core/shared/collection.model';
|
||||
import { SCRIPT } from '../scripts/script.resource-type';
|
||||
import { Script } from '../scripts/script.model';
|
||||
import { PaginatedList } from '../../core/data/paginated-list';
|
||||
|
||||
/**
|
||||
* Object representing a process
|
||||
@@ -83,7 +84,7 @@ export class Process implements CacheableObject {
|
||||
|
||||
/**
|
||||
* The Script that created this Process
|
||||
* Will be undefined unless the owningCollection {@link HALLink} has been resolved.
|
||||
* Will be undefined unless the script {@link HALLink} has been resolved.
|
||||
*/
|
||||
@link(SCRIPT)
|
||||
script?: Observable<RemoteData<Script>>;
|
||||
|
@@ -22,7 +22,11 @@ import { RouterLinkDirectiveStub } from './router-link-directive.stub';
|
||||
MySimpleItemActionComponent,
|
||||
RouterLinkDirectiveStub,
|
||||
NgComponentOutletDirectiveStub
|
||||
], schemas: [
|
||||
],
|
||||
exports: [
|
||||
QueryParamsDirectiveStub
|
||||
],
|
||||
schemas: [
|
||||
CUSTOM_ELEMENTS_SCHEMA
|
||||
]
|
||||
})
|
||||
|
@@ -1971,6 +1971,8 @@
|
||||
|
||||
"process.detail.status" : "Status",
|
||||
|
||||
"process.detail.create" : "Create similar process",
|
||||
|
||||
|
||||
|
||||
"process.overview.table.finish" : "Finish time",
|
||||
|
Reference in New Issue
Block a user