Add 'Req & Cap' screen for VF/PNF/Service - UI
Issue-ID: SDC-2142
Change-Id: I23a2de18862e18389f801cbec3e452d7094df8e9
Signed-off-by: miriame <miriam.eini@amdocs.com>
diff --git a/catalog-ui/src/app/app.ts b/catalog-ui/src/app/app.ts
index 8fa7f1e..ebcfdd2 100644
--- a/catalog-ui/src/app/app.ts
+++ b/catalog-ui/src/app/app.ts
@@ -388,6 +388,18 @@
}
);
+ $stateProvider.state(
+ States.WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE, {
+ url: 'req_and_capabilities_editable',
+ parent: 'workspace',
+ controller: viewModelsModuleName + '.ReqAndCapabilitiesViewModel',
+ templateUrl: './view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html',
+ data: {
+ bodyClass: 'attributes'
+ }
+ }
+ );
+
$stateProvider.state(
States.WORKSPACE_MANAGEMENT_WORKFLOW, {
diff --git a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html
index 6ddbd61..d59c44d 100644
--- a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html
+++ b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.html
@@ -18,7 +18,8 @@
{{actualText}}
<span class="ellipsis-directive-more-less"
- data-ng-click="collapsed = !collapsed; toggleText()"
- data-ng-hide="ellipsis.length <= maxChars">
- {{collapsed ? "More" : "Less"}}
+ data-ng-click="onMoreLessClick($event)"
+ data-ng-hide="ellipsis.length <= maxChars"
+ data-tests-id="ellipsis-more-less">
+ {{actualText ? (collapsed ? "More" : "Less") : ""}}
</span>
diff --git a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts
index 60baf3e..21e074a 100644
--- a/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts
+++ b/catalog-ui/src/app/directives/ellipsis/ellipsis-directive.ts
@@ -23,6 +23,7 @@
ellipsis:string;
maxChars:number;
toggleText():void;
+ onMoreLessClick(event): void;
collapsed:boolean;
actualText:string;
@@ -50,6 +51,12 @@
scope.collapsed = true;
+ scope.onMoreLessClick = (event): void => {
+ event.stopPropagation();
+ scope.collapsed = !scope.collapsed;
+ scope.toggleText();
+ };
+
scope.toggleText = ():void => {
if (scope.ellipsis && scope.collapsed) {
scope.actualText = scope.ellipsis.substr(0, scope.maxChars);
diff --git a/catalog-ui/src/app/models.ts b/catalog-ui/src/app/models.ts
index 3a31caf..5c79155 100644
--- a/catalog-ui/src/app/models.ts
+++ b/catalog-ui/src/app/models.ts
@@ -112,7 +112,11 @@
export * from './models/button';
export * from './models/wizard-step';
export * from './models/radio-button';
-export * from './models/filter-properties-assignment-data'
-export * from './models/properties-inputs/input-be-model'
-export * from './models/service-instance-properties'
+export * from './models/filter-properties-assignment-data';
+export * from './models/properties-inputs/input-be-model';
+export * from './models/service-instance-properties';
+export * from './models/relationship-types';
+export * from './models/tosca-presentation';
+export * from './models/node-types';
+export * from './models/capability-types';
diff --git a/catalog-ui/src/app/models/capability-types.ts b/catalog-ui/src/app/models/capability-types.ts
new file mode 100644
index 0000000..fc01f54
--- /dev/null
+++ b/catalog-ui/src/app/models/capability-types.ts
@@ -0,0 +1,41 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {ToscaPresentationData} from "./tosca-presentation";
+
+export class CapabilityTypesMap {
+ capabilityTypesMap: CapabilityTypesMapData;
+
+ constructor(capabilityTypesMap: CapabilityTypesMapData) {
+ this.capabilityTypesMap = capabilityTypesMap;
+ }
+}
+
+export class CapabilityTypesMapData {
+ [capabilityTypeId: string]: CapabilityTypeModel;
+}
+
+export class CapabilityTypeModel {
+ derivedFrom: string;
+ toscaPresentation: ToscaPresentationData;
+
+ constructor(capabilityType?: CapabilityTypeModel) {
+ if (capabilityType) {
+ this.derivedFrom = capabilityType.derivedFrom;
+ this.toscaPresentation = capabilityType.toscaPresentation;
+ }
+ }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/models/capability.ts b/catalog-ui/src/app/models/capability.ts
index 74994b1..caef2e8 100644
--- a/catalog-ui/src/app/models/capability.ts
+++ b/catalog-ui/src/app/models/capability.ts
@@ -57,7 +57,7 @@
uniqueId:string;
capabilitySources:Array<String>;
leftOccurrences:string;
- minOccurrences:string;
+ minOccurrences: number;
maxOccurrences:string;
description:string;
validSourceTypes:Array<string>;
diff --git a/catalog-ui/src/app/models/component-metadata.ts b/catalog-ui/src/app/models/component-metadata.ts
index 9f5e22c..0f0a30d 100644
--- a/catalog-ui/src/app/models/component-metadata.ts
+++ b/catalog-ui/src/app/models/component-metadata.ts
@@ -49,6 +49,7 @@
public systemName:string;
public archived:boolean;
public vspArchived: boolean;
+ public toscaResourceName: string;
//Resource only
public resourceType: string;
@@ -118,6 +119,7 @@
this.archived = response.archived;
this.instantiationType = response.instantiationType;
this.vspArchived = response.vspArchived;
+ this.toscaResourceName = response.toscaResourceName;
return this;
}
diff --git a/catalog-ui/src/app/models/node-types.ts b/catalog-ui/src/app/models/node-types.ts
new file mode 100644
index 0000000..54d2ef5
--- /dev/null
+++ b/catalog-ui/src/app/models/node-types.ts
@@ -0,0 +1,45 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {ComponentMetadata} from "./component-metadata";
+
+export class NodeTypesMap {
+ nodeTypesMap: NodeTypesMapData;
+
+ constructor(nodeTypesMap: NodeTypesMapData) {
+ this.nodeTypesMap = nodeTypesMap;
+ }
+}
+
+export class NodeTypesMapData {
+ [nodeTypeId: string]: NodeTypeModel;
+}
+
+export class NodeTypeModel {
+ componentMetadataDefinition: ComponentMetadataDefModel;
+
+ constructor(nodeType?: NodeTypeModel) {
+ this.componentMetadataDefinition = nodeType.componentMetadataDefinition;
+ }
+}
+
+export class ComponentMetadataDefModel {
+ componentMetadataDataDefinition: ComponentMetadata;
+
+ constructor(componentMetadataDef?: ComponentMetadataDefModel) {
+ this.componentMetadataDataDefinition = componentMetadataDef.componentMetadataDataDefinition;
+ }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/models/relationship-types.ts b/catalog-ui/src/app/models/relationship-types.ts
new file mode 100644
index 0000000..8ae827b
--- /dev/null
+++ b/catalog-ui/src/app/models/relationship-types.ts
@@ -0,0 +1,41 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {ToscaPresentationData} from "./tosca-presentation";
+
+export class RelationshipTypesMap {
+ relationshipTypesMap: RelationshipTypesMapData;
+
+ constructor(relationshipTypesMap: RelationshipTypesMapData) {
+ this.relationshipTypesMap = relationshipTypesMap;
+ }
+}
+
+export class RelationshipTypesMapData {
+ [relationshipTypeId: string]: RelationshipTypeModel;
+}
+
+export class RelationshipTypeModel {
+ derivedFrom: string;
+ toscaPresentation: ToscaPresentationData;
+
+ constructor(relationshipType?: RelationshipTypeModel) {
+ if (relationshipType) {
+ this.derivedFrom = relationshipType.derivedFrom;
+ this.toscaPresentation = relationshipType.toscaPresentation;
+ }
+ }
+}
diff --git a/catalog-ui/src/app/models/requirement.ts b/catalog-ui/src/app/models/requirement.ts
index 65428b3..3cc0cf2 100644
--- a/catalog-ui/src/app/models/requirement.ts
+++ b/catalog-ui/src/app/models/requirement.ts
@@ -51,7 +51,7 @@
uniqueId:string;
relationship:string;
leftOccurrences:string;
- minOccurrences:string;
+ minOccurrences: number;
maxOccurrences:string;
//custom
filterTerm:string;
diff --git a/catalog-ui/src/app/models/tosca-presentation.ts b/catalog-ui/src/app/models/tosca-presentation.ts
new file mode 100644
index 0000000..3fdddde
--- /dev/null
+++ b/catalog-ui/src/app/models/tosca-presentation.ts
@@ -0,0 +1,35 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+export class ToscaPresentationData {
+ creationTime: number;
+ description: string;
+ type: string;
+ validTargetTypes: Array<string>;
+ modificationTime: number;
+ uniqueId: string;
+
+ constructor(toscaPresentation?: ToscaPresentationData) {
+ if (toscaPresentation) {
+ this.creationTime = toscaPresentation.creationTime;
+ this.description = toscaPresentation.description;
+ this.type = toscaPresentation.type;
+ this.validTargetTypes = toscaPresentation.validTargetTypes;
+ this.modificationTime = toscaPresentation.modificationTime;
+ this.uniqueId = toscaPresentation.uniqueId;
+ }
+ }
+}
diff --git a/catalog-ui/src/app/modules/service-module.ts b/catalog-ui/src/app/modules/service-module.ts
index f4350a3..376a036 100644
--- a/catalog-ui/src/app/modules/service-module.ts
+++ b/catalog-ui/src/app/modules/service-module.ts
@@ -61,6 +61,7 @@
import {AutomatedUpgradeService} from "../ng2/pages/automated-upgrade/automated-upgrade.service";
import {ArchiveService as ArchiveServiceNg2} from "app/ng2/services/archive.service";
import {ComponentFactory} from "app/utils/component-factory";
+import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service";
let moduleName:string = 'Sdc.Services';
let serviceModule:ng.IModule = angular.module(moduleName, []);
@@ -115,3 +116,4 @@
serviceModule.factory('DynamicComponentService', downgradeInjectable(DynamicComponentService));
serviceModule.factory('ArchiveServiceNg2', downgradeInjectable(ArchiveServiceNg2));
serviceModule.factory('AutomatedUpgradeService', downgradeInjectable(AutomatedUpgradeService));
+serviceModule.factory('ToscaTypesServiceNg2', downgradeInjectable(ToscaTypesServiceNg2));
diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts
index 1ae2df2..0ff9378 100644
--- a/catalog-ui/src/app/ng2/app.module.ts
+++ b/catalog-ui/src/app/ng2/app.module.ts
@@ -43,6 +43,7 @@
import { ServiceServiceNg2 } from "./services/component-services/service.service";
import { ComponentInstanceServiceNg2 } from "./services/component-instance-services/component-instance.service";
import { WorkflowServiceNg2 } from './services/workflow.service';
+import {ToscaTypesServiceNg2} from "./services/tosca-types.service";
import { ModalService } from "./services/modal.service";
import { UiElementsModule } from "./components/ui/ui-elements.module";
import { ConnectionWizardModule } from "./pages/connection-wizard/connection-wizard.module";
@@ -73,6 +74,8 @@
import {PoliciesService} from "./services/policies.service";
import {AutomatedUpgradeService} from "./pages/automated-upgrade/automated-upgrade.service";
import {AutomatedUpgradeModule} from "./pages/automated-upgrade/automated-upgrade.module";
+import {RequirementsEditorModule} from "./pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module"
+import {CapabilitiesEditorModule} from "./pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module"
export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule));
@@ -114,12 +117,14 @@
ServicePathModule,
ServicePathSelectorModule,
ServiceDependenciesModule,
- ServiceDependenciesEditorModule
+ ServiceDependenciesEditorModule,
+ RequirementsEditorModule,
+ CapabilitiesEditorModule
],
exports: [],
entryComponents: [
// *** sdc-ui components to be used as downgraded:
- // SdcUiComponents.ButtonComponent
+ SdcUiComponents.SvgIconComponent
],
providers: [
WindowRef,
@@ -143,6 +148,7 @@
ServiceServiceNg2,
AutomatedUpgradeService,
WorkflowServiceNg2,
+ ToscaTypesServiceNg2,
HttpService,
UserService,
PoliciesService,
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html
new file mode 100644
index 0000000..c0bfc8a
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.html
@@ -0,0 +1,92 @@
+<div class="capability-editor">
+ <form class="w-sdc-form">
+ <div class="i-sdc-form-content-capability-content">
+ <div class="content-row">
+ <div class="i-sdc-form-item">
+ <sdc-input
+ label="{{ 'CAP_NAME' | translate}}"
+ required="true"
+ class="i-sdc-form-input"
+ testId="capName"
+ [disabled]="isReadonly"
+ [(value)]="capabilityData.name"
+ (valueChange)="validityChanged()">
+ </sdc-input>
+ </div>
+ </div>
+
+ <div class="group-with-border">
+ <div class="content-row i-sdc-form-item">
+ <sdc-dropdown
+ label="{{ 'CAP_TYPE' | translate}}"
+ required="true"
+ class="i-sdc-form-select"
+ testId="capType"
+ [disabled]="isReadonly"
+ [options]="capabilityTypesMappedList"
+ selectedOption="{{capabilityData.type}}"
+ (changed)="onSelectCapabilityType($event)">
+ </sdc-dropdown>
+ </div>
+ <div class="content-row i-sdc-form-item">
+ <label class="i-sdc-form-label"> {{ 'CAP_DESCRIPTION' | translate}} </label>
+ <textarea
+ rows="3"
+ class="i-sdc-form-input description"
+ data-tests-id="capDesc"
+ disabled
+ value="{{capabilityData.description}}">
+ </textarea>
+ </div>
+ <div class="content-row i-sdc-form-item">
+ <label class="i-sdc-form-label valid-source-label"> {{ 'CAP_VALID_SOURCE' | translate}} </label>
+ <textarea
+ rows="2"
+ class="i-sdc-form-input"
+ data-tests-id="capValidSrc"
+ disabled
+ value="{{capabilityData.validSourceTypes}}">
+ </textarea>
+ </div>
+ </div>
+
+ <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label>
+ <div class="content-row occurrences-section">
+ <div class="min-occurrences-value">
+ <sdc-input
+ label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}"
+ class="i-sdc-form-input"
+ testId="capOccurrencesMin"
+ [disabled]="isReadonly"
+ [(value)]="capabilityData.minOccurrences"
+ (valueChange)="validityChanged()"
+ type="number">
+ </sdc-input>
+ </div>
+ <div class="sdc-input">
+ <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label>
+ <div class="max-occurrences-value">
+ <sdc-checkbox
+ class="checkbox-label unbounded-value"
+ testId="capOccurrencesMaxUnbounded"
+ label="{{translatedUnboundTxt.toLowerCase()}}"
+ (checkedChange)="onUnboundedChanged()"
+ [checked]="isUnboundedChecked"
+ [disabled]="isReadonly">
+ </sdc-checkbox>
+
+ <sdc-input
+ *ngIf="!isUnboundedChecked"
+ class="i-sdc-form-input"
+ testId="capOccurrencesMax"
+ [disabled]="isReadonly"
+ [(value)]="capabilityData.maxOccurrences"
+ (valueChange)="validityChanged()"
+ type="number">
+ </sdc-input>
+ </div>
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less
new file mode 100644
index 0000000..9156281
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.less
@@ -0,0 +1,38 @@
+@import '../../../../../assets/styles/variables.less';
+
+.capability-editor {
+ .i-sdc-form-content-capability-content {
+ padding: 10px 25px;
+ .group-with-border {
+ margin: 25px 0;
+ padding: 15px 0;
+ border-top: 1px solid @tlv_color_u;
+ border-bottom: 1px solid @tlv_color_u;
+ .content-row:not(:last-of-type) {
+ padding-bottom: 13px;
+ }
+ }
+
+ .occurrences-label {
+ font-family: @font-opensans-bold;
+ margin-bottom: 19px;
+ }
+ .occurrences-section, /deep/ .max-occurrences-value {
+ display: flex;
+ .min-occurrences-value {
+ padding-right: 30px;
+ }
+ .unbounded-value {
+ padding-top: 7px;
+ padding-right: 20px;
+ .sdc-checkbox__label {
+ text-transform: capitalize;
+ }
+ }
+ }
+ textarea {
+ min-height: unset;
+ height: unset;
+ }
+ }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts
new file mode 100644
index 0000000..82e2e46
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component.ts
@@ -0,0 +1,73 @@
+import {Component} from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {Capability, CapabilityTypeModel} from 'app/models';
+import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+
+@Component({
+ selector: 'capabilities-editor',
+ templateUrl: './capabilities-editor.component.html',
+ styleUrls: ['./capabilities-editor.component.less'],
+ providers: [ServiceServiceNg2]
+})
+
+export class CapabilitiesEditorComponent {
+ input: {
+ capability: Capability,
+ capabilityTypesList: Array<CapabilityTypeModel>,
+ isReadonly: boolean;
+ validityChangedCallback: Function;
+ };
+ capabilityData: Capability;
+ capabilityTypesMappedList: Array<DropdownValue>;
+ isUnboundedChecked: boolean;
+ isReadonly: boolean;
+ translatedUnboundTxt: string;
+
+ constructor(private translateService: TranslateService) {
+ }
+
+ ngOnInit() {
+ this.capabilityData = new Capability(this.input.capability);
+ this.translatedUnboundTxt = '';
+ this.capabilityData.minOccurrences = this.capabilityData.minOccurrences || 0;
+ this.translateService.languageChangedObservable.subscribe(lang => {
+ this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED');
+ this.capabilityData.maxOccurrences = this.capabilityData.maxOccurrences || this.translatedUnboundTxt;
+ this.isUnboundedChecked = this.capabilityData.maxOccurrences === this.translatedUnboundTxt;
+ });
+ this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type));
+ this.isReadonly = this.input.isReadonly;
+ this.validityChanged();
+ }
+
+ onUnboundedChanged() {
+ this.isUnboundedChecked = !this.isUnboundedChecked;
+ this.capabilityData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null;
+ this.validityChanged();
+ }
+
+ checkFormValidForSubmit() {
+ return this.capabilityData.name && this.capabilityData.name.length &&
+ this.capabilityData.type && this.capabilityData.type.length && !_.isEqual(this.capabilityData.minOccurrences, "") && this.capabilityData.minOccurrences >= 0 &&
+ (
+ this.isUnboundedChecked ||
+ (this.capabilityData.maxOccurrences && (this.capabilityData.minOccurrences <= parseInt(this.capabilityData.maxOccurrences)))
+ );
+ }
+
+ onSelectCapabilityType(selectedCapType: DropdownValue) {
+ this.capabilityData.type = selectedCapType && selectedCapType.value;
+ if (selectedCapType && selectedCapType.value) {
+ let selectedCapabilityTypeObj: CapabilityTypeModel = this.input.capabilityTypesList.find(capType => capType.toscaPresentation.type === selectedCapType.value);
+ this.capabilityData.description = selectedCapabilityTypeObj.toscaPresentation.description;
+ this.capabilityData.validSourceTypes = selectedCapabilityTypeObj.toscaPresentation.validTargetTypes;
+ }
+ this.validityChanged();
+ }
+
+ validityChanged = () => {
+ let validState = this.checkFormValidForSubmit();
+ this.input.validityChangedCallback(validState);
+ }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts
new file mode 100644
index 0000000..1e767a5
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts
@@ -0,0 +1,28 @@
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {CapabilitiesEditorComponent} from "./capabilities-editor.component";
+import {FormsModule} from "@angular/forms";
+import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module";
+import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module";
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index";
+
+@NgModule({
+ declarations: [
+ CapabilitiesEditorComponent
+ ],
+ imports: [CommonModule,
+ FormsModule,
+ FormElementsModule,
+ UiElementsModule,
+ TranslateModule,
+ SdcUiComponentsModule
+ ],
+ exports: [],
+ entryComponents: [
+ CapabilitiesEditorComponent
+ ],
+ providers: []
+})
+export class CapabilitiesEditorModule {
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html
new file mode 100644
index 0000000..0fe326e
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.html
@@ -0,0 +1,88 @@
+<div class="requirement-editor">
+ <form class="w-sdc-form">
+ <div class="i-sdc-form-content-requirement-content">
+ <div class="content-row">
+ <div class="i-sdc-form-item">
+ <sdc-input
+ label="{{ 'REQ_NAME' | translate}}"
+ required="true"
+ testId="reqName"
+ [disabled]="isReadonly"
+ [(value)]="requirementData.name"
+ (valueChange)="validityChanged()">
+ </sdc-input>
+ </div>
+ </div>
+
+ <div class="group-with-border">
+ <div class="content-row i-sdc-form-item">
+ <sdc-dropdown
+ label="{{ 'REQ_RELATED_CAPABILITY' | translate}}"
+ testId="reqRelatedCapability"
+ required="true"
+ [disabled]="isReadonly"
+ [options]="capabilityTypesMappedList"
+ selectedOption="{{requirementData.capability}}"
+ (changed)="onCapabilityChanged($event)">
+ </sdc-dropdown>
+ </div>
+ <div class="content-row i-sdc-form-item">
+ <sdc-dropdown
+ label="{{ 'REQ_NODE' | translate}}"
+ testId="reqNode"
+ [disabled]="isReadonly"
+ [options]="nodeTypesMappedList"
+ selectedOption="{{requirementData.node}}"
+ (changed)="onNodeChanged($event)">
+ </sdc-dropdown>
+ </div>
+ <div class="content-row i-sdc-form-item">
+ <sdc-dropdown
+ label="{{ 'REQ_RELATIONSHIP' | translate}}"
+ testId="reqRelationship"
+ [disabled]="isReadonly"
+ [options]="relationshipTypesMappedList"
+ selectedOption="{{requirementData.relationship}}"
+ (changed)="onRelationshipChanged($event)">
+ </sdc-dropdown>
+ </div>
+ </div>
+
+ <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label>
+ <div class="content-row occurrences-section">
+ <div class="min-occurrences-value">
+ <sdc-input
+ label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}"
+ testId="reqOccurrencesMin"
+ [disabled]="isReadonly"
+ [(value)]="requirementData.minOccurrences"
+ (valueChange)="validityChanged()"
+ type="number">
+ </sdc-input>
+ </div>
+ <div class="sdc-input">
+ <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label>
+ <div class="max-occurrences-value">
+ <sdc-checkbox
+ class="checkbox-label unbounded-value"
+ testId="reqOccurrencesMaxUnbounded"
+ label="{{translatedUnboundTxt.toLowerCase()}}"
+ (checkedChange)="onUnboundedChanged()"
+ [checked]="isUnboundedChecked"
+ [disabled]="isReadonly">
+ </sdc-checkbox>
+ <sdc-input
+ *ngIf="!isUnboundedChecked"
+ testId="reqOccurrencesMax"
+ [disabled]="isReadonly"
+ [(value)]="requirementData.maxOccurrences"
+ (valueChange)="validityChanged()"
+ type="number">
+ </sdc-input>
+ </div>
+
+ </div>
+ </div>
+ </div>
+ </form>
+</div>
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less
new file mode 100644
index 0000000..f0ae3bf
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.less
@@ -0,0 +1,35 @@
+@import '../../../../../assets/styles/variables.less';
+
+.requirement-editor {
+ .i-sdc-form-content-requirement-content {
+ padding: 10px 25px;
+
+ .group-with-border {
+ margin: 25px 0;
+ padding: 15px 0;
+ border-top: 1px solid @tlv_color_u;
+ border-bottom: 1px solid @tlv_color_u;
+ .content-row:not(:last-of-type) {
+ padding-bottom: 13px;
+ }
+ }
+
+ .occurrences-label {
+ font-family: @font-opensans-bold;
+ margin-bottom: 19px;
+ }
+ .occurrences-section, /deep/ .max-occurrences-value {
+ display: flex;
+ .min-occurrences-value {
+ padding-right: 30px;
+ }
+ .unbounded-value {
+ padding-top: 7px;
+ padding-right: 20px;
+ .sdc-checkbox__label {
+ text-transform: capitalize;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts
new file mode 100644
index 0000000..464b581
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component.ts
@@ -0,0 +1,88 @@
+import {Component} from '@angular/core';
+import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service";
+import {Requirement, RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel} from 'app/models';
+import {TranslateService} from 'app/ng2/shared/translator/translate.service';
+import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component";
+
+@Component({
+ selector: 'requirements-editor',
+ templateUrl: 'requirements-editor.component.html',
+ styleUrls: ['requirements-editor.component.less'],
+ providers: [ServiceServiceNg2, TranslateService]
+})
+
+export class RequirementsEditorComponent {
+
+ input: {
+ requirement: Requirement,
+ relationshipTypesList: Array<RelationshipTypeModel>;
+ nodeTypesList: Array<NodeTypeModel>;
+ capabilityTypesList: Array<CapabilityTypeModel>;
+ isReadonly: boolean;
+ validityChangedCallback: Function;
+ };
+ requirementData: Requirement;
+ capabilityTypesMappedList: Array<DropdownValue>;
+ relationshipTypesMappedList: Array<DropdownValue>;
+ nodeTypesMappedList: Array<DropdownValue>;
+ isUnboundedChecked: boolean;
+ isReadonly: boolean;
+ translatedUnboundTxt: string;
+
+ constructor(private translateService: TranslateService) {
+ }
+
+ ngOnInit() {
+ this.requirementData = new Requirement(this.input.requirement);
+ this.requirementData.minOccurrences = this.requirementData.minOccurrences || 0;
+ this.translatedUnboundTxt = '';
+ this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type));
+ this.relationshipTypesMappedList = _.map(this.input.relationshipTypesList, rType => new DropdownValue(rType.toscaPresentation.type, rType.toscaPresentation.type));
+ this.nodeTypesMappedList = _.map(this.input.nodeTypesList, nodeType => {
+ return new DropdownValue(
+ nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName,
+ nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName)
+ });
+ this.translateService.languageChangedObservable.subscribe(lang => {
+ this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED');
+ this.requirementData.maxOccurrences = this.requirementData.maxOccurrences || this.translatedUnboundTxt;
+ this.isUnboundedChecked = this.requirementData.maxOccurrences === this.translatedUnboundTxt;
+ });
+ this.isReadonly = this.input.isReadonly;
+ this.validityChanged();
+ }
+
+ onUnboundedChanged() {
+ this.isUnboundedChecked = !this.isUnboundedChecked;
+ this.requirementData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null;
+ this.validityChanged();
+ }
+
+ onCapabilityChanged(selectedCapability: DropdownValue) {
+ this.requirementData.capability = selectedCapability && selectedCapability.value;
+ this.validityChanged();
+ }
+
+ onNodeChanged(selectedNode: DropdownValue) {
+ this.requirementData.node = selectedNode && selectedNode.value;
+ }
+
+ onRelationshipChanged(selectedRelationship: DropdownValue) {
+ this.requirementData.relationship = selectedRelationship && selectedRelationship.value;
+ }
+
+ checkFormValidForSubmit() {
+ return this.requirementData.name && this.requirementData.name.length &&
+ this.requirementData.capability && this.requirementData.capability.length && !_.isEqual(this.requirementData.minOccurrences, "") && this.requirementData.minOccurrences >= 0 &&
+ (
+ this.isUnboundedChecked ||
+ (this.requirementData.maxOccurrences && (this.requirementData.minOccurrences <= parseInt(this.requirementData.maxOccurrences)))
+ );
+ }
+
+ validityChanged = () => {
+ let validState = this.checkFormValidForSubmit();
+ this.input.validityChangedCallback(validState);
+ }
+
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts
new file mode 100644
index 0000000..1be8be5
--- /dev/null
+++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts
@@ -0,0 +1,26 @@
+import {NgModule} from "@angular/core";
+import {CommonModule} from "@angular/common";
+import {RequirementsEditorComponent} from "./requirements-editor.component";
+import {FormsModule} from "@angular/forms";
+import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module";
+import {TranslateModule} from 'app/ng2/shared/translator/translate.module';
+import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index";
+
+@NgModule({
+ declarations: [
+ RequirementsEditorComponent
+ ],
+ imports: [CommonModule,
+ FormsModule,
+ FormElementsModule,
+ TranslateModule,
+ SdcUiComponentsModule
+ ],
+ exports: [],
+ entryComponents: [
+ RequirementsEditorComponent
+ ],
+ providers: []
+})
+export class RequirementsEditorModule {
+}
\ No newline at end of file
diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
index 41bfc4e..46dfe01 100644
--- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts
+++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts
@@ -25,7 +25,8 @@
import 'rxjs/add/operator/toPromise';
import {Response, URLSearchParams} from '@angular/http';
import { Component, ComponentInstance, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData,
- PropertyBEModel, OperationModel, BEOperationModel} from "app/models";
+ PropertyBEModel, OperationModel, BEOperationModel, Capability, Requirement
+} from "app/models";
import {downgradeInjectable} from '@angular/upgrade/static';
import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils";
import {ComponentGenericResponse} from "../responses/component-generic-response";
@@ -207,6 +208,68 @@
return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]);
}
+ createCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> {
+ let capBEObj = {
+ 'capabilities': {
+ [capabilityData.type]: [capabilityData]
+ }
+ };
+ return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ updateCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> {
+ let capBEObj = {
+ 'capabilities': {
+ [capabilityData.type]: [capabilityData]
+ }
+ };
+ return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ deleteCapability(component: Component, capId: string): Observable<Capability> {
+ return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities/' + capId)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ createRequirement(component: Component, requirementData: Requirement): Observable<any> {
+ let reqBEObj = {
+ 'requirements': {
+ [requirementData.capability]: [requirementData]
+ }
+ };
+ return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ updateRequirement(component: Component, requirementData: Requirement): Observable<any> {
+ let reqBEObj = {
+ 'requirements': {
+ [requirementData.capability]: [requirementData]
+ }
+ };
+ return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ deleteRequirement(component: Component, reqId: string): Observable<Requirement> {
+ return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements/' + reqId)
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> {
return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]);
}
diff --git a/catalog-ui/src/app/ng2/services/tosca-types.service.ts b/catalog-ui/src/app/ng2/services/tosca-types.service.ts
new file mode 100644
index 0000000..66826c0
--- /dev/null
+++ b/catalog-ui/src/app/ng2/services/tosca-types.service.ts
@@ -0,0 +1,55 @@
+/*!
+ * Copyright © 2016-2018 European Support Limited
+ *
+ * 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.
+ */
+
+import {Injectable, Inject} from '@angular/core';
+import {Observable} from 'rxjs/Observable';
+import {HttpService} from './http.service';
+import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config";
+import {CapabilityTypesMap, NodeTypesMap, RelationshipTypesMap} from "app/models";
+import {Response} from '@angular/http';
+
+declare var angular: angular.IAngularStatic;
+
+@Injectable()
+export class ToscaTypesServiceNg2 {
+
+ protected baseUrl;
+
+ constructor(protected http: HttpService, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) {
+ this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root;
+ }
+
+ fetchRelationshipTypes(): Observable<RelationshipTypesMap> {
+ return this.http.get(this.baseUrl + 'relationshipTypes')
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ fetchNodeTypes(): Observable<NodeTypesMap> {
+ return this.http.get(this.baseUrl + 'nodeTypes')
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+
+ fetchCapabilityTypes(): Observable<CapabilityTypesMap> {
+ return this.http.get(this.baseUrl + 'capabilityTypes')
+ .map((res: Response) => {
+ return res.json();
+ });
+ }
+}
diff --git a/catalog-ui/src/app/utils/constants.ts b/catalog-ui/src/app/utils/constants.ts
index 379f2f1..104b5dc 100644
--- a/catalog-ui/src/app/utils/constants.ts
+++ b/catalog-ui/src/app/utils/constants.ts
@@ -264,6 +264,7 @@
public static WORKSPACE_DISTRIBUTION = 'workspace.distribution';
public static WORKSPACE_PROPERTIES_ASSIGNMENT = 'workspace.properties_assignment';
public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES = 'workspace.reqAndCap';
+ public static WORKSPACE_REQUIREMENTS_AND_CAPABILITIES_EDITABLE = 'workspace.reqAndCapEditable';
public static WORKSPACE_PLUGINS = 'workspace.plugins';
public static WORKSPACE_NG2 = 'workspace.ng2';
}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html
new file mode 100644
index 0000000..14bc49e
--- /dev/null
+++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-editable-view.html
@@ -0,0 +1,197 @@
+<!--
+ ~ Copyright © 2016-2018 European Support Limited
+ ~
+ ~ 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.
+-->
+
+<div class="workspace-req-and-cap-editable">
+ <loader data-display="isLoading"></loader>
+
+ <div class="tabs-header">
+ <div class="req-and-cap-tabs">
+ <div data-tests-id="req-tab" data-ng-click="onSwitchTab()" class="tab"
+ data-ng-class="{'selected':mode=='requirements'}">Requirements
+ </div>
+ <div data-tests-id="cap-tab" data-ng-click="onSwitchTab()" class="tab"
+ data-ng-class="{'selected':mode=='capabilities'}">Capabilities
+ </div>
+ </div>
+ <div class="buttons-in-right" data-ng-if="!isListEmpty()">
+ <div class="search">
+ <input id="search-box" data-ng-if="filter.show" data-tests-id="search-box" placeholder="Search"
+ data-ng-model-options="{debounce: 200}" data-ng-model="filter.txt" data-ng-change="onFilter()"/>
+ <div class="search-icon-container" data-tests-id="search-icon">
+ <svg-icon
+ class="hand"
+ [name]="'search-o'"
+ [mode]="'primary'"
+ [size]="'small'"
+ [clickable]="'true'"
+ data-ng-click="onSearchIconClick()">
+ </svg-icon>
+ </div>
+ </div>
+ <div class="add-button-icon-and-label" data-ng-if="isEditable" data-ng-click="onAddBtnClicked()"
+ data-ng-class="{'disabled': isReadonly()}" data-tests-id="add-button">
+ <svg-icon
+ name="plus"
+ mode="primary"
+ size="small"
+ clickable="true"
+ [disabled]="isReadonly()"
+ labelPlacement="top">
+ </svg-icon>
+ <span class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="empty-list-container" data-ng-if="isListEmpty() && !isLoading" data-tests-id="empty-list-container">
+ <div class="empty-list-add-btn add-button-icon-and-label" data-ng-class="{'disabled': isReadonly()}"
+ data-ng-click="onAddBtnClicked()" data-tests-id="empty-list-add-btn">
+ <svg-icon
+ name="plus-circle"
+ mode="primary"
+ size="x_large"
+ clickable="true"
+ [disabled]="isReadonly()">
+ </svg-icon>
+ <div class="icon-label-txt">{{mode === 'requirements' ? 'Add Requirement' : 'Add Capability'}}</div>
+ </div>
+ </div>
+
+ <div class="table-container-flex requirements-table" data-ng-if="mode=='requirements' && !isListEmpty()">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}" data-tests-id="requirement-table">
+ <div class="head flex-container">
+ <div data-ng-repeat="header in editableRequirementsTableHeadersList track by $index"
+ data-ng-click="sort(header.property, requirementsSortTableDefined)"
+ class="table-header head-row hand flex-item {{header.property}}"
+ data-tests-id="table-header-{{header.property}}">
+ {{header.title}}
+ <span data-ng-if="requirementsSortTableDefined.sortByField === header.property"
+ class="table-header-sort-arrow" data-tests-id="table-header-sort-arrow"
+ data-ng-class="{'down': requirementsSortTableDefined.reverse, 'up':!requirementsSortTableDefined.reverse}"> </span>
+ </div>
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data">
+ <div data-ng-if="filteredRequirementsList.length === 0" class="no-row-text"
+ data-tests-id="no-rows-in-table">
+ There are no requirements to display
+
+ </div>
+ <div data-ng-repeat="req in filteredRequirementsList | orderBy:requirementsSortTableDefined.sortByField:requirementsSortTableDefined.reverse track by $index"
+ data-tests-id="reqRow">
+ <div class="flex-container data-row" data-ng-class="{'editable-row': req.isCreatedManually}"
+ data-ng-click="req.isCreatedManually && onEditRequirement(req)">
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{req.name}}">
+ <span data-tests-id="{{req.name}}">{{req.name}}</span>
+ </div>
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{req.capability}}">
+ <span data-tests-id="{{req.capability}}">{{req.capability && cutToscaTypePrefix(req.capability, 'capabilities.')}}</span>
+ </div>
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{req.node}}">
+ <span data-tests-id="{{req.node}}">{{req.node && cutToscaTypePrefix(req.node, "nodes.")}}</span>
+ </div>
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{req.relationship}}">
+ <span data-tests-id="{{req.relationship}}">{{req.relationship && cutToscaTypePrefix(req.relationship, "relationships.")}}</span>
+ </div>
+ <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips
+ tooltip-content="{{req.minOccurrences}} - {{req.maxOccurrences}}">
+ <span data-tests-id="{{req.minOccurrences}} - {{req.maxOccurrences}}">{{req.minOccurrences}} - {{req.maxOccurrences}}</span>
+ </div>
+ <div class="table-col-general flex-item text other-col" data-tests-id="delete-req"
+ data-ng-class="{'disabled': isReadonly()}">
+ <svg-icon name="trash-o" class="trash-icon" size="small"
+ data-ng-if="req.isCreatedManually && !isReadonly()"
+ data-ng-click="onDeleteReq($event, req)"></svg-icon>
+ </div>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+ <div class="table-container-flex capabilities-table" data-ng-if="mode=='capabilities' && !isListEmpty()"
+ data-tests-id="capabilities-table">
+ <div class="table" data-ng-class="{'view-mode': isViewMode()}">
+ <div class="head flex-container">
+ <div data-ng-repeat="header in editableCapabilitiesTableHeadersList track by $index"
+ data-ng-click="sort(header.property, capabilitiesSortTableDefined)"
+ class="table-header head-row hand flex-item {{header.property}}"
+ data-tests-id="header-{{header.property}}">
+ {{header.title}}
+ <span data-ng-if="capabilitiesSortTableDefined.sortByField === header.property"
+ class="table-header-sort-arrow" data-tests-id=="table-header-sort-arrow"
+ data-ng-class="{'down': capabilitiesSortTableDefined.reverse, 'up':!capabilitiesSortTableDefined.reverse}"> </span>
+ </div>
+ </div>
+
+ <div class="body">
+ <perfect-scrollbar scroll-y-margin-offset="0" include-padding="true" class="editable-table-data">
+ <div data-ng-if="filteredCapabilitiesList.length === 0" class="no-row-text"
+ data-tests-id="no-rows-in-table">
+ There are no capabilities to display
+
+ </div>
+ <div data-ng-repeat="capability in filteredCapabilitiesList | orderBy:capabilitiesSortTableDefined.sortByField:capabilitiesSortTableDefined.reverse track by $index"
+ class="flex-container data-row"
+ data-ng-class="{'selected': capability.selected, 'editable-row': capability.isCreatedManually}"
+ data-ng-click="capability.isCreatedManually && onEditCapability(capability)"
+ data-tests-id="capabilities-table-row">
+
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{capability.name}}">
+ <span data-tests-id="{{capability.name}}">{{capability.name}}</span>
+ </div>
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{capability.type}}">
+ <span data-tests-id="{{capability.type}}">{{capability.type && cutToscaTypePrefix(capability.type, 'capabilities.')}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text description-col">
+ <div data-tests-id="{{capability.description}}" class="multiline-ellipsis"
+ ellipsis="capability.description" max-chars="60">{{capability.description}}
+ </div>
+ </div>
+
+ <div class="table-col-general flex-item text ellipsis-text" tooltips
+ tooltip-content="{{capability.validSourceTypes.join(',')}}">
+ <span data-tests-id="{{capability.validSourceTypes.join(',')}}">{{capability.validSourceTypes.join(',')}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text ellipsis-text occurrences-col" tooltips
+ tooltip-content="{{capability.minOccurrences}} - {{capability.maxOccurrences}}">
+ <span data-tests-id="{{capability.minOccurrences}} - {{capability.maxOccurrences}}">{{capability.minOccurrences}} - {{capability.maxOccurrences}}</span>
+ </div>
+
+ <div class="table-col-general flex-item text other-col" data-tests-id="delete-cap"
+ data-ng-class="{'disabled': isReadonly()}">
+ <svg-icon name="trash-o" class="trash-icon" size="small"
+ data-ng-if="capability.isCreatedManually && !isReadonly()"
+ data-ng-click="onDeleteCap($event, capability)"></svg-icon>
+ </div>
+ </div>
+ </perfect-scrollbar>
+ </div>
+
+ </div>
+ </div>
+</div>
+
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
index 6eaae44..165578d 100644
--- a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
+++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities-view-model.ts
@@ -23,33 +23,82 @@
*/
'use strict';
import * as _ from "lodash";
+import {ComponentRef} from '@angular/core';
import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model";
-import {ModalsHandler} from "app/utils";
-import {Capability, PropertyModel, Requirement} from "app/models";
-import {ComponentGenericResponse} from "../../../../ng2/services/responses/component-generic-response";
-import {ComponentServiceNg2} from "../../../../ng2/services/component-services/component.service";
+import {ModalsHandler, ResourceType} from "app/utils";
+import {ComponentType} from "app/utils/constants";
+import {
+ Capability, PropertyModel, Requirement, Resource,
+ RelationshipTypesMap, NodeTypesMap, CapabilityTypesMap
+} from "app/models";
+import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response";
+import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service";
+import {ToscaTypesServiceNg2} from "app/ng2/services/tosca-types.service";
+import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component';
+import {ModalService} from 'app/ng2/services/modal.service';
+import {RequirementsEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.component';
+import {CapabilitiesEditorComponent} from 'app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.component';
+import {ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service";
+import {IModalConfig} from "sdc-ui/lib/angular/modals/models/modal-config";
+import {ModalButtonComponent} from "sdc-ui/lib/angular/components";
export class SortTableDefined {
reverse:boolean;
sortByField:string;
}
+class RequirementUI extends Requirement {
+ isCreatedManually: boolean;
+
+ constructor(input: Requirement, componentUniqueId: string) {
+ super(input);
+ this.isCreatedManually = input.ownerId === componentUniqueId;
+ }
+}
+class CapabilityUI extends Capability {
+ isCreatedManually: boolean;
+
+ constructor(input: Capability, componentUniqueId: string) {
+ super(input);
+ this.isCreatedManually = input.ownerId === componentUniqueId;
+ }
+}
+
interface IReqAndCapabilitiesViewModelScope extends IWorkspaceViewModelScope {
requirementsTableHeadersList:Array<any>;
+ editableRequirementsTableHeadersList: Array<any>;
capabilitiesTableHeadersList:Array<any>;
+ editableCapabilitiesTableHeadersList: Array<any>;
capabilityPropertiesTableHeadersList:Array<any>;
requirementsSortTableDefined:SortTableDefined;
capabilitiesSortTableDefined:SortTableDefined;
propertiesSortTableDefined:SortTableDefined;
- requirements:Array<Requirement>;
- capabilities:Array<Capability>;
+ requirements: Array<RequirementUI>;
+ filteredRequirementsList: Array<RequirementUI>;
+ capabilities: Array<CapabilityUI>;
+ filteredCapabilitiesList: Array<CapabilityUI>;
mode:string;
filteredProperties:Array<Array<PropertyModel>>;
searchText:string;
+ isEditable: boolean;
+ modalInstance: ComponentRef<ModalComponent>;
+ filter: {txt: string; show: boolean};
sort(sortBy:string, sortByTableDefined:SortTableDefined):void;
+ sortByIsCreatedManually(arrToSort: Array<RequirementUI|CapabilityUI>): Array<any>;
updateProperty(property:PropertyModel, indexInFilteredProperties:number):void;
allCapabilitiesSelected(selected:boolean):void;
+ onAddBtnClicked(): void;
+ onEditRequirement(req: RequirementUI): void;
+ onEditCapability(cap: CapabilityUI): void;
+ onDeleteReq(event, req: RequirementUI): void;
+ onDeleteCap(event, cap: CapabilityUI): void;
+ onFilter(): void;
+ isListEmpty(): boolean;
+ onSwitchTab(): void;
+ onSearchIconClick(): void;
+ cutToscaTypePrefix(valToCut: string, textToStartCut: string): string;
+ isReadonly(): boolean;
}
export class ReqAndCapabilitiesViewModel {
@@ -58,33 +107,37 @@
'$scope',
'$filter',
'ModalsHandler',
- 'ComponentServiceNg2'
+ 'ComponentServiceNg2',
+ 'ToscaTypesServiceNg2',
+ 'ModalServiceNg2',
+ 'ModalServiceSdcUI'
];
constructor(private $scope:IReqAndCapabilitiesViewModelScope,
private $filter:ng.IFilterService,
private ModalsHandler:ModalsHandler,
- private ComponentServiceNg2: ComponentServiceNg2) {
+ private ComponentServiceNg2: ComponentServiceNg2,
+ private ToscaTypesServiceNg2: ToscaTypesServiceNg2,
+ private ModalServiceNg2: ModalService,
+ private ModalServiceSdcUI: ModalServiceSdcUI) {
this.initCapabilitiesAndRequirements();
+ this.fetchCapabilitiesRelatedData();
}
private initCapabilitiesAndRequirements = (): void => {
- if(!this.$scope.component.capabilities || !this.$scope.component.requirements) {
- this.$scope.isLoading = true;
- this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response:ComponentGenericResponse) => {
- this.$scope.component.capabilities = response.capabilities;
- this.$scope.component.requirements = response.requirements;
- this.initScope();
- this.$scope.isLoading = false;
- }, () => {
- this.$scope.isLoading = false;
- });
- } else {
+ this.$scope.isEditable = this.getIsEditableByComponentType();
+ this.$scope.isLoading = true;
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.component.componentType, this.$scope.component.uniqueId).subscribe((response: ComponentGenericResponse) => {
+ this.$scope.component.capabilities = response.capabilities;
+ this.$scope.component.requirements = response.requirements;
this.initScope();
- }
+ this.$scope.isLoading = false;
+ }, () => {
+ this.$scope.isLoading = false;
+ });
}
@@ -98,15 +151,18 @@
});
};
- private initScope = ():void => {
-
+ private initScope = (currentMode = 'requirements'): void => {
+ this.$scope.isReadonly = (): boolean => {
+ return this.$scope.isViewMode() || !this.$scope.isDesigner();
+ };
+ this.$scope.filter = {txt: '', show: false};
this.$scope.requirementsSortTableDefined = {
reverse: false,
- sortByField: 'name'
+ sortByField: this.$scope.isEditable ? 'other' : 'name'
};
this.$scope.capabilitiesSortTableDefined = {
reverse: false,
- sortByField: 'name'
+ sortByField: this.$scope.isEditable ? 'other' : 'name'
};
this.$scope.propertiesSortTableDefined = {
reverse: false,
@@ -129,6 +185,22 @@
{title: 'Valid Source', property: ''},
{title: 'Occurrences', property: ''}
];
+ this.$scope.editableRequirementsTableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Capability', property: 'capability'},
+ {title: 'Node', property: 'node'},
+ {title: 'Relationship', property: 'relationship'},
+ {title: 'Occurrences', property: 'occurrences'},
+ {title: '●●●', property: 'other'}
+ ];
+ this.$scope.editableCapabilitiesTableHeadersList = [
+ {title: 'Name', property: 'name'},
+ {title: 'Type', property: 'type'},
+ {title: 'Description', property: 'description'},
+ {title: 'Valid Sources', property: 'valid-sources'},
+ {title: 'Occurrences', property: 'occurrences'},
+ {title: '●●●', property: 'other'}
+ ];
this.$scope.capabilityPropertiesTableHeadersList = [
{title: 'Name', property: 'name'},
{title: 'Type', property: 'type'},
@@ -137,17 +209,26 @@
];
this.$scope.filteredProperties = [];
- this.$scope.mode = 'requirements';
+ this.$scope.mode = currentMode;
this.$scope.requirements = [];
_.forEach(this.$scope.component.requirements, (req:Array<Requirement>, capName)=> {
- this.$scope.requirements = this.$scope.requirements.concat(req);
+ let reqUIList: Array<RequirementUI> = _.map(req, reqObj => new RequirementUI(reqObj, this.$scope.component.uniqueId));
+ this.$scope.requirements = this.$scope.requirements.concat(reqUIList);
});
+ this.$scope.filteredRequirementsList = this.$scope.requirements;
this.$scope.capabilities = [];
_.forEach(this.$scope.component.capabilities, (cap:Array<Capability>, capName)=> {
- this.$scope.capabilities = this.$scope.capabilities.concat(cap);
+ let capUIList: Array<CapabilityUI> = _.map(cap, capObj => new CapabilityUI(capObj, this.$scope.component.uniqueId));
+ this.$scope.capabilities = this.$scope.capabilities.concat(capUIList);
});
+ this.$scope.sortByIsCreatedManually = (arrToSort: Array<RequirementUI|CapabilityUI>): Array<any> => {
+ return arrToSort.sort((elem1: RequirementUI|CapabilityUI, elem2: RequirementUI|CapabilityUI) => +elem2.isCreatedManually - (+elem1.isCreatedManually));
+ };
+ this.$scope.filteredCapabilitiesList = this.$scope.sortByIsCreatedManually(this.$scope.capabilities);
+ this.$scope.filteredRequirementsList = this.$scope.sortByIsCreatedManually(this.$scope.requirements);
+
this.$scope.sort = (sortBy:string, sortByTableDefined:SortTableDefined):void => {
sortByTableDefined.reverse = (sortByTableDefined.sortByField === sortBy) ? !sortByTableDefined.reverse : false;
sortByTableDefined.sortByField = sortBy;
@@ -162,6 +243,226 @@
cap.selected = selected;
});
};
+ this.$scope.onAddBtnClicked = (): void => {
+ switch (this.$scope.mode) {
+ case 'requirements':
+ this.openRequirementsModal();
+ break;
+ case 'capabilities':
+ this.openCapabilitiesModal();
+ break;
+ }
+ };
+ this.$scope.onEditRequirement = (req: RequirementUI): void => {
+ this.openRequirementsModal(req);
+ };
+ this.$scope.onEditCapability = (cap: CapabilityUI): void => {
+ this.openCapabilitiesModal(cap);
+ };
+ this.$scope.onDeleteReq = (event: Event, req: RequirementUI): void => {
+ event.stopPropagation();
+ this.ModalServiceSdcUI.openAlertModal('Delete Requirement',
+ `Are you sure you want to delete requirement: ${req.name}?`, 'OK', () => this.deleteRequirement(req), 'Cancel');
+ };
+ this.$scope.onDeleteCap = (event: Event, cap: CapabilityUI): void => {
+ event.stopPropagation();
+ this.ModalServiceSdcUI.openAlertModal('Delete Capability',
+ `Are you sure you want to delete capability: ${cap.name}?`, 'OK', () => this.deleteCapability(cap), 'Cancel');
+ };
+ this.$scope.onSearchIconClick = (): void => {
+ this.$scope.filter.show = !!this.$scope.filter.txt || !this.$scope.filter.show;
+ };
+ this.$scope.onFilter = (): void => {
+ switch (this.$scope.mode) {
+ case 'requirements':
+ this.$scope.filteredRequirementsList = _.filter(this.$scope.requirements, req => req.name.includes(this.$scope.filter.txt));
+ break;
+ case 'capabilities':
+ this.$scope.filteredCapabilitiesList = _.filter(this.$scope.capabilities, cap => cap.name.includes(this.$scope.filter.txt));
+ break;
+ }
+ };
+ this.$scope.isListEmpty = (): boolean => {
+ switch (this.$scope.mode) {
+ case 'requirements':
+ return this.$scope.requirements.length === 0;
+ case 'capabilities':
+ return this.$scope.capabilities.length === 0;
+ }
+ };
+ this.$scope.onSwitchTab = (): void => {
+ this.$scope.mode = this.$scope.mode === 'requirements' ? 'capabilities' : 'requirements';
+ this.$scope.filter.txt = '';
+ this.$scope.filter.show = false;
+ this.$scope.filteredRequirementsList = this.$scope.requirements;
+ this.$scope.filteredCapabilitiesList = this.$scope.capabilities;
+ };
+ this.$scope.cutToscaTypePrefix = (valToCut: string, textToStartCut: string): string => {
+ let index = valToCut.indexOf(textToStartCut);
+ return index !== -1 ? valToCut.substr(index + textToStartCut.length) : valToCut;
+ };
+ };
+
+ private getIsEditableByComponentType() {
+ if (this.$scope.componentType === ComponentType.SERVICE) {
+ return true;
+ }
+ if (this.$scope.component.isResource()) {
+ let componentAsResource: Resource = <Resource>this.$scope.component;
+ return componentAsResource.resourceType === ResourceType.VF ||
+ componentAsResource.resourceType === ResourceType.PNF;
+ }
+ return false;
+ };
+
+ private fetchCapabilitiesRelatedData() {
+ if (this.$scope.isEditable) {
+ this.$scope.capabilityTypesList = [];
+ this.ToscaTypesServiceNg2.fetchCapabilityTypes().subscribe((result: CapabilityTypesMap) => {
+ _.forEach(result, capabilityType => this.$scope.capabilityTypesList.push(capabilityType));
+ });
+ this.$scope.nodeTypesList = [];
+ this.ToscaTypesServiceNg2.fetchNodeTypes().subscribe((result: NodeTypesMap) => {
+ _.forEach(result, nodeType => this.$scope.nodeTypesList.push(nodeType));
+ });
+ this.$scope.relationshipTypesList = [];
+ this.ToscaTypesServiceNg2.fetchRelationshipTypes().subscribe((result: RelationshipTypesMap) => {
+ _.forEach(result, relshipType => this.$scope.relationshipTypesList.push(relshipType));
+ });
+ }
+ }
+
+ private openRequirementsModal(req?: RequirementUI) {
+ let modalConfig: IModalConfig = {
+ size: 'md',
+ title: (req ? 'Update' : 'Add') + ' Requirement',
+ type: 'custom',
+ buttons: [
+ {
+ id: 'saveButton',
+ text: (req ? 'Update' : 'Create'),
+ size: "'x-small'",
+ callback: () => this.createOrUpdateRequirement(),
+ closeModal: true
+ },
+ {text: "Cancel", size: "'x-small'", closeModal: true}]
+ };
+ let modalInputs = {
+ requirement: req,
+ relationshipTypesList: this.$scope.relationshipTypesList,
+ nodeTypesList: this.$scope.nodeTypesList,
+ capabilityTypesList: this.$scope.capabilityTypesList,
+ isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(),
+ validityChangedCallback: this.getDisabled
+ };
+
+ this.ModalServiceSdcUI.openCustomModal(modalConfig, RequirementsEditorComponent, {input: modalInputs});
+ }
+
+ private openCapabilitiesModal(cap?: CapabilityUI) {
+ let modalConfig: IModalConfig = {
+ size: 'md',
+ title: (cap ? 'Update' : 'Add') + ' Capability',
+ type: 'custom',
+ buttons: [
+ {
+ id: 'saveButton',
+ text: (cap ? 'Update' : 'Create'),
+ size: "'x-small'",
+ callback: () => this.createOrUpdateCapability(),
+ closeModal: true
+ },
+ {text: "Cancel", size: "'x-small'", closeModal: true}]
+ };
+ let modalInputs = {
+ capability: cap,
+ capabilityTypesList: this.$scope.capabilityTypesList,
+ isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(),
+ validityChangedCallback: this.getDisabled
+ };
+
+ this.ModalServiceSdcUI.openCustomModal(modalConfig, CapabilitiesEditorComponent, {input: modalInputs});
+ }
+
+ getDisabled = (shouldEnable: boolean): void => {
+ let saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton');
+ saveButton.disabled = this.$scope.isViewMode() || !this.$scope.isDesigner() || !shouldEnable;
+ };
+
+ private createOrUpdateRequirement() {
+ let requirement = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.requirementData;
+ this.$scope.isLoading = true;
+ if (!requirement.uniqueId) {
+ this.ComponentServiceNg2.createRequirement(this.$scope.component, requirement).subscribe(result => {
+ this.$scope.requirements.unshift(new RequirementUI(result[0], this.$scope.component.uniqueId));
+ this.$scope.isLoading = false;
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }
+ else {
+ this.ComponentServiceNg2.updateRequirement(this.$scope.component, requirement).subscribe(result => {
+ let index = this.$scope.requirements.findIndex(req => result[0].uniqueId === req.uniqueId);
+ this.$scope.requirements[index] = new RequirementUI(result[0], this.$scope.component.uniqueId);
+ this.$scope.isLoading = false;
+ this.$scope.$apply();
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }
+ }
+
+ private createOrUpdateCapability() {
+ let capability = this.ModalServiceSdcUI.getCurrentInstance().innerModalContent.instance.capabilityData;
+ this.$scope.isLoading = true;
+ if (!capability.uniqueId) {
+ this.ComponentServiceNg2.createCapability(this.$scope.component, capability).subscribe(result => {
+ this.$scope.capabilities.unshift(new CapabilityUI(result[0], this.$scope.component.uniqueId));
+ this.$scope.isLoading = false;
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }
+ else {
+ this.ComponentServiceNg2.updateCapability(this.$scope.component, capability).subscribe(result => {
+ let index = this.$scope.capabilities.findIndex(cap => result[0].uniqueId === cap.uniqueId);
+ this.$scope.capabilities[index] = new CapabilityUI(result[0], this.$scope.component.uniqueId);
+ this.$scope.isLoading = false;
+ this.$scope.$apply();
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }
+ }
+
+ private deleteRequirement(req) {
+ this.$scope.isLoading = true;
+ this.ComponentServiceNg2.deleteRequirement(this.$scope.component, req.uniqueId).subscribe(() => {
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => {
+ this.$scope.component.requirements = response.requirements;
+ this.initScope('requirements');
+ this.$scope.isLoading = false;
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }
+
+ private deleteCapability(cap) {
+ this.$scope.isLoading = true;
+ this.ComponentServiceNg2.deleteCapability(this.$scope.component, cap.uniqueId).subscribe(() => {
+ this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.$scope.componentType, this.$scope.component.uniqueId).subscribe(response => {
+ this.$scope.component.capabilities = response.capabilities;
+ this.initScope('capabilities');
+ this.$scope.isLoading = false;
+ }, () => {
+ this.$scope.isLoading = false;
+ });
+ }, () => {
+ this.$scope.isLoading = false;
+ });
}
}
diff --git a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
index 9b52fad..fa6623f 100644
--- a/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
+++ b/catalog-ui/src/app/view-models/workspace/tabs/req-and-capabilities/req-and-capabilities.less
@@ -65,13 +65,14 @@
}
}
}
- .expand-collapse-buttons{
+ .add-button {
+ color: @main_color_a;
+ }
+ .add-button, .expand-collapse-buttons {
float: right;
- width: 44px;
margin-left: 11px;
margin-top: 10px;
- span{
- vertical-align: bottom;
+ &, span {
.hand;
}
}
@@ -108,6 +109,28 @@
white-space: nowrap;
}
+ .editable-table-data {
+ max-height: 430px;
+ }
+
+ .data-row {
+ &:not(.editable-row) {
+ background: @tlv_color_t;
+ color: @main_color_n;
+ }
+ &.editable-row {
+ cursor: pointer;
+ }
+ .sprite-new.delete-icon {
+ visibility: hidden;
+ }
+ &:hover {
+ .sprite-new.delete-icon {
+ visibility: visible;
+ }
+ }
+ }
+
&.requirements-table{
border-top: 4px solid @main_color_a;
.flex-item:nth-child(1) {
@@ -194,3 +217,165 @@
}
}
+
+.workspace-req-and-cap-editable {
+ .tabs-header {
+ display: flex;
+ justify-content: space-between;
+ border-bottom: 1px solid @main_color_o;
+ .req-and-cap-tabs {
+ display: flex;
+ .tab {
+ font-family: @font-opensans-regular;
+ font-size: 22px;
+ padding: 5px;
+ .hand;
+ &:first-of-type {
+ margin-right: 35px;
+ }
+ &.selected {
+ color: @main_color_a;
+ border-bottom: 2px solid @main_color_a;
+ }
+ }
+ }
+ .buttons-in-right {
+ display: flex;
+ .search {
+ display: flex;
+ height: min-content;
+ margin-top: 10px;
+ padding-right: 11px;
+ border-right: 1px solid @main_color_o;
+ #search-box {
+ border: none;
+ border-bottom: 1px solid @main_color_o;
+ text-indent: 10px;
+ &:focus {
+ outline: none;
+ }
+ }
+ .search-icon-container {
+ margin-top: 3px;
+ padding-top: 4px;
+ }
+
+ }
+ .add-button-icon-and-label {
+ font-size: 14px;
+ margin-left: 11px;
+ margin-top: 10px;
+ padding-top: 5px;
+ /deep/ svg-icon {
+ vertical-align: bottom;
+ }
+ &:hover {
+ &:not(.disabled) {
+ cursor: pointer;
+ color: @sdcui_color_light-blue;
+ }
+ }
+ }
+ }
+ }
+ .add-button-icon-and-label {
+ .icon-label-txt {
+ text-transform: uppercase;
+ font-family: @font-opensans-medium;
+ color: @main_color_a;
+ &:hover {
+ &:not(.disabled) {
+ color: @sdcui_color_light-blue;
+ }
+ }
+ }
+ }
+ .empty-list-container {
+ width: 100%;
+ display: flex;
+ justify-content: center;
+
+ .empty-list-add-btn {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid @main_color_o;
+ margin-top: 50px;
+ height: 229px;
+ width: 480px;
+ &.disabled {
+ pointer-events: none;
+ }
+ &:hover {
+ &:not(.disabled) {
+ border: 1px solid @main_color_a;
+ cursor: pointer;
+ }
+ }
+ .icon-label-txt {
+ margin-top: 15px;
+ font-size: 16px;
+ }
+ }
+ }
+ .table-container-flex .table .head .head-row {
+ text-align: left;
+ &.description {
+ flex: 2;
+ }
+ &.other {
+ flex: 0.25;
+ text-align: center;
+ }
+ &.occurrences {
+ flex: 0.75;
+ }
+ }
+ .data-row {
+ .ellipsis-text {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ &:not(.editable-row) {
+ background: @tlv_color_t;
+ cursor: default;
+ color: @main_color_n;
+ }
+ &.editable-row {
+ cursor: pointer;
+ .table-col-general:hover {
+ color: @main_color_b;
+ }
+ }
+ .description-col {
+ flex: 2;
+ }
+ .occurrences-col {
+ flex: 0.75;
+ }
+ .other-col {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ flex: 0.25;
+ .trash-icon {
+ visibility: hidden;
+ }
+ }
+ &:hover {
+ .trash-icon {
+ visibility: visible;
+ }
+ }
+ .multiline-ellipsis {
+ line-height: 1.5em;
+ padding: 1px 0 1px 0;
+ /deep/ .ellipsis-directive-more-less {
+ float: none;
+ margin-left: 5px;
+ color: @main_color_a;
+ }
+ }
+ }
+}
diff --git a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
index 676a2d3..9429022 100644
--- a/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
+++ b/catalog-ui/src/app/view-models/workspace/workspace-view-model.ts
@@ -23,7 +23,10 @@
*/
'use strict';
import * as _ from "lodash";
-import {IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions} from "app/models";
+import {
+ IUserProperties, IAppMenu, Resource, Component, Plugin, PluginsConfiguration, PluginDisplayOptions,
+ RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel
+} from "app/models";
import {
WorkspaceMode, ComponentFactory, ChangeLifecycleStateHandler, Role, ComponentState, MenuItemGroup, MenuHandler,
MenuItem, ModalsHandler, States, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ResourceType, PREVIOUS_CSAR_COMPONENT
@@ -78,6 +81,9 @@
unsavedChanges:boolean;
unsavedChangesCallback:Function;
unsavedFile:boolean;
+ capabilityTypesList: Array<CapabilityTypeModel>;
+ relationshipTypesList: Array<RelationshipTypeModel>;
+ nodeTypesList: Array<NodeTypeModel>;
startProgress(message:string):void;
diff --git a/catalog-ui/src/assets/languages/en_US.json b/catalog-ui/src/assets/languages/en_US.json
index 41f543a..b81dadf 100644
--- a/catalog-ui/src/assets/languages/en_US.json
+++ b/catalog-ui/src/assets/languages/en_US.json
@@ -549,5 +549,18 @@
"SERVICE_CERTIFICATION_STATUS_TEXT": "Service {{serviceName}} was successfully certified",
"SERVICE_AUTOMATED_UPGRADE_WITH_COMPONENTS_TO_UPGRADE": "The following services reference <b>{{vspName}}</b>.<br/> One or more of the services were not yet upgraded with the most recently certified version of <b>{{vspName}}</b>.</br>Select services from the list to upgrade them with <b>{{vspName}} {{vspVersion}}</b>.",
"SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_LOCKED": "The listed services reference <b>{{vspName}}</b>.<br/> These services were not upgraded with the most recently certified version of <b>{{vspName}}</b>. Currently they are locked from being upgraded with <b>{{vspName}} {{vspVersion}}</b>",
- "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of the <b>{{vspName}} {{vspVersion}}</b>"
+ "SERVICE_AUTOMATED_UPGRADE_ALL_COMPONENTS_UPGRADED": "The listed services each reference <b>{{vspName}}</b> and have already been updated with the most recently certified version of the <b>{{vspName}} {{vspVersion}}</b>",
+ "=========== REQUIREMENTS AND CAPABILITIES ===========": "",
+ "REQ_NAME": "Requirement Name",
+ "REQ_RELATED_CAPABILITY": "Related Capability",
+ "REQ_NODE": "Node",
+ "REQ_RELATIONSHIP": "Relationship",
+ "REQ_CAP_OCCURRENCES": "Occurrences",
+ "REQ_CAP_OCCURRENCES_UNBOUNDED": "UNBOUNDED",
+ "REQ_CAP_OCCURRENCES_MIN": "Min",
+ "REQ_CAP_OCCURRENCES_MAX": "Max",
+ "CAP_NAME": "Capability Name",
+ "CAP_TYPE": "Capability Type",
+ "CAP_DESCRIPTION": "Description",
+ "CAP_VALID_SOURCE": "Valid Sources"
}
diff --git a/catalog-ui/src/assets/styles/sprite.less b/catalog-ui/src/assets/styles/sprite.less
index 383a830..5883094 100644
--- a/catalog-ui/src/assets/styles/sprite.less
+++ b/catalog-ui/src/assets/styles/sprite.less
@@ -31,6 +31,12 @@
.plus-icon { background-position: -50px -231px; width: 12px; height: 12px;}
.plus-icon-hover { background-position: -100px -231px; width: 12px; height: 12px;}
+.plus-icon-circle {
+ background-position: -49px -959px;
+ width: 20px;
+ height: 20px;
+}
+
.delete-icon { background-position: -675px -231px; width: 11px; height: 13px;}
.delete-icon-hover { background-position: -702px -231px; width: 11px; height: 13px;}