diff options
21 files changed, 1312 insertions, 27 deletions
diff --git a/catalog-ui/src/app/models/interfaceOperation.ts b/catalog-ui/src/app/models/interfaceOperation.ts index a6279a589f..fdf26aaad1 100644 --- a/catalog-ui/src/app/models/interfaceOperation.ts +++ b/catalog-ui/src/app/models/interfaceOperation.ts @@ -25,6 +25,17 @@ import {PROPERTY_DATA, PROPERTY_TYPES} from "../utils/constants"; import {ToscaFunction} from "./tosca-function"; import {SubPropertyToscaFunction} from "./sub-property-tosca-function"; +export enum MilestoneEnum { + on_entry = 'on_entry', + on_success = 'on_success', + on_failure = 'on_failure', + on_timeout = 'on_timeout' +} + +export enum ActivityTypesEnum { + Delegate = 'delegate' +} + export class InputOperationParameter { name: string; type: string; @@ -96,12 +107,63 @@ export interface IOperationParamsList { listToscaDataDefinition: Array<InputOperationParameter>; } +export class Milestone { + activities: IActivityParameterList; + filterParams: IActivityParameterList; + + constructor(param?: any) { + if (param) { + this.activities = param.activityParams; + this.filterParams = param.filterParams; + } + } +} + +export class ActivityParameter { + type: string; + workflow: string; + inputs: IOperationParamsList; + + constructor(param?: any) { + if (param) { + this.type = param.type; + this.workflow = param.workflow; + this.inputs = param.inputs; + } + } +} + +export interface IActivityParameterList { + listToscaDataDefinition: Array<ActivityParameter>; +} + +export class FilterParameter { + name: string; + constraint: string; + filterValue: any; + toscaFunction?: ToscaFunction; + + constructor(param?: any) { + if (param) { + this.name = param.name; + this.constraint = param.constraint; + this.filterValue = param.filterValue; + this.toscaFunction = param.toscaFunction; + } + } +} + +export interface IFilterParameterList { + listToscaDataDefinition: Array<FilterParameter>; +} + export class BEInterfaceOperationModel { name: string; description: string; uniqueId: string; inputs: IOperationParamsList; implementation: ArtifactModel; + milestones: Object; constructor(operation?: any) { if (operation) { @@ -110,6 +172,7 @@ export class BEInterfaceOperationModel { this.uniqueId = operation.uniqueId; this.inputs = operation.inputs; this.implementation = operation.implementation; + this.milestones = operation.milestones; } } } @@ -126,6 +189,7 @@ export class InterfaceOperationModel extends BEInterfaceOperationModel { uniqueId: string; inputParams: IOperationParamsList; implementation: ArtifactModel; + milestones: Object constructor(operation?: any) { super(operation); @@ -147,6 +211,7 @@ export class InterfaceOperationModel extends BEInterfaceOperationModel { if (operation.implementation) { this.implementation = new ArtifactModel(operation.implementation); } + this.milestones = operation.milestones; } } diff --git a/catalog-ui/src/app/models/operation.ts b/catalog-ui/src/app/models/operation.ts index 3d4917fd45..3b2f203476 100644 --- a/catalog-ui/src/app/models/operation.ts +++ b/catalog-ui/src/app/models/operation.ts @@ -101,6 +101,8 @@ export class OperationModel extends BEOperationModel{ workflowName: string; workflowVersion: string; + milestones: Object; + protected OperationTypeEnum: Array<String> = [ 'Create', 'Delete', @@ -125,6 +127,7 @@ export class OperationModel extends BEOperationModel{ this.artifactData = operation.artifactData; this.workflowName = operation.workflowName; this.workflowVersion = operation.workflowVersion; + this.milestones = operation.milestones; } } diff --git a/catalog-ui/src/app/ng2/components/ui/tabs/tab/tab.component.ts b/catalog-ui/src/app/ng2/components/ui/tabs/tab/tab.component.ts index e0eacdc43b..8e0b0a55b3 100644 --- a/catalog-ui/src/app/ng2/components/ui/tabs/tab/tab.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/tabs/tab/tab.component.ts @@ -34,6 +34,7 @@ export class Tab { @Input('tabTitle') title: string; @Input() active:boolean = false; @Input() show:boolean = true; + @Input() highlight?: string; @Input() indication?: number; } diff --git a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.html b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.html index d52dccc054..d6cf69355c 100644 --- a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.html +++ b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.html @@ -16,7 +16,7 @@ <div class="tabs {{tabStyle}}"> <ng-container *ngFor="let tab of tabs"> - <div class="tab" *ngIf="tab.show" (click)="selectTab(tab)" [class.active]="tab.active" [attr.data-tests-id]="tab.title"> + <div class="tab" *ngIf="tab.show" (click)="selectTab(tab)" [class.active]="tab.active" [attr.data-tests-id]="tab.title" [ngStyle]="{'color': tab.highlight}"> {{tab.title}} <div class="tab-indication" *ngIf="tab.indication" [@indicatorAnimation]="tab.indication">{{tab.indication}}</div> </div> diff --git a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.less b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.less index db60be5371..d37868d987 100644 --- a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.less +++ b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.less @@ -82,4 +82,24 @@ } } } + + &.basic-tabs .tab { + color: @main_color_n; + flex: 0 0 auto; + + &:after { + display:block; + content: ''; + border-bottom: 2px solid @main_color_a; + transform: scaleX(0); + transition: transform 200ms ease-in-out; + } + + &.active { + color: @main_color_a; + &:after { + transform: scaleX(1.2); + } + } + } } diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts index 04210ae39c..dfdaa77e21 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/interface-operations.component.ts @@ -142,6 +142,8 @@ export class InterfaceOperationsComponent { toscaArtifactTypes: Array<DropdownValue> = []; componentInstanceMap: Map<string, InstanceFeDetails> = new Map<string, InstanceFeDetails>(); validImplementationProps: boolean = true; + validMilestoneActivities: boolean = true; + validMilestoneFilters: boolean = true; @Input() component: ComponentInstance; @Input() isViewOnly: boolean; @@ -224,6 +226,11 @@ export class InterfaceOperationsComponent { return disable; } + const validMilestoneFilters = this.modalInstance.instance.dynamicContent.instance.validMilestoneFilters; + const validMilestoneActivities = this.modalInstance.instance.dynamicContent.instance.validMilestoneActivities; + if (!validMilestoneActivities || !validMilestoneFilters) { + return disable; + } let enableAddArtifactImplementation = this.modalInstance.instance.dynamicContent.instance.enableAddArtifactImplementation; if(enableAddArtifactImplementation) { const validImplementationProps = this.modalInstance.instance.dynamicContent.instance.validImplementationProps; @@ -273,6 +280,8 @@ export class InterfaceOperationsComponent { validityChangedCallback: this.disableSaveButton, isViewOnly: this.isViewOnly, validImplementationProps: this.validImplementationProps, + validMilestoneActivities: this.validMilestoneActivities, + validMilestoneFilters: this.validMilestoneFilters, isEdit: true, modelName: this.componentMetaData.model } diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html new file mode 100644 index 0000000000..ce8eb2f51d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.html @@ -0,0 +1,106 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2023 Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= +--> + +<div class="sub-operation-activities"> + <div class="group-with-border content-row"> + <form novalidate class="w-sdc-form two-columns" [formGroup]="activityForm"> + <div formArrayName="activityFormList"> + <label class="activities-label"> Activities </label> + <div *ngFor="let activity of activities; let idx = index"> + <div class="side-by-side group-with-border-blue"> + <div class="form-item"> + <label class="sdc-timeout-label">Type: </label> + <div class="sdc-dropdown"> + <select class="i-sdc-form-select" + data-tests-id="activity-type" + [value]="activity.type" + [disabled]="isViewOnly" + (change)="onActivityTypeChange($event.target.value, idx)" + required> + <option *ngIf="activity" [value]="activity.type" hidden selected> + {{ activity.type }} + </option> + <option *ngFor="let activityType of activityTypes" + [value]="activityType"> + {{ activityType }} + </option> + </select> + </div> + </div> + <div class="form-item"> + <div class="side-by-side"> + <div class="form-item-big"> + <label>Workflow: </label> + <input type="text" + class="i-sdc-form-input" + [disabled]="isViewOnly" + (input)="onActivityValueChange($event.target.value, idx)" + [value]="activity.workflow" + [ngClass]="{'disabled': isViewOnly}" + required/> + </div> + <div class="form-item-icon"> + <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromActivities(idx)"></span> + </div> + </div> + </div> + </div> + <div class="w-sdc-form-columns-wrapper"> + <div class="validation-errors"> + <ng-container *ngFor="let validation of validationMessages.activity"> + <div class="input-error" *ngIf="activityFormArray.at(idx).hasError(validation.type);"> + {{ validation.message }} + </div> + </ng-container> + </div> + </div> + <div class="group-with-border content-row" *ngIf="dataTypeMap"> + <input-list + [title]="'INPUT_LIST_TITLE' | translate" + [emptyMessage]="'INPUT_LIST_EMPTY' | translate" + [inputs]="getInputs(idx)" + [dataTypeMap]="dataTypeMap" + [isViewOnly]="isViewOnly" + [allowDeletion]="true" + [componentInstanceMap]="componentInstanceMap" + (onValueChange)="onInputValueChange($event, idx)" + (onDelete)="onInputDelete($event, idx)" + > + </input-list> + </div> + <div class="group-with-border content-row"> + <app-add-input + [dataTypeMap]="dataTypeMap$" + [isView]="isViewOnly" + [defaultType]="DEFAULT_INPUT_TYPE" + [existingInputNames]="collectInputNames(idx)" + (onAddInput)="onAddInput($event, idx)" + > + </app-add-input> + </div> + </div> + <div class="add-button-container group-with-border" *ngIf="!isViewOnly"> + <a class="add-btn" data-tests-id="add-input.add-input-link" + (click)="addActivity()">Add Activity</a> + </div> + </div> + </form> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less new file mode 100644 index 0000000000..99a053441c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.less @@ -0,0 +1,88 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +@import '../../../../../../../assets/styles/variables.less'; +@import '../../../../../../../assets/styles/override.less'; + +.group-with-border { + margin: 10px 0; + padding: 10px 0; + border-top: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } +} + +.group-with-border-blue { + margin: 10px 0; + padding: 10px 0; + border-top: 1px solid @tlv_color_x; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } +} + +.i-sdc-form-select, .i-sdc-form-input { + color: #5a5a5a; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; + background-color: #ffffff; + border-radius: 2px; + margin: 0; + padding: 0; + border: solid 1px #d2d2d2; + height: 30px; + width: 100%; + display: block; +} + +.side-by-side { + display: flex; + + .form-item { + flex: 1 1 auto; + + &:first-child { + margin-right: 14px; + flex-basis: 37%; + flex-grow: 0; + flex-shrink: 0; + } + + &:nth-child(3) { + margin-left: 14px; + flex: 0.4; + } + } + + .form-item-big { + flex: 1 1 auto; + flex-grow: 0; + flex-basis: 90%; + } + + .form-item-icon { + margin-left: auto; + margin-right: 14px; + + .sprite-new { + margin-top: 110%; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts new file mode 100644 index 0000000000..20b574b7eb --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.spec.ts @@ -0,0 +1,63 @@ + +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { ActivitiesListComponent } from './activities-list.component'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { TranslateModule } from '../../../../../shared/translator/translate.module'; +import { AddInputComponent } from '../add-input/add-input.component'; +import { InputListComponent } from '../input-list/input-list.component'; +import { InputListItemComponent } from '../input-list/input-list-item/input-list-item.component'; +import { SdcUiComponentsModule } from "onap-ui-angular/dist"; +import { ToscaFunctionModule } from '../../../../properties-assignment/tosca-function/tosca-function.module'; + +describe('ActivitiesListComponent', () => { + let component: ActivitiesListComponent; + let fixture: ComponentFixture<ActivitiesListComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + ActivitiesListComponent, + AddInputComponent, + InputListComponent, + InputListItemComponent, + ], + imports: [ + FormsModule, + ReactiveFormsModule, + TranslateModule, + SdcUiComponentsModule, + ToscaFunctionModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(ActivitiesListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts new file mode 100644 index 0000000000..b265464f86 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/activities-list/activities-list.component.ts @@ -0,0 +1,187 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; +import { + ActivityParameter, + IActivityParameterList, + InputOperationParameter, + IOperationParamsList, + ActivityTypesEnum +} from "../../../../../../models/interfaceOperation"; +import { DataTypeModel } from "../../../../../../models/data-types"; +import { Observable } from "rxjs/Observable"; +import { InstanceFeDetails } from "../../../../../../models/instance-fe-details"; +import { + AbstractControl, + FormArray, + FormControl, + FormGroup, + ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; + +@Component({ + selector: 'activities-list', + templateUrl: './activities-list.component.html', + styleUrls: ['./activities-list.component.less'] +}) +export class ActivitiesListComponent implements OnInit { + + @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map(); + @Input() dataTypeMap: Map<string, DataTypeModel>;; + @Input() dataTypeMap$: Observable<Map<string, DataTypeModel>>; + @Input() existingActivities: IActivityParameterList; + @Input() isViewOnly: boolean; + @Output('activitiesChangeEvent') activitiesChangeEvent: EventEmitter<any> = new EventEmitter<any>(); + + readonly DEFAULT_INPUT_TYPE: string = "tosca.dataTypes.tmf.milestoneJeopardyData"; + readonly DEFAULT_INPUT_NAME: string = "TMFMilestoneJeopardyData"; + activityTypes: string[] = []; + activities: ActivityParameter[] = []; + activityFormArray: FormArray = new FormArray([]); + activityForm: FormGroup = new FormGroup ( + { + 'activityFormList': this.activityFormArray + } + ); + validationMessages = { + activity: [ + { type: 'required', message: 'Activity type and value is required'} + ] + }; + + ngOnInit() { + Object.keys(ActivityTypesEnum).forEach(key => { + this.activityTypes.push(ActivityTypesEnum[key]) + }); + this.activityForm.valueChanges.subscribe(() => { + this.emitOnActivityChange(); + }); + if (this.existingActivities && this.existingActivities.listToscaDataDefinition && this.existingActivities.listToscaDataDefinition.length > 0) { + this.existingActivities.listToscaDataDefinition.forEach(val => { + this.activities.push(val); + this.activityFormArray.push( + new FormControl(val, [Validators.required, this.formControlValidator()]) + ); + }) + } + } + + private emitOnActivityChange(): void { + this.activitiesChangeEvent.emit({ + activities: this.activities, + valid: this.activityForm.valid + }); + } + + addActivity() { + let input = new class implements IOperationParamsList { + listToscaDataDefinition: Array<InputOperationParameter> = []; + } + let activityParameter: ActivityParameter = { + type: null, + workflow: null, + inputs: input + } + this.activities.push(activityParameter); + this.activityFormArray.push( + new FormControl(activityParameter, [Validators.required, this.formControlValidator()]) + ); + + let index = this.activities.indexOf(activityParameter); + let inputOperationParameter: InputOperationParameter = new InputOperationParameter(); + inputOperationParameter.name = this.DEFAULT_INPUT_NAME; + inputOperationParameter.type = this.DEFAULT_INPUT_TYPE; + inputOperationParameter.valid = true; + this.activities[index].inputs.listToscaDataDefinition.push(inputOperationParameter); + this.activities[index].inputs.listToscaDataDefinition = Array.from(this.activities[index].inputs.listToscaDataDefinition); + } + + private formControlValidator(): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const activity = control.value; + if (!activity || !activity.type || !activity.workflow) { + return {required:true}; + } + return null; + } + } + + removeFromActivities (index: number) { + this.activities.splice(index, 1); + this.activityFormArray.removeAt(index); + } + + onActivityTypeChange(type: string, index: number) { + let activity = this.activityFormArray.controls[index].value; + activity.type = type; + this.activityFormArray.controls[index].setValue(activity); + } + + onActivityValueChange (value: any, index: number) { + let activity = this.activityFormArray.controls[index].value; + activity.workflow = value; + this.activityFormArray.controls[index].setValue(activity); + } + + collectInputNames(index: number) { + return this.activities[index].inputs.listToscaDataDefinition.map((input) => input.name); +} + + onAddInput(inputOperationParameter: InputOperationParameter, index: number) { + this.activities[index].inputs.listToscaDataDefinition.push(inputOperationParameter); + this.activities[index].inputs.listToscaDataDefinition = Array.from(this.activities[index].inputs.listToscaDataDefinition); + } + + getInputs(index: number) { + if (this.activities[index].inputs.listToscaDataDefinition) { + let test: InputOperationParameter[] = this.activities[index].inputs.listToscaDataDefinition; + return test; + } + return {}; + } + + onInputValueChange(changedInput: InputOperationParameter, index: number) { + if (changedInput.value instanceof Object) { + changedInput.value = JSON.stringify(changedInput.value); + } + const inputOperationParameter = this.activities[index].inputs.listToscaDataDefinition.find(value => value.name == changedInput.name); + inputOperationParameter.toscaFunction = null; + inputOperationParameter.value = changedInput.value; + inputOperationParameter.subPropertyToscaFunctions = changedInput.subPropertyToscaFunctions; + if (changedInput.isToscaFunction()) { + inputOperationParameter.toscaFunction = changedInput.toscaFunction; + inputOperationParameter.value = changedInput.toscaFunction.buildValueString(); + } +} + + onInputDelete(inputName: string, index: number) { + const currentInputs = this.activities[index].inputs.listToscaDataDefinition; + const input1 = currentInputs.find(value => value.name === inputName); + const indexOfInput = currentInputs.indexOf(input1); + if (indexOfInput === -1) { + console.error(`Could not delete input '${inputName}'. Input not found.`); + return; + } + currentInputs.splice(currentInputs.indexOf(input1), 1); + this.activities[index].inputs.listToscaDataDefinition = Array.from(currentInputs); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/add-input/add-input.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/add-input/add-input.component.ts index c757d5f9e3..5620d193a8 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/add-input/add-input.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/add-input/add-input.component.ts @@ -38,6 +38,7 @@ export class AddInputComponent implements OnInit { @Input('dataTypeMap') dataTypeMap$: Observable<Map<string, DataTypeModel>>; @Input('isView') isView: boolean; @Input() existingInputNames: Array<string> = []; + @Input('defaultType') defaultType: string; @Output('onAddInput') onAddInputEvent: EventEmitter<InputOperationParameter>; dataTypeMap: Map<string, DataTypeModel>; @@ -143,6 +144,9 @@ export class AddInputComponent implements OnInit { } showAddInput() { + if (this.defaultType) { + this.inputToAdd.type = this.dataTypeMap.get(this.defaultType) ? this.defaultType : undefined; + } this.showForm = true; this.showAddLink = false; } diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html new file mode 100644 index 0000000000..3103421a8d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.html @@ -0,0 +1,168 @@ +<!-- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2023 Nordix Foundation. All rights reserved. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= +--> + +<div class="sub-operation-filters"> + <div class="group-with-border content-row"> + <form novalidate class="w-sdc-form two-columns" [formGroup]="filterForm"> + <div formArrayName="filterFormList"> + <label class="filters-label"> Filters </label> + <div *ngFor="let filter of filters; let idx = index"> + <div class="side-by-side group-with-border"> + <div class="form-item"> + <div class="side-by-side"> + <div class="form-item"> + <label class="sdc-timeout-label">Name: </label> + <input type="text" + class="i-sdc-form-input" + [disabled]="isViewOnly" + [value]="filter.name" + [ngClass]="{'disabled': isViewOnly}" + (input)="onFilterNameChange($event.target.value, idx)" + required/> + </div> + <div class="form-item"> + <label>Constraint: </label> + <div class="sdc-dropdown"> + <select class="i-sdc-form-select" + data-tests-id="filter-type" + [value]="filter.constraint" + [disabled]="isViewOnly" + (change)="onFilterConstraintChange($event.target.value, idx)" + required> + <option *ngIf="filter" [value]="filter.constraint" hidden selected> + {{ ConstraintTypesMapping[filter.constraint] }} + </option> + <option *ngFor="let operatorType of operatorTypes" + [value]="operatorType"> + {{ ConstraintTypesMapping[operatorType] }} + </option> + </select> + </div> + </div> + </div> + </div> + <div class="form-item"> + <div class="side-by-side"> + <div class="form-item-big"> + <fieldset class="padding-zero" [disabled]="isViewOnly" > + <label>Value: </label> + <input type="radio" [name]="'hasGetFunctionValue.' + filter.name + idx" + [value]="false" + [checked]="!isToscaFunction(idx)" + (change)="onValueTypeChange($event.target.value, idx)"/> Value + <input type="radio" [name]="'hasGetFunctionValue.' + filter.name + idx" + [checked]="isToscaFunction(idx)" + (change)="onValueTypeChange($event.target.value, idx)" + [value]="true"/> {{'TOSCA_FUNCTION_LABEL' | translate}} + </fieldset> + <div *ngIf="isToscaFunction(idx)"> + <div *ngIf="componentInstanceMap"> + <tosca-function + [property]="getAsProperty(idx)" + [allowClear]="false" + [customToscaFunctions]="customToscaFunctions" + [componentInstanceMap]="componentInstanceMap" + (onValidityChange)="onToscaFunctionValidityChange($event, idx)"> + </tosca-function> + </div> + <div *ngIf="!componentInstanceMap"> + <tosca-function + [property]="getAsProperty(idx)" + [allowClear]="false" + [customToscaFunctions]="customToscaFunctions" + (onValidityChange)="onToscaFunctionValidityChange($event, idx)"> + </tosca-function> + </div> + </div> + <div *ngIf="!isToscaFunction(idx)"> + <div *ngIf="filter.constraint == 'validValues'"> + <div class="add-btn padding-bottom" + [ngClass]="{'disabled': isViewOnly}" + (click)="addToList(idx)"> + Add to List + </div> + <div class="w-sdc-form-columns-wrapper padding-bottom" *ngFor="let value of constraintValuesArray(idx); let valueIndex = index; trackBy:trackByFn"> + <div class="w-sdc-form-column"> + <input type="text" required + (change)="onChangeConstrainValueIndex(idx, $event.target.value, valueIndex)" + [disabled]="isViewOnly" + [value]="getInRangeValue(idx, valueIndex)"/> + </div> + <div class="w-sdc-form-column"> + <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromList(idx, valueIndex)"></span> + </div> + </div> + </div> + <div *ngIf="filter.constraint == 'inRange'"> + <div class="side-by-side"> + <div class="form-item-50"> + <input type="text" required + (input)="onChangeConstrainValueIndex(idx, $event.target.value, 0)" + [disabled]="isViewOnly" + [value]="getInRangeValue(idx, 0)"/> + </div> + <div class="form-item-50"> + <input type="text" required + (input)="onChangeConstrainValueIndex(idx, $event.target.value, 1)" + [disabled]="isViewOnly" + [value]="getInRangeValue(idx, 1)"/> + </div> + </div> + </div> + <div *ngIf="filter.constraint != 'inRange' && filter.constraint != 'validValues'"> + <input type="text" + class="i-sdc-form-input" + [disabled]="isViewOnly" + [ngClass]="{'disabled': isViewOnly}" + [value]="filter.filterValue" + (input)="onFilterValueChange($event.target.value, idx)" + required/> + </div> + </div> + </div> + <div class="form-item-icon"> + <span class="sprite-new delete-btn" [ngClass]="{'disabled': isViewOnly}" (click)="removeFromFilters(idx)"></span> + </div> + </div> + </div> + </div> + <div class="w-sdc-form-columns-wrapper"> + <div class="validation-errors"> + <ng-container *ngFor="let validation of validationMessages.filter"> + <div class="input-error" *ngIf="filterFormArray.at(idx).hasError(validation.type);"> + {{ validation.message }} + </div> + </ng-container> + </div> + </div> + </div> + + <div class="add-button-container group-with-border" *ngIf="!isViewOnly && activitiesExist"> + <a class="add-btn" data-tests-id="add-input.add-input-link" + (click)="addFilter()">Add Filter</a> + </div> + <div *ngIf="!activitiesExist"> + Must have at leat one actifity before adding filters + </div> + </div> + </form> + </div> +</div> + diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less new file mode 100644 index 0000000000..a17b4b97e4 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.less @@ -0,0 +1,110 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +@import '../../../../../../../assets/styles/variables.less'; +@import '../../../../../../../assets/styles/override.less'; + +.group-with-border { + margin: 10px 0; + padding: 10px 0; + border-top: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } +} + +.group-with-border-blue { + margin: 10px 0; + padding: 10px 0; + border-top: 1px solid @tlv_color_x; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } +} + +.i-sdc-form-select, .i-sdc-form-input { + color: #5a5a5a; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; + background-color: #ffffff; + border-radius: 2px; + margin: 0; + padding: 0; + border: solid 1px #d2d2d2; + height: 30px; + width: 100%; + display: block; +} + +.padding-bottom { + padding-bottom: 10px; +} + +.padding-zero { + padding: 0px; +} + +.side-by-side { + display: flex; + + .form-item { + flex: 1 1 auto; + + &:first-child { + margin-right: 14px; + flex-basis: 37%; + flex-grow: 0; + flex-shrink: 0; + } + + &:nth-child(3) { + margin-left: 14px; + flex: 0.4; + } + } + + .form-item-50 { + flex: 1 1 auto; + flex-grow: 0; + flex-basis: 50%; + + &:first-child { + margin-right: 14px; + } + + &:nth-child(3) { + margin-left: 14px; + } + } + + .form-item-big { + flex: 1 1 auto; + flex-grow: 0; + flex-basis: 90%; + } + + .form-item-icon { + margin-left: auto; + margin-right: 14px; + + .sprite-new { + margin-top: 110%; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts new file mode 100644 index 0000000000..3b9fdbf345 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.spec.ts @@ -0,0 +1,53 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { FiltersListComponent } from './filters-list.component'; +import { TranslateModule } from '../../../../../shared/translator/translate.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { ToscaFunctionModule } from '../../../../properties-assignment/tosca-function/tosca-function.module'; +import { SdcUiComponentsModule } from "onap-ui-angular/dist"; + +describe('FiltersListComponent', () => { + let component: FiltersListComponent; + let fixture: ComponentFixture<FiltersListComponent>; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ FiltersListComponent ], + imports: [ + FormsModule, + ReactiveFormsModule, + TranslateModule, + SdcUiComponentsModule, + ToscaFunctionModule + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(FiltersListComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts new file mode 100644 index 0000000000..5aaaea5327 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/filters-list/filters-list.component.ts @@ -0,0 +1,241 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2023 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import { Component, EventEmitter, Input, OnInit, Output, SimpleChanges} from '@angular/core'; +import { + AbstractControl, + FormArray, + FormControl, + FormGroup, ValidationErrors, + ValidatorFn, + Validators +} from '@angular/forms'; +import { + FilterParameter, + IFilterParameterList +} from '../../../../../../models/interfaceOperation'; +import { InstanceFeDetails } from '../../../../../../models/instance-fe-details'; +import { ConstraintTypes, ConstraintTypesMapping } from '../../../../../pages/properties-assignment/constraints/constraints.component'; +import {CustomToscaFunction } from '../../../../../../models/default-custom-functions'; +import { ToscaFunction } from '../../../../../../models/tosca-function'; +import { PropertyBEModel } from '../../../../../../models/properties-inputs/property-be-model'; +import { ToscaFunctionValidationEvent } from '../../../../properties-assignment/tosca-function/tosca-function.component'; + +@Component({ + selector: 'filters-list', + templateUrl: './filters-list.component.html', + styleUrls: ['./filters-list.component.less'] +}) +export class FiltersListComponent implements OnInit { + + @Input() customToscaFunctions: Array<CustomToscaFunction> = []; + @Input() componentInstanceMap: Map<string, InstanceFeDetails> = new Map(); + @Input() activitiesExist: boolean; + @Input() existingFilters: IFilterParameterList; + @Input() isViewOnly: boolean; + @Output('filtersChangeEvent') filtersChangeEvent: EventEmitter<any> = new EventEmitter<any>(); + + isAProperty: Map<number, PropertyBEModel> = new Map(); + operatorTypes: any[]; + ConstraintTypesMapping = ConstraintTypesMapping; + filters: FilterParameter[] = []; + filterFormArray: FormArray = new FormArray([]); + filterForm: FormGroup = new FormGroup ( + { + 'filterFormList': this.filterFormArray + } + ); + validationMessages = { + filter: [ + { type: 'required', message: 'Filter name, constraint, and value is required'} + ] + }; + + ngOnInit () { + this.operatorTypes = Object.keys(ConstraintTypes).map((key) => ConstraintTypes[key]); + } + + ngOnChanges (changes: SimpleChanges) { + this.filterForm.valueChanges.subscribe(() => { + this.emitOnFilterChange(); + }); + if (!changes.activitiesExist) { + return; + } + if (changes.activitiesExist.currentValue) { + this.initFilters(); + } else { + this.filters = []; + this.filterFormArray = new FormArray([]); + this. filterForm = new FormGroup ( + { + 'filterFormList': this.filterFormArray + } + ); + } + } + + private initFilters () { + if (this.existingFilters && this.existingFilters.listToscaDataDefinition && this.existingFilters.listToscaDataDefinition.length > 0) { + this.existingFilters.listToscaDataDefinition.forEach(val => { + this.filters.push(val); + this.filterFormArray.push( + new FormControl(val, [Validators.required, this.formControlValidator()]) + ); + }) + } + } + + private emitOnFilterChange (): void { + this.filtersChangeEvent.emit({ + filters: this.filters, + valid: this.filterForm.valid + }); + } + + addFilter () { + let filterParameter: FilterParameter = { + name: null, + constraint: null, + filterValue: null + } + this.filters.push(filterParameter); + this.filterFormArray.push( + new FormControl(filterParameter, [Validators.required, this.formControlValidator()]) + ); + } + + private formControlValidator (): ValidatorFn { + return (control: AbstractControl): ValidationErrors | null => { + const filter = control.value; + + if (!filter || !filter.name || !filter.constraint || !filter.filterValue) { + return {required:true}; + } + return null; + } + } + + removeFromFilters (index: number) { + this.filters.splice(index, 1); + this.filterFormArray.removeAt(index); + } + + onFilterConstraintChange (type: string, index: number) { + let filter = this.filterFormArray.controls[index].value; + filter.constraint = type; + if (ConstraintTypes.valid_values == type || ConstraintTypes.in_range == type) { + filter.filterValue = []; + } else { + filter.filterValue = ''; + } + this.filterFormArray.controls[index].setValue(filter); + } + + onFilterValueChange (value: any, index: number) { + let filter = this.filterFormArray.controls[index].value; + filter.filterValue = value; + this.filterFormArray.controls[index].setValue(filter); + } + + onFilterNameChange (value: any, index: number) { + let filter = this.filterFormArray.controls[index].value; + filter.name = value; + this.filterFormArray.controls[index].setValue(filter); + } + + getInRangeValue (index: number, valueIndex: number): string { + const value = this.filters[index].filterValue; + + if (!value || !value[valueIndex]) { + return ''; + } + + return value[valueIndex]; + } + + onChangeConstrainValueIndex (index: number, newValue: any, valueIndex: number) { + let filter = this.filterFormArray.controls[index].value; + if (!filter.filterValue) { + filter.filterValue = []; + } + filter.filterValue[valueIndex] = newValue; + this.filterFormArray.controls[index].setValue(filter); + } + + constraintValuesArray (index: number) { + let filters = this.filterForm.get('filterFormList') as FormArray; + return filters.at(index).value.filterValue; + } + + addToList (filterIndex: number) { + this.constraintValuesArray(filterIndex).push(''); + } + + removeFromList (filterIndex: number, valueIndex: number) { + this.constraintValuesArray(filterIndex).splice(valueIndex, 1); + } + + onValueTypeChange (value: string, index: number) { + if (value === 'true') { + let filter = this.filterFormArray.controls[index].value; + if (!filter.toscaFunction) { + filter.toscaFunction = {} as ToscaFunction; + } + filter.filterValue = ''; + this.filterFormArray.controls[index].setValue(filter); + } else { + let filter = this.filterFormArray.controls[index].value; + filter.toscaFunction = undefined; + this.filterFormArray.controls[index].setValue(filter); + if (this.isAProperty.has(index)) { + this.isAProperty.delete(index); + } + } + } + + getAsProperty(index: number) { + if (!this.isAProperty.has(index)) { + let filter = this.filterFormArray.controls[index].value; + let property = new PropertyBEModel(); + property.type = 'any'; + property.toscaFunction = filter.toscaFunction; + property.value = filter.filterValue; + this.isAProperty.set(index, property); + return property; + } + return this.isAProperty.get(index); + } + + onToscaFunctionValidityChange(validationEvent: ToscaFunctionValidationEvent, index: number):void { + if (validationEvent.isValid) { + let filter = this.filterFormArray.controls[index].value; + filter.toscaFunction = validationEvent.toscaFunction; + filter.filterValue = validationEvent.toscaFunction.buildValueString(); + this.filterFormArray.controls[index].setValue(filter); + } + } + + isToscaFunction(index: number): boolean { + let filter = this.filterFormArray.controls[index].value; + let toscaFunction = filter.toscaFunction; + return toscaFunction; + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts index 116b9ebc9d..80dd252a66 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/input-list/input-list-item/input-list-item.component.ts @@ -95,6 +95,7 @@ export class InputListItemComponent implements OnInit { this.property.toscaFunction = this.toscaFunction; this.valueObjRef = this.toscaFunction.value; } else { + this.property = this.property ? this.property : new PropertyBEModel(); this.property.toscaFunction = undefined; } } diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html index 38aed30918..787655542f 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.html @@ -156,5 +156,31 @@ <option value="day">Days</option> </select> </div> + + <div class="group-with-border content-row"> + <label class="sub-operations-label"> Sub operations </label> + <tabs tabStyle="basic-tabs" [hideIndicationOnTabChange]="true" (tabChanged)="tabChanged($event)"> + <div *ngFor="let milestone of milestones"> + <tab tabTitle="{{milestone}}" [active]="isActiveTab(milestone)" [highlight]="isInvalidActivity(milestone)"> + <filters-list + [customToscaFunctions]="customToscaFunctions" + [activitiesExist]="getExistingActivities(milestone) ? true : false" + [isViewOnly]="isViewOnly" + [componentInstanceMap]="componentInstanceMap" + [existingFilters]="getExistingFilters(milestone)" + (filtersChangeEvent)="filtersChangeEvent($event, milestone)"> + </filters-list> + <activities-list + [isViewOnly]="isViewOnly" + [dataTypeMap]="dataTypeMap" + [dataTypeMap$]="dataTypeMap$" + [componentInstanceMap]="componentInstanceMap" + [existingActivities]="getExistingActivities(milestone)" + (activitiesChangeEvent)="activitiesChangeEvent($event, milestone)"> + </activities-list> + </tab> + </div> + </tabs> + </div> </form> </div> diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts index 5c5ec1be34..d3f02e71c1 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.component.ts @@ -21,7 +21,17 @@ import {Component, EventEmitter, Output, ViewChild} from '@angular/core'; import { FormControl } from '@angular/forms'; import {UIInterfaceModel} from "../interface-operations.component"; -import {InputOperationParameter, InterfaceOperationModel, IOperationParamsList} from "../../../../../models/interfaceOperation"; +import { + ActivityParameter, + IActivityParameterList, + IFilterParameterList, + InputOperationParameter, + InterfaceOperationModel, + IOperationParamsList, + FilterParameter, + Milestone, + MilestoneEnum +} from "../../../../../models/interfaceOperation"; import {TranslateService} from "../../../../shared/translator/translate.service"; import {DropdownValue} from "../../../../components/ui/form-components/dropdown/ui-element-dropdown.component"; import {ArtifactModel} from "../../../../../models/artifacts"; @@ -57,7 +67,9 @@ export class InterfaceOperationHandlerComponent { validityChangedCallback: Function; isViewOnly: boolean; isEdit: boolean; - validImplementationProps:boolean; + validImplementationProps: boolean; + validMilestoneActivities: boolean; + validMilestoneFilters: boolean; modelName: string; }; @@ -73,7 +85,9 @@ export class InterfaceOperationHandlerComponent { isLoading: boolean = false; isViewOnly: boolean; isEdit: boolean; - validImplementationProps:boolean; + validImplementationProps: boolean; + validMilestoneActivities: boolean; + validMilestoneFilters: boolean; interfaceTypes: Array<DropdownValue> = []; interfaceTypeOptions: Array<DropDownOption> = []; selectedInterfaceType: DropDownOption = undefined; @@ -92,6 +106,10 @@ export class InterfaceOperationHandlerComponent { inputTypeOptions: any[]; timeoutValue = new FormControl(''); timeoutType = new FormControl(''); + invalidMilestones: string[] = []; + activeTab: string; + milestones = Object.keys(MilestoneEnum) + .map(key => MilestoneEnum[key]); constructor(private dataTypeService: DataTypeService, private componentServiceNg2: ComponentServiceNg2, @@ -102,6 +120,8 @@ export class InterfaceOperationHandlerComponent { this.isViewOnly = this.input.isViewOnly; this.isEdit = this.input.isEdit; this.validImplementationProps = this.input.validImplementationProps; + this.validMilestoneActivities = this.input.validMilestoneActivities; + this.validMilestoneFilters = this.input.validMilestoneFilters; this.componentInstanceMap = this.input.componentInstanceMap ? this.input.componentInstanceMap : null; this.interfaceType = this.input.selectedInterface.type; this.operationToUpdate = new InterfaceOperationModel(this.input.selectedInterfaceOperation); @@ -125,6 +145,9 @@ export class InterfaceOperationHandlerComponent { this.timeoutType.setValue("day"); } } + if (!this.operationToUpdate.milestones) { + this.operationToUpdate.milestones = {}; + } this.initCustomToscaFunctions(); this.initInputs(); this.removeImplementationQuote(); @@ -441,6 +464,107 @@ export class InterfaceOperationHandlerComponent { this.operationToUpdate.operationType = dropDownOption ? dropDownOption.value : undefined; this.selectedInterfaceOperation = dropDownOption ? dropDownOption : undefined; } + + getExistingFilters(key: string) { + if (this.operationToUpdate.milestones[key] && this.operationToUpdate.milestones[key].filters) { + return this.operationToUpdate.milestones[key].filters + } + return undefined; + } + + filtersChangeEvent($event: any, milestone: string) { + if ($event.valid) { + if (this.invalidMilestones.indexOf(milestone) > -1) { + this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1); + this.validMilestoneFilters = this.invalidMilestones.length < 1; + this.validMilestoneActivities = this.invalidMilestones.length < 1; + } + let operationMilestone = this.operationToUpdate.milestones[milestone]; + if (!operationMilestone) { + operationMilestone = new Milestone(); + } + operationMilestone.filters = new class implements IFilterParameterList { + listToscaDataDefinition: Array<FilterParameter> = []; + } + let milestoneFilters = $event.filters; + for (let filter of milestoneFilters) { + let filterParameter = new FilterParameter(); + filterParameter.constraint = filter.constraint; + filterParameter.name = filter.name; + filterParameter.filterValue = filter.filterValue; + filterParameter.toscaFunction = filter.toscaFunction; + operationMilestone.filters.listToscaDataDefinition.push(filterParameter); + } + this.operationToUpdate.milestones[milestone] = operationMilestone; + } else { + if (this.invalidMilestones.indexOf(milestone) == -1) { + this.invalidMilestones.push(milestone); + } + this.validMilestoneFilters = false; + this.validMilestoneActivities = false; + } + } + + getExistingActivities(key: string) { + if ( + this.operationToUpdate.milestones[key] + && this.operationToUpdate.milestones[key].activities + && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition + && this.operationToUpdate.milestones[key].activities.listToscaDataDefinition.length > 0 + ) { + return this.operationToUpdate.milestones[key].activities + } + return undefined; + } + + activitiesChangeEvent($event: any, milestone: string) { + if ($event.valid) { + if (this.invalidMilestones.indexOf(milestone) > -1) { + this.invalidMilestones.splice(this.invalidMilestones.indexOf(milestone), 1); + this.validMilestoneActivities = this.invalidMilestones.length < 1; + this.validMilestoneFilters = this.invalidMilestones.length < 1; + } + let operationMilestone = this.operationToUpdate.milestones[milestone]; + if (!operationMilestone) { + operationMilestone = new Milestone(); + } + operationMilestone.activities = new class implements IActivityParameterList { + listToscaDataDefinition: Array<ActivityParameter> = []; + } + let milestoneActivities = $event.activities; + for (let activity of milestoneActivities) { + let activityParameter = new ActivityParameter(); + activityParameter.type = activity.type; + activityParameter.workflow = activity.workflow; + activityParameter.inputs = activity.inputs; + operationMilestone.activities.listToscaDataDefinition.push(activityParameter); + } + this.operationToUpdate.milestones[milestone] = operationMilestone; + } else { + if (this.invalidMilestones.indexOf(milestone) == -1) { + this.invalidMilestones.push(milestone); + } + this.validMilestoneActivities = false; + this.validMilestoneFilters = false; + } + } + + isActiveTab(title: string): boolean { + if (this.activeTab) { + return this.activeTab == title; + } + return this.milestones[0] == title; + } + + tabChanged = (event) => { + this.activeTab = event.title; + } + + isInvalidActivity(title: string) { + if (this.invalidMilestones.indexOf(title) > -1) { + return "#cf2a2a"; + } + } } class DropDownOption implements IDropDownOption { diff --git a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts index 3fa20ab624..f79de44ef7 100644 --- a/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/interface-operatons/operation-creator/interface-operation-handler.module.ts @@ -19,22 +19,25 @@ * ============LICENSE_END========================================================= */ -import {NgModule} from "@angular/core"; -import {CommonModule} from "@angular/common"; +import { NgModule} from '@angular/core'; +import { CommonModule} from '@angular/common'; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {BrowserModule} from '@angular/platform-browser'; -import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {TranslateModule} from "app/ng2/shared/translator/translate.module"; -import {AddInputComponent} from './add-input/add-input.component'; -import {InputListComponent} from './input-list/input-list.component'; -import {InputListItemComponent} from './input-list/input-list-item/input-list-item.component'; -import {PropertyParamRowComponent} from "./property-param-row/property-param-row.component"; -import {InterfaceOperationHandlerComponent} from "./interface-operation-handler.component"; -import {SdcUiComponentsModule} from "onap-ui-angular/dist"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {PropertyTableModule} from "app/ng2/components/logic/properties-table/property-table.module"; -import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function/tosca-function.module'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { AddInputComponent } from './add-input/add-input.component'; +import { InputListComponent } from './input-list/input-list.component'; +import { InputListItemComponent } from './input-list/input-list-item/input-list-item.component'; +import { PropertyParamRowComponent } from './property-param-row/property-param-row.component'; +import { InterfaceOperationHandlerComponent } from './interface-operation-handler.component'; +import { SdcUiComponentsModule } from 'onap-ui-angular/dist'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { PropertyTableModule } from 'app/ng2/components/logic/properties-table/property-table.module'; +import { ToscaFunctionModule } from '../../../properties-assignment/tosca-function/tosca-function.module'; +import { TabModule } from 'app/ng2/components/ui/tabs/tabs.module'; +import { FiltersListComponent } from './filters-list/filters-list.component'; +import { ActivitiesListComponent } from './activities-list/activities-list.component'; @NgModule({ declarations: [ @@ -42,7 +45,9 @@ import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function PropertyParamRowComponent, AddInputComponent, InputListComponent, - InputListItemComponent + InputListItemComponent, + FiltersListComponent, + ActivitiesListComponent ], imports: [ CommonModule, @@ -54,7 +59,8 @@ import {ToscaFunctionModule} from '../../../properties-assignment/tosca-function UiElementsModule, PropertyTableModule, ReactiveFormsModule, - ToscaFunctionModule + ToscaFunctionModule, + TabModule ], exports: [ PropertyParamRowComponent, diff --git a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts index 23c855ecb1..82afb0a3e7 100644 --- a/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-definition/interface-definition.page.component.ts @@ -156,7 +156,9 @@ export class InterfaceDefinitionComponent { openOperation: OperationModel; enableWorkflowAssociation: boolean; workflowIsOnline: boolean; - validImplementationProps:boolean = true; + validImplementationProps: boolean = true; + validMilestoneActivities: boolean = true; + validMilestoneFilters: boolean = true; serviceInterfaces: InterfaceModel[]; @Input() component: IComponent; @@ -186,7 +188,6 @@ export class InterfaceDefinitionComponent { ngOnInit(): void { this.isLoading = true; this.interfaces = []; - //this.disableFlag = this.readonly; this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner')); Observable.forkJoin( this.ComponentServiceNg2.getInterfaceOperations(this.component), @@ -218,7 +219,7 @@ export class InterfaceDefinitionComponent { }); this.onInstanceSelectedUpdate(this.instancesNavigationData[0]); this.loadingInstances = false; - + }; if (this.enableWorkflowAssociation && this.workflowIsOnline) { this.WorkflowServiceNg2.getWorkflows().subscribe( @@ -275,7 +276,6 @@ export class InterfaceDefinitionComponent { } this.interfaces = newInterfaces.map((interf) => new UIInterfaceModel(interf)); } else { - //this.disableFlag = this.readonly; this.interfaces = this.serviceInterfaces.map((interf) => new UIInterfaceModel(interf)); } this.sortInterfaces(); @@ -299,7 +299,12 @@ export class InterfaceDefinitionComponent { if (this.component.isService()) { return disable; } - + const validMilestoneActivities = this.modalInstance.instance.dynamicContent.instance.validMilestoneActivities; + const validMilestoneFilters = this.modalInstance.instance.dynamicContent.instance.validMilestoneFilters; + if (!validMilestoneActivities || !validMilestoneFilters) { + return disable; + } + let selectedInterfaceOperation = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceOperation; let isInterfaceOperation:boolean = !(typeof selectedInterfaceOperation == 'undefined' || _.isEmpty(selectedInterfaceOperation)); let selectedInterfaceType = this.modalInstance.instance.dynamicContent.instance.selectedInterfaceType; @@ -344,6 +349,8 @@ export class InterfaceDefinitionComponent { validityChangedCallback: this.disableSaveButton, isViewOnly: this.readonly, validImplementationProps: this.validImplementationProps, + validMilestoneActivities: this.validMilestoneActivities, + validMilestoneFilters: this.validMilestoneFilters, 'isEdit': isEdit, interfaceTypesMap: this.interfaceTypesMap, modelName: this.component.model diff --git a/catalog-ui/src/app/utils/common-utils.ts b/catalog-ui/src/app/utils/common-utils.ts index b4e184ab44..92398e871d 100644 --- a/catalog-ui/src/app/utils/common-utils.ts +++ b/catalog-ui/src/app/utils/common-utils.ts @@ -181,13 +181,16 @@ export class CommonUtils { newOperation.interfaceType = interf.type; newOperation.interfaceId = interf.uniqueId; - const {inputs, outputs} = operation; + const {inputs, outputs, milestones} = operation; if (inputs) { newOperation.createInputsList(inputs.listToscaDataDefinition); } if (outputs) { newOperation.createOutputsList(outputs.listToscaDataDefinition); } + if (milestones) { + newOperation.milestones = milestones; + } return newOperation; } |