diff options
Diffstat (limited to 'catalog-ui/src/app/ng2/pages/composition/panel')
69 files changed, 3860 insertions, 1144 deletions
diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/__snapshots__/composition-panel.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/panel/__snapshots__/composition-panel.component.spec.ts.snap new file mode 100644 index 0000000000..5f10806315 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/__snapshots__/composition-panel.component.spec.ts.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`composition-panel component should match current snapshot of composition-panel component. 1`] = ` +<ng2-composition-panel + activatePreviousActiveTab={[Function Function]} + classes={[Function String]} + initTabs={[Function Function]} + isComponentInstanceSelected={[Function Function]} + isConfiguration={[Function Function]} + isPNF={[Function Function]} + selectedComponentIsServiceProxyInstance={[Function Function]} + setActive={[Function Function]} + store={[Function Store]} + toggleSidebarDisplay={[Function Function]} +> + +</ng2-composition-panel> +`; diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.html new file mode 100644 index 0000000000..bd90b9a814 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.html @@ -0,0 +1,21 @@ +<panel-wrapper-component *ngIf="compositionState$ | async as state"> <!-- HEADER --> + + <ng2-composition-panel-header [isViewOnly]="state.isViewOnly" + [selectedComponent]="state.selectedComponent"></ng2-composition-panel-header> + + <!-- TABS --> + <div class="component-details-panel-tabs"> + <sdc-loader [global]="false" name="panel" testId="panel-loader" [active]="state.panelLoading"></sdc-loader> + <sdc-tabs (selectedTab)="setActive($event)" [iconsSize]="'large'"> + <sdc-tab *ngFor="let tab of tabs" [titleIcon]="tab.titleIcon" [active]="tab.isActive" + [tooltipText]="tab.tooltipText"> + <panel-tab [isActive]="tab.isActive" [component]="selectedComponent" + [componentType]="state.selectedComponentType" [isViewOnly]="isViewOnly$ | async" + [input]="tab.input" [panelTabType]="tab.component"></panel-tab> + </sdc-tab> + </sdc-tabs> + </div> + +</panel-wrapper-component> + + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.less new file mode 100644 index 0000000000..776ef68944 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.less @@ -0,0 +1,27 @@ +@import '../../../../../assets/styles/variables'; +@import '../../../../../assets/styles/mixins_old'; + +:host ::ng-deep .sdc-loader-wrapper { + position:static; +} + +.component-details-panel-tabs { + flex: 1; + display:flex; + overflow:hidden; + } + +.component-details-panel-tabs /deep/ sdc-tabs { + display:flex; + flex-direction:column; + + /deep/ sdc-tab { + display: flex; + flex-direction: column; + overflow-y: auto; + } + .svg-icon-wrapper.label-placement-left .svg-icon-label { + margin-right: 0; + } +} + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts new file mode 100644 index 0000000000..25a0c728a8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.spec.ts @@ -0,0 +1,228 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { NgxsModule, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { Mock } from 'ts-mockery'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { Service } from '../../../../models/components/service'; +import { Resource } from '../../../../models/components/resource'; +import { GroupInstance } from '../../../../models/graph/zones/group-instance'; +import { PolicyInstance } from '../../../../models/graph/zones/policy-instance'; +import { ArtifactGroupType, ResourceType } from '../../../../utils/constants'; +import { WorkspaceState } from '../../../store/states/workspace.state'; +import { CompositionPanelComponent } from './composition-panel.component'; +import { ArtifactsTabComponent } from './panel-tabs/artifacts-tab/artifacts-tab.component'; +import { GroupMembersTabComponent } from './panel-tabs/group-members-tab/group-members-tab.component'; +import { GroupOrPolicyPropertiesTab } from './panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component'; +import { InfoTabComponent } from './panel-tabs/info-tab/info-tab.component'; +import { PolicyTargetsTabComponent } from './panel-tabs/policy-targets-tab/policy-targets-tab.component'; +import { PropertiesTabComponent } from './panel-tabs/properties-tab/properties-tab.component'; +import { ReqAndCapabilitiesTabComponent } from './panel-tabs/req-capabilities-tab/req-capabilities-tab.component'; + +describe('composition-panel component', () => { + + let fixture: ComponentFixture<CompositionPanelComponent>; + let store: Store; + + const tabs = { + infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'}, + policyProperties: { + titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'policy'}, isActive: false, tooltipText: 'Properties' + }, + policyTargets: {titleIcon: 'inputs-o', component: PolicyTargetsTabComponent, input: {}, isActive: false, tooltipText: 'Targets'}, + groupMembers: {titleIcon: 'inputs-o', component: GroupMembersTabComponent, input: {}, isActive: false, tooltipText: 'Members'}, + groupProperties: { + titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'group'}, isActive: false, tooltipText: 'Properties' + }, + deploymentArtifacts: { + titleIcon: 'deployment-artifacts-o', component: ArtifactsTabComponent, + input: { type: ArtifactGroupType.DEPLOYMENT}, isActive: false, tooltipText: 'Deployment Artifacts' + }, + apiArtifacts: { + titleIcon: 'api-o', component: ArtifactsTabComponent, + input: { type: ArtifactGroupType.SERVICE_API}, isActive: false, tooltipText: 'API Artifacts' + }, + infoArtifacts: { + titleIcon: 'info-square-o', component: ArtifactsTabComponent, + input: { type: ArtifactGroupType.INFORMATION}, isActive: false, tooltipText: 'Information Artifacts' + }, + properties: { + titleIcon: 'settings-o', component: PropertiesTabComponent, + input: {title: 'Properties and Attributes'}, isActive: false, tooltipText: 'Properties' + }, + reqAndCapabilities : { + titleIcon: 'req-capabilities-o', component: ReqAndCapabilitiesTabComponent, input: {}, + isActive: false, tooltipText: 'Requirements and Capabilities' + }, + inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'}, + settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, + }; + + beforeEach( + async(() => { + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [CompositionPanelComponent], + imports: [NgxsModule.forRoot([WorkspaceState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(CompositionPanelComponent); + store = testBed.get(Store); + }); + }) + ); + + it('When PolicyInstance Selected => Expect (info, policyTargets and policyProperties) tabs appear', () => { + + const testInstance = new PolicyInstance(); + + fixture.componentInstance.initTabs(testInstance); + + expect (fixture.componentInstance.tabs.length).toBe(3); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.policyTargets); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.policyProperties); + }); + + it('should match current snapshot of composition-panel component.', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('When Topology Template is Service and no instance is selected Expect (info, deployment, inputs, info and api)', () => { + + const selectedComponent: Service = new Service(null, null); + selectedComponent.isResource = jest.fn(() => false); + selectedComponent.isService = jest.fn(() => true ); + + fixture.componentInstance.store.select = jest.fn(() => Observable.of(selectedComponent)); + + // const pnfMock = Mock.of<Service>({ isResource : () => false }); + fixture.componentInstance.topologyTemplate = selectedComponent; + + // Call ngOnInit + fixture.componentInstance.ngOnInit(); + + // Expect that + expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.deploymentArtifacts); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.inputs); + expect (fixture.componentInstance.tabs[3]).toEqual(tabs.infoArtifacts); + expect (fixture.componentInstance.tabs[4]).toEqual(tabs.apiArtifacts); + + }); + + it('When Topology Template is Resource and no instance is selected Expect (info, deployment, inputs, info and api)', () => { + + const selectedComponent: Service = new Service(null, null); + selectedComponent.isResource = jest.fn(() => true); + selectedComponent.isService = jest.fn(() => false ); + + fixture.componentInstance.store.select = jest.fn(() => Observable.of(selectedComponent)); + + fixture.componentInstance.topologyTemplate = selectedComponent; + + // Call ngOnInit + fixture.componentInstance.ngOnInit(); + + // Expect that + expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.deploymentArtifacts); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.properties); + expect (fixture.componentInstance.tabs[3]).toEqual(tabs.infoArtifacts); + expect (fixture.componentInstance.tabs[4]).toEqual(tabs.reqAndCapabilities); + + }); + + it('When Topology Template is Service and proxyService instance is selected ' + + 'Expect (info, deployment, inputs, info and api)', () => { + + const selectedComponent: Service = new Service(null, null); + selectedComponent.isResource = jest.fn(() => false); + selectedComponent.isService = jest.fn(() => true ); + + fixture.componentInstance.store.select = jest.fn(() => Observable.of(selectedComponent)); + fixture.componentInstance.selectedComponentIsServiceProxyInstance = jest.fn(() => true); + + // const pnfMock = Mock.of<Service>({ isResource : () => false }); + fixture.componentInstance.topologyTemplate = selectedComponent; + + // Call ngOnInit + fixture.componentInstance.ngOnInit(); + + // Expect that + expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.properties); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.reqAndCapabilities); + + }); + + it('When Topology Template is Resource and VL is selected ' + + 'Expect (info, deployment, inputs, info and api)', () => { + + const topologyTemplate: Resource = new Resource(null, null); + topologyTemplate.isResource = jest.fn(() => true); + topologyTemplate.isService = jest.fn(() => false ); + + const vlMock = Mock.of<Resource>({ resourceType : 'VL', isResource : () => true, isService : () => false }); + fixture.componentInstance.store.select = jest.fn(() => Observable.of(vlMock)); + + fixture.componentInstance.topologyTemplate = topologyTemplate; + + // Call ngOnInit + fixture.componentInstance.ngOnInit(); + + // Expect that + expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.deploymentArtifacts); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.properties); + expect (fixture.componentInstance.tabs[3]).toEqual(tabs.infoArtifacts); + expect (fixture.componentInstance.tabs[4]).toEqual(tabs.reqAndCapabilities); + + }); + + it('When Topology Template is Service and VL is selected ' + + 'Expect (info, deployment, inputs, info and api)', () => { + + const topologyTemplate: Service = new Service(null, null); + topologyTemplate.isResource = jest.fn(() => true); + topologyTemplate.isService = jest.fn(() => false ); + + const vlMock = Mock.of<Resource>({ resourceType : 'VL', isResource : () => true, isService : () => false }); + fixture.componentInstance.store.select = jest.fn(() => Observable.of(vlMock)); + + fixture.componentInstance.topologyTemplate = topologyTemplate; + + // Call ngOnInit + fixture.componentInstance.ngOnInit(); + + // Expect that + expect (fixture.componentInstance.tabs.length).toBe(5); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.deploymentArtifacts); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.properties); + expect (fixture.componentInstance.tabs[3]).toEqual(tabs.infoArtifacts); + expect (fixture.componentInstance.tabs[4]).toEqual(tabs.reqAndCapabilities); + + }); + + it('When GroupInstance Selected => Expect (info, groupMembers and groupProperties) tabs appear.', () => { + + const testInstance = new GroupInstance(); + fixture.componentInstance.initTabs(testInstance); + + expect (fixture.componentInstance.tabs.length).toBe(3); + expect (fixture.componentInstance.tabs[0]).toEqual(tabs.infoTab); + expect (fixture.componentInstance.tabs[1]).toEqual(tabs.groupMembers); + expect (fixture.componentInstance.tabs[2]).toEqual(tabs.groupProperties); + }); + +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts new file mode 100644 index 0000000000..c5ea41bcd1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.component.ts @@ -0,0 +1,171 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= + */ + +import { Component, HostBinding, Input } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { Component as TopologyTemplate, ComponentInstance, FullComponentInstance, GroupInstance, PolicyInstance, Resource, Service } from 'app/models'; +import { ArtifactsTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component'; +import { GroupMembersTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component'; +import { GroupOrPolicyPropertiesTab } from 'app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component'; +import { InfoTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component'; +import { PolicyTargetsTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component'; +import { PropertiesTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component'; +import { ReqAndCapabilitiesTabComponent } from 'app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component'; +import { ComponentType, ResourceType } from 'app/utils'; +import * as _ from 'lodash'; +import { Subscription } from 'rxjs'; +import { Observable } from 'rxjs/Observable'; +import { ArtifactGroupType, COMPONENT_FIELDS } from '../../../../utils/constants'; +import { WorkspaceState } from '../../../store/states/workspace.state'; +import { OnSidebarOpenOrCloseAction } from '../common/store/graph.actions'; +import { CompositionStateModel, GraphState } from '../common/store/graph.state'; +import { ServiceConsumptionTabComponent } from './panel-tabs/service-consumption-tab/service-consumption-tab.component'; +import { ServiceDependenciesTabComponent } from './panel-tabs/service-dependencies-tab/service-dependencies-tab.component'; + +const tabs = { + infoTab : {titleIcon: 'info-circle', component: InfoTabComponent, input: {}, isActive: true, tooltipText: 'Information'}, + policyProperties: {titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'policy'}, isActive: false, tooltipText: 'Properties'}, + policyTargets: {titleIcon: 'inputs-o', component: PolicyTargetsTabComponent, input: {}, isActive: false, tooltipText: 'Targets'}, + groupMembers: {titleIcon: 'inputs-o', component: GroupMembersTabComponent, input: {}, isActive: false, tooltipText: 'Members'}, + groupProperties: {titleIcon: 'settings-o', component: GroupOrPolicyPropertiesTab, input: {type: 'group'}, isActive: false, tooltipText: 'Properties'}, + deploymentArtifacts: {titleIcon: 'deployment-artifacts-o', component: ArtifactsTabComponent, input: { type: ArtifactGroupType.DEPLOYMENT}, isActive: false, tooltipText: 'Deployment Artifacts'}, + apiArtifacts: {titleIcon: 'api-o', component: ArtifactsTabComponent, input: { type: ArtifactGroupType.SERVICE_API}, isActive: false, tooltipText: 'API Artifacts'}, + infoArtifacts: {titleIcon: 'info-square-o', component: ArtifactsTabComponent, input: { type: ArtifactGroupType.INFORMATION}, isActive: false, tooltipText: 'Information Artifacts'}, + properties: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {title: 'Properties and Attributes'}, isActive: false, tooltipText: 'Properties'}, + reqAndCapabilities : { titleIcon: 'req-capabilities-o', component: ReqAndCapabilitiesTabComponent, input: {}, isActive: false, tooltipText: 'Requirements and Capabilities'}, + inputs: {titleIcon: 'inputs-o', component: PropertiesTabComponent, input: {title: 'Inputs'}, isActive: false, tooltipText: 'Inputs'}, + settings: {titleIcon: 'settings-o', component: PropertiesTabComponent, input: {}, isActive: false, tooltipText: 'Settings'}, + consumption: {titleIcon: 'api-o', component: ServiceConsumptionTabComponent, input: {title: 'OPERATION CONSUMPTION'}, isActive: false, tooltipText: 'Service Consumption'}, + dependencies: {titleIcon: 'archive', component: ServiceDependenciesTabComponent, input: {title: 'SERVICE DEPENDENCIES'}, isActive: false, tooltipText: 'Service Dependencies'} +}; + +@Component({ + selector: 'ng2-composition-panel', + templateUrl: './composition-panel.component.html', + styleUrls: ['./composition-panel.component.less', './panel-tabs/panel-tabs.less'], +}) +export class CompositionPanelComponent { + + @Input() topologyTemplate: TopologyTemplate; + @HostBinding('class') classes = 'component-details-panel'; + @Select(GraphState) compositionState$: Observable<CompositionStateModel>; + @Select(GraphState.withSidebar) withSidebar$: boolean; + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + tabs: any[]; + subscription: Subscription; + + private selectedComponent; + + constructor(public store: Store) { + } + + ngOnInit() { + this.subscription = this.store.select(GraphState.getSelectedComponent).subscribe((component) => { + this.selectedComponent = component; + this.initTabs(component); + this.activatePreviousActiveTab(); + }); + } + + ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + public setActive = (tabToSelect) => { + this.tabs.map((tab) => tab.isActive = (tab.titleIcon === tabToSelect.titleIcon) ? true : false); + } + + public activatePreviousActiveTab = () => { // sets the info tab to active if no other tab selected + + this.setActive(this.tabs.find((tab) => tab.isActive) || tabs.infoTab); + + } + + private initTabs = (component) => { + this.tabs = []; + + // Information + this.tabs.push(tabs.infoTab); + + if (component instanceof PolicyInstance) { + this.tabs.push(tabs.policyTargets); + this.tabs.push(tabs.policyProperties); + return; + } + + if (component instanceof GroupInstance) { + this.tabs.push(tabs.groupMembers); + this.tabs.push(tabs.groupProperties); + return; + } + + // Deployment artifacts + if (!this.isPNF() && !this.isConfiguration() && !this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.deploymentArtifacts); + } + + // Properties or Inputs + if (component.isResource() || this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.properties); + } else { + this.tabs.push(tabs.inputs); + } + + if (!this.isConfiguration() && !this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.infoArtifacts); + } + + if (!(component.isService()) || this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.reqAndCapabilities); + } + + if (component.isService() && !this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.apiArtifacts); + } + if (component.isService() && this.selectedComponentIsServiceProxyInstance()) { + this.tabs.push(tabs.consumption); + this.tabs.push(tabs.dependencies); + } + + } + + private toggleSidebarDisplay = () => { + // this.withSidebar = !this.withSidebar; + this.store.dispatch(new OnSidebarOpenOrCloseAction()); + } + + private isPNF = (): boolean => { + return this.topologyTemplate.isResource() && (this.topologyTemplate as Resource).resourceType === ResourceType.PNF; + } + + private isConfiguration = (): boolean => { + return this.topologyTemplate.isResource() && (this.topologyTemplate as Resource).resourceType === ResourceType.CONFIGURATION; + } + + private isComponentInstanceSelected = (): boolean => { + return this.selectedComponent instanceof FullComponentInstance; + } + + private selectedComponentIsServiceProxyInstance = (): boolean => { + return this.isComponentInstanceSelected() && this.selectedComponent.isServiceProxy(); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts new file mode 100644 index 0000000000..0fd1e51fa5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/composition-panel.module.ts @@ -0,0 +1,106 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= + */ +import { NgModule } from "@angular/core"; +import { FormsModule } from "@angular/forms"; +import { BrowserModule } from "@angular/platform-browser"; +import { CompositionPanelComponent } from "./composition-panel.component"; +import { CompositionPanelHeaderModule } from "app/ng2/pages/composition/panel/panel-header/panel-header.module"; +import { SdcUiComponentsModule, SdcUiServices } from "onap-ui-angular"; +// import { SdcUiServices } from "onap-ui-angular/"; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { AddElementsModule } from "../../../components/ui/modal/add-elements/add-elements.module"; +import { TranslateModule } from "app/ng2/shared/translator/translate.module"; +import { InfoTabComponent } from './panel-tabs/info-tab/info-tab.component'; +import { PanelTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/panel-tab.component"; +import { ArtifactsTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component"; +import { PropertiesTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component"; +import { ReqAndCapabilitiesTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component"; +import { RequirementListComponent } from "app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component"; +import { PolicyTargetsTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component"; +import { GroupMembersTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component"; +import { GroupOrPolicyPropertiesTab } from "app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component"; +import { GlobalPipesModule } from "app/ng2/pipes/global-pipes.module"; +import {ModalModule} from "../../../components/ui/modal/modal.module"; +import {EnvParamsComponent} from "../../../components/forms/env-params/env-params.component"; +import {ModalsModule} from "../../../components/modals/modals.module"; +// import {EnvParamsModule} from "../../../components/forms/env-params/env-params.module"; +import { NgxDatatableModule } from "@swimlane/ngx-datatable"; +import {EnvParamsModule} from "../../../components/forms/env-params/env-params.module"; +import { ServiceConsumptionTabComponent } from "./panel-tabs/service-consumption-tab/service-consumption-tab.component"; +import { ServiceDependenciesTabComponent } from "./panel-tabs/service-dependencies-tab/service-dependencies-tab.component"; +import { ServiceDependenciesModule } from "../../../components/logic/service-dependencies/service-dependencies.module"; +import { ServiceConsumptionModule } from "../../../components/logic/service-consumption/service-consumption.module"; + + + +@NgModule({ + declarations: [ + CompositionPanelComponent, + PolicyTargetsTabComponent, + GroupOrPolicyPropertiesTab, + GroupMembersTabComponent, + InfoTabComponent, + PanelTabComponent, + ArtifactsTabComponent, + PropertiesTabComponent, + ReqAndCapabilitiesTabComponent, + ServiceConsumptionTabComponent, + ServiceDependenciesTabComponent, + RequirementListComponent, + EnvParamsComponent + ], + imports: [ + GlobalPipesModule, + BrowserModule, + FormsModule, + CompositionPanelHeaderModule, + SdcUiComponentsModule, + UiElementsModule, + AddElementsModule, + TranslateModule, + NgxDatatableModule, + ServiceDependenciesModule, + ServiceConsumptionModule + // EnvParamsModule + ], + entryComponents: [ + CompositionPanelComponent, + PolicyTargetsTabComponent, + GroupOrPolicyPropertiesTab, + GroupMembersTabComponent, + InfoTabComponent, + ArtifactsTabComponent, + PropertiesTabComponent, + ReqAndCapabilitiesTabComponent, + ServiceConsumptionTabComponent, + ServiceDependenciesTabComponent, + RequirementListComponent, + PanelTabComponent, + EnvParamsComponent + ], + exports: [ + CompositionPanelComponent + // EnvParamsModule + ], + providers: [SdcUiServices.ModalService] +}) +export class CompositionPanelModule { + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.html new file mode 100644 index 0000000000..75ee2d520f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.html @@ -0,0 +1,28 @@ +<!-- + ~ Copyright (C) 2018 AT&T Intellectual Property. 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. + --> + +<div class="name-update-container"> + <sdc-input #updateNameInput + label="Instance Name" + required="true" + [maxLength]="50" + [(value)]="name" + testId="instanceName"></sdc-input> + <sdc-validation [validateElement]="updateNameInput" (validityChanged)="validityChanged($event)"> + <sdc-required-validator message="Name is required."></sdc-required-validator> + <sdc-regex-validator message="Special characters not allowed." [pattern]="pattern"></sdc-regex-validator> + </sdc-validation> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.less new file mode 100644 index 0000000000..b958ca17b7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.less @@ -0,0 +1,3 @@ +.name-update-container { + min-height: 90px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.ts new file mode 100644 index 0000000000..9c4aab206e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.ts @@ -0,0 +1,25 @@ +import { Component, Input } from "@angular/core"; + +@Component({ + selector: 'edit-name-modal', + templateUrl: './edit-name-modal.component.html', + styleUrls: ['./edit-name-modal.component.less'] +}) +export class EditNameModalComponent { + + @Input() name:String; + @Input() validityChangedCallback: Function; + + private pattern:string = "^[\\s\\w\&_.:-]{1,1024}$" + constructor(){ + } + + private validityChanged = (value):void => { + if(this.validityChangedCallback) { + this.validityChangedCallback(value); + } + } + + + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.html index 67c82389cc..d9c56198ea 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.html @@ -1,30 +1,23 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<div class="component-details-panel-header" data-tests-id="w-sdc-designer-sidebar-head"> - +<div *ngIf="selectedComponent" class="component-details-panel-header" data-tests-id="w-sdc-designer-sidebar-head"> <div class="icon"> - <div class="large {{iconClassName}}"> - <div [ngClass]="{'non-certified': nonCertified}" tooltip="Not certified"></div> - </div> + <div *ngIf="iconClassName; else svgIcon" class="large {{iconClassName}}"></div> + <ng-template #svgIcon> + <sdc-element-icon + [elementType]="selectedComponent.componentType === 'RESOURCE' ? selectedComponent.resourceType: (selectedComponent.originType || selectedComponent.componentType)" + [iconName]="selectedComponent.icon" + [uncertified]="!isTopologyTemplateSelected && selectedComponent.lifecycleState && 'CERTIFIED' !== selectedComponent.lifecycleState"></sdc-element-icon> + </ng-template> </div> - <div class="title" data-tests-id="selectedCompTitle" tooltip="​{{name}}">{{name}}</div> + <div class="title" data-tests-id="selectedCompTitle" tooltip="​{{selectedComponent.name}}"> + {{selectedComponent.name}} + </div> + + <svg-icon-label *ngIf="!isViewOnly && !isTopologyTemplateSelected && !selectedComponent.archived" name="edit-file-o" + clickable="true" size="small" class="rename-instance" data-tests-id="renameInstance" + (click)="renameInstance()"></svg-icon-label> + <svg-icon-label *ngIf="!isViewOnly && !isTopologyTemplateSelected && !selectedComponent.archived" name="trash-o" + clickable="true" size="small" class="delete-instance" data-tests-id="deleteInstance" + (click)="deleteInstance()"></svg-icon-label> - <svg-icon-label *ngIf="!isViewOnly" name="edit-file-o" clickable="true" size="small" class="rename-instance" data-tests-id="renameInstance" (click)="renameInstance()"></svg-icon-label> - <svg-icon-label *ngIf="!isViewOnly" name="trash-o" clickable="true" size="small" class="delete-instance" data-tests-id="deleteInstance" (click)="deleteInstance()"></svg-icon-label> - </div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.less index 9bbc765761..6685f74009 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.less @@ -7,6 +7,7 @@ .icon { margin: 0 20px; + display:flex; } .title { @@ -31,4 +32,17 @@ cursor: pointer; } + + .non-certified { + position: absolute; + background-image: url('../../../../../../assets/styles/images/sprites/sprite-global-old.png'); + background-position: -157px -3386px; width: 15px; height: 15px; + + &.smaller-icon { + left: 35px; + bottom: -14px; + } + } + + }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.spec.ts new file mode 100644 index 0000000000..76e84a2323 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.spec.ts @@ -0,0 +1,123 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { CompositionService } from 'app/ng2/pages/composition/composition.service'; +import { EventListenerService } from '../../../../../services/event-listener-service'; +import { ComponentInstanceServiceNg2 } from 'app/ng2/services/component-instance-services/component-instance.service'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { GroupsService } from 'app/services-ng2'; +import { PoliciesService } from 'app/services-ng2'; +import { CompositionPanelHeaderComponent } from './panel-header.component'; +import {SdcUiServices} from 'onap-ui-angular'; +import { Capability, Requirement, RequirementsGroup, CapabilitiesGroup, ComponentInstance, Component, FullComponentInstance, PolicyInstance, GroupInstance } from "app/models"; +import { of, Observable } from "rxjs"; + +describe('CompositionPanelHeaderComponent', () => { + let component: CompositionPanelHeaderComponent; + let fixture: ComponentFixture<CompositionPanelHeaderComponent>; + const componentInstanceServiceNg2Stub = { + updateComponentInstance: jest.fn() + }; + const valueEditModalInstance = { + innerModalContent : { + instance: { name : "VF Test" } + }, + buttons: [{id: 'saveButton', text: 'OK', size: 'xsm', callback: jest.fn(), closeModal: false}], + closeModal : jest.fn() + }; + + beforeEach( + () => { + const compositionServiceStub = {}; + const eventListenerServiceStub = {}; + + const workspaceServiceStub = { + metadata: { + componentType: "SERVICE", + uniqueId: "123" + } + }; + const groupsServiceStub = { + updateName: jest.fn() + }; + const policiesServiceStub = { + updateName: jest.fn() + }; + + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + declarations: [CompositionPanelHeaderComponent], + providers: [ + { provide: CompositionService, useValue: compositionServiceStub }, + { provide: EventListenerService, useValue: eventListenerServiceStub }, + { + provide: ComponentInstanceServiceNg2, + useValue: componentInstanceServiceNg2Stub + }, + { provide: WorkspaceService, useValue: workspaceServiceStub }, + { provide: GroupsService, useValue: groupsServiceStub }, + { provide: PoliciesService, useValue: policiesServiceStub }, + { provide: SdcUiServices.ModalService, useValue: {}} + ] + }); + fixture = TestBed.createComponent(CompositionPanelHeaderComponent); + component = fixture.componentInstance; + } + ); + + it('can load instance', () => { + expect(component).toBeTruthy(); + }); + + it('should close the modal without saving if the name has not changed', () => { + component.selectedComponent = <FullComponentInstance>{name: "VF Test"}; + component.valueEditModalInstance = valueEditModalInstance; + + component.saveInstanceName(); + expect(component.componentInstanceService.updateComponentInstance).not.toHaveBeenCalled(); + expect(component.valueEditModalInstance.closeModal).toHaveBeenCalled(); + }); + + it('after editing instance name, capabilities/requirements should be updated with new name', () => { + const newName = "New VF NAME"; + component.selectedComponent = new FullComponentInstance(<ComponentInstance>{ + name: "VF Test", + requirements: <RequirementsGroup>{"key": [<Requirement>{ownerName: "VF Test"}, <Requirement>{ownerName: "VF Test"}]}, + capabilities: new CapabilitiesGroup() + }, <Component>{}); + component.selectedComponent.capabilities['key'] = [<Capability>{ownerName: "VF Test"}]; + component.valueEditModalInstance = valueEditModalInstance; + component.valueEditModalInstance.innerModalContent.instance.name = newName; + jest.spyOn(component.componentInstanceService, 'updateComponentInstance').mockReturnValue(of(<ComponentInstance>{name: newName})); + component.saveInstanceName(); + + expect(component.selectedComponent.name).toBe(newName); + expect(component.selectedComponent.requirements['key'][0].ownerName).toEqual(newName); + expect(component.selectedComponent.requirements['key'][1].ownerName).toEqual(newName); + expect(component.selectedComponent.capabilities['key'][0].ownerName).toEqual(newName); + }); + + it('if update fails, name is reverted to old value', () => { + component.selectedComponent = new GroupInstance(<GroupInstance>{name: "GROUP NAME"}); + component.valueEditModalInstance = valueEditModalInstance; + jest.spyOn(component.groupService, 'updateName').mockReturnValue(Observable.throw(new Error('Error'))); + component.saveInstanceName(); + expect(component.selectedComponent.name).toEqual("GROUP NAME"); + }); + + it('policy instance uses policies service for update name', () => { + component.selectedComponent = new PolicyInstance(<PolicyInstance>{name: "Policy OLD NAME"}); + component.valueEditModalInstance = valueEditModalInstance; + jest.spyOn(component.policiesService, 'updateName').mockReturnValue(of(true)); + component.saveInstanceName(); + expect(component.policiesService.updateName).toHaveBeenCalledTimes(1); + }); + + it('group instance uses groups service for update name', () => { + component.selectedComponent = new GroupInstance(<GroupInstance>{name: "GROUP NAME"}); + component.valueEditModalInstance = valueEditModalInstance; + jest.spyOn(component.groupService, 'updateName').mockReturnValue(of(true)); + component.saveInstanceName(); + expect(component.groupService.updateName).toHaveBeenCalledTimes(1); + }); + +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.ts index ab659a3b8f..90a98147e9 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.component.ts @@ -18,64 +18,70 @@ * ============LICENSE_END========================================================= */ -import { Component, Input, AfterViewInit, SimpleChanges, OnInit, OnChanges } from "@angular/core"; -import { SdcUiComponents } from "sdc-ui/lib/angular"; -import { IModalConfig } from 'sdc-ui/lib/angular/modals/models/modal-config'; -import { ZoneInstanceType } from 'app/models/graph/zones/zone-instance'; -import { ValueEditComponent } from './../../../../components/ui/forms/value-edit/value-edit.component'; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; -import { PoliciesService } from '../../../../services/policies.service'; -import { GroupsService } from '../../../../services/groups.service'; -import {IZoneService} from "../../../../../models/graph/zones/zone"; -import { EventListenerService, LoaderService } from "../../../../../services"; -import { GRAPH_EVENTS, EVENTS } from "../../../../../utils"; +import { Component, Input, OnInit } from "@angular/core"; +import { SdcUiComponents, SdcUiCommon, SdcUiServices } from "onap-ui-angular"; +import { EditNameModalComponent } from "app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component"; +import {Component as TopologyTemplate, FullComponentInstance, GroupInstance, PolicyInstance, Requirement, Capability, ComponentInstance} from "app/models"; +import { Select } from "@ngxs/store"; +import { Observable } from "rxjs/Observable"; +import { Subscription } from "rxjs"; +import {GRAPH_EVENTS} from "../../../../../utils/constants"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import {EventListenerService} from "../../../../../services/event-listener-service"; +import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; +import { GroupsService, PoliciesService } from "app/services-ng2"; import { UIZoneInstanceObject } from "../../../../../models/ui-models/ui-zone-instance-object"; -import { ModalButtonComponent } from "sdc-ui/lib/angular/components"; +import {SelectedComponentType} from "../../common/store/graph.actions"; +import * as _ from 'lodash'; +import {GraphState} from "../../common/store/graph.state"; + @Component({ selector: 'ng2-composition-panel-header', templateUrl: './panel-header.component.html', styleUrls: ['./panel-header.component.less'] }) -export class CompositionPanelHeaderComponent implements OnInit, OnChanges { - - @Input() topologyTemplate: TopologyTemplate; - @Input() selectedZoneInstanceType: ZoneInstanceType; - @Input() selectedZoneInstanceId: string; - @Input() name: string; - @Input() nonCertified: boolean; +export class CompositionPanelHeaderComponent implements OnInit { @Input() isViewOnly: boolean; - @Input() isLoading: boolean; + @Input() selectedComponent: FullComponentInstance | TopologyTemplate | GroupInstance | PolicyInstance; + @Select(GraphState.getSelectedComponentType) selectedComponentType$:Observable<SelectedComponentType>; + - constructor(private groupsService:GroupsService, private policiesService: PoliciesService, - private modalService:SdcUiComponents.ModalService, private eventListenerService:EventListenerService) { } + constructor(private modalService: SdcUiServices.ModalService, + private groupService: GroupsService, + private policiesService: PoliciesService, + private eventListenerService: EventListenerService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private componentInstanceService: ComponentInstanceServiceNg2) { } - private service:IZoneService; private iconClassName: string; + private valueEditModalInstance: SdcUiComponents.ModalComponent; + private isTopologyTemplateSelected: boolean; + private componentTypeSubscription: Subscription; ngOnInit(): void { - this.init(); - } + this.componentTypeSubscription = this.selectedComponentType$.subscribe((newComponentType) => { - ngOnChanges (changes:SimpleChanges):void { - if(changes.selectedZoneInstanceId){ - this.init(); - } + this.initClasses(newComponentType); + this.isTopologyTemplateSelected = (newComponentType === SelectedComponentType.TOPOLOGY_TEMPLATE) ? true : false; + }); } ngOnDestroy() { - - + if(this.componentTypeSubscription) { + this.componentTypeSubscription.unsubscribe(); + } } - private init = (): void => { - if (this.selectedZoneInstanceType === ZoneInstanceType.POLICY) { + + private initClasses = (componentType:SelectedComponentType): void => { + if (componentType === SelectedComponentType.POLICY) { this.iconClassName = "sprite-policy-icons policy"; - this.service = this.policiesService; - } else if (this.selectedZoneInstanceType === ZoneInstanceType.GROUP) { + } else if (componentType === SelectedComponentType.GROUP) { this.iconClassName = "sprite-group-icons group"; - this.service = this.groupsService; } else { - this.iconClassName = "sprite-resource-icons defaulticon"; + this.iconClassName = undefined; } } @@ -83,53 +89,95 @@ export class CompositionPanelHeaderComponent implements OnInit, OnChanges { const modalConfig = { title: "Edit Name", size: "sm", - type: "custom", + type: SdcUiCommon.ModalType.custom, testId: "renameInstanceModal", buttons: [ {id: 'saveButton', text: 'OK', size: 'xsm', callback: this.saveInstanceName, closeModal: false}, - {id: 'cancelButton', text: 'Cancel', size: 'sm', closeModal: true} - ] as ModalButtonComponent[] - } as IModalConfig; - this.modalService.openCustomModal(modalConfig, ValueEditComponent, {name: this.name, validityChangedCallback: this.enableOrDisableSaveButton}); + {id: 'cancelButton', text: 'Cancel', size: 'sm', closeModal: true} + ] as SdcUiCommon.IModalButtonComponent[] + } as SdcUiCommon.IModalConfig; + this.valueEditModalInstance = this.modalService.openCustomModal(modalConfig, EditNameModalComponent, {name: this.selectedComponent.name, validityChangedCallback: this.enableOrDisableSaveButton}); }; private enableOrDisableSaveButton = (shouldEnable: boolean): void => { - let saveButton: ModalButtonComponent = this.modalService.getCurrentInstance().getButtonById('saveButton'); + let saveButton: SdcUiComponents.ModalButtonComponent = this.valueEditModalInstance.getButtonById('saveButton'); saveButton.disabled = !shouldEnable; } private saveInstanceName = ():void => { - let currentModal = this.modalService.getCurrentInstance(); - let nameFromModal:string = currentModal.innerModalContent.instance.name; - - if(nameFromModal != this.name){ - currentModal.buttons[0].disabled = true; - this.service.updateName(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.selectedZoneInstanceId, nameFromModal).subscribe((success)=>{ - this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_ZONE_INSTANCE_NAME_CHANGED, nameFromModal); - this.modalService.closeModal(); - }, (error)=> { - currentModal.buttons[0].disabled = false; - }); - } else { - this.modalService.closeModal(); + let nameFromModal:string = this.valueEditModalInstance.innerModalContent.instance.name; + + if(nameFromModal != this.selectedComponent.name){ + let oldName = this.selectedComponent.name; + this.selectedComponent.name = nameFromModal; + this.valueEditModalInstance.buttons[0].disabled = true; + + let onFailed = (error) => { + this.selectedComponent.name = oldName; + this.valueEditModalInstance.buttons[0].disabled = false; + }; + + if(this.selectedComponent instanceof FullComponentInstance){ + let onSuccess = (componentInstance:ComponentInstance) => { + //update requirements and capabilities owner name + _.forEach((<FullComponentInstance>this.selectedComponent).requirements, (requirementsArray:Array<Requirement>) => { + _.forEach(requirementsArray, (requirement:Requirement):void => { + requirement.ownerName = componentInstance.name; + }); + }); + + _.forEach((<FullComponentInstance>this.selectedComponent).capabilities, (capabilitiesArray:Array<Capability>) => { + _.forEach(capabilitiesArray, (capability:Capability):void => { + capability.ownerName = componentInstance.name; + }); + }); + this.valueEditModalInstance.closeModal(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, this.selectedComponent); + }; + + this.componentInstanceService.updateComponentInstance(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, new ComponentInstance(this.selectedComponent)) + .subscribe(onSuccess, onFailed); + } else if (this.selectedComponent instanceof PolicyInstance) { + this.policiesService.updateName(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.selectedComponent.uniqueId, nameFromModal).subscribe((success)=>{ + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, this.selectedComponent); + this.valueEditModalInstance.closeModal(); + }, onFailed); + } else if (this.selectedComponent instanceof GroupInstance){ + this.groupService.updateName(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.selectedComponent.uniqueId, nameFromModal).subscribe((success)=>{ + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.selectedComponent); + this.valueEditModalInstance.closeModal(); + }, onFailed); + } + } else { + this.valueEditModalInstance.closeModal(); } }; - + private deleteInstance = (): void => { let title:string = "Delete Confirmation"; - let message:string = "Are you sure you would like to delete "+ this.name + "?"; - this.modalService.openAlertModal(title, message, "OK", this.deleteInstanceConfirmed, "deleteInstanceModal"); + let message:string = "Are you sure you would like to delete "+ this.selectedComponent.name + "?"; + const okButton = {testId: "OK", text: "OK", type: SdcUiCommon.ButtonType.warning, callback: this.deleteInstanceConfirmed, closeModal: true} as SdcUiComponents.ModalButtonComponent; + this.modalService.openWarningModal(title, message, "delete-modal", [okButton]); }; - private deleteInstanceConfirmed = () => { - this.eventListenerService.notifyObservers(EVENTS.SHOW_LOADER_EVENT + 'composition-graph'); - this.service.deleteZoneInstance(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.selectedZoneInstanceId).finally(()=> { - this.eventListenerService.notifyObservers(EVENTS.HIDE_LOADER_EVENT + 'composition-graph'); - }).subscribe(()=> { - let deletedItem:UIZoneInstanceObject = new UIZoneInstanceObject(this.selectedZoneInstanceId, this.selectedZoneInstanceType, this.name); - this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE, deletedItem); - }); - }; + private deleteInstanceConfirmed: Function = () => { + if(this.selectedComponent instanceof FullComponentInstance){ + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE , this.selectedComponent.uniqueId); + } + else if(this.selectedComponent instanceof PolicyInstance){ + this.policiesService.deletePolicy(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.selectedComponent.uniqueId).subscribe((success)=>{ + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE , + new UIZoneInstanceObject(this.selectedComponent.uniqueId, 1)); + }, (err) => {}); + + } + else if(this.selectedComponent instanceof GroupInstance){ + this.groupService.deleteGroup(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.selectedComponent.uniqueId).subscribe((success)=>{ + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE , + new UIZoneInstanceObject(this.selectedComponent.uniqueId, 0)); + }, (err) => {}); + } + }; } diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.module.ts index bde0a14669..a11bc99fee 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/panel-header.module.ts @@ -18,29 +18,26 @@ * ============LICENSE_END========================================================= */ import { NgModule } from "@angular/core"; -import { HttpModule } from "@angular/http"; import { FormsModule } from "@angular/forms"; import { BrowserModule } from "@angular/platform-browser"; import { CompositionPanelHeaderComponent } from "./panel-header.component"; import { UiElementsModule } from './../../../../components/ui/ui-elements.module'; -import { ValueEditComponent } from './../../../../components/ui/forms/value-edit/value-edit.component'; -import { SdcUiComponentsModule } from "sdc-ui/lib/angular"; -import { ModalFormsModule } from "app/ng2/components/ui/forms/modal-forms.module"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import { EditNameModalComponent } from "app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component"; @NgModule({ declarations: [ - CompositionPanelHeaderComponent + CompositionPanelHeaderComponent, + EditNameModalComponent ], imports: [ BrowserModule, FormsModule, - HttpModule, UiElementsModule, - SdcUiComponentsModule, - ModalFormsModule + SdcUiComponentsModule ], entryComponents: [ - CompositionPanelHeaderComponent, ValueEditComponent + CompositionPanelHeaderComponent, EditNameModalComponent ], exports: [ CompositionPanelHeaderComponent diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/__snapshots__/artifact-tab.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/__snapshots__/artifact-tab.component.spec.ts.snap new file mode 100644 index 0000000000..c143e8106b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/__snapshots__/artifact-tab.component.spec.ts.snap @@ -0,0 +1,50 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`artifact-tab component should match current snapshot of artifact-tab component 1`] = ` +<artifacts-tab + addOrUpdate={[Function Function]} + allowDeleteAndUpdateArtifact={[Function Function]} + artifactService={[Function Object]} + componentInstanceService="undefined" + compositionService={[Function Object]} + delete={[Function Function]} + getEnvArtifact={[Function Function]} + getTitle={[Function Function]} + heatToEnv={[Function Map]} + isLicenseArtifact={[Function Function]} + loadArtifacts={[Function Function]} + store={[Function Store]} + topologyTemplateService="undefined" + updateEnvParams={[Function Function]} + viewEnvParams={[Function Function]} + workspaceService="undefined" +> + <div + class="w-sdc-designer-sidebar-tab-content artifacts" + > + <div + class="w-sdc-designer-sidebar-section" + > + <ng2-expand-collapse + state="0" + > + <header + sdc-tooltip="" + > + + </header> + <content + class="artifacts-container" + > + <div + class="w-sdc-designer-sidebar-section-content" + > + + </div> + + </content> + </ng2-expand-collapse> + </div> + </div> +</artifacts-tab> +`; diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifact-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifact-tab.component.spec.ts new file mode 100644 index 0000000000..258f2295ab --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifact-tab.component.spec.ts @@ -0,0 +1,303 @@ +import { async, ComponentFixture } from '@angular/core/testing'; +import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper'; +import { NgxsModule, Store } from '@ngxs/store'; +import { WorkspaceState } from '../../../../../store/states/workspace.state'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ArtifactsTabComponent } from './artifacts-tab.component'; +import { CompositionService } from '../../../composition.service'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { ComponentInstanceServiceNg2 } from '../../../../../services/component-instance-services/component-instance.service'; +import { TopologyTemplateService } from '../../../../../services/component-services/topology-template.service'; +import { ArtifactsService } from '../../../../../components/forms/artifacts-form/artifacts.service'; +import { ArtifactModel } from '../../../../../../models/artifacts'; +import { ArtifactType } from '../../../../../../utils/constants'; +import { FullComponentInstance } from '../../../../../../models/componentsInstances/fullComponentInstance'; +import { ComponentInstance } from '../../../../../../models/componentsInstances/componentInstance'; +import { Component } from '../../../../../../models/components/component'; +import { GetInstanceArtifactsByTypeAction } from '../../../../../store/actions/instance-artifacts.actions'; +import { Observable } from 'rxjs'; + + +describe('artifact-tab component', () => { + + let fixture: ComponentFixture<ArtifactsTabComponent>; + let compositionMockService: Partial<CompositionService>; + const workspaceMockService: Partial<WorkspaceService>; + const componentInstanceMockService: Partial<ComponentInstanceServiceNg2>; + const topologyTemplateMockService: Partial<TopologyTemplateService>; + let artifactsServiceMockService: Partial<ArtifactsService>; + let store: Store; + + beforeEach( + async(() => { + compositionMockService = { + updateInstance: jest.fn() + } + + artifactsServiceMockService = { + deleteArtifact: jest.fn(), + openUpdateEnvParams: jest.fn(), + openArtifactModal: jest.fn() + } + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [ArtifactsTabComponent], + imports: [NgxsModule.forRoot([WorkspaceState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: CompositionService, useValue: compositionMockService}, + {provide: WorkspaceService, useValue: workspaceMockService}, + {provide: ComponentInstanceServiceNg2, useValue: componentInstanceMockService}, + {provide: TopologyTemplateService, useValue: topologyTemplateMockService}, + {provide: ArtifactsService, useValue: artifactsServiceMockService} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(ArtifactsTabComponent); + store = testBed.get(Store); + }); + }) + ); + + it ('on delete -> deleteArtifact is being called from artifactService', () => { + const artifact = new ArtifactModel(); + const topologyTemplateType: string = undefined; + const topologyTemplateId: string = undefined; + + fixture.componentInstance.delete(artifact); + expect(artifactsServiceMockService.deleteArtifact).toHaveBeenCalledWith(topologyTemplateType, topologyTemplateId, artifact); + }); + + it('should match current snapshot of artifact-tab component', () => { + expect(fixture).toMatchSnapshot(); + }); + + + it ('should get API Artifacts as Title', () => { + const artifactType = ArtifactType.SERVICE_API; + + const res = fixture.componentInstance.getTitle(artifactType); + expect(res).toBe('API Artifacts'); + }); + + + it ('should get Deployment Artifacts as Title', () => { + const artifactType = ArtifactType.DEPLOYMENT; + + const res = fixture.componentInstance.getTitle(artifactType); + expect(res).toBe('Deployment Artifacts'); + }); + + it ('should get Informational Artifacts as Title', () => { + const artifactType = ArtifactType.INFORMATION; + + const res = fixture.componentInstance.getTitle(artifactType); + expect(res).toBe('Informational Artifacts'); + }); + + it ('should get SomeString as Title - This is the default case (return the last val)', () => { + // So the last value will be "SomeString" + fixture.componentInstance.getTitle('SomeString'); + + const res = fixture.componentInstance.getTitle('SomeString'); + expect(res).toBe('SomeString Artifacts'); + }); + + + it ('should return isLicenseArtifact false', () => { + const artifact = new ArtifactModel(); + const componentInstance = new ComponentInstance(); + const component = new Component(); + fixture.componentInstance.component = new FullComponentInstance(componentInstance, component); + + let res = fixture.componentInstance.isLicenseArtifact(artifact); + expect(res).toBe(false); + }); + + it ('should return isLicenseArtifact true', () => { + const artifact = new ArtifactModel(); + const componentInstance = new ComponentInstance(); + const component = new Component(); + fixture.componentInstance.component = new FullComponentInstance(componentInstance, component); + fixture.componentInstance.component.isResource = jest.fn(() => true); + fixture.componentInstance.component.isCsarComponent = true; + + artifact.artifactType = ArtifactType.VENDOR_LICENSE; + const res = fixture.componentInstance.isLicenseArtifact(artifact); + expect(res).toBe(true); + }); + + it ('should verify getEnvArtifact with match', () => { + const artifact = new ArtifactModel(); + artifact.uniqueId = 'matchUniqueID'; + + const testItem1 = new ArtifactModel(); + testItem1.generatedFromId = 'matchUniqueID'; + + const testItem2 = new ArtifactModel(); + testItem2.generatedFromId = '123456'; + + const artifacts: ArtifactModel[] = [testItem1, testItem2]; + + const res = fixture.componentInstance.getEnvArtifact(artifact, artifacts); + expect(res.generatedFromId).toBe('matchUniqueID'); + }); + + it ('should verify getEnvArtifact with no match', () => { + const artifact = new ArtifactModel(); + artifact.uniqueId = 'matchUniqueID'; + + const testItem1 = new ArtifactModel(); + testItem1.generatedFromId = '654321'; + + const testItem2 = new ArtifactModel(); + testItem2.generatedFromId = '123456'; + + const artifacts: ArtifactModel[] = [testItem1, testItem2]; + + const res = fixture.componentInstance.getEnvArtifact(artifact, artifacts); + expect(res).toBe(undefined); + }); + + it ('on updateEnvParams -> openUpdateEnvParams is being called from artifactService when isComponentInstanceSelected = true', () => { + const artifact = new ArtifactModel(); + artifact.envArtifact = new ArtifactModel(); + + const topologyTemplateType: string = undefined; + const topologyTemplateId: string = undefined; + + const component = new Component(); + component.uniqueId = 'id'; + + const isComponentInstanceSelected = true; + + fixture.componentInstance.component = component; + fixture.componentInstance.isComponentInstanceSelected = isComponentInstanceSelected; + fixture.componentInstance.updateEnvParams(artifact); + + expect(artifactsServiceMockService.openUpdateEnvParams).toHaveBeenCalledWith(topologyTemplateType, topologyTemplateId, undefined, component.uniqueId); + }); + + it ('on updateEnvParams -> openUpdateEnvParams is being called from artifactService when isComponentInstanceSelected = false', () => { + const artifact = new ArtifactModel(); + + const topologyTemplateType: string = undefined + const topologyTemplateId: string = undefined; + + const component = new Component(); + + const isComponentInstanceSelected = false; + + fixture.componentInstance.component = component; + fixture.componentInstance.isComponentInstanceSelected = isComponentInstanceSelected; + fixture.componentInstance.updateEnvParams(artifact); + + expect(artifactsServiceMockService.openUpdateEnvParams).toHaveBeenCalledWith(topologyTemplateType, topologyTemplateId, artifact); + }); + + it ('on addOrUpdate -> openArtifactModal is being called from artifactService when isComponentInstanceSelected = true', () => { + const artifact = new ArtifactModel(); + + const topologyTemplateType: string = 'testType'; + const topologyTemplateId: string = 'testID'; + const type: string = 'testType'; + const isViewOnly: boolean = false; + + const component = new Component(); + component.uniqueId = 'id'; + + const isComponentInstanceSelected = true; + + fixture.componentInstance.component = component; + fixture.componentInstance.type = type; + fixture.componentInstance.topologyTemplateId = topologyTemplateId; + fixture.componentInstance.topologyTemplateType = topologyTemplateType; + fixture.componentInstance.isComponentInstanceSelected = isComponentInstanceSelected; + fixture.componentInstance.isViewOnly = isViewOnly; + fixture.componentInstance.addOrUpdate(artifact); + + + expect(artifactsServiceMockService.openArtifactModal).toHaveBeenCalledWith(topologyTemplateId, topologyTemplateType, artifact, type, isViewOnly, component.uniqueId); + }); + + it ('on addOrUpdate -> openArtifactModal is being called from artifactService when isComponentInstanceSelected = false', () => { + const artifact = new ArtifactModel(); + + const topologyTemplateType: string = 'testType'; + const topologyTemplateId: string = 'testID'; + const type: string = 'testType'; + const isViewOnly: boolean = false; + + const isComponentInstanceSelected = false; + + fixture.componentInstance.type = type; + fixture.componentInstance.isComponentInstanceSelected = isComponentInstanceSelected; + fixture.componentInstance.topologyTemplateId = topologyTemplateId; + fixture.componentInstance.topologyTemplateType = topologyTemplateType; + fixture.componentInstance.isViewOnly = isViewOnly; + fixture.componentInstance.addOrUpdate(artifact); + + expect(artifactsServiceMockService.openArtifactModal).toHaveBeenCalledWith(topologyTemplateId, topologyTemplateType, artifact, type, isViewOnly); + }); + + + it ('verify allowDeleteAndUpdateArtifact return false since isViewOnly=true', () => { + const artifact = new ArtifactModel(); + fixture.componentInstance.isViewOnly = true; + + const res = fixture.componentInstance.allowDeleteAndUpdateArtifact(artifact); + expect(res).toBe(false) + }); + + it ('verify allowDeleteAndUpdateArtifact return artifact.isFromCsar since isViewOnly=false && artifactGroupType = DEPLOYMENT', () => { + const artifact = new ArtifactModel(); + artifact.artifactGroupType = ArtifactType.DEPLOYMENT; + artifact.isFromCsar = false; + + fixture.componentInstance.isViewOnly = false; + + const res = fixture.componentInstance.allowDeleteAndUpdateArtifact(artifact); + expect(res).toBe(!artifact.isFromCsar); + }); + + it ('verify allowDeleteAndUpdateArtifact return !artifact.isHEAT() && !artifact.isThirdParty() &&' + + ' !this.isLicenseArtifact(artifact) since isViewOnly=false && artifactGroupType != DEPLOYMENT', () => { + const artifact = new ArtifactModel(); + artifact.artifactGroupType = 'NOT_DEPLOYMENT'; + artifact.isHEAT = () => false; + artifact.isThirdParty = () => false; + + fixture.componentInstance.isLicenseArtifact = jest.fn(() => false); + + fixture.componentInstance.isViewOnly = false; + + const res = fixture.componentInstance.allowDeleteAndUpdateArtifact(artifact); + expect(res).toBe(true ) + }); + + it('verify action on loadArtifacts in case isComponentInstanceSelected = true', () => { + fixture.componentInstance.isComponentInstanceSelected = true; + fixture.componentInstance.topologyTemplateType = 'topologyTemplateType'; + fixture.componentInstance.topologyTemplateId = 'topologyTemplateId'; + const component = new Component(); + component.uniqueId = 'uniqueId'; + fixture.componentInstance.component = component; + fixture.componentInstance.type = 'type'; + + const action = new GetInstanceArtifactsByTypeAction(({ + componentType: 'topologyTemplateType', + componentId: 'topologyTemplateId', + instanceId: 'uniqueId', + artifactType: 'type' + })) + + fixture.componentInstance.store.dispatch = jest.fn(() => Observable.of(true)); + fixture.componentInstance.loadArtifacts(); + + expect(store.dispatch).toBeCalledWith(action); + + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.html new file mode 100644 index 0000000000..264444b674 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.html @@ -0,0 +1,119 @@ +<div class="w-sdc-designer-sidebar-tab-content artifacts"> + <div class="w-sdc-designer-sidebar-section"> + <ng2-expand-collapse state="0"> + <header sdc-tooltip tooltip-text="{{title}}">{{title}}</header> + <content class="artifacts-container"> + <div class="w-sdc-designer-sidebar-section-content"> + <div class="i-sdc-designer-sidebar-section-content-item" *ngFor="let artifact of artifacts$ | async"> + <div class="i-sdc-designer-sidebar-section-content-item-artifact" + *ngIf="(!isComponentInstanceSelected || artifact.esId) && 'HEAT_ENV' !== artifact.artifactType" + attr.data-tests-id="'artifact-item-' + artifact.artifactDisplayName"> + <span *ngIf="artifact.heatParameters?.length" + class="i-sdc-designer-sidebar-section-content-item-file-link"></span> + <div class="i-sdc-designer-sidebar-section-content-item-artifact-details" + [class.heat]="artifact.isHEAT() && artifact.heatParameters?.length"> + <div *ngIf="artifact.artifactName" + class="i-sdc-designer-sidebar-section-content-item-artifact-filename" + attr.data-tests-id="artifactName-{{artifact.artifactDisplayName}}" + sdc-tooltip tooltip-text="{{artifact.artifactName}}">{{artifact.artifactName}} + </div> + <div class="artifact-buttons-container upper-buttons"> + + + <svg-icon + *ngIf="!isViewOnly && !artifact.isFromCsar && artifact.artifactName" + name="trash-o" clickable="true" size="medium" mode="info" + class="artifact-button" testId="delete_{{artifact.artifactDisplayName}}" + (click)="delete(artifact)"></svg-icon> + + <!--Display env parameters edit button for Instance --> + <svg-icon + *ngIf="!isViewOnly && artifact.isHEAT() && isComponentInstanceSelected && artifact.heatParameters?.length" + name="indesign_status" clickable="true" size="medium" mode="info" + class="artifact-button" + testId="edit-parameters-of-{{artifact.artifactDisplayName}}" + (click)="updateEnvParams(artifact)" + tooltip="Edit ENV Params" + ></svg-icon> + + <!--Display env parameters VIEW button for Instance --> + <svg-icon + *ngIf="isViewOnly && artifact.isHEAT() && isComponentInstanceSelected && artifact.heatParameters?.length" + name="inputs-o" clickable="true" size="medium" mode="info" + class="artifact-button" + testId="view-parameters-of-{{artifact.artifactDisplayName}}" + (click)="viewEnvParams(artifact)" + tooltip="View ENV Params" + ></svg-icon> + + <!--Display env parameters edit button for VF --> + <svg-icon + *ngIf = "!isViewOnly && !isComponentInstanceSelected && artifact.heatParameters?.length" + name="indesign_status" clickable="true" size="medium" mode="info" + class="artifact-button" + testId="edit-parameters-of-{{artifact.artifactDisplayName}}" + (click)="updateEnvParams(artifact)"></svg-icon> + + + <download-artifact *ngIf="artifact.esId && 'deployment' != type" + class="artifact-button" + [artifact]="artifact" [componentType]="component.componentType" + [componentId]="component.uniqueId" + testId="download_{{artifact.artifactDisplayName}}" + [isInstance]="isComponentInstanceSelected"></download-artifact> + <download-artifact *ngIf="artifact.esId && 'deployment' == type" + class="artifact-button" + [artifact]="artifact" [componentType]="component.componentType" + [componentId]="component.uniqueId" + [isInstance]="isComponentInstanceSelected" + testId="download_{{artifact.artifactDisplayName}}" + [showLoader]="artifact.isHEAT()"></download-artifact> + + <button *ngIf="!isViewOnly && !artifact.esId && type==='deployment' && !isComponentInstanceSelected && !artifact.isThirdParty()" + class="artifact-button attach sprite e-sdc-small-icon-upload" + (click)="addOrUpdate(artifact)" type="button" + attr.data-tests-id="add_Artifact"></button> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-name" + attr.data-tests-id="artifact_Display_Name-{{artifact.artifactDisplayName}}" + [ngClass]="{'hand enabled': artifact.allowDeleteAndUpdate}" + (click)="artifact.allowDeleteAndUpdate && addOrUpdate(artifact)" + sdc-tooltip tooltip-text="{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}}</span> + <div class="i-sdc-designer-sidebar-section-content-item-artifact-heat-env" + *ngIf="artifact.heatParameters?.length"> + <span attr.data-tests-id="heat_env_{{artifact.artifactDisplayName}}">{{artifact.artifactDisplayName}} (ENV)</span> + <div class="artifact-buttons-container"> + <svg-icon *ngIf="!isViewOnly && envArtifactOf(artifact)" + name="edit-o" clickable="true" size="medium" + mode="info" class="artifact-button edit-pencil" + testId="edit_{{artifact.artifactDisplayName}}" + (click)="addOrUpdate(envArtifactOf(artifact))"></svg-icon> + + <download-artifact [artifact]="envArtifactOf(artifact)" + class="artifact-button" + [componentType]="component.componentType" + [componentId]="component.uniqueId" + [isInstance]="isComponentInstanceSelected" + testId="download_env_{{artifact.artifactDisplayName}}"></download-artifact> + </div> + </div> + </div> + + <div class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc"> + <span class="i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label" + *ngIf="artifact.description">Description:</span>{{artifact.description}} + </div> + </div> + </div> + </div> + </div> + <div class="w-sdc-designer-sidebar-section-footer" + *ngIf="!isViewOnly && type!=='api' && (!isComponentInstanceSelected || isVfOrPnf() && (type !== 'deployment') || isComplex)"> + <sdc-button testId="add_Artifact_Button" size="large" type="primary" text="Add Artifact" + (click)="addOrUpdate({})"></sdc-button> + </div> + </content> + </ng2-expand-collapse> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.less new file mode 100644 index 0000000000..fef199dd97 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.less @@ -0,0 +1,169 @@ +@import '../../../../../../../assets/styles/override'; + + +.artifacts /deep/ .expand-collapse-content { + padding: 10px 0px; + + &.collapsed { + padding: 0 0; + } +} + +.i-sdc-designer-sidebar-section-content-item-artifact { + + &:not(:hover) .artifact-button { + display:none; + } + .artifact-buttons-container { + display: inline-flex; + flex-direction: row-reverse; + position: absolute; + right:0; + + &.upper-buttons { + margin-top: 8px; + } + + .artifact-button { + cursor:pointer; + padding-right:5px; + + &.edit-pencil { + margin-top: 10px; + } + } + } +} + +.w-sdc-designer-sidebar-section-footer { + padding: 20px; + display: flex; + justify-content: center; + +} + + +.w-sdc-designer-sidebar-tab-content.artifacts { + + .i-sdc-designer-sidebar-section-content-item-artifact.hand { + cursor: pointer; + } + + .w-sdc-designer-sidebar-section-content { + padding: 0; + } + .w-sdc-designer-sidebar-section-title { + &.expanded { + margin-bottom: 0; + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details { + display: inline-block; + margin-left: 5px; + vertical-align: middle; + width: 180px; + &.heat { + line-height: 18px; + width: 250px; + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-name { + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width:220px; + display: inline-block; + //text-transform: capitalize; + &.enabled { + &:hover { + color: @sdcui_color_dark-blue; + } + } + + } + + .i-sdc-designer-sidebar-section-content-item-artifact-heat-env { + color: @sdcui_color_dark-gray; + margin-top: 6px; + line-height: 42px; + padding-top: 10px; + border-top:1px solid #c8cdd1; + .enabled { + &:hover { + cursor: pointer; + color: @sdcui_color_dark-blue; + } + } + } + + .i-sdc-designer-sidebar-section-content-item-artifact-filename { + color: @sdcui_color_dark-gray; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + max-width: 225px; + display: inline-block; + font-weight: bold; + &.enabled { + &:hover { + color: @sdcui_color_dark-blue; + } + } + } + + + .i-sdc-designer-sidebar-section-content-item-file-link{ + border-left: 1px #848586 solid; + height: 58px; + margin-left: -11px; + margin-top: 11px; + border-top: 1px #848586 solid; + border-bottom: 1px #848586 solid; + width: 12px; + float: left; + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-desc { + display: none; + line-height: 16px; + word-wrap: break-word; + white-space: normal; + } + + .i-sdc-designer-sidebar-section-content-item-artifact-details-desc-label { + color: @sdcui_color_dark-gray; + } + + + .i-sdc-designer-sidebar-section-content-item-artifact { + border-bottom: 1px solid #c8cdd1; + padding: 5px 10px 5px 18px; + position: relative; + // line-height: 36px; + min-height: 61px; + //cursor: default; + display: flex; + align-items: center; + + + .i-sdc-designer-sidebar-section-content-item-button { + top: 20px; + line-height: 10px; + } + + &:hover { + //background-color: @color_c; + background-color: white; + transition: all .3s; + + .i-sdc-designer-sidebar-section-content-item-button { + display: block; + + } + + } + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.ts new file mode 100644 index 0000000000..53a6c267e2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/artifacts-tab/artifacts-tab.component.ts @@ -0,0 +1,204 @@ +import { Component, Input } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { ArtifactModel, Component as TopologyTemplate, FullComponentInstance, Resource } from 'app/models'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { ResourceNamePipe } from 'app/ng2/pipes/resource-name.pipe'; +import { ComponentInstanceServiceNg2 } from 'app/ng2/services/component-instance-services/component-instance.service'; +import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service'; +import { ArtifactType } from 'app/utils'; +import * as _ from 'lodash'; +import { SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/Observable'; +import { map } from 'rxjs/operators'; +import { ArtifactsService } from '../../../../../components/forms/artifacts-form/artifacts.service'; +import { GetArtifactsByTypeAction } from '../../../../../store/actions/artifacts.action'; +import { GetInstanceArtifactsByTypeAction } from '../../../../../store/actions/instance-artifacts.actions'; +import { ArtifactsState } from '../../../../../store/states/artifacts.state'; +import { InstanceArtifactsState } from '../../../../../store/states/instance-artifacts.state'; +import { SelectedComponentType, TogglePanelLoadingAction } from '../../../common/store/graph.actions'; +import { CompositionService } from '../../../composition.service'; + +@Component({ + selector: 'artifacts-tab', + styleUrls: ['./artifacts-tab.component.less'], + templateUrl: './artifacts-tab.component.html', + providers: [SdcUiServices.ModalService] +}) + +export class ArtifactsTabComponent { + + @Input() component: FullComponentInstance | TopologyTemplate; + @Input() isViewOnly: boolean; + @Input() input: any; + @Input() componentType: SelectedComponentType; + + public title: string; + public type: string; + public isComponentInstanceSelected: boolean; + public artifacts$: Observable<ArtifactModel[]>; + private topologyTemplateType: string; + private topologyTemplateId: string; + private heatToEnv: Map<string, ArtifactModel>; + private resourceType: string; + private isComplex: boolean; + + constructor(private store: Store, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private componentInstanceService: ComponentInstanceServiceNg2, + private topologyTemplateService: TopologyTemplateService, + private artifactService: ArtifactsService) { + this.heatToEnv = new Map(); + } + + ngOnInit() { + this.topologyTemplateType = this.workspaceService.metadata.componentType; + this.topologyTemplateId = this.workspaceService.metadata.uniqueId; + this.type = this.input.type; + this.title = this.getTitle(this.type); + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + this.resourceType = this.component['resourceType']; + this.isComplex = this.component.isComplex(); + this.loadArtifacts(); + } + + public addOrUpdate = (artifact: ArtifactModel): void => { + if (this.isComponentInstanceSelected) { + this.artifactService.openArtifactModal(this.topologyTemplateId, this.topologyTemplateType, artifact, this.type, this.isViewOnly, this.component.uniqueId); + } else { + this.artifactService.openArtifactModal(this.topologyTemplateId, this.topologyTemplateType, artifact, this.type, this.isViewOnly); + } + } + + public updateEnvParams = (artifact: ArtifactModel) => { + if (this.isComponentInstanceSelected) { + this.artifactService.openUpdateEnvParams(this.topologyTemplateType, this.topologyTemplateId, this.heatToEnv.get(artifact.uniqueId), this.component.uniqueId); + } else { + this.artifactService.openUpdateEnvParams(this.topologyTemplateType, this.topologyTemplateId, artifact); + } + } + + public viewEnvParams = (artifact: ArtifactModel) => { + if (this.isComponentInstanceSelected) { + this.artifactService.openViewEnvParams(this.topologyTemplateType, this.topologyTemplateId, this.heatToEnv.get(artifact.uniqueId), this.component.uniqueId); + } else { + this.artifactService.openViewEnvParams(this.topologyTemplateType, this.topologyTemplateId, artifact); + } + } + + public getEnvArtifact = (heatArtifact: ArtifactModel, artifacts: ArtifactModel[]): ArtifactModel => { + const envArtifact = _.find(artifacts, (item: ArtifactModel) => { + return item.generatedFromId === heatArtifact.uniqueId; + }); + if (envArtifact && heatArtifact) { + envArtifact.artifactDisplayName = heatArtifact.artifactDisplayName; + envArtifact.timeout = heatArtifact.timeout; + } + return envArtifact; + } + + public delete = (artifact: ArtifactModel): void => { + if (this.isComponentInstanceSelected) { + this.artifactService.deleteArtifact(this.topologyTemplateType, this.topologyTemplateId, artifact, this.component.uniqueId); + } else { + this.artifactService.deleteArtifact(this.topologyTemplateType, this.topologyTemplateId, artifact); + } + } + + public isVfOrPnf(): boolean { + if (this.component.isResource()){ + if (this.resourceType) { + return this.resourceType === 'VF' || this.resourceType == 'PNF'; + } + return false; + } + + return false; + } + + private envArtifactOf(artifact: ArtifactModel): ArtifactModel { + return this.heatToEnv.get(artifact.uniqueId); + } + + private isLicenseArtifact = (artifact: ArtifactModel): boolean => { + let isLicense: boolean = false; + if (this.component.isResource && (this.component as Resource).isCsarComponent) { + if (ArtifactType.VENDOR_LICENSE === artifact.artifactType || ArtifactType.VF_LICENSE === artifact.artifactType) { + isLicense = true; + } + } + + return isLicense; + } + + private getTitle = (artifactType: string): string => { + switch (artifactType) { + case ArtifactType.SERVICE_API: + return 'API Artifacts'; + case ArtifactType.DEPLOYMENT: + return 'Deployment Artifacts'; + case ArtifactType.INFORMATION: + return 'Informational Artifacts'; + default: + return ResourceNamePipe.getDisplayName(artifactType) + ' Artifacts'; + } + } + + private loadArtifacts = (forceLoad?: boolean): void => { + + this.store.dispatch(new TogglePanelLoadingAction({isLoading: true})); + + let action; + if (this.isComponentInstanceSelected) { + action = new GetInstanceArtifactsByTypeAction(({ + componentType: this.topologyTemplateType, + componentId: this.topologyTemplateId, + instanceId: this.component.uniqueId, + artifactType: this.type + })); + } else { + action = new GetArtifactsByTypeAction({ + componentType: this.topologyTemplateType, + componentId: this.topologyTemplateId, + artifactType: this.type + }); + } + this.store.dispatch(action).subscribe(() => { + const stateSelector = this.isComponentInstanceSelected ? InstanceArtifactsState.getArtifactsByType : ArtifactsState.getArtifactsByType; + this.artifacts$ = this.store.select(stateSelector).pipe(map((filterFn) => filterFn(this.type))).pipe(map((artifacts) => { + _.forEach(artifacts, (artifact: ArtifactModel): void => { + const envArtifact = this.getEnvArtifact(artifact, artifacts); // Extract the env artifact (if exist) of the HEAT artifact + if (envArtifact) { + // Set a mapping between HEAT to HEAT_ENV + this.heatToEnv.set(artifact.uniqueId, envArtifact); + } + }); + return _.orderBy(artifacts, ['mandatory', 'artifactDisplayName'], ['desc', 'asc']); + })); + + this.artifacts$.subscribe((artifacts) => { + _.forEach(artifacts, (artifact: ArtifactModel) => { + artifact.allowDeleteAndUpdate = this.allowDeleteAndUpdateArtifact(artifact); + }); + if (this.component instanceof FullComponentInstance) { + this.compositionService.updateInstance(this.component); + } + }); + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + }, () => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + }); + } + + private allowDeleteAndUpdateArtifact = (artifact: ArtifactModel): boolean => { + if (!this.isViewOnly) { + if (artifact.artifactGroupType === ArtifactType.DEPLOYMENT) { + return !artifact.isFromCsar; + } else { + + return (!artifact.isHEAT() && !artifact.isThirdParty() && !this.isLicenseArtifact(artifact)); + } + } + return false; + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.html index 6585ad2da9..8c5c9c7663 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.html @@ -14,7 +14,7 @@ ~ limitations under the License. --> -<div class="w-sdc-designer-sidebar-section-title" tooltip="Members">Members +<h1 class="w-sdc-designer-sidebar-section-title" tooltip="Members">Members <svg-icon-label *ngIf="!isViewOnly" class="add-members-btn" name="plus-circle-o" @@ -24,7 +24,7 @@ labelPlacement="right" (click)="openAddMembersModal()"> </svg-icon-label> -</div> +</h1> <div class="expand-collapse-content"> <ul> <li *ngFor="let member of members; let i = index" class="component-details-panel-large-item" @@ -40,7 +40,7 @@ </li> </ul> - <div *ngIf="members.length===0" class="component-details-panel-tab-no-data"> + <div *ngIf="!members || members.length===0" class="component-details-panel-tab-no-data"> <div class="component-details-panel-tab-no-data-title">No data to display yet</div> <div class="component-details-panel-tab-no-data-content">Add members to group to see members</div> </div> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.spec.ts new file mode 100644 index 0000000000..43f6aac2c7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.spec.ts @@ -0,0 +1,127 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/Rx'; +import { Mock } from 'ts-mockery'; +import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../../../models/component-metadata'; +import { GroupInstance } from '../../../../../../models/graph/zones/group-instance'; +import { EventListenerService } from '../../../../../../services/event-listener-service'; +import { GroupsService } from '../../../../../services/groups.service'; +import { TranslateService } from '../../../../../shared/translator/translate.service'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { CompositionService } from '../../../composition.service'; +import { GroupMembersTabComponent } from './group-members-tab.component'; + +describe('group members tab component', () => { + + let fixture: ComponentFixture<GroupMembersTabComponent>; + + // Mocks + let workspaceServiceMock: Partial<WorkspaceService>; + let eventsListenerServiceMock: Partial<EventListenerService>; + let groupServiceMock: Partial<GroupsService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let compositionServiceMock: Partial<CompositionService>; + let modalServiceMock: Partial<SdcUiServices.ModalService>; + + const membersToAdd = [ + {uniqueId: '1', name: 'inst1'}, + {uniqueId: '2', name: 'inst2'}, + ]; + + beforeEach( + async(() => { + + eventsListenerServiceMock = {}; + + groupServiceMock = Mock.of<GroupsService>( + { + updateMembers: jest.fn().mockImplementation((compType, uid, groupUniqueId, updatedMembers) => { + if (updatedMembers === undefined) { + return Observable.throwError('error'); + } else { + return Observable.of(updatedMembers); + } + } + )}); + + compositionServiceMock = { + getComponentInstances: jest.fn().mockImplementation( () => { + return [{uniqueId: '1', name: 'inst1'}, + {uniqueId: '2', name: 'inst2'}, + {uniqueId: '3', name: 'inst3'}, + {uniqueId: '4', name: 'inst4'}, + {uniqueId: '5', name: 'inst5'} + ]; + } + ) + }; + + workspaceServiceMock = { + metadata: Mock.of<ComponentMetadata>() + }; + + const addMemberModalInstance = { + innerModalContent: { instance: { existingElements: membersToAdd }}, + closeModal: jest.fn() + }; + + modalServiceMock = { + openInfoModal: jest.fn(), + openCustomModal: jest.fn().mockImplementation(() => addMemberModalInstance) + }; + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + + const groupInstanceMock = Mock.of<GroupInstance>(); + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [GroupMembersTabComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: TranslateService, useValue: { translate: jest.fn() }}, + {provide: GroupsService, useValue: groupServiceMock}, + {provide: SdcUiServices.ModalService, useValue: modalServiceMock }, + {provide: EventListenerService, useValue: eventsListenerServiceMock }, + {provide: CompositionService, useValue: compositionServiceMock }, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(GroupMembersTabComponent); + fixture.componentInstance.group = groupInstanceMock; + }); + }) + ); + + it('test that initially all members are available for adding', () => { + const testedComponent = fixture.componentInstance; + + // No members are currently in the group, all 5 members should be returned + const optionalMembersToAdd = testedComponent.getOptionalsMembersToAdd(); + expect(optionalMembersToAdd).toHaveLength(5); + }); + + it('test list of available instances to add does not include existing members', () => { + const testedComponent = fixture.componentInstance; + + // Mock the group instance to return the members that we are about to add + testedComponent.group.getMembersAsUiObject = jest.fn().mockImplementation( () => membersToAdd); + + // The opened modal shall return 2 members to be added + testedComponent.openAddMembersModal(); + testedComponent.addMembers(); // Shall add 2 members (1,2) + + // Now the getOptionalsMembersToAdd shall return 3 which are the members that were no added yet + const optionalMembersToAdd = testedComponent.getOptionalsMembersToAdd(); + expect(optionalMembersToAdd).toHaveLength(3); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.ts new file mode 100644 index 0000000000..7f1222367d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-members-tab/group-members-tab.component.ts @@ -0,0 +1,158 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= + */ + +import { Component, HostBinding, Input, OnDestroy, OnInit } from '@angular/core'; +import { Select } from '@ngxs/store'; +import { GroupInstance } from 'app/models/graph/zones/group-instance'; +import { CompositionService } from 'app/ng2/pages/composition/composition.service'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { EventListenerService } from 'app/services/event-listener-service'; +import { GRAPH_EVENTS } from 'app/utils'; +import * as _ from 'lodash'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { Observable, Subscription } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import { ComponentInstance } from '../../../../../../models/componentsInstances/componentInstance'; +import { MemberUiObject } from '../../../../../../models/ui-models/ui-member-object'; +import { AddElementsComponent } from '../../../../../components/ui/modal/add-elements/add-elements.component'; +import {GraphState} from "../../../common/store/graph.state"; +import { GroupsService } from '../../../../../services/groups.service'; +import { TranslateService } from '../../../../../shared/translator/translate.service'; + +@Component({ + selector: 'group-members-tab', + templateUrl: './group-members-tab.component.html', + styleUrls: ['./../policy-targets-tab/policy-targets-tab.component.less'] +}) + +export class GroupMembersTabComponent implements OnInit, OnDestroy { + + @Input() group: GroupInstance; + @Input() isViewOnly: boolean; + @Select(GraphState.getSelectedComponent) group$: Observable<GroupInstance>; + @HostBinding('class') classes = 'component-details-panel-tab-group-members'; + + private members: MemberUiObject[]; + private addMemberModalInstance: SdcUiComponents.ModalComponent; + private subscription: Subscription; + + constructor( + private translateService: TranslateService, + private groupsService: GroupsService, + private modalService: SdcUiServices.ModalService, + private eventListenerService: EventListenerService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private loaderService: SdcUiServices.LoaderService + ) { + } + + ngOnInit() { + this.subscription = this.group$.pipe( + tap((group) => { + this.group = group; + this.members = this.group.getMembersAsUiObject(this.compositionService.componentInstances); + })).subscribe(); + } + + ngOnDestroy() { + if (this.subscription) { + this.subscription.unsubscribe(); + } + } + + deleteMember = (member: MemberUiObject): void => { + this.loaderService.activate(); + this.groupsService.deleteGroupMember( + this.workspaceService.metadata.componentType, + this.workspaceService.metadata.uniqueId, + this.group, + member.uniqueId).subscribe( + (updatedMembers: string[]) => { + this.group.members = updatedMembers; + this.initMembers(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.group); + }, + () => console.log('Error deleting member!'), + () => this.loaderService.deactivate() + ); + } + + addMembers = (): void => { + // TODO refactor sdc-ui modal in order to return the data + const membersToAdd: MemberUiObject[] = this.addMemberModalInstance.innerModalContent.instance.existingElements; + if (membersToAdd.length > 0) { + this.addMemberModalInstance.closeModal(); + this.loaderService.activate(); + const locallyUpdatedMembers: MemberUiObject[] = _.union(this.members, membersToAdd); + this.groupsService.updateMembers( + this.workspaceService.metadata.componentType, + this.workspaceService.metadata.uniqueId, + this.group.uniqueId, + locallyUpdatedMembers).subscribe( + (updatedMembers: string[]) => { + this.group.members = updatedMembers; + this.initMembers(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.group); + }, + () => { + console.log('Error updating members!'); + }, () => + this.loaderService.deactivate() + ); + } + } + + getOptionalsMembersToAdd(): MemberUiObject[] { + const optionalsMembersToAdd: MemberUiObject[] = []; + // adding all instances as optional members to add if not already exist + _.forEach(this.compositionService.getComponentInstances(), (instance: ComponentInstance) => { + if (!_.some(this.members, (member: MemberUiObject) => { + return member.uniqueId === instance.uniqueId; + })) { + optionalsMembersToAdd.push(new MemberUiObject(instance.uniqueId, instance.name)); + } + }); + return optionalsMembersToAdd; + } + + openAddMembersModal(): void { + const addMembersModalConfig = { + title: this.group.name + ' ADD MEMBERS', + size: 'md', + type: SdcUiCommon.ModalType.custom, + testId: 'addMembersModal', + buttons: [ + {text: 'ADD MEMBERS', size: 'medium', callback: this.addMembers, closeModal: false}, + {text: 'CANCEL', size: 'sm', type: 'secondary', closeModal: true} + ] + } as SdcUiCommon.IModalConfig; + const optionalsMembersToAdd = this.getOptionalsMembersToAdd(); + this.addMemberModalInstance = this.modalService.openCustomModal(addMembersModalConfig, AddElementsComponent, { + elementsToAdd: optionalsMembersToAdd, + elementName: 'member' + }); + } + + private initMembers = (groupInstance?: GroupInstance) => { + this.group = groupInstance ? groupInstance : this.group; + this.members = this.group.getMembersAsUiObject(this.compositionService.getComponentInstances()); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component.html index fe1f6b4f0d..c57f99786c 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component.html @@ -18,7 +18,7 @@ <header tooltip="Properties">Properties</header> <content> <ul> - <li *ngFor="let property of properties; let i = index" + <li *ngFor="let property of component.properties; let i = index" class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" data-tests-id="propertyRow"> <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label hand" [attr.data-tests-id]="'propertyName_'+property.name" @@ -32,7 +32,7 @@ </li> </ul> - <div *ngIf="properties.length===0" class="component-details-panel-tab-no-data"> + <div *ngIf="!component.properties || component.properties.length===0" class="component-details-panel-tab-no-data"> <div class="component-details-panel-tab-no-data-title">No properties to display</div> </div> </content> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component.ts index 5862135df2..24ae8b2833 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/group-or-policy-properties-tab/group-or-policy-properties-tab.component.ts @@ -19,44 +19,32 @@ */ import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter, OnChanges, SimpleChanges } from "@angular/core"; +import { Component, Inject, Input} from "@angular/core"; import { TranslateService } from './../../../../../shared/translator/translate.service'; import { PolicyInstance } from 'app/models/graph/zones/policy-instance'; -import { PropertyBEModel } from 'app/models'; import { PropertyModel } from './../../../../../../models/properties'; import { ModalsHandler } from "app/utils"; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; +import { Component as TopologyTemplate, GroupInstance } from "app/models"; @Component({ - selector: 'policy-properties-tab', - templateUrl: './policy-properties-tab.component.html', - styleUrls: ['./../base/base-tab.component.less', 'policy-properties-tab.component.less'], - host: {'class': 'component-details-panel-tab-policy-properties'} + selector: 'group-or-policy-properties-tab', + templateUrl: './group-or-policy-properties-tab.component.html', + styleUrls: ['./../properties-tab/properties-tab.component.less'], }) -export class PolicyPropertiesTabComponent implements OnChanges { +export class GroupOrPolicyPropertiesTab { - @Input() policy:PolicyInstance; + @Input() component: GroupInstance | PolicyInstance; @Input() topologyTemplate:TopologyTemplate; @Input() isViewOnly: boolean; + @Input() input: {type: string}; - private properties:Array<PropertyModel>; constructor(private translateService:TranslateService, private ModalsHandler:ModalsHandler) { } - ngOnChanges(changes: SimpleChanges): void { - console.log("PolicyPropertiesTabComponent: ngAfterViewInit: "); - console.log("policy: " + this.policy); - this.properties = []; - this.initProperties(); - } - - initProperties = ():void => { - this.properties= this.policy.properties; - } editProperty = (property?:PropertyModel):void => { - this.ModalsHandler.openEditPropertyModal((property ? property : new PropertyModel()), this.topologyTemplate, this.properties, false, 'policy', this.policy.uniqueId).then((updatedProperty:PropertyModel) => { + this.ModalsHandler.openEditPropertyModal((property ? property : new PropertyModel()), this.topologyTemplate, this.component.properties, false, this.input.type, this.component.uniqueId).then((updatedProperty:PropertyModel) => { console.log("ok"); }); } diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.html deleted file mode 100644 index 953b57bda1..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.html +++ /dev/null @@ -1,47 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - - -<ng2-expand-collapse state="0"> - - <header tooltip="General Information">General Info</header> - - <content> - <!-- CATEGORY --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_CATEGORY' | translate"></span> - <span class="value" data-tests-id="rightTab_category" tooltip="Group">Group</span> - </div> - - <!-- SUB CATEGORY --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_SUB_CATEGORY' | translate"></span> - <span class="value" data-tests-id="rightTab_subCategory" tooltip="Group">Group</span> - </div> - - <!-- VERSION --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_VERSION' | translate"></span> - <span class="value" data-tests-id="rightTab_version" tooltip="{{group.version}}">{{group.version}}</span> - </div> - - <!-- DESCRIPTION --> - <div class="component-details-panel-item description"> - <span class="name" [innerHTML]="'GENERAL_LABEL_DESCRIPTION' | translate"></span> - <span class="value" ellipsis="group.description" max-chars="55" data-tests-id="rightTab_description">{{group.description}}</span> - </div> - </content> -</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.ts deleted file mode 100644 index 26602224da..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-information-tab.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter } from "@angular/core"; -import { GroupInstance } from 'app/models/graph/zones/group-instance'; - -@Component({ - selector: 'group-information-tab', - templateUrl: './group-information-tab.component.html', - styleUrls: ['./../base/base-tab.component.less'] -}) -export class GroupInformationTabComponent { - - @Input() group: GroupInstance; - @Input() isViewOnly: boolean; - - constructor() { - - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.less deleted file mode 100644 index 1006e864fa..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.less +++ /dev/null @@ -1,13 +0,0 @@ -/deep/ -.component-details-panel-tab-group-members { - .component-details-panel-large-item { - display: flex; - flex-direction: row; - justify-content: space-between; - } - - .w-sdc-designer-sidebar-section-title { - display: flex; - justify-content: space-between; - } -}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.ts deleted file mode 100644 index 148f2133e8..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-members-tab.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Input, Output, EventEmitter, OnChanges, HostBinding } from "@angular/core"; -import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { Component as TopologyTemplate } from "app/models"; -import { GroupInstance } from "app/models/graph/zones/group-instance"; -import { GroupsService } from "../../../../../services/groups.service"; -import { SimpleChanges } from "@angular/core/src/metadata/lifecycle_hooks"; -import { MemberUiObject } from "../../../../../../models/ui-models/ui-member-object"; -import { IModalConfig } from "sdc-ui/lib/angular/modals/models/modal-config"; -import { AddElementsComponent } from "../../../../../components/ui/modal/add-elements/add-elements.component"; -import { GRAPH_EVENTS } from 'app/utils'; -import { EventListenerService } from 'app/services/event-listener-service'; -import { ComponentInstance } from "../../../../../../models/componentsInstances/componentInstance"; -import { SdcUiComponents } from "sdc-ui/lib/angular"; - -@Component({ - selector: 'group-members-tab', - templateUrl: './group-members-tab.component.html', - styleUrls: ['./../base/base-tab.component.less', 'group-members-tab.component.less'] -}) - -export class GroupMembersTabComponent implements OnChanges { - - - private members: Array<MemberUiObject>; - - @Input() group: GroupInstance; - @Input() topologyTemplate: TopologyTemplate; - @Input() isViewOnly: boolean; - @Output() isLoading: EventEmitter<boolean> = new EventEmitter<boolean>(); - @HostBinding('class') classes = 'component-details-panel-tab-group-members'; - - constructor(private translateService: TranslateService, - private groupsService: GroupsService, - private modalService: SdcUiComponents.ModalService, - private eventListenerService: EventListenerService - ) { - this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.initMembers) - } - - ngOnChanges(changes:SimpleChanges):void { - this.initMembers(); - } - - deleteMember = (member: MemberUiObject):void => { - this.isLoading.emit(true); - this.groupsService.deleteGroupMember(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.group, member.uniqueId).subscribe( - (updatedMembers:Array<string>) => { - this.group.members = updatedMembers; - this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.group); - }, - error => console.log("Error deleting member!"), - () => this.isLoading.emit(false) - ); - } - - private initMembers = (groupInstance?: GroupInstance) => { - this.group = groupInstance ? groupInstance : this.group; - this.members = this.group.getMembersAsUiObject(this.topologyTemplate.componentInstances); - } - - addMembers = ():void => { - var membersToAdd:Array<MemberUiObject> = this.modalService.getCurrentInstance().innerModalContent.instance.existingElements; //TODO refactor sdc-ui modal in order to return the data - if(membersToAdd.length > 0) { - this.modalService.closeModal(); - this.isLoading.emit(true); - var updatedMembers: Array<MemberUiObject> = _.union(this.members, membersToAdd); - this.groupsService.updateMembers(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.group.uniqueId, updatedMembers).subscribe( - (updatedMembers:Array<string>) => { - this.group.members = updatedMembers; - this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.group); - }, - error => { - console.log("Error updating members!"); - }, () => - this.isLoading.emit(false) - ); - } - } - - getOptionalsMembersToAdd():Array<MemberUiObject> { - - let optionalsMembersToAdd:Array<MemberUiObject> = []; - - // adding all instances as optional members to add if not already exist - _.forEach(this.topologyTemplate.componentInstances, (instance:ComponentInstance) => { - if (!_.some(this.members, (member:MemberUiObject) => { - return member.uniqueId === instance.uniqueId - })) { - optionalsMembersToAdd.push(new MemberUiObject(instance.uniqueId, instance.name)); - } - }); - return optionalsMembersToAdd; - } - - openAddMembersModal():void { - let addMembersModalConfig:IModalConfig = { - title: this.group.name + " ADD MEMBERS", - size: "md", - type: "custom", - testId: "addMembersModal", - buttons: [ - {text: 'ADD MEMBERS', size: 'xsm', callback: this.addMembers, closeModal: false}, - {text: 'CANCEL', size: 'sm', type: "secondary", closeModal: true} - ] - }; - var optionalsMembersToAdd = this.getOptionalsMembersToAdd(); - this.modalService.openCustomModal(addMembersModalConfig, AddElementsComponent, { - elementsToAdd: optionalsMembersToAdd, - elementName: "member" - }); - } -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.html deleted file mode 100644 index fe1f6b4f0d..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.html +++ /dev/null @@ -1,39 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<ng2-expand-collapse state="0"> - <header tooltip="Properties">Properties</header> - <content> - <ul> - <li *ngFor="let property of properties; let i = index" - class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" data-tests-id="propertyRow"> - <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label hand" - [attr.data-tests-id]="'propertyName_'+property.name" - tooltip="{{property.name}}" - (click)="!isViewOnly && editProperty(property)">{{property.name}} - </div> - <div class="i-sdc-designer-sidebar-section-content-item-property-value" - [attr.data-tests-id]="'value_'+property.name" - tooltip="{{property.value || property.defaultValue}}">{{property.value || property.defaultValue}} - </div> - </li> - </ul> - - <div *ngIf="properties.length===0" class="component-details-panel-tab-no-data"> - <div class="component-details-panel-tab-no-data-title">No properties to display</div> - </div> - </content> -</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.ts deleted file mode 100644 index 69079347c4..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.ts +++ /dev/null @@ -1,64 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter, OnChanges, SimpleChanges } from "@angular/core"; -import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { GroupInstance } from 'app/models/graph/zones/group-instance'; -import { PropertyBEModel } from 'app/models'; -import { PropertyModel } from './../../../../../../models/properties'; -import { ModalsHandler } from "app/utils"; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; - -@Component({ - selector: 'group-properties-tab', - templateUrl: './group-properties-tab.component.html', - styleUrls: ['./../base/base-tab.component.less', 'group-properties-tab.component.less'], - host: {'class': 'component-details-panel-tab-group-properties'} -}) -export class GroupPropertiesTabComponent implements OnChanges { - - @Input() group:GroupInstance; - @Input() topologyTemplate:TopologyTemplate; - @Input() isViewOnly: boolean; - - private properties:Array<PropertyModel>; - - constructor(private translateService:TranslateService, private ModalsHandler:ModalsHandler) { - } - - ngOnChanges(changes: SimpleChanges): void { - console.log("GroupPropertiesTabComponent: ngAfterViewInit: "); - console.log("group: " + JSON.stringify(this.group)); - this.properties = []; - this.initProperties(); - } - - initProperties = ():void => { - this.properties= this.group.properties; - } - - editProperty = (property?:PropertyModel):void => { - this.ModalsHandler.openEditPropertyModal((property ? property : new PropertyModel()), this.topologyTemplate, this.properties, false, 'group', this.group.uniqueId).then((updatedProperty:PropertyModel) => { - console.log("ok"); - }); - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.html deleted file mode 100644 index 482de5eacf..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.html +++ /dev/null @@ -1,27 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<sdc-tabs> - <sdc-tab titleIcon="info-circle"> - <group-information-tab [group]="group" [isViewOnly]="isViewOnly" *ngIf="group"></group-information-tab> - </sdc-tab> - <sdc-tab titleIcon="inputs-o"> - <group-members-tab [group]="group" [topologyTemplate]="topologyTemplate" [isViewOnly]="isViewOnly" (isLoading)="setIsLoading($event)" *ngIf="group"></group-members-tab> - </sdc-tab> - <sdc-tab titleIcon="settings-o"> - <group-properties-tab [group]="group" [topologyTemplate]="topologyTemplate" [isViewOnly]="isViewOnly" *ngIf="group"></group-properties-tab> - </sdc-tab> -</sdc-tabs> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.ts deleted file mode 100644 index 975d5c6153..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.ts +++ /dev/null @@ -1,67 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter, SimpleChanges, OnChanges } from "@angular/core"; -import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; -import { GroupsService } from '../../../../../services/groups.service'; -import { GroupInstance } from "app/models/graph/zones/group-instance"; - -@Component({ - selector: 'group-tabs', - templateUrl: './group-tabs.component.html' -}) -export class GroupTabsComponent implements OnChanges { - - @Input() topologyTemplate:TopologyTemplate; - @Input() selectedZoneInstanceType:string; - @Input() selectedZoneInstanceId:string; - @Input() isViewOnly: boolean; - @Output() isLoading: EventEmitter<boolean> = new EventEmitter<boolean>(); - - private group:GroupInstance; - - constructor(private translateService:TranslateService, - private groupsService:GroupsService - ) { - } - - ngOnChanges(changes: SimpleChanges): void { - this.initGroup(); - } - - private initGroup = ():void => { - this.isLoading.emit(true); - this.groupsService.getSpecificGroup(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.selectedZoneInstanceId).subscribe( - group => { - this.group = group; - console.log(JSON.stringify(group)); - }, - error => console.log("Error getting group!"), - () => this.isLoading.emit(false) - ); - } - - private setIsLoading = (value) :void => { - this.isLoading.emit(value); - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.module.ts deleted file mode 100644 index 50797f862c..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.module.ts +++ /dev/null @@ -1,71 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ -import { NgModule } from "@angular/core"; -import { HttpModule } from "@angular/http"; -import { FormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; -import { ExpandCollapseComponent } from 'app/ng2/components/ui/expand-collapse/expand-collapse.component'; -import { PoliciesService } from "../../../../../services/policies.service"; -import { GroupInformationTabComponent } from './group-information-tab.component'; -import { TooltipModule } from './../../../../../components/ui/tooltip/tooltip.module'; -import { GroupTabsComponent } from "./group-tabs.component"; -import { SdcUiComponentsModule } from "sdc-ui/lib/angular"; -import { GroupMembersTabComponent } from './group-members-tab.component'; -import { TranslateModule } from './../../../../../shared/translator/translate.module'; -import { GroupPropertiesTabComponent } from "./group-properties-tab.component"; - -@NgModule({ - declarations: [ - GroupInformationTabComponent, - GroupMembersTabComponent, - GroupTabsComponent, - GroupPropertiesTabComponent - ], - imports: [ - BrowserModule, - FormsModule, - HttpModule, - TooltipModule, - UiElementsModule, - SdcUiComponentsModule, - TranslateModule - ], - entryComponents: [ - GroupInformationTabComponent, - GroupMembersTabComponent, - GroupTabsComponent, - GroupPropertiesTabComponent, - ExpandCollapseComponent - ], - exports: [ - TooltipModule, - GroupInformationTabComponent, - GroupMembersTabComponent, - GroupTabsComponent, - GroupPropertiesTabComponent - ], - providers: [ - PoliciesService - ] -}) -export class GroupTabsModule { - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap new file mode 100644 index 0000000000..fdd0dcf75c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/__snapshots__/info-tab.component.spec.ts.snap @@ -0,0 +1,66 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`InfoTabComponent can load instance 1`] = ` +<panel-info-tab + componentInstanceService={[Function Object]} + compositionPaletteService={[Function Object]} + compositionService={[Function Object]} + eventListenerService={[Function Object]} + flatLeftPaletteElementsFromService={[Function Function]} + getPathNamesVersionChangeModal={[Function Function]} + initEditResourceVersion={[Function Function]} + modalService={[Function Object]} + onChangeVersion={[Function Function]} + sdcMenu={[Function Object]} + serviceService={[Function Object]} + store={[Function Object]} + versioning={[Function Function]} + workspaceService={[Function Object]} +> + <ng2-expand-collapse + state="0" + > + <header + tooltip="General Information" + > + General Info + </header> + <content + class="general-info-container" + > + + + <div + class="component-details-panel-item" + > + <span + class="name" + /> + + + </div> + + + + + + + + + + + + <div + class="component-details-panel-item description" + > + <span + class="name" + /> + <chars-ellipsis /> + </div> + + + </content> + </ng2-expand-collapse> +</panel-info-tab> +`; diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html new file mode 100644 index 0000000000..71545f8143 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.html @@ -0,0 +1,174 @@ +<!-- + ~ Copyright (C) 2018 AT&T Intellectual Property. 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. + --> + +<ng2-expand-collapse state="0"> + <header tooltip="General Information">General Info</header> + <content class="general-info-container"> + <!-- TYPE --> + <div class="component-details-panel-item" *ngIf="component.componentType"> + <span class="name" [innerHTML]="'Type:'"></span> + <span class="value" data-tests-id="rightTab_componentType" tooltip="{{component.componentType}}">{{component.componentType}}</span> + </div> + + <!-- RESOURCE TYPE--> + <div class="component-details-panel-item" *ngIf="component.resourceType"> + <span class="name" [innerHTML]="'Resource Type:'"></span> + <span class="value" data-tests-id="rightTab_resourceType" tooltip="{{component.resourceType}}">{{component.resourceType}}</span> + </div> + + <!-- VERSION --> + <div class="component-details-panel-item" > + <span class="name" [innerHTML]="'GENERAL_LABEL_VERSION' | translate"></span> + <span class="value" *ngIf="!isComponentSelectedFlag" data-tests-id="rightTab_version" tooltip="{{component.version}}">{{component.version}}</span> + <ng-container *ngIf="isComponentSelectedFlag"> + <select #versionDropdown (change)="onChangeVersion(versionDropdown)" [ngModel]="component.getComponentUid()" data-tests-id="changeVersion"> + <option *ngFor="let version of versions" value="{{version.value}}" + [disabled]="isDisabledFlag" [class.minor]="(component.componentVersion)%1" + >{{version.label}}</option> + </select> + </ng-container> + </div> + + <!-- CATEGORY --> + <ng-container *ngIf="component.categories && component.categories[0]"> + <div class="component-details-panel-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_category" tooltip="{{component.categories[0].name}}">{{component.categories[0].name}}</span> + </div> + + <!-- SUB CATEGORY --> + <div class="component-details-panel-item" *ngIf="component.categories[0].subcategories && component.categories[0].subcategories[0]"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SUB_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_subCategory" tooltip="{{component.categories[0].subcategories[0].name}}">{{component.categories[0].subcategories[0].name}}</span> + </div> + </ng-container> + + <!-- CREATION DATE --> + <div class="component-details-panel-item" *ngIf="component.creationDate"> + <span class="name" [innerHTML]="'Creation Date:'"></span> + <span class="value" data-tests-id="rightTab_version" tooltip="{{component.creationDate | date: 'MM/dd/yyyy'}}">{{component.creationDate | date: 'MM/dd/yyyy'}}</span> + </div> + + <!-- AUTHOR --> + <div class="component-details-panel-item" *ngIf="component.creatorFullName"> + <span class="name" [innerHTML]="'Author:'"></span> + <span class="value" data-tests-id="rightTab_author" tooltip="{{component.creatorFullName}}">{{component.creatorFullName}}</span> + </div> + + <!-- Vendor Name data-ng-if="selectedComponent.isResource()"--> + <div class="component-details-panel-item" *ngIf="component.vendorName"> + <span class="name" [innerHTML]="'Vendor Name:'"></span> + <span class="value" data-tests-id="rightTab_vendorName" tooltip="{{component.vendorName}}">{{component.vendorName}}</span> + </div> + + <!-- Vendor Release data-ng-if="selectedComponent.isResource()"--> + <div class="component-details-panel-item" *ngIf="component.vendorRelease"> + <span class="name" [innerHTML]="'Vendor Release:'"></span> + <span class="value" data-tests-id="rightTab_vendorRelease" tooltip="{{component.vendorRelease}}">{{component.vendorRelease}}</span> + </div> + + <!-- Vendor Release data-ng-if="selectedComponent.isResource()"--> + <div class="component-details-panel-item" *ngIf="component.resourceVendorModelNumber"> + <span class="name" [innerHTML]="'GENERAL_LABEL_RESOURCE_MODEL_NUMBER' | translate"></span> + <span class="value" data-tests-id="rightTab_resourceVendorModelNumber" tooltip="{{component.resourceVendorModelNumber}}">{{component.resourceVendorModelNumber}}</span> + </div> + + <!-- Service Type data-ng-if="selectedComponent.isService()"--> + <div class="component-details-panel-item" *ngIf="component.serviceType"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SERVICE_TYPE' | translate"></span> + <span class="value" data-tests-id="rightTab_serviceType" tooltip="{{component.serviceType}}">{{component.serviceType}}</span> + </div> + + <!-- Service Role data-ng-if="selectedComponent.isService()"--> + <div class="component-details-panel-item" *ngIf="component.serviceRole"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SERVICE_ROLE' | translate"></span> + <span class="value" data-tests-id="rightTab_serviceRole" tooltip="{{component.serviceRole}}">{{component.serviceRole}}</span> + </div> + + <!-- Contact ID --> + <div class="component-details-panel-item" *ngIf="component.contactId"> + <span class="name" [innerHTML]="'GENERAL_LABEL_CONTACT_ID' | translate"></span> + <span class="value" data-tests-id="rightTab_contactId" tooltip="{{component.contactId}}">{{component.contactId}}</span> + </div> + + <!-- Service Name data-ng-if="isComponentInstanceSelected() && currentComponent.selectedInstance.isServiceProxy()"--> + <div class="component-details-panel-item" *ngIf="component.sourceModelName"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SOURCE_SERVICE_NAME' | translate"></span> + <span class="value" data-tests-id="rightTab_sourceModelName" tooltip="{{component.sourceModelName}}">{{component.sourceModelName}}</span> + </div> + + <!-- Customization UUID data-ng-if="isViewMode() && currentComponent.isService() && selectedComponent.isResource()"--> + <div class="component-details-panel-item" *ngIf="component.customizationUUID"> + <span class="name" [innerHTML]="'GENERAL_LABEL_RESOURCE_CUSTOMIZATION_UUID' | translate"></span> + <span class="value" data-tests-id="rightTab_customizationModuleUUID" tooltip="{{component.customizationUUID}}">{{component.customizationUUID}}</span> + </div> + + <!-- DESCRIPTION --> + <div class="component-details-panel-item description"> + <span class="name" [innerHTML]="'GENERAL_LABEL_DESCRIPTION' | translate"></span> + <chars-ellipsis [text]="component.description" [maxChars]="55" [testId]="'rightTab_description'"></chars-ellipsis> + </div> + + + <!--TODO: move to separate component!--> + <ng-container *ngIf="componentType == 'POLICY'"> + <!-- TYPE --> + <div class="component-details-panel-item policy-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_TYPE' | translate"></span> + <span class="value" data-tests-id="rightTab_componentType" tooltip="{{component.policyTypeUid}}">{{component.policyTypeUid}}</span> + </div> + + <!-- CATEGORY --> + <div class="component-details-panel-item policy-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_category" tooltip="Policy">Policy</span> + </div> + + <!-- SUB CATEGORY --> + <div class="component-details-panel-item policy-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SUB_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_subCategory" tooltip="Policy">Policy</span> + </div> + </ng-container> + + <!--TODO: move to separate component!--> + <ng-container *ngIf="componentType == 'GROUP'"> + <!-- CATEGORY --> + <div class="component-details-panel-item group-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_category" tooltip="Group">Group</span> + </div> + + <!-- SUB CATEGORY --> + <div class="component-details-panel-item group-item"> + <span class="name" [innerHTML]="'GENERAL_LABEL_SUB_CATEGORY' | translate"></span> + <span class="value" data-tests-id="rightTab_subCategory" tooltip="Group">Group</span> + </div> + + </ng-container> + + </content> +</ng2-expand-collapse> + +<ng2-expand-collapse *ngIf="component.tags || isComponentInstanceSelected()"> + <header tooltip="Tags">Tags</header> + <content class="tags-container"> + <span *ngIf="component.tags?.indexOf(component.name)===-1" class="i-sdc-designer-sidebar-section-content-item-tag" + data-tests-id="rightTab_tag" tooltip="{{component.name}}">{{component.name}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-tag" *ngFor="let tag of component.tags" + data-tests-id="rightTab_tag" tooltip="{{tag}}">{{tag}}</span> + </content> +</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.less new file mode 100644 index 0000000000..c8da4e3e68 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.less @@ -0,0 +1,51 @@ +@import '../../../../../../../assets/styles/variables'; + +.general-info-container { + display: flex; + flex-direction: column; + padding: 10px 20px; +} + +.component-details-panel-item { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-bottom: 5px; + order:1; + + .name { font-family: OpenSans-Semibold, sans-serif; } + .value { padding-left: 10px; } + + + &.description { + margin-top: 28px; + white-space: normal; + word-wrap: break-word; + overflow: ellipsis; + + .value { + padding-left: 0; + max-width: none; + font-weight: normal; + font-family: @font-opensans-regular; + } + } + + &.group-item, &.policy-item { + order:0; + } +} + +.tags-container { + display: flex; + flex-wrap: wrap; + padding: 10px 20px; + + .i-sdc-designer-sidebar-section-content-item-tag { + padding: 5px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + user-select: all; + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts new file mode 100644 index 0000000000..6915d651f1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.spec.ts @@ -0,0 +1,98 @@ +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { CompositionPaletteService } from '../../../../../pages/composition/palette/services/palette.service'; +import { IAppMenu, SdcMenuToken } from '../../../../../../../app/ng2/config/sdc-menu.config'; +import { CompositionService } from '../../../../../pages/composition/composition.service'; +import { ServiceServiceNg2 } from '../../../../../../../app/services-ng2'; +import { WorkspaceService } from '../../../../../../../app/ng2/pages/workspace/workspace.service'; +import { ComponentInstanceServiceNg2 } from '../../../../../../../app/ng2/services/component-instance-services/component-instance.service'; +import { EventListenerService } from '../../../../../../../app/services'; +import { InfoTabComponent } from './info-tab.component'; +import { ConfigureFn, configureTests } from "../../../../../../../jest/test-config.helper"; +import { Observable } from "rxjs"; +import { leftPaletteElements } from "../../../../../../../jest/mocks/left-paeltte-elements.mock"; +import { TranslatePipe } from "../../../../../shared/translator/translate.pipe"; +import { HttpClientModule } from "@angular/common/http"; +import { TranslateModule } from "../../../../../../../app/ng2/shared/translator/translate.module"; +import _ from "lodash"; +import { TranslateService } from "../../../../../shared/translator/translate.service"; +import { SdcUiServices } from "onap-ui-angular"; +import { Component as TopologyTemplate, FullComponentInstance, ComponentInstance } from '../../../../../../../app/models'; + + +describe('InfoTabComponent', () => { + // let comp: InfoTabComponent; + let fixture: ComponentFixture<InfoTabComponent>; + + // let eventServiceMock: Partial<EventListenerService>; + let storeStub:Partial<Store>; + let compositionPaletteServiceStub:Partial<CompositionPaletteService>; + let iAppMenuStub:Partial<IAppMenu>; + let compositionServiceStub:Partial<CompositionService>; + let serviceServiceNg2Stub:Partial<ServiceServiceNg2>; + let workspaceServiceStub:Partial<WorkspaceService>; + let componentInstanceServiceNg2Stub:Partial<ComponentInstanceServiceNg2>; + let eventListenerServiceStub:Partial<EventListenerService>; + + beforeEach( + async(() => { + storeStub = {}; + iAppMenuStub = {}; + eventListenerServiceStub = { + notifyObservers: jest.fn() + } + compositionPaletteServiceStub = { + getLeftPaletteElements: jest.fn().mockImplementation(()=> Observable.of(leftPaletteElements)) + } + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + imports: [ ], + declarations: [ InfoTabComponent, TranslatePipe ], + schemas: [ NO_ERRORS_SCHEMA ], + providers: [ + { provide: Store, useValue: {} }, + { provide: CompositionPaletteService, useValue: compositionPaletteServiceStub }, + { provide: SdcMenuToken, useValue: {} }, + { provide: CompositionService, useValue: {} }, + { provide: SdcUiServices.ModalService, useValue: {}}, + { provide: ServiceServiceNg2, useValue: {} }, + { provide: WorkspaceService, useValue: {} }, + { provide: ComponentInstanceServiceNg2, useValue: {} }, + { provide: EventListenerService, useValue: eventListenerServiceStub }, + { provide: TranslateService, useValue: {}} + ] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(InfoTabComponent); + let comp = fixture.componentInstance; + + }); + }) + ); + + + it('can load instance', () => { + expect(fixture).toMatchSnapshot(); + }); + + describe('Version dropdown', () => { + it('is undefined for topologyTemplate', () => { + fixture.componentInstance.component = <TopologyTemplate>{}; + fixture.componentInstance.initEditResourceVersion(fixture.componentInstance.component, fixture.componentInstance.flatLeftPaletteElementsFromService(leftPaletteElements)); + expect(fixture.componentInstance.versions).toBe(undefined); + }); + it('does not contain the highest minor version if it is checked out', () => { + fixture.componentInstance.component = new ComponentInstance(); + fixture.componentInstance.component.allVersions = + {'1.0': "9c829122-af05-4bc9-b537-5d84f4c8ae25", '1.1': "930d56cb-868d-4e35-bd0f-e737d2fdb171"}; + fixture.componentInstance.component.version = "1.0"; + fixture.componentInstance.component.uuid = "a8cf015e-e4e5-4d4b-a01e-8624e8d36095"; + fixture.componentInstance.initEditResourceVersion(fixture.componentInstance.component, fixture.componentInstance.flatLeftPaletteElementsFromService(leftPaletteElements)); + expect(fixture.componentInstance.versions).toHaveLength(1); + }); + }); + +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.ts new file mode 100644 index 0000000000..45f31e7b35 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/info-tab/info-tab.component.ts @@ -0,0 +1,189 @@ +import { Component, OnInit, Input, Inject, OnDestroy } from '@angular/core'; +import { + PolicyInstance, + GroupInstance, + Component as TopologyTemplate, + ComponentInstance, + LeftPaletteComponent, + FullComponentInstance +} from "app/models"; +import {Store} from "@ngxs/store"; +import { EVENTS, GRAPH_EVENTS } from 'app/utils'; +import {IDropDownOption} from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models"; +import { CompositionPaletteService } from "app/ng2/pages/composition/palette/services/palette.service"; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from "onap-ui-angular"; +import { SdcMenuToken, IAppMenu } from "app/ng2/config/sdc-menu.config"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import { ServiceServiceNg2 } from "app/services-ng2"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; +import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service"; +import { EventListenerService } from "app/services"; +import * as _ from 'lodash'; +import {SelectedComponentType, TogglePanelLoadingAction} from "../../../common/store/graph.actions"; +import Dictionary = _.Dictionary; + + +@Component({ + selector: 'panel-info-tab', + templateUrl: './info-tab.component.html', + styleUrls: ['./info-tab.component.less'], + // providers: [SdcUiServices.ModalService] +}) +export class InfoTabComponent implements OnInit, OnDestroy { + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: TopologyTemplate | PolicyInstance | GroupInstance | ComponentInstance; + public versions: IDropDownOption[]; + private leftPalletElements: LeftPaletteComponent[]; + private isDisabledFlag: boolean; + private isComponentSelectedFlag: boolean; + + constructor(private store: Store, + private compositionPaletteService: CompositionPaletteService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private modalService: SdcUiServices.ModalService, + private componentInstanceService: ComponentInstanceServiceNg2, + private serviceService: ServiceServiceNg2, + private eventListenerService: EventListenerService, + @Inject(SdcMenuToken) public sdcMenu:IAppMenu) { + } + + ngOnInit() { + this.leftPalletElements = this.flatLeftPaletteElementsFromService(this.compositionPaletteService.getLeftPaletteElements()); + this.initEditResourceVersion(this.component, this.leftPalletElements); + this.eventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, (comp) => { + this.component = comp; + }); + this.isComponentSelectedFlag = this.isComponentInstanceSelected(); + this.isDisabledFlag = this.isDisabled(); + + } + + ngOnDestroy() { + this.eventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); + } + + flatLeftPaletteElementsFromService = (leftPalleteElementsFromService: Dictionary<Dictionary<LeftPaletteComponent[]>>): LeftPaletteComponent[] => { + let retValArr = []; + for (const category in leftPalleteElementsFromService) { + for (const subCategory in leftPalleteElementsFromService[category]) { + retValArr = retValArr.concat(leftPalleteElementsFromService[category][subCategory].slice(0)); + } + } + return retValArr; + } + + private isComponentInstanceSelected () { + return this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + } + + private versioning: Function = (versionNumber: string): string => { + let version: Array<string> = versionNumber && versionNumber.split('.'); + return '00000000'.slice(version[0].length) + version[0] + '.' + '00000000'.slice(version[1].length) + version[1]; + }; + + + private onChangeVersion = (versionDropdown) => { + let newVersionValue = versionDropdown.value; + versionDropdown.value = (<FullComponentInstance>this.component).getComponentUid(); + + this.store.dispatch(new TogglePanelLoadingAction({isLoading: true})); + + // let service = <Service>this.$scope.currentComponent; + if(this.component instanceof FullComponentInstance) { + + let onCancel = (error:any) => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + if (error) { + console.log(error); + } + }; + + let onUpdate = () => { + //this function will update the instance version than the function call getComponent to update the current component and return the new instance version + this.componentInstanceService.changeResourceInstanceVersion(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.component.uniqueId, newVersionValue) + .subscribe((component) => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_VERSION_CHANGED, component); + }, onCancel); + }; + + if (this.component.isService() || this.component.isServiceProxy()) { + this.serviceService.checkComponentInstanceVersionChange(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, + this.component.uniqueId, newVersionValue).subscribe((pathsToDelete:string[]) => { + if (pathsToDelete && pathsToDelete.length) { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + + + const {title, message} = this.sdcMenu.alertMessages['upgradeInstance']; + let pathNames:string = this.getPathNamesVersionChangeModal(pathsToDelete); + let onOk: Function = () => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: true})); + + onUpdate(); + }; + const okButton = {testId: "OK", text: "OK", type: SdcUiCommon.ButtonType.info, callback: onOk, closeModal: true} as SdcUiComponents.ModalButtonComponent; + const cancelButton = {testId: "Cancel", text: "Cancel", type: SdcUiCommon.ButtonType.secondary, callback: <Function>onCancel, closeModal: true} as SdcUiComponents.ModalButtonComponent; + const modal = this.modalService.openInfoModal(title, message.format([pathNames]), 'confirm-modal', [okButton, cancelButton]); + modal.getCloseButton().onClick(onCancel); + } else { + onUpdate(); + } + }, onCancel); + } else { + onUpdate(); + } + } + }; + + + private getPathNamesVersionChangeModal = (pathsToDelete:string[]):string => { + const relatedPaths = _.filter(this.compositionService.forwardingPaths, path => + _.find(pathsToDelete, id => + path.uniqueId === id + ) + ).map(path => path.name); + const pathNames = _.join(relatedPaths, ', ') || 'none'; + return pathNames; + }; + + + private initEditResourceVersion = (component, leftPaletteComponents): void => { + if(this.component instanceof ComponentInstance) { + + this.versions = []; + let sorted:any = _.sortBy(_.toPairs(component.allVersions), (item) => { + return item[0] !== "undefined" && this.versioning(item[0]); + }); + _.forEach(sorted, (item) => { + this.versions.push({label: item[0], value: item[1]}); + }); + + let highestVersion = _.last(sorted)[0]; + + if (parseFloat(highestVersion) % 1) { //if highest is minor, make sure it is the latest checked in - + let latestVersionComponent: LeftPaletteComponent = _.maxBy( + _.filter(leftPaletteComponents, (leftPaletteComponent: LeftPaletteComponent) => { //latest checked in + return (leftPaletteComponent.systemName === component.systemName || leftPaletteComponent.uuid === component.uuid); + }) + , (component) => { + return component.version + }); + + let latestVersion: string = latestVersionComponent ? latestVersionComponent.version : highestVersion; + + if (latestVersion && highestVersion != latestVersion) { //highest is checked out - remove from options + this.versions = this.versions.filter(version => version.label != highestVersion); + } + } + } + } + + private isDisabled() { + return this.isViewOnly || this.component['archived'] || this.component['resourceType'] === 'CVFC' + } + +}; + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tab.component.ts new file mode 100644 index 0000000000..c148a4e579 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tab.component.ts @@ -0,0 +1,55 @@ +import { NgModule, Component, Compiler, ViewContainerRef, ViewChild, Input, ComponentRef, ComponentFactoryResolver, ChangeDetectorRef } from '@angular/core'; +import {Component as TopologyTemplate} from "app/models"; +import { SdcUiServices } from "onap-ui-angular"; + +// Helper component to add dynamic tabs +@Component({ + selector: 'panel-tab', + template: `<div #content></div>` +}) +export class PanelTabComponent { + @ViewChild('content', { read: ViewContainerRef }) content; + @Input() isActive:boolean; + @Input() panelTabType; + @Input() input; + @Input() isViewOnly:boolean; + @Input() component:TopologyTemplate; + @Input() componentType; + cmpRef: ComponentRef<any>; + private isViewInitialized: boolean = false; + + constructor(private componentFactoryResolver: ComponentFactoryResolver, + private cdRef: ChangeDetectorRef) { } + + updateComponent() { + if (!this.isViewInitialized || !this.isActive) { + return; + } + if (this.cmpRef) { + this.cmpRef.destroy(); + } + + let factory = this.componentFactoryResolver.resolveComponentFactory(this.panelTabType); + this.cmpRef = this.content.createComponent(factory); + this.cmpRef.instance.input = this.input; + this.cmpRef.instance.isViewOnly = this.isViewOnly; + this.cmpRef.instance.component = this.component; + this.cmpRef.instance.componentType = this.componentType; + this.cdRef.detectChanges(); + } + + ngOnChanges() { + this.updateComponent(); + } + + ngAfterViewInit() { + this.isViewInitialized = true; + this.updateComponent(); + } + + ngOnDestroy() { + if (this.cmpRef) { + this.cmpRef.destroy(); + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tabs.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tabs.less new file mode 100644 index 0000000000..b3c03f85c5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/panel-tabs.less @@ -0,0 +1,65 @@ +@import '../../../../../../assets/styles/variables'; +@import '../../../../../../assets/styles/override'; + + +// --------------------------------------------------------------------------------------------------- +///* override sdc-ui library tabs */ +// --------------------------------------------------------------------------------------------------- + + +:host ::ng-deep .sdc-tabs { + + .sdc-tabs-list { + display: flex; + border-bottom: 1px solid @sdcui_color_silver; + min-height: min-content; + } + .sdc-tab { + background-color: @sdcui_color_white; + border: 1px solid @sdcui_color_silver; + border-left: none; + border-bottom: none; + height: 36px; + width: 60px; + display: flex; + align-content: center; + justify-content: center; + cursor: pointer; + padding: 0; + margin: 0; + + + &.sdc-tab-active { + background-color: @sdcui_color_silver; + border-bottom: none; + } + &[disabled] { + opacity: 0.3; + cursor: default; + } + } + &.sdc-tabs-header { + .sdc-tab { + font-size: 24px; + } + } + &.sdc-tabs-menu { + .sdc-tab { + font-size: 14px; + padding: 0px 10px 4px 10px; + } + } + .sdc-tab-content { + margin-top: 0; + flex:1; + overflow-y:auto; + } +} + + +:host ::ng-deep .expand-collapse-title { + margin-top: 1px; + background-color: #eaeaea; + color: #5a5a5a; + font-family: OpenSans-Semibold, sans-serif; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.html deleted file mode 100644 index 2a1c58c4cf..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.html +++ /dev/null @@ -1,50 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<ng2-expand-collapse state="0"> - <header tooltip="General Information">General Info</header> - <content> - <!-- TYPE --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_TYPE' | translate"></span> - <span class="value" data-tests-id="rightTab_componentType" tooltip="{{policy.policyTypeUid}}">{{policy.policyTypeUid}}</span> - </div> - - <!-- CATEGORY --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_CATEGORY' | translate"></span> - <span class="value" data-tests-id="rightTab_category" tooltip="Policy">Policy</span> - </div> - - <!-- SUB CATEGORY --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_SUB_CATEGORY' | translate"></span> - <span class="value" data-tests-id="rightTab_subCategory" tooltip="Policy">Policy</span> - </div> - - <!-- VERSION --> - <div class="component-details-panel-item"> - <span class="name" [innerHTML]="'GENERAL_LABEL_VERSION' | translate"></span> - <span class="value" data-tests-id="rightTab_version" tooltip="{{policy.version}}">{{policy.version}}</span> - </div> - - <!-- DESCRIPTION --> - <div class="component-details-panel-item description"> - <span class="name" [innerHTML]="'GENERAL_LABEL_DESCRIPTION' | translate"></span> - <span class="value" ellipsis="policy.description" max-chars="55" data-tests-id="rightTab_description">{{policy.description}}</span> - </div> - </content> -</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.ts deleted file mode 100644 index 3639639c88..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.ts +++ /dev/null @@ -1,39 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter } from "@angular/core"; -import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { PolicyInstance } from 'app/models/graph/zones/policy-instance'; - -@Component({ - selector: 'policy-information-tab', - templateUrl: './policy-information-tab.component.html', - styleUrls: ['./../base/base-tab.component.less'] -}) -export class PolicyInformationTabComponent { - - @Input() policy:PolicyInstance; - @Input() isViewOnly: boolean; - - constructor(private translateService:TranslateService) { - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.less deleted file mode 100644 index e69de29bb2..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-properties-tab.component.less +++ /dev/null diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.html deleted file mode 100644 index 8d1730f68c..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.html +++ /dev/null @@ -1,28 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<sdc-tabs> - <sdc-tab titleIcon="info-circle"> - <policy-information-tab [policy]="policy" [isViewOnly]="isViewOnly" *ngIf="policy"></policy-information-tab> - </sdc-tab> - <sdc-tab titleIcon="inputs-o"> - <policy-targets-tab [policy]="policy" [topologyTemplate]="topologyTemplate" [isViewOnly]="isViewOnly" (isLoading)="setIsLoading($event)" *ngIf="policy"></policy-targets-tab> - </sdc-tab> - <sdc-tab titleIcon="settings-o"> - <policy-properties-tab [policy]="policy" [topologyTemplate]="topologyTemplate" [isViewOnly]="isViewOnly" *ngIf="policy"></policy-properties-tab> - </sdc-tab> -</sdc-tabs> - diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.ts deleted file mode 100644 index 1e2739901d..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.component.ts +++ /dev/null @@ -1,72 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter, AfterViewInit, OnChanges } from "@angular/core"; -import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { PoliciesService } from "../../../../../services/policies.service"; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; -import { PolicyInstance } from 'app/models/graph/zones/policy-instance'; -import { GRAPH_EVENTS } from './../../../../../../utils/constants'; -import { EventListenerService } from 'app/services/event-listener-service'; -import { ZoneInstance } from 'app/models/graph/zones/zone-instance'; -import { SimpleChanges } from "@angular/core/src/metadata/lifecycle_hooks"; - -@Component({ - selector: 'policy-tabs', - templateUrl: './policy-tabs.component.html' -}) -export class PolicyTabsComponent implements OnChanges { - - @Input() topologyTemplate:TopologyTemplate; - @Input() selectedZoneInstanceType:string; - @Input() selectedZoneInstanceId:string; - @Input() isViewOnly: boolean; - @Output() isLoading: EventEmitter<boolean> = new EventEmitter<boolean>(); - - private policy:PolicyInstance; - - constructor(private translateService:TranslateService, - private policiesService:PoliciesService - ) { - - } - - ngOnChanges(changes: SimpleChanges): void { - this.initPolicy(); - } - - private initPolicy = ():void => { - this.isLoading.emit(true); - this.policiesService.getSpecificPolicy(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.selectedZoneInstanceId).subscribe( - policy => { - this.policy = policy; - console.log(JSON.stringify(policy)); - }, - error => console.log("Error getting policy!"), - () => this.isLoading.emit(false) - ); - } - - private setIsLoading = (value) :void => { - this.isLoading.emit(value); - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.module.ts deleted file mode 100644 index 38dc19e1af..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-tabs.module.ts +++ /dev/null @@ -1,68 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ -import { NgModule } from "@angular/core"; -import { HttpModule } from "@angular/http"; -import { FormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; -import { ExpandCollapseComponent } from 'app/ng2/components/ui/expand-collapse/expand-collapse.component'; -import { PoliciesService } from "../../../../../services/policies.service"; -import { PolicyInformationTabComponent } from "./policy-information-tab.component"; -import { PolicyTargetsTabComponent } from "./policy-targets-tab.component"; -import { PolicyTabsComponent } from "./policy-tabs.component"; -import { PolicyPropertiesTabComponent } from "./policy-properties-tab.component"; -import { SdcUiComponentsModule } from "sdc-ui/lib/angular"; -import { TranslateModule } from './../../../../../shared/translator/translate.module'; - -@NgModule({ - declarations: [ - PolicyInformationTabComponent, - PolicyTargetsTabComponent, - PolicyPropertiesTabComponent, - PolicyTabsComponent - ], - imports: [ - BrowserModule, - FormsModule, - HttpModule, - SdcUiComponentsModule, - TranslateModule, - UiElementsModule - ], - entryComponents: [ - PolicyInformationTabComponent, - PolicyTargetsTabComponent, - PolicyPropertiesTabComponent, - PolicyTabsComponent, - ExpandCollapseComponent - ], - exports: [ - PolicyInformationTabComponent, - PolicyTargetsTabComponent, - PolicyPropertiesTabComponent, - PolicyTabsComponent - ], - providers: [ - PoliciesService - ] -}) -export class PolicyTabsModule { - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.less deleted file mode 100644 index cd7ace2b6f..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.less +++ /dev/null @@ -1,12 +0,0 @@ -/deep/ -.component-details-panel-tab-policy-targets { - .component-details-panel-large-item { - display: flex; - flex-direction: row; - justify-content: space-between; - } - .w-sdc-designer-sidebar-section-title { - display: flex; - justify-content: space-between; - } -}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.html index e263836fb1..838fd8bb51 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.html @@ -14,7 +14,7 @@ ~ limitations under the License. --> -<div class="w-sdc-designer-sidebar-section-title" titleTooltip="Targets">Targets +<h1 class="w-sdc-designer-sidebar-section-title" titleTooltip="Targets">Targets <svg-icon-label *ngIf="!isViewOnly" class="add-policy-button" name="plus-circle-o" @@ -24,7 +24,7 @@ labelPlacement="right" (click)="openAddTargetModal()"> </svg-icon-label> -</div> +</h1> <div class="expand-collapse-content"> <ul> <li *ngFor="let target of targets; let i = index" class="component-details-panel-large-item" @@ -40,7 +40,7 @@ </li> </ul> - <div *ngIf="targets.length===0" class="component-details-panel-tab-no-data"> + <div *ngIf="!targets || targets.length===0" class="component-details-panel-tab-no-data"> <div class="component-details-panel-tab-no-data-title">No data to display yet</div> <div class="component-details-panel-tab-no-data-content">Add targets to policy to see targets</div> </div> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/base/base-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.less index aa8e75115f..d16a1595df 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/base/base-tab.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.less @@ -1,8 +1,6 @@ @import './../../../../../../../assets/styles/mixins'; -@import "./../../../../../../../assets/styles/variables-old"; -@import './../../../../../../../assets/styles/mixins_old'; -/deep/ + .expand-collapse-content { padding: 20px; } @@ -25,7 +23,9 @@ white-space: nowrap; height: 32px; line-height: 32px; - vertical-align: middle; + display: flex; + flex-direction: row; + justify-content: space-between; &:hover { background-color: #f8f8f8; @@ -37,30 +37,25 @@ } } -.component-details-panel-item { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - height: 22px; - line-height: 22px; - vertical-align: middle; - - &.description { - margin-top: 28px; - white-space: normal; - word-wrap: break-word; - .value { - max-width: none; - font-weight: normal; - font-family: @font-opensans-regular; - } - } - - .name { font-family: OpenSans-Semibold, sans-serif; } - .value { } -} - .component-details-panel-item-delete { cursor: pointer; visibility: hidden; } + +/deep/ .w-sdc-designer-sidebar-section-title { + color: #5a5a5a; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; + background-color: #eaeaea; + cursor: pointer; + display: flex; + justify-content: space-between; + align-items: center; + text-transform: uppercase; + line-height: 32px; + padding: 0 10px 0 20px; + margin-top: 1px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.spec.ts new file mode 100644 index 0000000000..7774138cab --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.spec.ts @@ -0,0 +1,113 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/Rx'; +import { Mock } from 'ts-mockery'; +import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../../../models/component-metadata'; +import { EventListenerService } from '../../../../../../services/event-listener-service'; +import { TranslateService } from '../../../../../shared/translator/translate.service'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { CompositionService } from '../../../composition.service'; +import { PolicyTargetsTabComponent } from "app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component"; +import { PoliciesService } from "app/services-ng2"; +import { PolicyInstance, GroupInstance } from "app/models"; +import { NgxsModule } from "@ngxs/store"; +import { GraphState } from "app/ng2/pages/composition/common/store/graph.state"; +import { WorkspaceState } from "app/ng2/store/states/workspace.state"; +import { TargetUiObject } from "app/models/ui-models/ui-target-object"; +import { TargetOrMemberType } from "app/utils"; + + + + +describe('policy targets tab component', () => { + + let fixture: ComponentFixture<PolicyTargetsTabComponent>; + let component: PolicyTargetsTabComponent; + + let policiesServiceMock = Mock.of<PoliciesService>( + { + updateTargets: jest.fn().mockImplementation((compType, uid, policyUniqueId, updatedTargets) => { + if (updatedTargets === undefined) { + return Observable.throwError('error'); + } else { + return Observable.of(updatedTargets); + } + } + )}); + + let compositionServiceMock = { + componentInstances: [{uniqueId: '1', name: 'inst1'}, + {uniqueId: '2', name: 'inst2'}, + {uniqueId: '3', name: 'inst3'}, + {uniqueId: '4', name: 'inst4'}, + {uniqueId: '5', name: 'inst5'} + ], + groupInstances : [ + Mock.of<GroupInstance>({uniqueId: "group1", name: "group1"}), + Mock.of<GroupInstance>({uniqueId: "group2", name: "group2"}), + Mock.of<GroupInstance>({uniqueId: "group3", name: "group3"}) + ] + }; + + let workspaceServiceMock = { + metadata: Mock.of<ComponentMetadata>() + }; + + let modalServiceMock = { + openInfoModal: jest.fn(), + openCustomModal: jest.fn().mockImplementation(() => { return { + innerModalContent: { instance: { existingElements: targetsToAdd }}, + closeModal: jest.fn() + }}) + }; + + let loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + + const targetsToAdd = [ + <TargetUiObject>{uniqueId: '1', name: 'inst1', type: TargetOrMemberType.COMPONENT_INSTANCES}, + <TargetUiObject>{uniqueId: "group1", name: "group1", type: TargetOrMemberType.GROUPS} + ]; + + const policyInstanceMock = Mock.of<PolicyInstance>( + { getTargetsAsUiObject: jest.fn().mockImplementation( () => targetsToAdd) + }); + + beforeEach(() => { + TestBed.configureTestingModule({ + declarations: [PolicyTargetsTabComponent], + imports: [NgxsModule.forRoot([WorkspaceState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: TranslateService, useValue: { translate: jest.fn() }}, + {provide: PoliciesService, useValue: policiesServiceMock}, + {provide: SdcUiServices.ModalService, useValue: modalServiceMock }, + {provide: EventListenerService, useValue: {} }, + {provide: CompositionService, useValue: compositionServiceMock }, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + + fixture = TestBed.createComponent(PolicyTargetsTabComponent); + component = fixture.componentInstance; + component.policy = policyInstanceMock; + }); + + + it('if there are no existing targets, all component instances AND all groups are available for adding', () => { + component.targets = []; + const optionalTargetsToAdd = component.getOptionalsTargetsToAdd(); + expect(optionalTargetsToAdd).toHaveLength(8); + }); + + it('list of available instances to add does not include existing targets', () => { + component.targets = targetsToAdd; + const optionalMembersToAdd = component.getOptionalsTargetsToAdd(); + expect(optionalMembersToAdd).toHaveLength(6); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.ts index b79f4d9e07..f117290397 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-targets-tab.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policy-targets-tab/policy-targets-tab.component.ts @@ -19,84 +19,106 @@ */ import * as _ from "lodash"; -import { Component, Input, Output, EventEmitter, OnChanges, HostBinding, OnDestroy } from "@angular/core"; +import { Component, Input, Output, EventEmitter, OnChanges, HostBinding, OnDestroy, OnInit } from "@angular/core"; import { TranslateService } from './../../../../../shared/translator/translate.service'; -import { Component as TopologyTemplate } from "app/models"; import { PoliciesService } from "../../../../../services/policies.service"; -import { PolicyInstance, PolicyTargetsMap } from './../../../../../../models/graph/zones/policy-instance'; -import { SimpleChanges } from "@angular/core/src/metadata/lifecycle_hooks"; -import { SdcUiComponents } from "sdc-ui/lib/angular"; -import { IModalConfig } from "sdc-ui/lib/angular/modals/models/modal-config"; +import { PolicyInstance } from './../../../../../../models/graph/zones/policy-instance'; +import { SdcUiComponents, SdcUiCommon, SdcUiServices } from "onap-ui-angular"; import { AddElementsComponent } from "../../../../../components/ui/modal/add-elements/add-elements.component"; import { TargetUiObject } from "../../../../../../models/ui-models/ui-target-object"; import { ComponentInstance } from "../../../../../../models/componentsInstances/componentInstance"; import { TargetOrMemberType } from "../../../../../../utils/constants"; import { GRAPH_EVENTS } from 'app/utils'; import { EventListenerService } from 'app/services/event-listener-service'; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; +import { Store } from "@ngxs/store"; +import { Select } from "@ngxs/store"; +import { Observable } from "rxjs"; +import { tap } from "rxjs/operators"; +import {GraphState} from "../../../common/store/graph.state"; @Component({ selector: 'policy-targets-tab', templateUrl: './policy-targets-tab.component.html', - styleUrls: ['./../base/base-tab.component.less', 'policy-targets-tab.component.less'] + styleUrls: ['policy-targets-tab.component.less'] }) + +export class PolicyTargetsTabComponent implements OnInit { -export class PolicyTargetsTabComponent implements OnChanges, OnDestroy { + @Input() input:any; - private targets: Array<TargetUiObject>; // UI object to hold all targets with names. - @Input() policy: PolicyInstance; - @Input() topologyTemplate: TopologyTemplate; @Input() isViewOnly: boolean; - @Output() isLoading: EventEmitter<boolean> = new EventEmitter<boolean>(); @HostBinding('class') classes = 'component-details-panel-tab-policy-targets'; + @Select(GraphState.getSelectedComponent) policy$: Observable<PolicyInstance>; + public policy: PolicyInstance; + private subscription; + + private addModalInstance: SdcUiComponents.ModalComponent; + public targets: Array<TargetUiObject>; // UI object to hold all targets with names. + constructor(private translateService: TranslateService, private policiesService: PoliciesService, - private modalService: SdcUiComponents.ModalService, - private eventListenerService: EventListenerService - ) { - this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, this.initTargets) - } + private modalService: SdcUiServices.ModalService, + private eventListenerService: EventListenerService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private loaderService: SdcUiServices.LoaderService, + private store: Store + ) { } - ngOnChanges(changes:SimpleChanges):void { - this.initTargets(); + ngOnInit() { + this.subscription = this.policy$.pipe( + tap((policy) => { + if(policy instanceof PolicyInstance){ + this.policy = policy; + this.targets = this.policy.getTargetsAsUiObject(<ComponentInstance[]>this.compositionService.componentInstances, this.compositionService.groupInstances); + } + })).subscribe(); } - ngOnDestroy() { - this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE); + ngOnDestroy () { + if(this.subscription) + this.subscription.unsubscribe(); } deleteTarget(target: TargetUiObject): void { - this.isLoading.emit(true); - this.policiesService.deletePolicyTarget(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.policy, target.uniqueId, target.type).subscribe( + this.loaderService.activate(); + this.policiesService.deletePolicyTarget(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.policy, target.uniqueId, target.type).subscribe( (policyInstance:PolicyInstance) => { + this.targets = this.targets.filter(item => item.uniqueId !== target.uniqueId); this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, policyInstance); + // this.store.dispatch(new UpdateSelectedComponentAction({uniqueId: policyInstance.uniqueId, type:ComponentType.})); + }, + error => { + console.log("Error deleting target!"); + this.loaderService.deactivate(); }, - error => console.log("Error deleting target!"), - () => this.isLoading.emit(false) + () => this.loaderService.deactivate() ); } - private initTargets = (policyInstance?: PolicyInstance) => { - this.policy = policyInstance ? policyInstance : this.policy; - this.targets = this.policy.getTargetsAsUiObject(this.topologyTemplate.componentInstances, this.topologyTemplate.groupInstances); - } addTargets = ():void => { - var targetsToAdd:Array<TargetUiObject> = this.modalService.getCurrentInstance().innerModalContent.instance.existingElements; //TODO refactor sdc-ui modal in order to return the data + var targetsToAdd:Array<TargetUiObject> = this.addModalInstance.innerModalContent.instance.existingElements; //TODO refactor sdc-ui modal in order to return the data if(targetsToAdd.length > 0) { - this.modalService.closeModal(); - this.isLoading.emit(true); - var updatedTarget: Array<TargetUiObject> = _.union(this.targets, targetsToAdd); - this.policiesService.updateTargets(this.topologyTemplate.componentType, this.topologyTemplate.uniqueId, this.policy.uniqueId, updatedTarget).subscribe( + this.addModalInstance.closeModal(); + this.loaderService.activate(); + var updatedTargets: Array<TargetUiObject> = _.union(this.targets, targetsToAdd); + this.policiesService.updateTargets(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.policy.uniqueId, updatedTargets).subscribe( (updatedPolicyInstance:PolicyInstance) => { + this.targets = updatedTargets; this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, updatedPolicyInstance); + // this.store.dispatch(new UpdateSelectedComponentAction({component: updatedPolicyInstance})); }, error => { console.log("Error updating targets!"); + this.loaderService.deactivate(); }, - () => this.isLoading.emit(false) + () => this.loaderService.deactivate() ); } } @@ -104,7 +126,7 @@ export class PolicyTargetsTabComponent implements OnChanges, OnDestroy { getOptionalsTargetsToAdd():Array<TargetUiObject> { let optionalsTargetsToAdd:Array<TargetUiObject> = []; // adding all instances as optional targets to add if not already exist - _.forEach(this.topologyTemplate.componentInstances, (instance:ComponentInstance) => { + _.forEach(this.compositionService.componentInstances, (instance:ComponentInstance) => { if (!_.some(this.targets, (target:TargetUiObject) => { return target.uniqueId === instance.uniqueId })) { @@ -113,7 +135,7 @@ export class PolicyTargetsTabComponent implements OnChanges, OnDestroy { }); // adding all groups as optional targets to add if not already exist - _.forEach(this.topologyTemplate.groupInstances, (groupInstance:ComponentInstance) => { // adding all instances as optional targets to add if not already exist + _.forEach(this.compositionService.groupInstances, (groupInstance:ComponentInstance) => { // adding all instances as optional targets to add if not already exist if (!_.some(this.targets, (target:TargetUiObject) => { return target.uniqueId === groupInstance.uniqueId })) { @@ -125,21 +147,20 @@ export class PolicyTargetsTabComponent implements OnChanges, OnDestroy { } openAddTargetModal(): void { - let addTargetModalConfig: IModalConfig = { + let addTargetModalConfig = { title: this.policy.name + " ADD TARGETS", size: "md", - type: "custom", + type: SdcUiCommon.ModalType.custom, testId: "addTargetsModal", buttons: [ {text: "ADD TARGETS", size: 'xsm', callback: this.addTargets, closeModal: false}, {text: 'CANCEL', size: 'sm', type: "secondary", closeModal: true} ] - }; + } as SdcUiCommon.IModalConfig; var optionalTargetsToAdd = this.getOptionalsTargetsToAdd(); - this.modalService.openCustomModal(addTargetModalConfig, AddElementsComponent, { + this.addModalInstance = this.modalService.openCustomModal(addTargetModalConfig, AddElementsComponent, { elementsToAdd: optionalTargetsToAdd, elementName: "target" }); - } } diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.html new file mode 100644 index 0000000000..86c6fea1ef --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.html @@ -0,0 +1,97 @@ +<ng2-expand-collapse state="0"> + <header sdc-tooltip tooltip-text="{{input.title}}">{{input.title}}</header> + <content> + <div class="w-sdc-designer-sidebar-section"> + <div *ngIf="properties"> + <ng-container *ngFor="let key of objectKeys(properties); let idx = index"> + <sdc-accordion [title]="groupNameByKey(key) + ' Properties'" [css-class]="'properties-accordion'" [arrow-direction]="'right'" [testId]="groupNameByKey(key) + 'properties'" [open]="true"> + + <!--ng-show="isShowDetailsSection" --> + <div class="i-sdc-designer-sidebar-section-content-item" *ngIf="!groupPropertiesByInstance"> + <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" attr.data-tests-id="propertyRow" + *ngFor="let property of properties[key]"> + + <div class="property-details"> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + [ngClass]="{'hand enabled': !isViewOnly}" + sdc-tooltip tooltip-text="{{property.name}}" + (click)="!isViewOnly && updateProperty(property)" + attr.data-tests-id="{{property.name}}">{{property.name}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" *ngIf="isPropertyOwner()" + sdc-tooltip tooltip-text="{{property.defaultValue}}">{{property.defaultValue}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" *ngIf="!isPropertyOwner()" + sdc-tooltip tooltip-text="{{property.value}}" + attr.data-tests-id="value_{{property.name}}">{{property.value}}</span> + </div> + <div class="property-buttons"> + <svg-icon *ngIf="!isViewOnly && (isPropertyOwner() && !property.readonly)" name="trash-o" clickable="true" size="medium" mode="info" testId="delete_{{property.name}}" (click)="deleteProperty(property)"></svg-icon> + </div> + </div> + </div> + <div class="i-sdc-designer-sidebar-section-content-item" *ngIf="groupPropertiesByInstance"> + <ng-container *ngFor="let InstanceProperties of properties[key]; let propIndex = index"> + <div class="vfci-properties-group"> + <div class="second-level"> + <div class="expand-collapse-title-icon"></div> + <span class="w-sdc-designer-sidebar-section-title-text" sdc-tooltip tooltip-text="{{getComponentInstanceNameFromInstanceByKey(InstanceProperties.key)}} Properties" + attr.data-tests-id="vfci-properties">{{getComponentInstanceNameFromInstanceByKey(InstanceProperties.key) + ' Properties'}}</span> + </div> + </div> + <div class="w-sdc-designer-sidebar-section-content instance-properties {{propIndex}}"> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" attr.data-tests-id="propertyRow" + *ngFor="let instanceProperty of InstanceProperties.value"> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + [ngClass]="{'hand enabled': !isViewOnly}" + sdc-tooltip tooltip-text="{{instanceProperty.name}}" + attr.data-tests-id="vfci-property">{{instanceProperty.name}}</span> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" + sdc-tooltip tooltip-text="{{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}"> + {{instanceProperty.value === undefined ? instanceProperty.defaultValue : instanceProperty.value}}</span> + </div> + </div> + </div> + </div> + </ng-container> + </div> + <!--<div class="w-sdc-designer-sidebar-section-footer" *ngIf="(!isViewOnly && isPropertyOwner()) || showAddPropertyButton">--> + <!--<button class="w-sdc-designer-sidebar-section-footer-action tlv-btn blue" attr.data-tests-id="addGrey" (click)="addProperty()" type="button">--> + <!--Add Property--> + <!--</button>--> + <!--</div>--> + </sdc-accordion> + </ng-container> + </div> + + <!--attributes--> + <div *ngIf="attributes"> + <ng-container *ngFor="let key of objectKeys(attributes); let attrIndex = index"> + <sdc-accordion [title]="groupNameByKey(key) + ' Attributes'" [arrow-direction]="'right'" [testId]="groupNameByKey(key) + 'attributes'" [css-class]="'attributes-accordion'"> + <!--ng-show="isShowDetailsSection" --> + <div class="i-sdc-designer-sidebar-section-content-item"> + <div class="i-sdc-designer-sidebar-section-content-item-property-and-attribute" + *ngFor="let attribute of attributes[key]"> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-and-attribute-label" + [ngClass]="{'hand enabled': !isViewOnly}" + sdc-tooltip tooltip-text="{{attribute.name}}" + (click)="!isViewOnly && viewAttribute(attribute)" + attr.data-tests-id="{{attribute.name}}-attr">{{attribute.name}}</span> + </div> + <div> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" *ngIf="isPropertyOwner()" + sdc-tooltip tooltip-text="{{attribute.defaultValue}}">{{attribute.defaultValue}}</span> + <span class="i-sdc-designer-sidebar-section-content-item-property-value" *ngIf="!isPropertyOwner()" + sdc-tooltip tooltip-text="{{attribute.value}}" attr.data-tests-id="value-of-{{attribute.name}}">{{attribute.value}}</span> + </div> + </div> + </div> + </sdc-accordion> + </ng-container> + </div> + </div> + </content> +</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.less new file mode 100644 index 0000000000..5cb0697da1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.less @@ -0,0 +1,66 @@ +.scroll-container { + display: flex; + overflow-y: auto; +} + +.i-sdc-designer-sidebar-section-content-item-property-and-attribute { + color: #666666; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; + border-bottom: 1px solid #cdcdcd; + min-height: 72px; + padding: 15px 10px 10px 18px; + // position: relative; + display:flex; + + .property-details { + flex:1; + } + + .property-buttons { + flex: 0 0 auto; + align-self: center; + } +} + +.i-sdc-designer-sidebar-section-content-item-property-and-attribute-label { + display: block; + font-weight: bold; + &:hover { + color: #3b7b9b; + } +} + +.i-sdc-designer-sidebar-section-content-item-property-and-attribute-label, .i-sdc-designer-sidebar-section-content-item-property-value { + overflow: hidden; + text-overflow: ellipsis; + max-width: 245px; + white-space: nowrap; + display: block; +} + + + +/deep/ .expand-collapse-content { + max-height: max-content; + padding: 10px 0; + + .sdc-accordion .sdc-accordion-header { + + background-color: #e6f6fb; + border-left: solid #009fdb 4px; + box-shadow: 0 0px 3px -1px rgba(0, 0, 0, 0.3); + margin-bottom: 2px; + width: auto; + height: auto; + padding: 10px; + color: #666666; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; + + } + + /deep/.sdc-accordion .sdc-accordion-body { + padding-left: 0; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.ts new file mode 100644 index 0000000000..b4b8248ed0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/properties-tab/properties-tab.component.ts @@ -0,0 +1,212 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { + AttributeModel, + AttributesGroup, + Component as TopologyTemplate, + ComponentMetadata, + FullComponentInstance, + PropertiesGroup, + PropertyModel +} from 'app/models'; +import { CompositionService } from 'app/ng2/pages/composition/composition.service'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { GroupByPipe } from 'app/ng2/pipes/groupBy.pipe'; +import { ResourceNamePipe } from 'app/ng2/pipes/resource-name.pipe'; +import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service'; +import { ComponentGenericResponse } from 'app/ng2/services/responses/component-generic-response'; +import { TranslateService } from 'app/ng2/shared/translator/translate.service'; +import { ModalsHandler } from 'app/utils'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import {SelectedComponentType, TogglePanelLoadingAction} from "../../../common/store/graph.actions"; + +@Component({ + selector: 'properties-tab', + templateUrl: './properties-tab.component.html', + styleUrls: ['./properties-tab.component.less'] +}) +export class PropertiesTabComponent implements OnInit { + attributes: AttributesGroup; + isComponentInstanceSelected: boolean; + properties: PropertiesGroup; + groupPropertiesByInstance: boolean; + propertiesMessage: string; + metadata: ComponentMetadata; + objectKeys = Object.keys; + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: FullComponentInstance | TopologyTemplate; + @Input() input: {title: string}; + + constructor(private store: Store, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private modalsHandler: ModalsHandler, + private topologyTemplateService: TopologyTemplateService, + private modalService: SdcUiServices.ModalService, + private translateService: TranslateService, + private groupByPipe: GroupByPipe) { + } + + ngOnInit() { + this.metadata = this.workspaceService.metadata; + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + this.getComponentInstancesPropertiesAndAttributes(); + } + + public isPropertyOwner = (): boolean => { + return this.component instanceof TopologyTemplate && this.component.isResource(); + } + + public updateProperty = (property: PropertyModel): void => { + this.openEditPropertyModal(property); + } + + public deleteProperty = (property: PropertyModel): void => { + + const onOk: Function = (): void => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: true})); + this.topologyTemplateService.deleteProperty(this.component.componentType, this.component.uniqueId, property.uniqueId) + .subscribe((response) => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + this.component.properties = this.component.properties.filter((prop) => prop.uniqueId !== property.uniqueId); + this.initComponentProperties(); + }, () => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + }); + }; + + const title: string = this.translateService.translate('PROPERTY_VIEW_DELETE_MODAL_TITLE'); + const message: string = this.translateService.translate('PROPERTY_VIEW_DELETE_MODAL_TEXT', {name: property.name}); + const okButton = { + testId: 'OK', + text: 'OK', + type: SdcUiCommon.ButtonType.info, + callback: onOk, + closeModal: true} as SdcUiComponents.ModalButtonComponent; + this.modalService.openInfoModal(title, message, 'delete-modal', [okButton]); + } + + public groupNameByKey = (key: string): string => { + switch (key) { + case 'derived': + return 'Derived'; + + case this.metadata.uniqueId: + return ResourceNamePipe.getDisplayName(this.metadata.name); + + default: + return this.getComponentInstanceNameFromInstanceByKey(key); + } + } + + public getComponentInstanceNameFromInstanceByKey = (key: string): string => { + let instanceName: string = ''; + const componentInstance = this.compositionService.getComponentInstances().find((item) => item.uniqueId === key); + if (key !== undefined && componentInstance) { + + instanceName = ResourceNamePipe.getDisplayName(componentInstance.name); + } + return instanceName; + } + + private getComponentInstancesPropertiesAndAttributes = () => { + this.topologyTemplateService.getComponentInstanceAttributesAndProperties( + this.workspaceService.metadata.uniqueId, + this.workspaceService.metadata.componentType) + .subscribe((genericResponse: ComponentGenericResponse) => { + this.compositionService.componentInstancesAttributes = genericResponse.componentInstancesAttributes || new AttributesGroup(); + this.compositionService.componentInstancesProperties = genericResponse.componentInstancesProperties; + this.initPropertiesAndAttributes(); + }); + } + + private initComponentProperties = (): void => { + let result: PropertiesGroup = {}; + + this.propertiesMessage = undefined; + this.groupPropertiesByInstance = false; + if (this.component instanceof FullComponentInstance) { + result[this.component.uniqueId] = _.orderBy(this.compositionService.componentInstancesProperties[this.component.uniqueId], ['name']); + if (this.component.originType === 'VF') { + this.groupPropertiesByInstance = true; + result[this.component.uniqueId] = Array.from(this.groupByPipe.transform(result[this.component.uniqueId], 'path')); + } + } else if (this.metadata.isService()) { + // Temporally fix to hide properties for service (UI stack when there are many properties) + result = this.compositionService.componentInstancesProperties; + this.propertiesMessage = 'Note: properties for service are disabled'; + } else { + const componentUid = this.component.uniqueId; + result[componentUid] = Array<PropertyModel>(); + const derived = Array<PropertyModel>(); + _.forEach(this.component.properties, (property: PropertyModel) => { + if (componentUid === property.parentUniqueId) { + result[componentUid].push(property); + } else { + property.readonly = true; + derived.push(property); + } + }); + if (derived.length) { + result['derived'] = derived; + } + this.objectKeys(result).forEach((key) => { result[key] = _.orderBy(result[key], ['name']); }); + } + this.properties = result; + } + + private initComponentAttributes = (): void => { + let result: AttributesGroup = {}; + + if (this.component) { + if (this.component instanceof FullComponentInstance) { + result[this.component.uniqueId] = this.compositionService.componentInstancesAttributes[this.component.uniqueId] || []; + } else if (this.metadata.isService()) { + result = this.compositionService.componentInstancesAttributes; + } else { + result[this.component.uniqueId] = (this.component as TopologyTemplate).attributes; + } + this.attributes = result; + this.objectKeys(this.attributes).forEach((key) => { + this.attributes[key] = _.orderBy(this.attributes[key], ['name']); + }); + + } + } + + /** + * This function is checking if the component is the value owner of the current property + * in order to notify the edit property modal which fields to disable + */ + private isPropertyValueOwner = (): boolean => { + return this.metadata.isService() || !!this.component; + } + + /** + * The function opens the edit property modal. + * It checks if the property is from the VF or from one of it's resource instances and sends the needed property list. + * For create property reasons an empty array is transferd + * + * @param property the wanted property to edit/create + */ + private openEditPropertyModal = (property: PropertyModel): void => { + this.modalsHandler.newOpenEditPropertyModal(property, + (this.isPropertyOwner() ? + this.properties[property.parentUniqueId] : + this.properties[property.resourceInstanceUniqueId]) || [], + this.isPropertyValueOwner(), 'component', property.resourceInstanceUniqueId).then((updatedProperty: PropertyModel) => { + if (updatedProperty) { + const oldProp = _.find(this.properties[updatedProperty.resourceInstanceUniqueId], + (prop: PropertyModel) => prop.uniqueId === updatedProperty.uniqueId); + oldProp.value = updatedProperty.value; + } + }); + } + + private initPropertiesAndAttributes = (): void => { + this.initComponentProperties(); + this.initComponentAttributes(); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.html new file mode 100644 index 0000000000..27e05ec1f0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.html @@ -0,0 +1,36 @@ +<div class="w-sdc-designer-sidebar-tab-content sdc-general-tab relations"> + <div *ngIf="!isCurrentDisplayComponentIsComplex(); else complexComponentTemplate"> + <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations"> + <sdc-accordion [title]="'Capabilities'" [arrow-direction]="'right'" [testId]="'Capabilities-accordion'"> + <div *ngFor="let capability of capabilities" class="relations-details-container"> + <div class="relations-name">{{capability.name}} </div> + <div class="relations-desc"> {{capability.type}} </div> + </div> + </sdc-accordion> + </div> + <div class="w-sdc-designer-sidebar-section w-sdc-designer-sidebar-section-relations"> + <sdc-accordion [title]="'Requirements'" [arrow-direction]="'right'" [testId]="'Requirements-accordion'"> + <requirement-list [component]='component' [requirements]="requirements" [isInstanceSelected]="isComponentInstanceSelected"></requirement-list> + </sdc-accordion> + + </div> + </div> + + <ng-template #complexComponentTemplate> + <sdc-accordion *ngIf="capabilitiesInstancesMap" [title]="'Capabilities'" [arrow-direction]="'right'" [testId]="'Capabilities-accordion'"> + <sdc-accordion *ngFor="let key of objectKeys(capabilitiesInstancesMap); let i = index" [title]="key"> + <div *ngFor="let capability of capabilitiesInstancesMap[key]" class="relations-details-container"> + <div class="relations-name">{{capability.name}} </div> + <div class="relations-desc"> {{capability.type}} </div> + </div> + </sdc-accordion> + </sdc-accordion> + + <sdc-accordion *ngIf="requirementsInstancesMap" [title]="'Requirements'" [arrow-direction]="'right'" [testId]="'Requirements-accordion'"> + <sdc-accordion *ngFor="let key of objectKeys(requirementsInstancesMap); let i = index" [title]="key"> + <requirement-list [component]='component' [requirements]="requirementsInstancesMap[key]" [isInstanceSelected]="isComponentInstanceSelected"></requirement-list> + </sdc-accordion> + </sdc-accordion> + + </ng-template> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.less new file mode 100644 index 0000000000..fe4573aadc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.less @@ -0,0 +1,57 @@ + +/deep/.sdc-accordion { + margin-bottom: 0; + display: grid; + + .sdc-accordion-header { + background-color: #e6f6fb; + border-left: solid #009fdb 4px; + box-shadow: 0 0px 3px -1px rgba(0, 0, 0, 0.3); + margin-bottom: 2px; + width: auto; + height: auto; + padding: 10px; + color: #666666; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; + } + + .sdc-accordion-body.open { + padding-left: 0; + padding-top: 0; + .sdc-accordion-header { /*Second level - nested accordion */ + background-color: #f8f8f8; + padding: 4px 20px 4px 37px; + border-bottom: 1px solid #d2d2d2; + border-left:none; + height: 30px; + } + } +} + + +.relations-details-container { + border-bottom: 1px solid #cdcdcd; + padding: 10px 10px 10px 18px; + + font-size: 14px; + font-family: OpenSans-Regular, sans-serif; + + .relations-name { + color: #666666; + font-weight: bold; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + text-transform: capitalize; + max-width: 240px; + display: inline-block; + } + + .relations-desc { + color: #8c8c8c; + word-wrap: break-word; + white-space: normal; + max-width: 265px; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.ts new file mode 100644 index 0000000000..03697b38f2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/req-capabilities-tab.component.ts @@ -0,0 +1,165 @@ +import { Component, OnInit, Input, OnDestroy } from '@angular/core'; +import { Component as TopologyTemplate, Capability, Requirement, CapabilitiesGroup, RequirementsGroup, ComponentInstance, FullComponentInstance } from "app/models"; +import { Store } from "@ngxs/store"; +import { GRAPH_EVENTS } from "app/utils"; +import { ComponentGenericResponse } from "app/ng2/services/responses/component-generic-response"; +import { TopologyTemplateService } from "app/ng2/services/component-services/topology-template.service"; +import { EventListenerService } from "app/services"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import {SelectedComponentType, TogglePanelLoadingAction} from "../../../common/store/graph.actions"; + + +export class InstanceCapabilitiesMap { + [key:string]:Array<Capability>; +} + +export class InstanceRequirementsMap { + [key:string]:Array<Requirement>; +} + +@Component({ + selector: 'req-capabilities-tab', + templateUrl: './req-capabilities-tab.component.html', + styleUrls: ['./req-capabilities-tab.component.less'] +}) +export class ReqAndCapabilitiesTabComponent implements OnInit, OnDestroy { + + isComponentInstanceSelected: boolean; + capabilities:Array<Capability>; + requirements:Array<Requirement>; + capabilitiesInstancesMap:InstanceCapabilitiesMap; + requirementsInstancesMap:InstanceRequirementsMap; + objectKeys = Object.keys; + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: TopologyTemplate | FullComponentInstance; + @Input() input: any; + + + constructor(private store: Store, + private topologyTemplateService:TopologyTemplateService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private eventListenerService:EventListenerService) { } + + ngOnInit(): void { + + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + + this.requirements = []; + this.capabilities = []; + this.initEvents(); + this.initRequirementsAndCapabilities(); + + } + + private initEvents = ():void => { + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + } + + ngOnDestroy(): void { + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_NODE_SELECTED, this.initRequirementsAndCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED, this.updateRequirementCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + this.eventListenerService.unRegisterObserver(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, this.updateRequirementCapabilities); + } + + public isCurrentDisplayComponentIsComplex = ():boolean => { + + if (this.component instanceof FullComponentInstance) { + if (this.component.originType === 'VF') { + return true; + } + return false; + } else { + return this.component.isComplex(); + } + } + + private loadComplexComponentData = () => { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: true})); + + this.topologyTemplateService.getCapabilitiesAndRequirements(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId).subscribe((response:ComponentGenericResponse) => { + this.workspaceService.metadata.capabilities = response.capabilities; + this.workspaceService.metadata.requirements = response.requirements; + this.setScopeCapabilitiesRequirements(response.capabilities, response.requirements); + this.initInstancesMap(); + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + }, (error) => { this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); }); + } + + + private extractValuesFromMap = (map:CapabilitiesGroup | RequirementsGroup):Array<any> => { + let values = []; + _.forEach(map, (capabilitiesOrRequirements:Array<Capability> | Array<Requirement>, key) => { + values = values.concat(capabilitiesOrRequirements) + } + ); + return values; + } + + private setScopeCapabilitiesRequirements = (capabilities:CapabilitiesGroup, requirements:RequirementsGroup) => { + this.capabilities = this.extractValuesFromMap(capabilities); + this.requirements = this.extractValuesFromMap(requirements); + } + + + private initInstancesMap = ():void => { + + this.capabilitiesInstancesMap = new InstanceCapabilitiesMap(); + _.forEach(this.capabilities, (capability:Capability) => { + if (this.capabilitiesInstancesMap[capability.ownerName]) { + this.capabilitiesInstancesMap[capability.ownerName] = this.capabilitiesInstancesMap[capability.ownerName].concat(capability); + } else { + this.capabilitiesInstancesMap[capability.ownerName] = new Array<Capability>(capability); + } + }); + + this.requirementsInstancesMap = new InstanceRequirementsMap(); + _.forEach(this.requirements, (requirement:Requirement) => { + if (this.requirementsInstancesMap[requirement.ownerName]) { + this.requirementsInstancesMap[requirement.ownerName] = this.requirementsInstancesMap[requirement.ownerName].concat(requirement); + } else { + this.requirementsInstancesMap[requirement.ownerName] = new Array<Requirement>(requirement); + } + }); + } + + private initRequirementsAndCapabilities = (needUpdate?: boolean) => { + + // if instance selected, we take the requirement and capabilities of the instance - always exist because we load them with the graph + if (this.component instanceof FullComponentInstance) { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + this.setScopeCapabilitiesRequirements(this.component.capabilities, this.component.requirements); + if (this.component.originType === 'VF') { + this.initInstancesMap(); + } + } else { + // if instance not selected, we take the requirement and capabilities of the VF/SERVICE, if not exist we call api + if (needUpdate || !this.component.capabilities || !this.component.requirements) { + this.loadComplexComponentData(); + + } else { + this.store.dispatch(new TogglePanelLoadingAction({isLoading: false})); + this.setScopeCapabilitiesRequirements(this.component.capabilities, this.component.requirements); + this.initInstancesMap(); + } + } + } + + private updateRequirementCapabilities = () => { + if (!this.isComponentInstanceSelected) { + this.loadComplexComponentData(); + } + } + + + + +} + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.html new file mode 100644 index 0000000000..8292729cf8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.html @@ -0,0 +1,20 @@ +<div class="i-sdc-designer-sidebar-capabilities-requirements"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-group"> + <div class="i-sdc-designer-sidebar-section-content-item-relations" + *ngFor="let requirement of requirements"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{requirement.name}} </div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{requirement.node}} + <div *ngIf="getRelation(requirement) != null"> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-indent-box"></div> + <div class="i-sdc-designer-sidebar-section-content-item-relations-details-child"> + <span class="i-sdc-designer-sidebar-section-content-item-relations-details-desc">{{getRelation(requirement).type}} <br/></span> + <span class="i-sdc-designer-sidebar-section-content-item-relations-details-name">{{getRelation(requirement).requirementName}}</span> + </div> + </div> + </div> + </div> + </div> + </div> + </div> +
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.ts new file mode 100644 index 0000000000..e167c47dcc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/req-capabilities-tab/requirement-list/requirement-list.component.ts @@ -0,0 +1,40 @@ +import { Component, Input } from '@angular/core'; +import { Component as TopologyTemplate, RelationshipModel, Relationship, Requirement } from "app/models"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import { ResourceNamePipe } from "app/ng2/pipes/resource-name.pipe"; + +@Component({ + selector: 'requirement-list', + templateUrl: './requirement-list.component.html' +}) +export class RequirementListComponent { + @Input() component: TopologyTemplate; + @Input() requirements: Array<Requirement>; + @Input() isInstanceSelected:boolean; + + + constructor(private compositionService: CompositionService) { } + + + public getRelation = (requirement:any):any => { + if (this.isInstanceSelected && this.component.componentInstancesRelations) { + let relationItem:Array<RelationshipModel> = _.filter(this.component.componentInstancesRelations, (relation:RelationshipModel) => { + return relation.fromNode === this.component.uniqueId && + _.filter(relation.relationships, (relationship:Relationship) => { + return relationship.relation.requirement == requirement.name && relationship.relation.requirementOwnerId == requirement.ownerId; + }).length; + }); + + if (relationItem && relationItem.length) { + return { + type: requirement.relationship.split('.').pop(), + requirementName: ResourceNamePipe.getDisplayName(this.compositionService.componentInstances[_.map + (this.compositionService.componentInstances, "uniqueId").indexOf(relationItem[0].toNode)].name) + }; + } + } + return null; + }; + +}; + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.html new file mode 100644 index 0000000000..a52c841156 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.html @@ -0,0 +1,15 @@ +<ng2-expand-collapse state="0"> + <header sdc-tooltip tooltip-text="{{input.title}}">{{input.title}}</header> + <content> + <service-consumption + [parentService]="metadata" + [selectedService]="component" + [selectedServiceInstanceId]="component.uniqueId" + [instancesMappedList]="instancesMappedList" + [parentServiceInputs]="componentInputs" + [instancesCapabilitiesMap]="instancesCapabilitiesMap" + [readonly]="isViewOnly"> + </service-consumption> + </content> +</ng2-expand-collapse> + diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.less index e69de29bb2..e69de29bb2 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-properties-tab.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.less diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.ts new file mode 100644 index 0000000000..8715afd047 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-consumption-tab/service-consumption-tab.component.ts @@ -0,0 +1,89 @@ + +import { Component, Input } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { + CapabilitiesGroup, + Capability, + Component as TopologyTemplate, + ComponentInstance, + FullComponentInstance, + InputBEModel, + InputsGroup, + InterfaceModel, + PropertiesGroup +} from 'app/models'; +import { ComponentMetadata } from '../../../../../../models/component-metadata'; +import { ServiceInstanceObject } from '../../../../../../models/service-instance-properties-and-interfaces'; +import { EventListenerService } from '../../../../../../services/event-listener-service'; +import { TopologyTemplateService } from '../../../../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../../../../services/responses/component-generic-response'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { SelectedComponentType } from '../../../common/store/graph.actions'; +import { CompositionService } from '../../../composition.service'; + +@Component({ + selector: 'service-consumption-tab', + templateUrl: './service-consumption-tab.component.html', + styleUrls: ['./service-consumption-tab.component.less'], +}) +export class ServiceConsumptionTabComponent { + isComponentInstanceSelected: boolean; + + instancesMappedList: ServiceInstanceObject[]; + componentInstancesProperties: PropertiesGroup; + componentInstancesInputs: InputsGroup; + componentInstancesInterfaces: Map<string, InterfaceModel[]>; + componentInputs: InputBEModel[]; + componentCapabilities: Capability[]; + instancesCapabilitiesMap: Map<string, Capability[]>; + metadata: ComponentMetadata; + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: TopologyTemplate | FullComponentInstance; + @Input() input: any; + + constructor(private store: Store, + private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private eventListenerService: EventListenerService ) {} + ngOnInit() { + this.metadata = this.workspaceService.metadata; + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + this.initInstances(); + } + + private initInstances = (): void => { + this.topologyTemplateService.getServiceConsumptionData(this.metadata.componentType, this.metadata.uniqueId).subscribe((genericResponse: ComponentGenericResponse) => { + this.componentInstancesProperties = genericResponse.componentInstancesProperties; + this.componentInstancesInputs = genericResponse.componentInstancesInputs; + this.componentInstancesInterfaces = genericResponse.componentInstancesInterfaces; + this.componentInputs = genericResponse.inputs; + this.buildInstancesCapabilitiesMap(genericResponse.componentInstances); + this.updateInstanceAttributes(); + }); + } + + private buildInstancesCapabilitiesMap = (componentInstances: Array<ComponentInstance>): void => { + this.instancesCapabilitiesMap = new Map(); + let flattenCapabilities = []; + _.forEach(componentInstances, (componentInstance) => { + flattenCapabilities = CapabilitiesGroup.getFlattenedCapabilities(componentInstance.capabilities); + this.instancesCapabilitiesMap[componentInstance.uniqueId] = _.filter(flattenCapabilities, cap => cap.properties && cap.ownerId === componentInstance.uniqueId); + }); + } + + private updateInstanceAttributes = (): void => { + if (this.isComponentInstanceSelected && this.componentInstancesProperties) { + this.instancesMappedList = this.compositionService.componentInstances.map((coInstance) => new ServiceInstanceObject({ + id: coInstance.uniqueId, + name: coInstance.name, + properties: this.componentInstancesProperties[coInstance.uniqueId] || [], + inputs: this.componentInstancesInputs[coInstance.uniqueId] || [], + interfaces: this.componentInstancesInterfaces[coInstance.uniqueId] || [] + })); + } + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.html new file mode 100644 index 0000000000..47351a46a1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.html @@ -0,0 +1,18 @@ +<ng2-expand-collapse state="0"> + <header sdc-tooltip tooltip-text="{{input.title}}">{{input.title}}</header> + <content> + <div *ngIf="isComponentInstanceSelected"> + <service-dependencies + [compositeService]="metaData" + [currentServiceInstance]="component" + [selectedInstanceProperties]="selectedInstanceProperties" + [selectedInstanceSiblings]="selectedInstanceSiblings" + [selectedInstanceConstraints]="selectedInstanceConstraints" + [readonly]="isViewOnly" + (dependencyStatus)="notifyDependencyEventsObserver($event)" + (updateRulesListEvent)="updateSelectedInstanceConstraints($event)" + (loadRulesListEvent)="loadConstraints()"> + </service-dependencies> + </div> + </content> +</ng2-expand-collapse> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.less new file mode 100644 index 0000000000..47e26e2d64 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.less @@ -0,0 +1,3 @@ +:host /deep/ .expand-collapse-content { + padding: 0 0 10px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.ts new file mode 100644 index 0000000000..5171e3b607 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/service-dependencies-tab/service-dependencies-tab.component.ts @@ -0,0 +1,95 @@ + +import { Component, Input } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { + CapabilitiesGroup, + Capability, + Component as TopologyTemplate, + ComponentInstance, + FullComponentInstance, + InputBEModel, + InputsGroup, + InterfaceModel, + PropertiesGroup, + PropertyBEModel, +} from 'app/models'; +import { DEPENDENCY_EVENTS } from 'app/utils/constants'; +import { ComponentMetadata } from '../../../../../../models/component-metadata'; +import { ServiceInstanceObject } from '../../../../../../models/service-instance-properties-and-interfaces'; +import { EventListenerService } from '../../../../../../services/event-listener-service'; +import { ConstraintObject } from '../../../../../components/logic/service-dependencies/service-dependencies.component'; +import { TopologyTemplateService } from '../../../../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../../../../services/responses/component-generic-response'; +import { WorkspaceService } from '../../../../workspace/workspace.service'; +import { SelectedComponentType } from '../../../common/store/graph.actions'; +import { CompositionService } from '../../../composition.service'; + +@Component({ + selector: 'service-dependencies-tab', + templateUrl: 'service-dependencies-tab.component.html', + styleUrls: ['service-dependencies-tab.component.less'] +}) +export class ServiceDependenciesTabComponent { + isComponentInstanceSelected: boolean; + + selectedInstanceSiblings: ServiceInstanceObject[]; + componentInstancesConstraints: any[]; + selectedInstanceConstraints: ConstraintObject[]; + selectedInstanceProperties: PropertyBEModel[]; + componentInstanceProperties: PropertiesGroup; + metaData: ComponentMetadata; + + @Input() isViewOnly: boolean; + @Input() componentType: SelectedComponentType; + @Input() component: FullComponentInstance | TopologyTemplate; + @Input() input: any; + + constructor(private store: Store, + private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private eventListenerService: EventListenerService) { + } + + ngOnInit() { + this.metaData = this.workspaceService.metadata; + this.isComponentInstanceSelected = this.componentType === SelectedComponentType.COMPONENT_INSTANCE; + this.initInstancesWithProperties(); + this.loadConstraints(); + this.initInstancesWithProperties(); + } + + public loadConstraints = (): void => { + this.topologyTemplateService.getServiceFilterConstraints(this.metaData.componentType, this.metaData.uniqueId).subscribe((response) => { + this.componentInstancesConstraints = response.nodeFilterData; + }); + } + + public notifyDependencyEventsObserver = (isChecked: boolean): void => { + this.eventListenerService.notifyObservers(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE, isChecked); + } + + public updateSelectedInstanceConstraints = (constraintsList:Array<ConstraintObject>):void => { + this.componentInstancesConstraints[this.component.uniqueId].properties = constraintsList; + this.selectedInstanceConstraints = this.componentInstancesConstraints[this.component.uniqueId].properties; + } + + private initInstancesWithProperties = (): void => { + this.topologyTemplateService.getComponentInstanceProperties(this.metaData.componentType, this.metaData.uniqueId).subscribe((genericResponse: ComponentGenericResponse) => { + this.componentInstanceProperties = genericResponse.componentInstancesProperties; + this.updateInstanceAttributes(); + }); + } + + private updateInstanceAttributes = (): void => { + if (this.isComponentInstanceSelected && this.componentInstanceProperties) { + const instancesMappedList = this.compositionService.componentInstances.map((coInstance) => new ServiceInstanceObject({ + id: coInstance.uniqueId, + name: coInstance.name, + properties: this.componentInstanceProperties[coInstance.uniqueId] || [] + })); + this.selectedInstanceProperties = this.componentInstanceProperties[this.component.uniqueId]; + this.selectedInstanceSiblings = instancesMappedList.filter((coInstance) => coInstance.id !== this.component.uniqueId); + } + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.html deleted file mode 100644 index 9bb809249a..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.html +++ /dev/null @@ -1,50 +0,0 @@ -<!-- - ~ Copyright (C) 2018 AT&T Intellectual Property. 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. - --> - -<ng2-composition-panel-header - [name]="selectedZoneInstanceName" - [topologyTemplate]="topologyTemplate" - [selectedZoneInstanceType]="selectedZoneInstanceType" - [selectedZoneInstanceId]="selectedZoneInstanceId" - [nonCertified]="nonCertified" - [isViewOnly]="isViewOnly" - [isLoading]="isLoading" -></ng2-composition-panel-header> - -<div class="component-details-panel-tabs"> - <loader [display]="isLoading" [size]="'large'" [relative]="true" [loaderDelay]="500"></loader> - - <div *ngIf="selectedZoneInstanceType === zoneInstanceType.POLICY"> - <policy-tabs - [topologyTemplate]="topologyTemplate" - [selectedZoneInstanceType]="selectedZoneInstanceType" - [selectedZoneInstanceId]="selectedZoneInstanceId" - [isViewOnly]="isViewOnly" - (isLoading)="setIsLoading($event)" - ></policy-tabs> - </div> - - <div *ngIf="selectedZoneInstanceType === zoneInstanceType.GROUP"> - <group-tabs - [topologyTemplate]="topologyTemplate" - [selectedZoneInstanceType]="selectedZoneInstanceType" - [selectedZoneInstanceId]="selectedZoneInstanceId" - [isViewOnly]="isViewOnly" - (isLoading)="setIsLoading($event)" - ></group-tabs> - </div> - -</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.less deleted file mode 100644 index 1777d54486..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.less +++ /dev/null @@ -1,11 +0,0 @@ -/deep/ -.component-details-panel { - - color: #666666; - font-family: OpenSans-Regular, sans-serif; - font-size: 14px; - - .component-details-panel-tabs { - - } -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.ts deleted file mode 100644 index 53599d6366..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel.component.ts +++ /dev/null @@ -1,60 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ - -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter, AfterViewInit, SimpleChanges, HostBinding } from "@angular/core"; -import { Component as TopologyTemplate, ComponentInstance, IAppMenu } from "app/models"; -import { PolicyInstance } from 'app/models/graph/zones/policy-instance'; -import { TranslateService } from 'app/ng2/shared/translator/translate.service'; -import { ZoneInstanceType } from "app/models/graph/zones/zone-instance"; -import { GroupsService } from "../../../services/groups.service"; -import { PoliciesService } from "../../../services/policies.service"; -import { SdcUiComponents } from "sdc-ui/lib/angular"; -import { IZoneService } from "../../../../models/graph/zones/zone"; - -@Component({ - selector: 'ng2-composition-panel', - templateUrl: './panel.component.html', - styleUrls: ['./panel.component.less'], - providers: [TranslateService] -}) -export class CompositionPanelComponent { - - @Input() topologyTemplate: TopologyTemplate; - @Input() selectedZoneInstanceType: ZoneInstanceType; - @Input() selectedZoneInstanceId: string; - @Input() selectedZoneInstanceName: string; - @Input() nonCertified: boolean; - @Input() isViewOnly: boolean; - @Input() isLoading: boolean; - - - @HostBinding('class') classes = 'component-details-panel'; - - private zoneInstanceType = ZoneInstanceType; // Expose ZoneInstanceType to use in template. - - constructor(){ - } - - private setIsLoading = (value):void => { - this.isLoading = value; - } - -} diff --git a/catalog-ui/src/app/ng2/pages/composition/panel/panel.module.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel.module.ts deleted file mode 100644 index 57f6be8b8e..0000000000 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel.module.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*- - * ============LICENSE_START======================================================= - * SDC - * ================================================================================ - * Copyright (C) 2017 AT&T Intellectual Property. 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========================================================= - */ -import {NgModule} from "@angular/core"; -import {HttpModule} from "@angular/http"; -import {FormsModule} from "@angular/forms"; -import {BrowserModule} from "@angular/platform-browser"; -import {CompositionPanelComponent} from "./panel.component"; -import {CompositionPanelHeaderModule} from "app/ng2/pages/composition/panel/panel-header/panel-header.module"; -import {GroupTabsModule} from "./panel-tabs/groups/group-tabs.module"; -import {PolicyTabsModule} from "./panel-tabs/policies/policy-tabs.module"; -import {SdcUiComponents} from "sdc-ui/lib/angular"; -import {UiElementsModule} from 'app/ng2/components/ui/ui-elements.module'; -import {AddElementsModule} from "../../../components/ui/modal/add-elements/add-elements.module"; - -@NgModule({ - declarations: [ - CompositionPanelComponent - ], - imports: [ - BrowserModule, - FormsModule, - HttpModule, - CompositionPanelHeaderModule, - PolicyTabsModule, - GroupTabsModule, - UiElementsModule, - AddElementsModule - ], - entryComponents: [ - CompositionPanelComponent - ], - exports: [], - providers: [SdcUiComponents.ModalService] -}) -export class CompositionPanelModule { - -} |