diff options
Diffstat (limited to 'catalog-ui/src/app/ng2')
473 files changed, 26332 insertions, 3517 deletions
diff --git a/catalog-ui/src/app/ng2/app.component.html b/catalog-ui/src/app/ng2/app.component.html index c98da88fb3..c66c360843 100644 --- a/catalog-ui/src/app/ng2/app.component.html +++ b/catalog-ui/src/app/ng2/app.component.html @@ -13,11 +13,5 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - -<!--<nav> - <app-navbar></app-navbar> -</nav> -<main> - <router-outlet></router-outlet> -</main>-->
\ No newline at end of file + +<sdc-loader global="true" name="general" testId="loader"></sdc-loader> diff --git a/catalog-ui/src/app/ng2/app.component.ts b/catalog-ui/src/app/ng2/app.component.ts index cb10581c93..06139ca9af 100644 --- a/catalog-ui/src/app/ng2/app.component.ts +++ b/catalog-ui/src/app/ng2/app.component.ts @@ -7,9 +7,9 @@ * 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. @@ -17,18 +17,16 @@ * limitations under the License. * ============LICENSE_END========================================================= */ - -import { Component, Inject, ViewContainerRef } from '@angular/core'; +import { Component, ViewContainerRef} from '@angular/core'; import { AuthenticationService } from './services/authentication.service'; @Component({ selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] + templateUrl: './app.component.html' }) export class AppComponent { - constructor(auth:AuthenticationService, public viewContainerRef:ViewContainerRef){ + constructor(auth: AuthenticationService, public viewContainerRef: ViewContainerRef){ } diff --git a/catalog-ui/src/app/ng2/app.module.ts b/catalog-ui/src/app/ng2/app.module.ts index b541beb3d7..3f43feb7b2 100644 --- a/catalog-ui/src/app/ng2/app.module.ts +++ b/catalog-ui/src/app/ng2/app.module.ts @@ -3,7 +3,6 @@ * SDC * ================================================================================ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved. - * Modifications Copyright (C) 2019 Nokia. 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. @@ -19,6 +18,7 @@ * ============LICENSE_END========================================================= */ +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { BrowserModule } from '@angular/platform-browser'; import { NgModule, APP_INITIALIZER } from '@angular/core'; import { FormsModule } from '@angular/forms'; @@ -26,84 +26,110 @@ import { forwardRef } from '@angular/core'; import { AppComponent } from './app.component'; import { UpgradeAdapter } from '@angular/upgrade'; import { UpgradeModule } from '@angular/upgrade/static'; -import { SdcUiComponentsModule, SdcUiComponents } from "sdc-ui/lib/angular"; +import { SdcUiComponentsModule, SdcUiComponents } from 'onap-ui-angular'; import { PropertiesAssignmentModule } from './pages/properties-assignment/properties-assignment.module'; -import { PropertyCreatorModule } from './pages/properties-assignment/property-creator/property-creator.module'; import { - DataTypesServiceProvider, SharingServiceProvider, CookieServiceProvider, StateServiceFactory, - StateParamsServiceFactory, CacheServiceProvider, EventListenerServiceProvider, ScopeServiceFactory, + DataTypesServiceProvider, CookieServiceProvider, StateServiceFactory, + StateParamsServiceFactory, ScopeServiceFactory, NotificationServiceProvider, ComponentFactoryProvider -} from "./utils/ng1-upgraded-provider"; -import { ConfigService } from "./services/config.service"; -import { HttpModule } from '@angular/http'; -import { HttpService } from './services/http.service'; +} from './utils/ng1-upgraded-provider'; +import { ConfigService } from './services/config.service'; import { AuthenticationService } from './services/authentication.service'; -import { Cookie2Service } from "./services/cookie.service"; -import { ComponentServiceNg2 } from "./services/component-services/component.service"; -import { ComponentServiceFactoryNg2 } from "./services/component-services/component.service.factory"; -import { ServiceServiceNg2 } from "./services/component-services/service.service"; -import { ComponentInstanceServiceNg2 } from "./services/component-instance-services/component-instance.service"; -import { WorkflowServiceNg2 } from './services/workflow.service'; -import {ToscaTypesServiceNg2} from "./services/tosca-types.service"; -import { ModalService } from "./services/modal.service"; -import { UiElementsModule } from "./components/ui/ui-elements.module"; -import { ConnectionWizardModule } from "./pages/connection-wizard/connection-wizard.module"; -import { InterfaceOperationModule } from "./pages/interface-operation/interface-operation.module"; -import { OperationCreatorModule } from "./pages/interface-operation/operation-creator/operation-creator.module"; -import { LayoutModule } from "./components/layout/layout.module"; -import { UserService } from "./services/user.service"; -import { DynamicComponentService } from "./services/dynamic-component.service"; -import { SdcConfig } from "./config/sdc-config.config"; -import { SdcMenu } from "./config/sdc-menu.config"; -import { TranslateModule } from "./shared/translator/translate.module"; -import { TranslationServiceConfig } from "./config/translation.service.config"; -import { MultilineEllipsisModule } from "./shared/multiline-ellipsis/multiline-ellipsis.module"; -import { ServicePathCreatorModule } from './pages/service-path-creator/service-path-creator.module'; -import { ServicePathsListModule } from './pages/service-paths-list/service-paths-list.module'; -import { ServicePathModule } from 'app/ng2/components/logic/service-path/service-path.module'; -import { ServicePathSelectorModule } from 'app/ng2/components/logic/service-path-selector/service-path-selector.module'; -import { ServiceConsumptionModule } from 'app/ng2/components/logic/service-consumption/service-consumption.module'; -import { ServiceConsumptionCreatorModule } from './pages/service-consumption-editor/service-consumption-editor.module'; -import {ServiceDependenciesModule} from 'app/ng2/components/logic/service-dependencies/service-dependencies.module'; -import {ServiceDependenciesEditorModule} from './pages/service-dependencies-editor/service-dependencies-editor.module'; -import { CompositionPanelModule } from 'app/ng2/pages/composition/panel/panel.module'; -import { WindowRef } from "./services/window.service"; -import {ArchiveService} from "./services/archive.service"; +import { Cookie2Service } from './services/cookie.service'; +import { ComponentServiceNg2 } from './services/component-services/component.service'; +import { ComponentServiceFactoryNg2 } from './services/component-services/component.service.factory'; +import { ServiceServiceNg2 } from './services/component-services/service.service'; +import { ComponentInstanceServiceNg2 } from './services/component-instance-services/component-instance.service'; +import { ModalService } from './services/modal.service'; +import { UiElementsModule } from './components/ui/ui-elements.module'; +import { ConnectionWizardModule } from './pages/composition/graph/connection-wizard/connection-wizard.module'; +import { InterfaceOperationModule } from './pages/interface-operation/interface-operation.module'; +import { OperationCreatorModule } from './pages/interface-operation/operation-creator/operation-creator.module'; +import { LayoutModule } from './components/layout/layout.module'; +import { UserService } from './services/user.service'; +import { DynamicComponentService } from './services/dynamic-component.service'; +import { SdcConfig } from './config/sdc-config.config'; +import { SdcMenu } from './config/sdc-menu.config'; +import { TranslateModule } from './shared/translator/translate.module'; +import { TranslationServiceConfig } from './config/translation.service.config'; +import { MultilineEllipsisModule } from './shared/multiline-ellipsis/multiline-ellipsis.module'; +import { ServicePathCreatorModule } from './pages/composition/graph/service-path-creator/service-path-creator.module'; +import { ServicePathsListModule } from './pages/composition/graph/service-paths-list/service-paths-list.module'; +import { ServicePathSelectorModule } from 'app/ng2/pages/composition/graph/service-path-selector/service-path-selector.module'; +import { CompositionPanelModule } from 'app/ng2/pages/composition/panel/composition-panel.module'; +import { CatalogModule } from './pages/catalog/catalog.module'; +import { HomeModule } from './pages/home/home.module'; +import { WindowRef } from './services/window.service'; +import { CatalogService } from './services/catalog.service'; import { ModalsHandlerProvider } from './utils/ng1-upgraded-provider'; -import {PluginFrameModule} from "./components/ui/plugin/plugin-frame.module"; -import {PluginsService} from "./services/plugins.service"; -import {EventBusService} from "./services/event-bus.service"; -import {GroupsService} from "./services/groups.service"; -import {PoliciesService} from "./services/policies.service"; -import {AutomatedUpgradeService} from "./pages/automated-upgrade/automated-upgrade.service"; -import {AutomatedUpgradeModule} from "./pages/automated-upgrade/automated-upgrade.module"; -import {RequirementsEditorModule} from "./pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module" -import {CapabilitiesEditorModule} from "./pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module" -import {GenericArtifactBrowserModule} from "./components/logic/generic-artifact-browser/generic-artifact-browser.module"; -import {GabService} from "./services/gab.service"; -import {DeclareListModule} from "./pages/properties-assignment/declare-list/declare-list.module" +import { PluginFrameModule } from './components/ui/plugin/plugin-frame.module'; +import { PluginsService } from './services/plugins.service'; +import { EventBusService } from './services/event-bus.service'; +import { GroupsService } from './services/groups.service'; +import { PoliciesService } from './services/policies.service'; +import { AutomatedUpgradeService } from './pages/automated-upgrade/automated-upgrade.service'; +import { AutomatedUpgradeModule } from './pages/automated-upgrade/automated-upgrade.module'; +import {WorkspaceModule} from './pages/workspace/workspace.module'; +import { ModalsModule } from './components/modals/modals.module'; +import { SharingService, CacheService, HomeService } from 'app/services-ng2'; +import { IUserProperties } from 'app/models'; +import { PluginsModule } from './pages/plugins/plugins-module'; +import {WorkspaceNg1BridgeService} from './pages/workspace/workspace-ng1-bridge-service'; +import {NgxsModule} from '@ngxs/store'; +import {NgxsLoggerPluginModule} from '@ngxs/logger-plugin'; +import {NgxsReduxDevtoolsPluginModule} from '@ngxs/devtools-plugin'; +import {EventListenerService} from '../services/event-listener-service'; +import { HttpClientModule } from '@angular/common/http'; +import { httpInterceptorProviders } from './http-interceptor'; +import { HttpHelperService } from './services/http-hepler.service'; +import { ModulesService } from "./services/modules.service"; +import { TranslateService } from 'app/ng2/shared/translator/translate.service'; +import { FileUtilsService } from './services/file-utils.service'; +import { ImportVSPService } from './components/modals/onboarding-modal/import-vsp.service'; +import { OnboardingService } from './services/onboarding.service'; +import { ServiceConsumptionCreatorModule } from './pages/service-consumption-editor/service-consumption-editor.module'; +import { ServiceDependenciesModule } from './components/logic/service-dependencies/service-dependencies.module'; +import { ServiceDependenciesEditorModule } from './pages/service-dependencies-editor/service-dependencies-editor.module'; +import { PropertyCreatorModule } from './pages/properties-assignment/property-creator/property-creator.module'; +import { DeclareListModule } from './pages/properties-assignment/declare-list/declare-list.module'; +import { WorkflowServiceNg2 } from './services/workflow.service'; +import { ToscaTypesServiceNg2 } from "./services/tosca-types.service"; + + +declare const __ENV__: string; export const upgradeAdapter = new UpgradeAdapter(forwardRef(() => AppModule)); -export function configServiceFactory(config: ConfigService) { +export function configServiceFactory(config: ConfigService, authService: AuthenticationService, eventListener: EventListenerService) { + return () => { - return Promise.all([ - config.loadValidationConfiguration(), - config.loadPluginsConfiguration() - ]); - } + return authService.authenticate().toPromise() + .then((userInfo: IUserProperties) => { + authService.setLoggedinUser(userInfo); + return Promise.all([ + config.loadSdcSetupData(), + config.loadValidationConfiguration(), + config.loadPluginsConfiguration(), + ]) + }).then(() => { + eventListener.notifyObservers('ON_FINISH_LOADING'); + }) + .catch(() => { + console.log('AUTH FAILED! from app module'); + }); + }; } - @NgModule({ declarations: [ AppComponent ], imports: [ + BrowserAnimationsModule, BrowserModule, UpgradeModule, FormsModule, - HttpModule, + HttpClientModule, LayoutModule, TranslateModule, MultilineEllipsisModule, @@ -111,80 +137,90 @@ export function configServiceFactory(config: ConfigService) { CompositionPanelModule, SdcUiComponentsModule, AutomatedUpgradeModule, - //We need to import them here since we use them in angular1 + + // We need to import them here since we use them in angular1 ConnectionWizardModule, PropertiesAssignmentModule, PropertyCreatorModule, DeclareListModule, PluginFrameModule, + PluginsModule, InterfaceOperationModule, OperationCreatorModule, ServicePathCreatorModule, ServicePathsListModule, - ServicePathModule, ServicePathSelectorModule, - ServiceConsumptionModule, ServiceConsumptionCreatorModule, ServiceDependenciesModule, ServiceDependenciesEditorModule, - RequirementsEditorModule, - CapabilitiesEditorModule, - GenericArtifactBrowserModule + WorkspaceModule, + ModalsModule, + CatalogModule, + HomeModule, + NgxsModule.forRoot([]), + NgxsLoggerPluginModule.forRoot({ logger: console, collapsed: false }), + NgxsReduxDevtoolsPluginModule.forRoot({ + disabled: __ENV__ === 'prod' + }) ], exports: [], entryComponents: [ - // *** sdc-ui components to be used as downgraded: - SdcUiComponents.SvgIconComponent ], providers: [ WindowRef, + httpInterceptorProviders, DataTypesServiceProvider, - SharingServiceProvider, + SharingService, + CacheService, + HomeService, ComponentFactoryProvider, CookieServiceProvider, StateServiceFactory, StateParamsServiceFactory, ScopeServiceFactory, - CacheServiceProvider, - EventListenerServiceProvider, NotificationServiceProvider, ModalsHandlerProvider, - AuthenticationService, + UserService, Cookie2Service, ConfigService, ComponentServiceNg2, ComponentServiceFactoryNg2, ModalService, + ImportVSPService, + OnboardingService, ServiceServiceNg2, AutomatedUpgradeService, WorkflowServiceNg2, ToscaTypesServiceNg2, - HttpService, - UserService, + WorkspaceNg1BridgeService, + HttpHelperService, + AuthenticationService, PoliciesService, GroupsService, + ModulesService, DynamicComponentService, SdcConfig, SdcMenu, ComponentInstanceServiceNg2, + EventListenerService, TranslationServiceConfig, + TranslateService, PluginsService, - GabService, - ArchiveService, + CatalogService, EventBusService, + FileUtilsService, { provide: APP_INITIALIZER, useFactory: configServiceFactory, - deps: [ConfigService], + deps: [ConfigService, AuthenticationService, EventListenerService], multi: true }, ], bootstrap: [AppComponent] }) - export class AppModule { - constructor(public upgrade: UpgradeModule, public eventBusService:EventBusService) { + constructor(public upgrade: UpgradeModule) { } } diff --git a/catalog-ui/src/app/ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component.ts b/catalog-ui/src/app/ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component.ts index 85d1899d96..269a8708fe 100644 --- a/catalog-ui/src/app/ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component.ts +++ b/catalog-ui/src/app/ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.component.ts @@ -1,21 +1,19 @@ /*- - * ============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========================================================= + <!-- + ~ 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. + --> */ import { Component, Input } from '@angular/core'; diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/__snapshots__/artifact-form.component.spec.ts.snap b/catalog-ui/src/app/ng2/components/forms/artifacts-form/__snapshots__/artifact-form.component.spec.ts.snap new file mode 100644 index 0000000000..8cd085e248 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/__snapshots__/artifact-form.component.spec.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`artifact form component should match current snapshot of artifact form component 1`] = ` +<artifact-form + artifactTypesOptions={[Function Array]} + cacheService={[Function Object]} + initArtifactTypes={[Function Function]} + onDescriptionChange={[Function Function]} + onLabelChange={[Function Function]} + onTypeChange={[Function Function]} + onUploadFile={[Function Function]} + onValidationChange={[Function Subject]} + verifyTypeAndFileWereFilled={[Function Function]} +> + <form + class="artifact-form" + name="artifactForm" + novalidate="" + > + <onap-file-upload /> + <div + class="artifact-form-container" + > + + <div + class="right-form-container" + > + <sdc-textarea + label="Description" + testid="description" + /> + <sdc-validation> + <sdc-required-validator /> + <sdc-regex-validator /> + </sdc-validation> + </div> + </div> + </form> +</artifact-form> +`; diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.html b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.html new file mode 100644 index 0000000000..c84d6de512 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.html @@ -0,0 +1,55 @@ +<form class="artifact-form" novalidate name="artifactForm"> + + <onap-file-upload [disabled]="isViewOnly || showTypeFields && !artifact.artifactType" [convertToBase64]="true" [(value)]="artifact.artifactName" (fileUpload)="onUploadFile($event)" [placeHolder]="'Select File'" [label]="'Upload File'" [testId]="'fileUploadElement'" [required]="true"> + + </onap-file-upload> + <div class="artifact-form-container"> + <div class="left-form-container" *ngIf="showTypeFields"> + <sdc-input #artifactLabel + required="true" + [(value)]="artifact.artifactLabel" + [maxLength]="25" + [label]="'Artifact Label'" + [disabled]="isViewOnly || artifact && artifact.uniqueId" + [testId]="'artifactLabel'" + (keyup)="verifyTypeAndFileWereFilled()"> + </sdc-input> + <sdc-validation [validateElement]="artifactLabel" (validityChanged)="onLabelChange($event)"> + <sdc-required-validator [message]="'ADD_ARTIFACT_ERROR_LABEL_REQUIRED' | translate"></sdc-required-validator> + <sdc-regex-validator [message]="'VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED' | translate" + [pattern]="validationPatterns['label']"></sdc-regex-validator> + </sdc-validation> + + <sdc-dropdown #artifactType [disabled]="isViewOnly || artifact && artifact.uniqueId" label="Type" [required]="true" + [selectedOption]="selectedFileType" placeHolder="Please choose type" (changed)="onTypeChange($event)" + [options]="artifactTypesOptions" [testId]="'artifacttype'"></sdc-dropdown> + <sdc-validation [validateElement]="artifactType"> + <sdc-required-validator + [message]="'ADD_ARTIFACT_ERROR_TYPE_REQUIRED' | translate"></sdc-required-validator> + </sdc-validation> + </div> + + <div class="right-form-container"> + <sdc-textarea #artifactDescription + [(value)]="artifact.description" + [required]="true" + testId="description" + [maxLength]="256" + label="Description" + [disabled]="isViewOnly" + (keyup)="verifyTypeAndFileWereFilled()"> + </sdc-textarea> + <sdc-validation [validateElement]="artifactDescription" (validityChanged)="onDescriptionChange($event)"> + <sdc-required-validator + [message]="'ADD_ARTIFACT_ERROR_DESCRIPTION_REQUIRED' | translate:{'field': 'Message' }"></sdc-required-validator> + <sdc-regex-validator [message]="'VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED' | translate" + [pattern]="validationPatterns['comment']"></sdc-regex-validator> + </sdc-validation> + </div> + </div> +</form> + +<div *ngIf="artifact && artifact.esId"> + <div>UUID: {{artifact.artifactUUID}}</div> + <div>Version: {{artifact.artifactVersion}}</div> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.less b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.less new file mode 100644 index 0000000000..3b04122085 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.less @@ -0,0 +1,23 @@ +.artifact-form { + display: flex; + justify-content: space-between; + flex-direction: column; + + .artifact-form-container { + margin-top: 10px; + display: flex; + flex-direction: row; + .left-form-container { + flex: 1; + padding-right: 10px; + } + + .right-form-container { + flex: 1; + + /deep/.sdc-textarea .sdc-textarea__textarea{ + min-height: 110px; + } + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.spec.ts b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.spec.ts new file mode 100644 index 0000000000..fc69509ee9 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.spec.ts @@ -0,0 +1,242 @@ +import {ArtifactFormComponent} from "./artifact-form.component"; +import {async, ComponentFixture} from "@angular/core/testing"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {TranslateModule} from "../../../shared/translator/translate.module"; +import {CacheService} from "../../../services/cache.service"; +import {TranslateService} from "../../../shared/translator/translate.service"; +import {ArtifactModel} from "../../../../models/artifacts"; +import {IDropDownOption} from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models"; +import {Observable, Subject} from "rxjs"; +import {getValue} from "@ngxs/store"; + + +describe('artifact form component', () => { + + let fixture: ComponentFixture<ArtifactFormComponent>; + let cacheServiceMock: Partial<CacheService>; + let onValidationChangeMock: Partial<Subject<boolean>>; + + let artifactModel = new ArtifactModel(); + + + beforeEach( + async(() => { + + onValidationChangeMock = { + next: jest.fn() + } + + cacheServiceMock = { + contains: jest.fn(), + remove: jest.fn(), + set: jest.fn(), + get: jest.fn() + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [ArtifactFormComponent], + imports: [TranslateModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: CacheService, useValue: cacheServiceMock}, + {provide: TranslateService, useValue: {}} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(ArtifactFormComponent); + }); + }) + ); + + + it('should verify initArtifactTypes for DEPLOYMENT and ArtifactType = HEAT_ENV', () =>{ + + cacheServiceMock.get.mockImplementation(() =>{ + return { + artifacts: { + deployment:{ + resourceInstanceDeploymentArtifacts: [{name: "Dummy Value Returned from ui api"}], + } + } + } + }); + + fixture.componentInstance.artifactType = 'DEPLOYMENT'; + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.artifactType = 'HEAT_ENV'; + + fixture.componentInstance.initArtifactTypes(); + + expect(fixture.componentInstance.selectedFileType).toEqual(undefined); + + }); + + it('should verify initArtifactTypes for DEPLOYMENT and ComponentType = RESOURCE', () =>{ + + cacheServiceMock.get.mockImplementation(() =>{ + return { + artifacts: { + deployment:{ + resourceDeploymentArtifacts: [{name: "Dummy Value Returned from ui api"}], + } + } + } + }); + + fixture.componentInstance.artifactType = 'DEPLOYMENT'; + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.componentType = 'RESOURCE'; + + fixture.componentInstance.initArtifactTypes(); + + expect(fixture.componentInstance.selectedFileType).toEqual(undefined); + + }); + + it('should verify initArtifactTypes for DEPLOYMENT and NOT ComponentType = RESOURCE OR NOT ArtifactType = HEAT_ENV', () =>{ + + cacheServiceMock.get.mockImplementation(() =>{ + return { + artifacts: { + deployment:{ + serviceDeploymentArtifacts: [{name: "Dummy Value Returned from ui api"}], + } + } + } + }); + + fixture.componentInstance.artifactType = 'DEPLOYMENT'; + fixture.componentInstance.artifact = artifactModel; + // fixture.componentInstance.componentType = 'RESOURCE'; + + fixture.componentInstance.initArtifactTypes(); + + expect(fixture.componentInstance.selectedFileType).toEqual(undefined); + + }); + + it('should verify initArtifactTypes for INFORMATION', () =>{ + + cacheServiceMock.get.mockImplementation(() =>{ + return { + artifacts: { + other: [{name: "Val1"}, {name: "ExpectedValToBeSelected"}, {name: "Val3"}] + } + } + }); + + fixture.componentInstance.artifactType = 'INFORMATIONAL'; + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.artifactType = 'ExpectedValToBeSelected'; + + fixture.componentInstance.initArtifactTypes(); + + expect(fixture.componentInstance.selectedFileType).toEqual({"label": "ExpectedValToBeSelected", "value": "ExpectedValToBeSelected"}); + + }); + + + it('should match current snapshot of artifact form component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should verify onUploadFile -> file gets file name', () => { + let file = { + filename:'dummyFileName' + } + + fixture.componentInstance.verifyTypeAndFileWereFilled = jest.fn(); + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.onUploadFile(file); + + expect(fixture.componentInstance.artifact.artifactName).toBe('dummyFileName'); + + const spy1 = jest.spyOn(fixture.componentInstance,'verifyTypeAndFileWereFilled'); + expect(spy1).toHaveBeenCalled(); + }); + + it('should verify onUploadFile -> file is null', () => { + let file = null; + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.onUploadFile(file); + + expect(fixture.componentInstance.artifact.artifactName).toBe(null); + }); + + it('should verify onTypeChange -> verifyTypeAndFileWereFilled is being called', () => { + let selectedFileType:IDropDownOption; + selectedFileType = {"label": "dummyLabel", "value": "dummyValue"}; + fixture.componentInstance.verifyTypeAndFileWereFilled = jest.fn(); + + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.onTypeChange(selectedFileType); + + const spy1 = jest.spyOn(fixture.componentInstance,'verifyTypeAndFileWereFilled'); + expect(spy1).toHaveBeenCalled(); + }); + + it('should verify onDescriptionChange -> verifyTypeAndFileWereFilled is being called', () => { + fixture.componentInstance.verifyTypeAndFileWereFilled = jest.fn(); + fixture.componentInstance.onValidationChange.next = jest.fn(() => true); + + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.onDescriptionChange(); + + const spy1 = jest.spyOn(fixture.componentInstance,'verifyTypeAndFileWereFilled'); + expect(spy1).toHaveBeenCalled(); + }); + + it('should verify onLabelChange -> verifyTypeAndFileWereFilled is being called', () => { + fixture.componentInstance.verifyTypeAndFileWereFilled = jest.fn(); + fixture.componentInstance.onValidationChange.next = jest.fn(() => true); + + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.onLabelChange(true); + + const spy1 = jest.spyOn(fixture.componentInstance,'verifyTypeAndFileWereFilled'); + expect(spy1).toHaveBeenCalled(); + }); + + it('should verify verifyTypeAndFileWereFilled -> verify branch this.artifact.artifactType !== \'DEPLOYMENT\' ==>> onValidationChange.next(false)', () => { + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.artifactType = 'NOT_DEPLOYMENT'; + fixture.componentInstance.artifact.mandatory = true; + fixture.componentInstance.descriptionIsValid = false; + + let onValidationChangeResult; + + fixture.componentInstance.onValidationChange.subscribe((data) => { + onValidationChangeResult = data; + // console.log("Subscriber got data >>>>> "+ data); + }); + + fixture.componentInstance.verifyTypeAndFileWereFilled(); + + expect(onValidationChangeResult).toBe(false); + }); + + it('should verify verifyTypeAndFileWereFilled -> verify branch this.artifact.artifactType !== \'DEPLOYMENT\' ==>> onValidationChange.next(true)', () => { + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.artifactType = 'NOT_DEPLOYMENT'; + fixture.componentInstance.artifact.mandatory = true; + fixture.componentInstance.artifact.artifactName = 'Something'; + fixture.componentInstance.descriptionIsValid = true; + + let onValidationChangeResult; + + fixture.componentInstance.onValidationChange.subscribe((data) => { + onValidationChangeResult = data; + // console.log("Subscriber got data >>>>> "+ data); + }); + + fixture.componentInstance.verifyTypeAndFileWereFilled(); + + expect(onValidationChangeResult).toBe(true); + }); + + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.ts b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.ts new file mode 100644 index 0000000000..905d1a25ad --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.component.ts @@ -0,0 +1,132 @@ +/** + * Created by rc2122 on 5/31/2018. + */ +import { Component, Input } from '@angular/core'; +import * as _ from 'lodash'; +import { IDropDownOption } from 'onap-ui-angular/dist/form-elements/dropdown/dropdown-models'; +import { Subject } from 'rxjs/Subject'; +import { ArtifactModel } from '../../../../models'; +import { ArtifactType, ComponentType } from '../../../../utils'; +import { Dictionary } from '../../../../utils/dictionary/dictionary'; +import { CacheService } from '../../../services/cache.service'; + +@Component({ + selector: 'artifact-form', + templateUrl: './artifact-form.component.html', + styleUrls: ['./artifact-form.component.less'] +}) +export class ArtifactFormComponent { + + @Input() artifact: ArtifactModel; + @Input() artifactType: ArtifactType; + @Input() componentType: string; + @Input() instanceId: string; + @Input() isViewOnly: boolean; + + public artifactTypesOptions: IDropDownOption[] = []; + public validationPatterns: Dictionary<string, RegExp>; + public selectedFileType: IDropDownOption; + public showTypeFields: boolean; + private onValidationChange: Subject<boolean> = new Subject(); + private descriptionIsValid: boolean; + private labelIsValid: boolean; + + constructor(private cacheService: CacheService) { + } + + ngOnInit(): void { + this.validationPatterns = this.cacheService.get('validation').validationPatterns; + this.initArtifactTypes(); + this.artifact.artifactGroupType = this.artifact.artifactGroupType || this.artifactType.toString(); + this.showTypeFields = (this.artifact.artifactGroupType === 'DEPLOYMENT' || !this.artifact.mandatory) && this.artifact.artifactGroupType !== 'SERVICE_API'; + } + + public onTypeChange = (selectedFileType: IDropDownOption) => { + this.artifact.artifactType = selectedFileType.value; + this.verifyTypeAndFileWereFilled(); + } + + public onUploadFile = (file) => { + if (file) { + this.artifact.artifactName = file.filename; + this.artifact.payloadData = file.base64; + console.log('FILE UPLOADED', file); + } else { + this.artifact.artifactName = null; + } + this.verifyTypeAndFileWereFilled(); + } + + private initArtifactTypes = (): void => { + const artifactTypes: any = this.cacheService.get('UIConfiguration'); + let validExtensions: string[]; + let artifactTypesList: string[]; + + switch (this.artifactType) { + case ArtifactType.DEPLOYMENT: + if (this.artifact.artifactType === ArtifactType.HEAT_ENV || this.instanceId) { + validExtensions = artifactTypes.artifacts.deployment.resourceInstanceDeploymentArtifacts; + } else if (this.componentType === ComponentType.RESOURCE) { + validExtensions = artifactTypes.artifacts.deployment.resourceDeploymentArtifacts; + } else { + validExtensions = artifactTypes.artifacts.deployment.serviceDeploymentArtifacts; + } + if (validExtensions) { + artifactTypesList = Object.keys(validExtensions); + } + break; + case ArtifactType.INFORMATION: + artifactTypesList = artifactTypes.artifacts.other.map((element: any) => { + return element.name; + }); + _.remove(artifactTypesList, (item: string) => { + return _.has(ArtifactType.THIRD_PARTY_RESERVED_TYPES, item) || + _.has(ArtifactType.TOSCA, item); + }); + break; + } + + _.forEach(artifactTypesList, (artifactType: string) => { + this.artifactTypesOptions.push({ label: artifactType, value: artifactType }); + }); + + this.selectedFileType = _.find(this.artifactTypesOptions, (artifactType) => { + return artifactType.value === this.artifact.artifactType; + }); + + } + + // Verify that the Type and the Name (file) are filled in the Modal + // For Description and Label - I used this.descriptionIsValid:boolean & this.labelIsValid:boolean as part of the sdc-validation Element + private verifyTypeAndFileWereFilled = () => { + if (this.artifact.artifactType === 'DEPLOYMENT' || !this.artifact.mandatory && this.artifact.artifactGroupType !== 'SERVICE_API') { + // In case of all fields are required: + // File, Description, Type and Label + if (this.artifact.artifactType && this.artifact.artifactName && this.descriptionIsValid && this.labelIsValid) { + this.onValidationChange.next(true); + } else { + this.onValidationChange.next(false); + } + } else { + // In case of like Information Artifact + // Only file and description are required + if (this.descriptionIsValid && this.artifact.artifactName) { + this.onValidationChange.next(true); + } else { + this.onValidationChange.next(false); + } + } + } + + // sdc-validation for Description + private onDescriptionChange = (isValid: boolean): void => { + this.descriptionIsValid = isValid; + this.onValidationChange.next(isValid) && this.verifyTypeAndFileWereFilled(); + } + + // sdc-validation for Label + private onLabelChange = (isValid: boolean): void => { + this.labelIsValid = isValid; + this.onValidationChange.next(isValid) && this.verifyTypeAndFileWereFilled(); + } +} diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.module.ts b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.module.ts new file mode 100644 index 0000000000..dba801212e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifact-form.module.ts @@ -0,0 +1,21 @@ +/** + * Created by rc2122 on 5/24/2018. + */ +import { NgModule } from "@angular/core"; +import { TranslateModule } from "app/ng2/shared/translator/translate.module"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import { CommonModule } from '@angular/common'; +import {ArtifactFormComponent} from "./artifact-form.component"; + +@NgModule({ + declarations: [ArtifactFormComponent], + imports: [TranslateModule, + SdcUiComponentsModule, + CommonModule], + exports: [ArtifactFormComponent], + entryComponents: [ArtifactFormComponent] +}) + + +export class ArtifactFormModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifacts.service.ts b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifacts.service.ts new file mode 100644 index 0000000000..f9400e9d18 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/artifacts-form/artifacts.service.ts @@ -0,0 +1,175 @@ +import { Injectable } from '@angular/core'; +import { Store } from '@ngxs/store'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { ArtifactModel } from '../../../../models'; +import { ArtifactGroupType, ArtifactType } from '../../../../utils/constants'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { CreateOrUpdateArtifactAction, DeleteArtifactAction } from '../../../store/actions/artifacts.action'; +import { EnvParamsComponent } from '../env-params/env-params.component'; +import { ArtifactFormComponent } from './artifact-form.component'; + +import { + CreateInstanceArtifactAction, + DeleteInstanceArtifactAction, + UpdateInstanceArtifactAction +} from '../../../store/actions/instance-artifacts.actions'; + +@Injectable() +export class ArtifactsService { + + constructor(private serviceLoader: SdcUiServices.LoaderService, + private modalService: SdcUiServices.ModalService, + private topologyTemplateService: TopologyTemplateService, + private translateService: TranslateService, + private store: Store) { + } + + public dispatchArtifactAction = (componentId: string, componentType: string, artifact: ArtifactModel, artifactType: ArtifactGroupType, instanceId: string) => { + const artifactObj = { + componentType, + componentId, + instanceId, + artifact + }; + + // Create or update instance artifact + if (instanceId) { + if (!artifact.uniqueId) { + // create instance artifact + return this.store.dispatch(new CreateInstanceArtifactAction(artifactObj)); + } else { + // update instance artifact + return this.store.dispatch(new UpdateInstanceArtifactAction(artifactObj)); + } + } else { + // Create or update artifact + return this.store.dispatch(new CreateOrUpdateArtifactAction(artifactObj)); + } + } + + public openArtifactModal = (componentId: string, componentType: string, artifact: ArtifactModel, artifactType: ArtifactGroupType, isViewOnly?: boolean, instanceId?: string) => { + + let modalInstance; + + const onOkPressed = () => { + const updatedArtifact = modalInstance.innerModalContent.instance.artifact; + this.serviceLoader.activate(); + this.dispatchArtifactAction(componentId, componentType, updatedArtifact, artifactType, instanceId) + .subscribe().add(() => this.serviceLoader.deactivate()); + }; + + const addOrUpdateArtifactModalConfig = { + title: (artifact && artifact.uniqueId) ? 'Update Artifact' : 'Create Artifact', + size: 'md', + type: SdcUiCommon.ModalType.custom, + testId: 'upgradeVspModal', + buttons: [ + { + id: 'done', + text: 'DONE', + disabled: isViewOnly, + size: 'Add Another', + closeModal: true, + callback: onOkPressed + }, + {text: 'CANCEL', size: 'sm', closeModal: true, type: 'secondary'} + ] as SdcUiCommon.IModalButtonComponent[] + } as SdcUiCommon.IModalConfig; + + modalInstance = this.modalService.openCustomModal(addOrUpdateArtifactModalConfig, ArtifactFormComponent, { + artifact: new ArtifactModel(artifact), + artifactType, + instanceId, + componentType, + isViewOnly + }); + + if (!isViewOnly) { + modalInstance.innerModalContent.instance.onValidationChange.subscribe((isValid) => { + modalInstance.getButtonById('done').disabled = !isValid; + }); + } + } + + public openViewEnvParams(componentType: string, componentId: string, artifact: ArtifactModel, instanceId?: string) { + const envParamsModal = { + title: artifact.artifactDisplayName, + size: 'xl', + type: SdcUiCommon.ModalType.custom, + testId: 'viewEnvParams', + isDisabled: false, + } as SdcUiCommon.IModalConfig; + + this.modalService.openCustomModal(envParamsModal, EnvParamsComponent, { + isInstanceSelected: !!instanceId, // equals to instanceId ? true : false + artifact: new ArtifactModel(artifact), + isViewOnly: true + }); + } + + public openUpdateEnvParams(componentType: string, componentId: string, artifact: ArtifactModel, instanceId?: string) { + let modalInstance; + const onOkPressed = () => { + const updatedArtifact = modalInstance.innerModalContent.instance.artifact; + this.serviceLoader.activate(); + this.dispatchArtifactAction(componentId, componentType, updatedArtifact, ArtifactType.DEPLOYMENT, instanceId) + .subscribe().add(() => this.serviceLoader.deactivate()); + }; + + const envParamsModal = { + title: artifact.artifactDisplayName, + size: 'xl', + type: SdcUiCommon.ModalType.custom, + testId: 'envParams', + isDisabled: false, + buttons: [ + { + id: 'save', + text: 'Save', + spinner_position: 'left', + size: 'sm', + callback: onOkPressed, + closeModal: true + }, + {text: 'Cancel', size: 'sm', closeModal: true, type: 'secondary'} + ] as SdcUiCommon.IModalButtonComponent[] + } as SdcUiCommon.IModalConfig; + + modalInstance = this.modalService.openCustomModal(envParamsModal, EnvParamsComponent, { + isInstanceSelected: !!instanceId, // equals to instanceId ? true : false + artifact: new ArtifactModel(artifact) + }); + + modalInstance.innerModalContent.instance.onValidationChange.subscribe((isValid) => { + modalInstance.getButtonById('save').disabled = !isValid; + }); + } + + public deleteArtifact = (componentType: string, componentId: string, artifact: ArtifactModel, instanceId?: string) => { + + const artifactObject = { + componentType, + componentId, + artifact, + instanceId + }; + + const onOkPressed: Function = () => { + this.serviceLoader.activate(); + this.store.dispatch((instanceId) ? new DeleteInstanceArtifactAction(artifactObject) : new DeleteArtifactAction(artifactObject)) + .subscribe().add(() => this.serviceLoader.deactivate()); + }; + + const title = this.translateService.translate('ARTIFACT_VIEW_DELETE_MODAL_TITLE'); + const text = this.translateService.translate('ARTIFACT_VIEW_DELETE_MODAL_TEXT', {name: artifact.artifactDisplayName}); + const okButton = { + testId: 'OK', + text: 'OK', + type: SdcUiCommon.ButtonType.warning, + callback: onOkPressed, + closeModal: true + } as SdcUiComponents.ModalButtonComponent; + this.modalService.openWarningModal(title, text, 'delete-information-artifact-modal', [okButton]); + } +} diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/__snapshots__/env-params.component.spec.ts.snap b/catalog-ui/src/app/ng2/components/forms/env-params/__snapshots__/env-params.component.spec.ts.snap new file mode 100644 index 0000000000..aa567bbef2 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/__snapshots__/env-params.component.spec.ts.snap @@ -0,0 +1,42 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`environment parameters component should match current snapshot of env-params component 1`] = ` +<env-params + cacheService={[Function Object]} + clearCurrentValue={[Function Function]} + copiedWorkingArtifactHeatParameters={[Function Array]} + defaultDeploymentTimeout={[Function Number]} + displayRegexValid={[Function String]} + maxDeploymentTimeout={[Function Number]} + minDeploymentTimeout={[Function Number]} + onValidationChange={[Function Subject]} + onValidityChange={[Function Function]} + openPopOver={[Function Function]} + popoverService={[Function Object]} + textArea="undefined" +> + <div + class="filter-bar" + > + <sdc-filter-bar /> + </div><ngx-datatable + class="material ngx-datatable" + > + <div + visibilityobserver="" + > + + <datatable-body + class="datatable-body" + > + <datatable-selection> + + + + </datatable-selection> + </datatable-body> + + </div> + </ngx-datatable> +</env-params> +`; diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.html b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.html new file mode 100644 index 0000000000..f55aff5132 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.html @@ -0,0 +1,71 @@ +<div class="filter-bar"> + <sdc-filter-bar + [placeHolder]="'Search...'" + [testId]="'search-env-param-name'" + (keyup)="updateFilter($event)"> + </sdc-filter-bar> +</div> + +<ngx-datatable + class='material' + [rows]='artifact.heatParameters' + [columnMode]="'flex'" + [headerHeight]="40" + [rowHeight]="'auto'" + [scrollbarV]="false"> + + <ngx-datatable-column name="Parameter" [flexGrow]="2"> + <ng-template let-row="row" ngx-datatable-cell-template prop="name"> + {{row.name}} + <span *ngIf="row.description.length > 0" class="info"> + <svg-icon [name]="'comment'" (click)="openPopOver('',row.description,{x:$event.pageX , y:$event.pageY },'bottom')"></svg-icon> + </span> + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-column name="DefaultValue"[flexGrow]="1"> + <ng-template let-row="row" let-value="value" ngx-datatable-cell-template> + {{row.defaultValue}} + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-column name="CurrentValue" [flexGrow]="2"> + <ng-template let-row="row" let-value="value" ngx-datatable-cell-template> + <sdc-input class="sdc-input-wrapper" #numberValidator + [placeHolder]="'Enter text'" + [isViewMode]="isViewOnly" + [size]="'medium'" + [(value)]=row.currentValue + [isIconClickable]="true" + (onRighIconClicked)="clearCurrentValue(row.name)" + [righIconName]="'trash-o'" + [testId] = "'value-field-of-' + row.name"> + </sdc-input> + + <sdc-validation [validateElement]="numberValidator" (validityChanged)="onValidityChange($event)" [disabled]="false" [testId]="_testId"> + <sdc-regex-validator *ngIf="displayRegexValid && row.type == 'number' && row.currentValue !== null" [message]="'Value should be of type number.'" [pattern]="displayRegexValid" [disabled]="false"></sdc-regex-validator> + </sdc-validation> + </ng-template> + </ngx-datatable-column> + +</ngx-datatable> + +<div *ngIf="isInstanceSelected" class="artifactTimeout"> + + <sdc-number-input + label="Deployment Timeout ({{minDeploymentTimeout}}-{{maxDeploymentTimeout}} minutes)" + [required]="true" + [disabled]="false" + name="artifactTimeout" + testId="deploymentTimeout" + value="{{artifact.timeout}}" + [maxValue]="maxDeploymentTimeout" + [minValue]="minDeploymentTimeout" + (valueChange)="timeoutChanged($event)" + [isViewMode]="isViewOnly" + [step]="1" + > + + </sdc-number-input> + +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.less b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.less new file mode 100644 index 0000000000..48b4cba184 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.less @@ -0,0 +1,21 @@ +.filter-bar { + padding-bottom: 25px; +} + +:host ::ng-deep { + + .ngx-datatable { + //border: 1px solid red; + .datatable-body-cell { + .info { + float: right; + } + } + } +} + +.artifactTimeout{ + padding-top: 25px; + justify-content: start; + width: 230px; +} diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.spec.ts b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.spec.ts new file mode 100644 index 0000000000..f6b0eb4a21 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.spec.ts @@ -0,0 +1,98 @@ +import {async, ComponentFixture} from "@angular/core/testing"; +import {EnvParamsComponent} from "./env-params.component"; +import {SdcUiServices, SdcUiCommon} from "onap-ui-angular"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {ArtifactModel} from "../../../../models/artifacts"; +import { CacheService } from '../../../services/cache.service'; + +describe('environment parameters component', () => { + + let fixture: ComponentFixture<EnvParamsComponent>; + let popoverServiceMock: Partial<SdcUiServices.PopoverService>; + let regexPatterns: any; + + let artifactModel = new ArtifactModel(); + + let mockHeatParameters = [ + {currentValue: "1", defaultValue: null, description: "Description 1", empty:false, name: "Param1", ownerId: null, type: "string", uniqueId: null, envDisplayName:null, version: null, filterTerm:null}, + {currentValue: "2", defaultValue: null, description: "Description 2", empty:false, name: "Param2", ownerId: null, type: "string", uniqueId: null, envDisplayName:null, version: null, filterTerm:null}, + {currentValue: "3", defaultValue: null, description: "Description 3", empty:false, name: "Param3", ownerId: null, type: "string", uniqueId: null, envDisplayName:null, version: null, filterTerm:null} + ]; + + let keyboardEvent = new KeyboardEvent("keyup"); + + beforeEach( + async(() => { + + popoverServiceMock = { + createPopOver : jest.fn() + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [EnvParamsComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: SdcUiServices.PopoverService, useValue: popoverServiceMock }, + { provide: CacheService, useValue: { get: jest.fn() } } + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(EnvParamsComponent); + }); + }) + ); + + it('should match current snapshot of env-params component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should clear CurrentValue for a given name in the heat parameter', () => { + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.heatParameters = mockHeatParameters; + expect(fixture.componentInstance.artifact.heatParameters.length).toBe(3); + expect(fixture.componentInstance.artifact.heatParameters[0].currentValue).toBe("1"); + fixture.componentInstance.clearCurrentValue("Param1"); + expect(fixture.componentInstance.artifact.heatParameters[0].currentValue).toBe(""); + }); + + it("should update filter heatParameters so there won''t be any value to be displayed", () => { + + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.heatParameters = mockHeatParameters; + fixture.componentInstance.ngOnInit(); + + let event = { + target : { + value: 'paramNotExist' + } + } + + expect(fixture.componentInstance.artifact.heatParameters.length).toBe(3); + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.artifact.heatParameters.length).toBe(0); + }); + + it("should update filter heatParameters so there will be only 1 value to be displayed", () => { + + fixture.componentInstance.artifact = artifactModel; + fixture.componentInstance.artifact.heatParameters = mockHeatParameters; + fixture.componentInstance.ngOnInit(); + + let event = { + target : { + value: 'param1' + } + } + + expect(fixture.componentInstance.artifact.heatParameters.length).toBe(3); + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.artifact.heatParameters.length).toBe(1); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.ts b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.ts new file mode 100644 index 0000000000..58d266a01d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.component.ts @@ -0,0 +1,91 @@ +/*- + * ============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, ElementRef, Input, OnInit, ViewChild } from '@angular/core'; +import { SdcUiCommon, SdcUiServices } from 'onap-ui-angular'; +import { Subject } from 'rxjs/Rx'; +import { ArtifactModel } from '../../../../models/artifacts'; +import { CacheService } from '../../../services/cache.service'; + +export interface IPoint { + x: number; + y: number; +} + +@Component({ + selector: 'env-params', + templateUrl: './env-params.component.html', + styleUrls: ['../../../../../assets/styles/table-style.less', './env-params.component.less'] +}) +export class EnvParamsComponent implements OnInit { + + @Input() public artifact: ArtifactModel; + @Input() public isInstanceSelected: boolean; + @Input() public isViewOnly: boolean; + + @ViewChild('textArea') textArea: ElementRef; + private copiedWorkingArtifactHeatParameters = []; + private onValidationChange: Subject<boolean> = new Subject(); + private displayRegexValid = SdcUiCommon.RegexPatterns.numberOrEmpty; + + // Deployment timeout in minutes + private maxDeploymentTimeout: number = 120; + private minDeploymentTimeout: number = 1; + private defaultDeploymentTimeout: number = 60; + + constructor(private cacheService: CacheService, private popoverService: SdcUiServices.PopoverService) { + const configuration = cacheService.get('UIConfiguration'); + if (configuration && configuration.heatDeploymentTimeout) { + this.maxDeploymentTimeout = configuration.heatDeploymentTimeout.maxMinutes; + this.minDeploymentTimeout = configuration.heatDeploymentTimeout.minMinutes; + this.defaultDeploymentTimeout = configuration.heatDeploymentTimeout.defaultMinutes; + } + } + + ngOnInit(): void { + this.copiedWorkingArtifactHeatParameters = [...this.artifact.heatParameters]; + } + + public clearCurrentValue = (name: string) => { + this.artifact.heatParameters.filter((param) => param.name === name)[0].currentValue = ''; + } + + public timeoutChanged(timeout) { + this.artifact.timeout = timeout; + } + + updateFilter(event) { + const val = event.target.value.toLowerCase(); + // filter our data + const temp = this.copiedWorkingArtifactHeatParameters.filter((param) => { + return !val || param.name ? param.name.toLowerCase().indexOf(val) !== -1 : -1 || param.currentValue ? param.currentValue.toLowerCase().indexOf(val) !== -1 : -1; + }); + // update the rows + this.artifact.heatParameters = temp; + } + + private openPopOver = (title: string, content: string, positionOnPage: IPoint, location: string) => { + this.popoverService.createPopOver(title, content, positionOnPage, location); + } + + private onValidityChange = (isValid: boolean): void => { + this.onValidationChange.next(isValid); + } +} diff --git a/catalog-ui/src/app/ng2/components/forms/env-params/env-params.module.ts b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.module.ts new file mode 100644 index 0000000000..85797bd45f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/forms/env-params/env-params.module.ts @@ -0,0 +1,28 @@ +import { NgModule } from "@angular/core"; +import { EnvParamsComponent } from "./env-params.component"; +import { NgxDatatableModule } from "@swimlane/ngx-datatable"; +import { SdcUiComponentsModule, SdcUiServices } from "onap-ui-angular"; + + +@NgModule({ + declarations: [ + EnvParamsComponent + ], + imports: [ + NgxDatatableModule, + SdcUiComponentsModule + ], + exports: [ + EnvParamsComponent + ], + entryComponents: [ //need to add anything that will be dynamically created + EnvParamsComponent + ], + providers: [ + SdcUiServices.ModalService + ] +}) + +export class EnvParamsModule { + +} diff --git a/catalog-ui/src/app/ng2/components/layout/layout.module.ts b/catalog-ui/src/app/ng2/components/layout/layout.module.ts index 827209326c..f6d2cf487e 100644 --- a/catalog-ui/src/app/ng2/components/layout/layout.module.ts +++ b/catalog-ui/src/app/ng2/components/layout/layout.module.ts @@ -13,7 +13,9 @@ import { TopNavComponent } from "./top-nav/top-nav.component"; FormsModule, TranslateModule ], - exports: [], + exports: [ + TopNavComponent + ], entryComponents: [ //need to add anything that will be dynamically created TopNavComponent ], diff --git a/catalog-ui/src/app/ng2/components/layout/top-nav/__snapshots__/top-nav.comonent.spec.ts.snap b/catalog-ui/src/app/ng2/components/layout/top-nav/__snapshots__/top-nav.comonent.spec.ts.snap new file mode 100644 index 0000000000..c650a9ca56 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/layout/top-nav/__snapshots__/top-nav.comonent.spec.ts.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`artifact form component should match current snapshot of top-nav component 1`] = ` +<top-nav + $state={[Function Object]} + _getTopLvlSelectedIndexByState={[Function Function]} + authService={[Function Object]} + sdcConfig={[Function Object]} + searchTermChange={[Function EventEmitter]} + translateService={[Function Object]} +> + <nav + class="top-nav" + > + <div + class="asdc-app-title-wrapper" + > + <a + class="asdc-app-title" + > + + </a> + <div + class="asdc-version" + > + v. + </div> + </div> + + + <div + class="top-search" + > + <input + class="search-text" + data-tests-id="main-menu-input-search" + placeholder="Search" + type="text" + /> + <span + class="w-sdc-search-icon magnification" + /> + </div> + + </nav> +</top-nav> +`; diff --git a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.comonent.spec.ts b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.comonent.spec.ts new file mode 100644 index 0000000000..54fbb36dcf --- /dev/null +++ b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.comonent.spec.ts @@ -0,0 +1,161 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { PluginsConfiguration } from 'app/models'; +import { Observable } from 'rxjs'; +import { Mock } from 'ts-mockery'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { MenuItem, MenuItemGroup } from '../../../../utils/menu-handler'; +import { SdcConfigToken } from '../../../config/sdc-config.config'; +import { AuthenticationService } from '../../../services/authentication.service'; +import { TranslateModule } from '../../../shared/translator/translate.module'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { TopNavComponent } from './top-nav.component'; + +describe('artifact form component', () => { + + let fixture: ComponentFixture<TopNavComponent>; + let translateServiceMock: Partial<TranslateService>; + let mockStateService; + let authServiceMock; + + const designerUser = { + email: 'designer@sdc.com', + firstName: 'Carlos', + fullName: 'Carlos Santana', + lastLoginTime: 1555587266566, + lastName: 'Santana', + role: 'DESIGNER', + status: 'ACTIVE', + userId: 'cs0008' + }; + + const pluginDisplayOptions = { + displayName : '', + displayContext : new Array<string>(), + displayRoles : new Array<string>() + }; + + let roleToReturn = designerUser; + + const map1 = new Map(); + map1.otherValue = pluginDisplayOptions; + + const map2 = new Map(); + pluginDisplayOptions.displayRoles = ['DESIGNER']; + pluginDisplayOptions.displayName = 'DCAE-DS'; + map2.tab = pluginDisplayOptions; + + PluginsConfiguration.plugins = + [ + {pluginId: 'DCAED', pluginDiscoveryUrl: 'DCAED_discoveryURL', pluginSourceUrl: 'DCAED_sourceURL', pluginStateUrl: 'DCAED_stateURL', pluginDisplayOptions: map1, isOnline: true}, + {pluginId: 'DCAE-DS', pluginDiscoveryUrl: 'DCAE-DS_discoveryURL', pluginSourceUrl: 'DCAE-DS_sourceURL', pluginStateUrl: 'DCAE-DS_stateURL', pluginDisplayOptions: map2, isOnline: true} + ]; + + beforeEach( + async(() => { + authServiceMock = { + getLoggedinUser: jest.fn().mockImplementation(() => { + return roleToReturn; + }) + }; + + mockStateService = { + go: jest.fn(), + includes: jest.fn(() => true), + current : { + name : 'plugins' + }, + params : {} + }; + + translateServiceMock = { + languageChangedObservable: Mock.of<Observable<string>>( { + subscribe : jest.fn().mockImplementation((cb) => { + cb(); + }) + }), + translate: jest.fn((str: string) => { + if (str === 'TOP_MENU_HOME_BUTTON') { + return 'HOME'; + } else if (str === 'TOP_MENU_CATALOG_BUTTON') { + return 'CATALOG'; + } else if (str === 'TOP_MENU_ON_BOARD_BUTTON') { + return 'ONBOARD'; + } else { + return 'TBD...'; + } + }) + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [TopNavComponent], + imports: [TranslateModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: TranslateService, useValue: translateServiceMock}, + {provide: '$state', useValue: mockStateService}, + {provide: AuthenticationService, useValue: authServiceMock}, + {provide: SdcConfigToken, useValue: {csarFileExtension: 'csar', toscaFileExtension: 'yaml,yml'}}, + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(TopNavComponent); + }); + }) + ); + + it('should match current snapshot of top-nav component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('Once a Designer logged in, Menu Items will contain HOME, CATALOG, ONBOARD & DCAE-DS; HOME will be selected', () => { + + // topLvlSelectedIndex = 1 => Ignore the inner call to _getTopLvlSelectedIndexByState. + fixture.componentInstance.topLvlSelectedIndex = 0; + fixture.componentInstance.ngOnInit(); + + expect(fixture.componentInstance.topLvlMenu.itemClick).toBe(true); + + expect(fixture.componentInstance.topLvlMenu.menuItems.length).toBe(4); + + expect(fixture.componentInstance.topLvlMenu.menuItems[0]).toEqual({ + action: 'goToState', + blockedForTypes: null, + callback: null, + params: null, + state: 'dashboard', + text: 'HOME' + }); + expect(fixture.componentInstance.topLvlMenu.menuItems[1]).toEqual({ + action: 'goToState', + blockedForTypes: null, + callback: null, + params: null, + state: 'catalog', + text: 'CATALOG' + }); + expect(fixture.componentInstance.topLvlMenu.menuItems[2]).toEqual({ + action: 'goToState', + blockedForTypes: null, + callback: null, + params: null, + state: 'onboardVendor', + text: 'ONBOARD' + }); + expect(fixture.componentInstance.topLvlMenu.menuItems[3]).toEqual({ + action: 'goToState', + blockedForTypes: null, + callback: null, + params: + {path: 'DCAE-DS_stateURL'}, + state: 'plugins', + text: 'DCAE-DS' + }); + + expect(fixture.componentInstance.topLvlMenu.selectedIndex).toBe(0); + }); + +}); diff --git a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.html b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.html index f82e110f1f..2636dd96bb 100644 --- a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.html +++ b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.html @@ -65,6 +65,6 @@ <span class="w-sdc-search-icon magnification"></span> </div> - <div class="notification-icon" [ngClass]="{'disabled' : progress > 0}" *ngIf="user.role === 'DESIGNER' && notificationIconCallback" (click)="notificationIconCallback()" tooltip="Vendor Software Product Repository" tooltipPlacement="left" data-tests-id="repository-icon"></div> + <div class="notification-icon" [ngClass]="{'disabled' : progress > 0}" *ngIf="user && user.role === 'DESIGNER' && notificationIconCallback" (click)="notificationIconCallback()" tooltip="Vendor Software Product Repository" tooltipPlacement="left" data-tests-id="repository-icon"></div> </nav> diff --git a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.less b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.less index 5c99015e7d..2bcdfc28d5 100644 --- a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.less +++ b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.less @@ -120,7 +120,7 @@ height: 35px; background-color: white; font-size: 13px; - width: 150px; + width: auto; line-height: 35px; padding: 0 10px; diff --git a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.ts b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.ts index a253b3a933..3bd2255488 100644 --- a/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.ts +++ b/catalog-ui/src/app/ng2/components/layout/top-nav/top-nav.component.ts @@ -19,14 +19,17 @@ */ import * as _ from "lodash"; -import {Component, Inject, Input, Output, EventEmitter} from "@angular/core"; +import {Component, Inject, Input, Output, EventEmitter, OnInit, OnDestroy, OnChanges} from "@angular/core"; import {IHostedApplication, IUserProperties} from "app/models"; import {MenuItemGroup, MenuItem} from "app/utils"; -import {UserService} from "../../../services/user.service"; +import {AuthenticationService} from "../../../services/authentication.service"; import {SdcConfigToken, ISdcConfig} from "../../../config/sdc-config.config"; import {TranslateService} from "../../../shared/translator/translate.service"; import {PluginsConfiguration, Plugin} from "app/models"; - +import { Subscription } from "rxjs"; +// import { Store } from "@ngrx/store"; +// import { AppState } from "app/ng2/store/app.state"; +// import * as unsavedChangesReducer from 'app/ng2/store/reducers/unsaved-changes.reducer'; declare const window:any; @Component({ @@ -34,7 +37,7 @@ declare const window:any; templateUrl: './top-nav.component.html', styleUrls:['./top-nav.component.less'] }) -export class TopNavComponent { +export class TopNavComponent implements OnInit, OnChanges { @Input() public version:string; @Input() public menuModel:Array<MenuItemGroup>; @Input() public topLvlSelectedIndex:number; @@ -48,15 +51,18 @@ export class TopNavComponent { this.searchTermChange.emit(event); } + private subscription: Subscription; + private hasUnsavedChanges: boolean; public topLvlMenu:MenuItemGroup; public user:IUserProperties; private topNavPlugins: Array<Plugin>; constructor(private translateService:TranslateService, @Inject('$state') private $state:ng.ui.IStateService, - private userService:UserService, + private authService:AuthenticationService, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { window.nav = this; + } private _getTopLvlSelectedIndexByState = ():number => { @@ -112,7 +118,7 @@ export class TopNavComponent { ngOnInit() { console.log('Nav is init!', this.menuModel); - this.user = this.userService.getLoggedinUser(); + this.user = this.authService.getLoggedinUser(); this.topNavPlugins = _.filter(PluginsConfiguration.plugins, (plugin: Plugin) => { return plugin.pluginDisplayOptions["tab"] !== undefined; }); @@ -142,7 +148,6 @@ export class TopNavComponent { this.topLvlMenu = new MenuItemGroup(0, tmpArray, true); this.topLvlMenu.selectedIndex = isNaN(this.topLvlSelectedIndex) ? this._getTopLvlSelectedIndexByState() : this.topLvlSelectedIndex; - this.generateMenu(); }); } @@ -160,6 +165,7 @@ export class TopNavComponent { }); } + menuItemClick(itemGroup:MenuItemGroup, item:MenuItem) { let onSuccessFunction = () => { diff --git a/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/__snapshots__/filter-properties-assignment.component.spec.ts.snap b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/__snapshots__/filter-properties-assignment.component.spec.ts.snap new file mode 100644 index 0000000000..02e98e2219 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/__snapshots__/filter-properties-assignment.component.spec.ts.snap @@ -0,0 +1,59 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`filter-properties-assignemnt component should match current snapshot of artifact-tab component 1`] = ` +<filter-properties-assignment + allSelected="false" + clearAll={[Function Function]} + close={[Function Function]} + filterData={[Function FilterPropertiesAssignmentData]} + filterPopover={[Function ElementRef]} + footerButtons={[Function Object]} + onTypeSelected={[Function Function]} + search={[Function Function]} + searchProperties={[Function EventEmitter]} + selectAll={[Function Function]} + selectedTypes={[Function Object]} + someTypesSelectedAndThereIsPropertyName={[Function Function]} +> + <popover-content + placement="bottom-right" + > + <form> + <div + class="field" + > + <label> + Resource Type + </label> + <div> + <checkbox + data-tests-id="filter-checkbox-all" + /> + </div> + + </div> + <div + class="field" + > + <label> + Property Name + </label> + <input + class="i-sdc-form-input" + data-tests-id="filter-box" + name="propertyName" + placeholder="Type here" + required="" + /> + </div> + </form> + </popover-content><div + class="open-filter-button" + > + <div + class="sprite-new filter-icon" + data-tests-id="filter-button" + /> + </div> +</filter-properties-assignment> +`; diff --git a/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.html b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.html index 9593ade48b..1c2d77a728 100644 --- a/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.html +++ b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.html @@ -38,6 +38,6 @@ </div> </form> </popover-content> -<div class="open-filter-button" [popover]="filterPopover" [ngClass]="{'open':showPopover}" (onShown)="showPopover = true" (onHidden)="showPopover = false"> +<div class="open-filter-button" [popover]="filterPopover" > <div class="sprite-new filter-icon" data-tests-id="filter-button"></div> </div> diff --git a/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.spec.ts b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.spec.ts new file mode 100644 index 0000000000..bcc5535b32 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/filter-properties-assignment/filter-properties-assignment.component.spec.ts @@ -0,0 +1,141 @@ +import {async, ComponentFixture} from "@angular/core/testing"; +import {FilterPropertiesAssignmentComponent} from "./filter-properties-assignment.component"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {FilterPropertiesAssignmentData} from "../../../../models/filter-properties-assignment-data"; +import {PopoverComponent} from "../../ui/popover/popover.component"; + + + +describe('filter-properties-assignemnt component', () => { + + let fixture: ComponentFixture<FilterPropertiesAssignmentComponent>; + + beforeEach( + async(() => { + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [FilterPropertiesAssignmentComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(FilterPropertiesAssignmentComponent); + + }); + }) + ); + + + it('should match current snapshot of artifact-tab component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('on selectAll', () => { + let filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + filterData.propertyName = 'testVal'; + let typesOptions:Array<string> = ['option1', 'option2', 'option3']; + let selectedTypes:Object = {}; + + fixture.componentInstance.filterData = filterData; + fixture.componentInstance.typesOptions = typesOptions; + fixture.componentInstance.selectedTypes = selectedTypes; + + fixture.componentInstance.selectAll(); + + let expectedRes = {"option1": false,"option2": false,"option3": false}; + expect(fixture.componentInstance.selectedTypes).toEqual(expectedRes); + }); + + + it ('on onTypeSelected allSelected set to False', () => { + let selectedTypes:Object = {"option1": true,"option2": false,"option3": true}; + fixture.componentInstance.selectedTypes = selectedTypes; + fixture.componentInstance.allSelected = true; + fixture.componentInstance.onTypeSelected('option2'); + + expect(fixture.componentInstance.allSelected).toBe(false); + }); + + it ('on onTypeSelected allSelected remains True', () => { + let selectedTypes:Object = {"option1": true,"option2": true,"option3": true}; + fixture.componentInstance.selectedTypes = selectedTypes; + fixture.componentInstance.allSelected = true; + fixture.componentInstance.onTypeSelected('option2'); + + expect(fixture.componentInstance.allSelected).toBe(true); + }); + + it ('on clearAll', () => { + let filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + filterData.propertyName = 'testVal'; + let selectedTypes:Object = {"option1": true,"option2": false,"option3": true}; + + fixture.componentInstance.filterData = filterData; + fixture.componentInstance.selectedTypes = selectedTypes; + fixture.componentInstance.allSelected = true; + + fixture.componentInstance.clearAll(); + + expect(fixture.componentInstance.filterData.propertyName).toBe(''); + expect(fixture.componentInstance.allSelected).toBe(false); + }); + + it ('someTypesSelectedAndThereIsPropertyName return True', ()=> { + let res = fixture.componentInstance.someTypesSelectedAndThereIsPropertyName(); + + expect(res).toBe(true) + }); + + it ('someTypesSelectedAndThereIsPropertyName return Null', ()=> { + let selectedTypes:Object = {"option1": true,"option2": false,"option3": true}; + let filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + filterData.propertyName = 'testVal'; + + fixture.componentInstance.selectedTypes = selectedTypes; + fixture.componentInstance.filterData = filterData; + + let res = fixture.componentInstance.someTypesSelectedAndThereIsPropertyName(); + + expect(res).toBe(null) + }); + + it ('search', ()=> { + + let filterData:FilterPropertiesAssignmentData = new FilterPropertiesAssignmentData(); + filterData.selectedTypes = ["CP"]; + fixture.componentInstance.filterData = filterData; + + let componentType: string = 'resource'; + fixture.componentInstance.componentType = componentType; + + let selectedTypes:Object = {"option1": true,"CP": true,"option3": true}; + fixture.componentInstance.selectedTypes = selectedTypes; + + let temp:any; + let filterPopover: PopoverComponent = new PopoverComponent(temp , temp ); + fixture.componentInstance.filterPopover = filterPopover; + fixture.componentInstance.filterPopover.hide = jest.fn(); + + fixture.componentInstance.search(); + + expect(fixture.componentInstance.filterData.selectedTypes).toEqual(["CP"]); + expect(fixture.componentInstance.filterPopover.hide).toHaveBeenCalled(); + }); + + it('close', () => { + let temp:any; + let filterPopover: PopoverComponent = new PopoverComponent(temp , temp ); + fixture.componentInstance.filterPopover = filterPopover; + fixture.componentInstance.filterPopover.hide = jest.fn(); + + fixture.componentInstance.close(); + + expect(fixture.componentInstance.filterPopover.hide).toHaveBeenCalled(); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.html b/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.html index 41ecaa899e..9d1a2fa734 100644 --- a/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.html +++ b/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.html @@ -29,8 +29,8 @@ [rowHeight]="200" [reorderable]="false" > - <ngx-datatable-column prop="{{col.prop}}" [minWidth]="100" *ngFor="let col of columns"> - <template let-column="column" height="100" ngx-datatable-header-template> + <ngx-datatable-column *ngFor="let col of columns" prop="{{col.prop}}" [minWidth]="100" > + <ng-template let-column="column" height="100" ngx-datatable-header-template> <span class="datatable-column-span"> <b>{{col.name}}</b> <div *ngIf="canBeDeleted(col.name)" style="width: 45px !important; color: red; " @@ -44,23 +44,23 @@ placeholder='Filter column...' (keyup)='updateColumnFilter($event, col.prop)' /> - </template> + </ng-template> </ngx-datatable-column> <ngx-datatable-column *ngIf="addNewColumn" class="datatable-white-body-cell" [minWidth]="220" [maxWidth]="220" [width]="220" > - <template ngx-datatable-header-template> + <ng-template ngx-datatable-header-template> <gab-column-provider [pathsAndNames]="pathsandnames" (onCancel)="hideAddNewColumn()" (onSave)="refresh()"></gab-column-provider> - </template> - <template class="datatable-white-body-cell" ngx-datatable-cell-template> - </template> + </ng-template> + <ng-template class="datatable-white-body-cell" ngx-datatable-cell-template> + </ng-template> </ngx-datatable-column> <ngx-datatable-column class="datatable-white-body-cell" [minWidth]="50" [maxWidth]="50" [width]="50" > - <template ngx-datatable-header-template> + <ng-template ngx-datatable-header-template> <div data-tests-id="gab-add-btn" class="add-btn add-btn-div" (click)="showAddNewColumn()">Add</div> - </template> - <template class="datatable-white-body-cell" ngx-datatable-cell-template> - </template> + </ng-template> + <ng-template class="datatable-white-body-cell" ngx-datatable-cell-template> + </ng-template> </ngx-datatable-column> </ngx-datatable> -</div>
\ No newline at end of file +</div> diff --git a/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.ts b/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.ts index 4de7ff07d9..ea8039e1b4 100644 --- a/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component.ts @@ -64,8 +64,8 @@ export class GenericArtifactBrowserComponent { this.gabService.getArtifact(this.artifactid, this.resourceid, paths) .subscribe( response => { - let typedServerResponse: IServerResponse = response.json() as IServerResponse; - this.normalizeDataForNgxDatatable(typedServerResponse.data); + // let typedServerResponse: IServerResponse = response.json() as IServerResponse; + this.normalizeDataForNgxDatatable(response['data']); }, () => { this.ready = false; diff --git a/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.spec.ts b/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.spec.ts new file mode 100644 index 0000000000..fc76463970 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.spec.ts @@ -0,0 +1,52 @@ +import { async, ComponentFixture } from '@angular/core/testing'; +import { By } from '@angular/platform-browser'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import 'jest-dom/extend-expect'; +import {HierarchyNavigationComponent} from "./hierarchy-navigation.component"; +import {HierarchyDisplayOptions} from "./hierarchy-display-options"; + + +describe('hierarchyNavigationComponent', () => { + let fixture: ComponentFixture<HierarchyNavigationComponent>; + let hierarchyDisplayOptions: HierarchyDisplayOptions; + beforeEach( + async(() => { + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [HierarchyNavigationComponent] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(HierarchyNavigationComponent); + hierarchyDisplayOptions = new HierarchyDisplayOptions("id", "name", "children"); + fixture.componentInstance.displayOptions = hierarchyDisplayOptions; + fixture.detectChanges(); + fixture.componentInstance.displayData = [{name: "aaa", id: "1", children: [{name: "bbb", id: "1.1"}, {name: "ccc", id: "1.2", children: [{name: "aaa", id: "1.2.1"}]}]}, {name: "bbb", id: "2"}]; + fixture.detectChanges(); + }); + }) + ); + + it('should have a selected class after user click on a tree node', + () => { + let firstNodeElement = fixture.debugElement.query(By.css('.node-item')); + fixture.componentInstance.updateSelected.subscribe((item) => { + fixture.componentInstance.selectedItem = item.id; + fixture.detectChanges(); + }); + firstNodeElement.nativeElement.click(); + fixture.whenStable().then(() => { + expect(firstNodeElement.children[0].nativeElement).toHaveClass('selected'); + }); + }); + + it('should call onClick function when user click on a tree node', + () => { + spyOn(fixture.componentInstance, 'onClick'); + let firstNodeElement = fixture.debugElement.query(By.css('.node-item')).nativeElement; + firstNodeElement.click(); + expect(fixture.componentInstance.onClick).toHaveBeenCalled(); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.ts b/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.ts index 1698157e90..8b20aa28f8 100644 --- a/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/hierarchy-navigtion/hierarchy-navigation.component.ts @@ -21,7 +21,6 @@ import {Component, Input, Output, EventEmitter} from '@angular/core'; import {HierarchyDisplayOptions} from './hierarchy-display-options'; - @Component({ selector: 'hierarchy-navigation', templateUrl: './hierarchy-navigation.component.html', @@ -32,7 +31,6 @@ export class HierarchyNavigationComponent { @Input() displayData: Array<any>; @Input() selectedItem: any; @Input() displayOptions: HierarchyDisplayOptions; - @Output() updateSelected:EventEmitter<any> = new EventEmitter(); onClick = ($event, item) => { diff --git a/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.html b/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.html index 3ef1f57bf2..eeba590046 100644 --- a/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.html +++ b/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.html @@ -35,33 +35,37 @@ <div class="no-data" *ngIf="!inputs || !inputs.length">No data to display</div> <div> <div class="table-row" *ngFor="let input of inputs" (click)="selectedInputId = input.path" [ngClass]="{'selected': selectedInputId && selectedInputId === input.path}"> + <!-- Property Name --> <div class="table-cell col1"> <div class="inner-cell-div" tooltip="{{input.name}}"><span class="property-name">{{input.name}}</span></div> <span *ngIf="input.description" class="property-description-icon sprite-new show-desc" tooltip="{{input.description}}" tooltipDelay="0"></span> </div> + <!-- From Instance --> <div class="table-cell col3"> <div class="inner-cell-div" tooltip="{{instanceNamesMap[input.instanceUniqueId]?.name}}"> <span>{{instanceNamesMap[input.instanceUniqueId]?.name}}</span> </div> </div> + <!-- Type --> <div class="table-cell col2"> <div class="inner-cell-div" tooltip="{{input.type | contentAfterLastDot}}"> <span>{{input.type | contentAfterLastDot}}</span> </div> </div> + <!-- Value --> <div class="table-cell valueCol input-value-col" [class.inner-table-container]="input.childrenProperties || !input.isSimpleType"> <dynamic-element class="value-input" - *ngIf="input.isSimpleType" + *ngIf="checkInstanceFePropertiesMapIsFilled() && input.isSimpleType" pattern="validationUtils.getValidationPattern(input.type)" [value]="input.defaultValueObj" [type]="input.type" - [constraints]="input.constraints" [name]="input.name" (elementChanged)="onInputChanged(input, $event)" [readonly]="readonly" - [testId]="'input-' + input.name"> + [testId]="'input-' + input.name" + [constraints] = "getConstraints(input)"> </dynamic-element> <div class="delete-button-container"> <span *ngIf="input.instanceUniqueId && !readonly" class="sprite-new delete-btn" (click)="openDeleteModal(input)" data-tests-id="delete-input-button"></span> diff --git a/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.ts b/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.ts index d95198f162..f45d5a85c7 100644 --- a/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/inputs-table/inputs-table.component.ts @@ -26,6 +26,8 @@ import { Component, Input, Output, EventEmitter } from "@angular/core"; import { InputFEModel } from "app/models"; import { ModalService } from "../../../services/modal.service"; import { InstanceFeDetails } from "app/models/instance-fe-details"; +import { InstanceFePropertiesMap } from "../../../../models/properties-inputs/property-fe-map"; +import { DataTypeService } from "../../../services/data-type.service"; @Component({ selector: 'inputs-table', @@ -41,6 +43,8 @@ export class InputsTableComponent { @Output() inputChanged: EventEmitter<any> = new EventEmitter<any>(); @Output() deleteInput: EventEmitter<any> = new EventEmitter<any>(); + @Input() fePropertiesMap: InstanceFePropertiesMap; + sortBy: String; reverse: boolean; selectedInputToDelete: InputFEModel; @@ -74,7 +78,8 @@ export class InputsTableComponent { }; - constructor(private modalService: ModalService) { + constructor(private modalService: ModalService, private dataTypeService: DataTypeService){ + var x = 5 } @@ -89,9 +94,40 @@ export class InputsTableComponent { }; openDeleteModal = (input: InputFEModel) => { + console.log('exist inputs: ' + this.inputs) + + this.selectedInputToDelete = input; this.modalService.createActionModal("Delete Input", "Are you sure you want to delete this input?", "Delete", this.onDeleteInput, "Close").instance.open(); } + + getConstraints(input:InputFEModel): string[]{ + + if (input.inputPath){ + const pathValuesName = input.inputPath.split('#'); + const rootPropertyName = pathValuesName[0]; + const propertyName = pathValuesName[1]; + let filterredRootPropertyType = _.values(this.fePropertiesMap)[0].filter(property => + property.name == rootPropertyName); + if (filterredRootPropertyType.length > 0){ + let rootPropertyType = filterredRootPropertyType[0].type; + return this.dataTypeService.getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName); + }else{ + return null; + } + + } + // else if(input.constraints.length > 0){ + // return input.constraints[0].validValues + // } + else{ + return null; + } + } + + checkInstanceFePropertiesMapIsFilled(){ + return _.keys(this.fePropertiesMap).length > 0 + } } diff --git a/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.component.ts b/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.component.ts index aa5f2420b3..9613439859 100644 --- a/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.component.ts @@ -14,12 +14,11 @@ * permissions and limitations under the License. */ - -import { Component, Input, Output, EventEmitter } from "@angular/core"; -import { PolicyInstance } from "app/models"; -import { ModalService } from "../../../services/modal.service"; -import { InstanceFeDetails } from "app/models/instance-fe-details"; -import {TranslateService} from 'app/ng2/shared/translator/translate.service'; +import { Component, EventEmitter, Input, Output } from '@angular/core'; +import { PolicyInstance } from 'app/models'; +import { InstanceFeDetails } from 'app/models/instance-fe-details'; +import { TranslateService } from 'app/ng2/shared/translator/translate.service'; +import { ModalService } from '../../../services/modal.service'; @Component({ selector: 'policies-table', @@ -28,13 +27,13 @@ import {TranslateService} from 'app/ng2/shared/translator/translate.service'; }) export class PoliciesTableComponent { - @Input() policies: Array<PolicyInstance>; + @Input() policies: PolicyInstance[]; @Input() instanceNamesMap: Map<string, InstanceFeDetails>; @Input() readonly: boolean; @Input() isLoading: boolean; @Output() deletePolicy: EventEmitter<any> = new EventEmitter<any>(); - sortBy: String; + sortBy: string; reverse: boolean; selectedPolicyToDelete: PolicyInstance; deleteMsgTitle: string; @@ -42,43 +41,39 @@ export class PoliciesTableComponent { modalDeleteBtn: string; modalCancelBtn: string; + constructor(private modalService: ModalService, private translateService: TranslateService) { + } + sort = (sortBy) => { this.reverse = (this.sortBy === sortBy) ? !this.reverse : true; - let reverse = this.reverse ? 1 : -1; + const reverse = this.reverse ? 1 : -1; this.sortBy = sortBy; - let instanceNameMapTemp = this.instanceNamesMap; - let itemIdx1Val = ""; - let itemIdx2Val = ""; - this.policies.sort(function (itemIdx1, itemIdx2) { - if (sortBy == 'instanceUniqueId') { - itemIdx1Val = (itemIdx1[sortBy] && instanceNameMapTemp[itemIdx1[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx1[sortBy]].name : ""; - itemIdx2Val = (itemIdx2[sortBy] && instanceNameMapTemp[itemIdx2[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx2[sortBy]].name : ""; - } - else { + const instanceNameMapTemp = this.instanceNamesMap; + let itemIdx1Val = ''; + let itemIdx2Val = ''; + this.policies.sort((itemIdx1, itemIdx2) => { + if (sortBy === 'instanceUniqueId') { + itemIdx1Val = (itemIdx1[sortBy] && instanceNameMapTemp[itemIdx1[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx1[sortBy]].name : ''; + itemIdx2Val = (itemIdx2[sortBy] && instanceNameMapTemp[itemIdx2[sortBy]] !== undefined) ? instanceNameMapTemp[itemIdx2[sortBy]].name : ''; + } else { itemIdx1Val = itemIdx1[sortBy]; itemIdx2Val = itemIdx2[sortBy]; - } + } if (itemIdx1Val < itemIdx2Val) { return -1 * reverse; - } - else if (itemIdx1Val > itemIdx2Val) { + } else if (itemIdx1Val > itemIdx2Val) { return 1 * reverse; - } - else { + } else { return 0; } }); - }; - - - constructor(private modalService: ModalService, private translateService: TranslateService) { } ngOnInit() { - this.translateService.languageChangedObservable.subscribe(lang => { - this.deleteMsgTitle = this.translateService.translate("DELETE_POLICY_TITLE"); - this.modalDeleteBtn = this.translateService.translate("MODAL_DELETE"); - this.modalCancelBtn = this.translateService.translate("MODAL_CANCEL"); + this.translateService.languageChangedObservable.subscribe((lang) => { + this.deleteMsgTitle = this.translateService.translate('DELETE_POLICY_TITLE'); + this.modalDeleteBtn = this.translateService.translate('MODAL_DELETE'); + this.modalCancelBtn = this.translateService.translate('MODAL_CANCEL'); }); } @@ -86,15 +81,13 @@ export class PoliciesTableComponent { onDeletePolicy = () => { this.deletePolicy.emit(this.selectedPolicyToDelete); this.modalService.closeCurrentModal(); - }; + } openDeleteModal = (policy: PolicyInstance) => { this.selectedPolicyToDelete = policy; - this.translateService.languageChangedObservable.subscribe(lang => { - this.deleteMsgBodyTxt = this.translateService.translate("DELETE_POLICY_MSG", {policyName: policy.name}); + this.translateService.languageChangedObservable.subscribe((lang) => { + this.deleteMsgBodyTxt = this.translateService.translate('DELETE_POLICY_MSG', {policyName: policy.name}); this.modalService.createActionModal(this.deleteMsgTitle, this.deleteMsgBodyTxt, this.modalDeleteBtn, this.onDeletePolicy, this.modalCancelBtn).instance.open(); }); } } - - diff --git a/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.module.ts b/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.module.ts index f780c62c0b..57cb13c281 100644 --- a/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.module.ts +++ b/catalog-ui/src/app/ng2/components/logic/policies-table/policies-table.module.ts @@ -14,12 +14,12 @@ * permissions and limitations under the License. */ -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {PoliciesTableComponent} from "./policies-table.component"; -import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { GlobalPipesModule } from '../../../pipes/global-pipes.module'; +import { PoliciesTableComponent } from './policies-table.component'; @NgModule({ declarations: [ @@ -38,4 +38,4 @@ import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; providers: [] }) export class PoliciesTableModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html index c4639aeda0..f6396e6f2c 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.html @@ -12,8 +12,8 @@ * See the License for the specific language governing permissions and * limitations under the License. --> - - + + <div *ngIf="!property.hidden" class="dynamic-property-row nested-level-{{nestedLevel}}" [@fadeIn] [ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.propertiesName, 'readonly': property.isDisabled ||property.isDeclared}" [class.with-top-border]="property.isChildOfListOrMap" @@ -48,13 +48,13 @@ pattern="validationUtils.getValidationPattern(property.type)" [value]="property.isDeclared ? property.value : property.valueObj" [type]="property.isDeclared ? 'string' : property.type" - [childType]="property.schema.property.type" [name]="property.name" [path]="property.propertiesName" - [constraints]="property.constraints" (elementChanged)="onElementChanged($event)" [readonly]="readonly || property.isDeclared || property.isDisabled" [testId]="'prop-' + propertyTestsId" + [declared] = "property.isDeclared" + [constraints] = "constraints" ></dynamic-element> </div> </ng-container> @@ -69,7 +69,7 @@ <ng-container *ngIf="!property.isDeclared"> <a *ngIf="(propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && !property.isChildOfListOrMap" class="property-icon add-item" (click)="createNewChildProperty();" [ngClass]="{'disabled':readonly || preventInsertItem(property)}" [attr.data-tests-id]="'add-to-list-' + propertyTestsId">Add value to list</a> <span *ngIf="property.isChildOfListOrMap" (click)="deleteItem.emit(property);" class="property-icon sprite-new delete-item-icon" [ngClass]="{'disabled':readonly}" [attr.data-tests-id]="'delete-from-list-' + propertyTestsId"></span> - <span *ngIf="!isPropertyFEModel && (propType == derivedPropertyTypes.COMPLEX || ((propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && hasChildren))" (click)="expandChildById(propPath)" class="property-icon sprite-new round-expand-icon" [class.open]="expandedChildId.indexOf(propPath) == 0" [attr.data-tests-id]="'expand-' + propertyTestsId"></span> + <span *ngIf="!isPropertyFEModel && (propType == derivedPropertyTypes.COMPLEX || ((propType == derivedPropertyTypes.LIST || propType == derivedPropertyTypes.MAP) && hasChildren))" (click)="expandChildById(propPath)" class="property-icon sprite-new round-expand-icon" [class.open]="expandedChildId.indexOf(propPath) == 0" [attr.data-tests-id]="'expand-' + propertyTestsId" ></span> </ng-container> </div> diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.less b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.less index 0adce2c99d..1007292854 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.less +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.less @@ -60,7 +60,7 @@ align-self:center; cursor:pointer; } - + } .filtered { diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.ts b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.ts index 6e19c95003..715426f212 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/dynamic-property/dynamic-property.component.ts @@ -23,7 +23,7 @@ import {Component, Input, Output, EventEmitter, ViewChild, ComponentRef} from "@ import { PropertyFEModel, DerivedFEProperty, DerivedPropertyType } from "app/models"; import { PROPERTY_TYPES } from 'app/utils'; import { DataTypeService } from "../../../../services/data-type.service"; -import { trigger, state, style, transition, animate } from '@angular/core'; +import { trigger, state, style, transition, animate } from '@angular/animations'; import {PropertiesUtils} from "../../../../pages/properties-assignment/services/properties.utils"; import {IUiElementChangeEvent} from "../../../ui/form-components/ui-element-base.component"; import {DynamicElementComponent} from "../../../ui/dynamic-element/dynamic-element.component"; @@ -42,6 +42,7 @@ export class DynamicPropertyComponent { isPropertyFEModel: boolean; nestedLevel: number; propertyTestsId: string; + constraints:string[]; @Input() canBeDeclared: boolean; @Input() property: PropertyFEModel | DerivedFEProperty; @@ -72,7 +73,30 @@ export class DynamicPropertyComponent { this.propPath = (this.property instanceof PropertyFEModel) ? this.property.name : this.property.propertiesName; this.nestedLevel = (this.property.propertiesName.match(/#/g) || []).length; this.rootProperty = (this.rootProperty) ? this.rootProperty : <PropertyFEModel>this.property; - this.propertyTestsId = this.getPropertyTestsId(); + this.propertyTestsId = this.getPropertyTestsId(); + + this.initConsraintsValues(); + + + } + + initConsraintsValues(){ + let primitiveProperties = ['string', 'integer', 'float', 'boolean']; + + //Property has constraints + if(this.property.constraints){ + this.constraints = this.property.constraints[0].validValues + } + + //Complex Type + else if (primitiveProperties.indexOf(this.rootProperty.type) == -1 && primitiveProperties.indexOf(this.property.type) >= 0 ){ + this.constraints = this.dataTypeService.getConstraintsByParentTypeAndUniqueID(this.rootProperty.type, this.property.name); + } + + else{ + this.constraints = null; + } + } ngDoCheck() { diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.html b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.html index 2068b170b3..89b85d3578 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.html +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.html @@ -37,18 +37,18 @@ <div class="no-data" *ngIf="!fePropertiesMap || !(fePropertiesMap | keys).length">No data to display</div> <ng-container *ngFor="let instanceId of fePropertiesMap | keys; trackBy:vspId"> + <!-- Icon & Instance Name --> <div class="table-rows-header white-sub-header" *ngIf="feInstanceNamesMap"> - - <span [ngClass]="['prop-instance-icon', feInstanceNamesMap[instanceId].iconClass, 'small']"></span> - {{feInstanceNamesMap[instanceId].name}} + {{feInstanceNamesMap[instanceId].name}} <div class="sprite-new archive-label" *ngIf="feInstanceNamesMap[instanceId].originArchived == true"></div> </div> - - <div class="table-row" *ngFor="let property of fePropertiesMap[instanceId] | searchFilter:'name':searchTerm | orderBy:{path: path, direction: direction}; trackBy:property?.name " + + <div class="table-row" *ngFor="let property of fePropertiesMap[instanceId] | searchFilter:'name':searchTerm | propertiesOrderBy:{path: path, direction: direction}; trackBy:property?.name " (click)="onClickPropertyRow(property, instanceId, $event)" [ngClass]="{'selected': selectedPropertyId && selectedPropertyId === property.name, 'readonly': property.isDisabled || property.isDeclared}"> <div class="table-cell col1" [ngClass]="{'filtered':property.name === propertyNameSearchText}" [class.round-checkbox]="property.isDeclared"> + <!-- Property Name --> <div class="property-name"> <checkbox *ngIf="hasDeclareOption" [(checked)]="property.isSelected" [disabled]="property.isDisabled || property.isDeclared || readonly" (checkedChange)="propertyChecked(property)" [attr.data-tests-id]="property.name"></checkbox> @@ -62,16 +62,19 @@ <span *ngIf="showDelete" class="sprite-new delete-btn" [ngClass]="{'disabled' : property.isDisabled || property.isDeclared}" (click)="openDeleteModal(property)" data-tests-id="delete-input-button"></span> </div> </div> + <!-- Property Type --> <div class="table-cell col2" *ngIf="!hidePropertyType"> <div class="inner-cell-div" tooltip="{{property.type | contentAfterLastDot}}"> <span>{{property.type | contentAfterLastDot}}</span> </div> </div> + <!-- Property ES (Entry Schema) --> <div class="table-cell col3" *ngIf="!hidePropertyType"> <div *ngIf="property.schema && property.schema.property && property.schema.property.type" class="inner-cell-div" tooltip="{{property.schema.property.type | contentAfterLastDot}}"> <span>{{property.schema.property.type | contentAfterLastDot}}</span> </div> </div> + <!-- Property Value --> <div class="table-cell valueCol"> <!-- [ngClass]="{'filtered':property.name === propertyNameSearchText}" (selectProperty)="propertySelected(property, $event, flatProperty.propertiesName)" [propType]="property.type" [propSchema]="property.schema" [propKey]="" [propValue]="property.value"--> <dynamic-property @@ -86,9 +89,10 @@ (expandChild)="property.updateExpandedChildPropertyId($event)" (clickOnPropertyRow)="onClickPropertyInnerRow($event, instanceId)" (checkProperty)="propertyChecked(property, $event)" + > + </dynamic-property> - </div> </div> </ng-container> diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.spec.ts b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.spec.ts new file mode 100644 index 0000000000..ea524e54b1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.spec.ts @@ -0,0 +1,175 @@ +import { NO_ERRORS_SCHEMA, SimpleChange } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { DerivedFEProperty } from '../../../../models/properties-inputs/derived-fe-property'; +import { PropertyBEModel } from '../../../../models/properties-inputs/property-be-model'; +import { PropertyFEModel } from '../../../../models/properties-inputs/property-fe-model'; +import { ContentAfterLastDotPipe } from '../../../pipes/contentAfterLastDot.pipe'; +import { KeysPipe } from '../../../pipes/keys.pipe'; +import { PropertiesOrderByPipe } from '../../../pipes/properties-order-by.pipe'; +import { SearchFilterPipe } from '../../../pipes/searchFilter.pipe'; +import { ModalService } from '../../../services/modal.service'; +import { PropertiesService } from '../../../services/properties.service'; +import { PropertiesTableComponent, PropertyRowSelectedEvent } from './properties-table.component'; + +describe('properties-table component', () => { + + let fixture: ComponentFixture<PropertiesTableComponent>; + let propertiesServiceMock: Partial<PropertiesService>; + let modalServiceMock: Partial<ModalService>; + + beforeEach( + () => { + propertiesServiceMock = { + undoDisableRelatedProperties: jest.fn(), + disableRelatedProperties: jest.fn() + }; + modalServiceMock = { + + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [ + PropertiesTableComponent, + KeysPipe, + PropertiesOrderByPipe, + SearchFilterPipe, + ContentAfterLastDotPipe + ], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: PropertiesService, useValue: propertiesServiceMock}, + {provide: ModalService, useValue: modalServiceMock} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(PropertiesTableComponent); + }); + } + ); + + it('When Properties assignment page is loaded, it is sorted by property name (acsending)', () => { + const fePropertiesMapValues = new SimpleChange('previousValue', 'currentValue', true); + const changes = { + fePropertiesMap: fePropertiesMapValues + }; + + // init values before ngOnChanges was called + fixture.componentInstance.sortBy = 'existingValue'; + + fixture.componentInstance.ngOnChanges(changes); + + expect (fixture.componentInstance.reverse).toEqual(true); + // expect (fixture.componentInstance.direction).toEqual(1); + expect (fixture.componentInstance.direction).toEqual(fixture.componentInstance.ascUpperLettersFirst); + expect (fixture.componentInstance.sortBy).toEqual('name'); + expect (fixture.componentInstance.path.length).toEqual(1); + expect (fixture.componentInstance.path[0]).toEqual('name'); + }); + + it('When ngOnChanges is called without fePropertiesMap,' + + ' sortBy will remain as it was', () => { + const fePropertiesMapValues = new SimpleChange('previousValue', 'currentValue', true); + const changes = { + dummyKey: fePropertiesMapValues + }; + + // init values before ngOnChanges was called + fixture.componentInstance.sortBy = 'existingValue'; + fixture.componentInstance.sort = jest.fn(); + + fixture.componentInstance.ngOnChanges(changes); + + expect (fixture.componentInstance.sortBy).toEqual('existingValue'); + }); + + it ('When sort is called init this.direction to 1', () => { + // init values + fixture.componentInstance.reverse = false; + fixture.componentInstance.direction = 0; + fixture.componentInstance.sortBy = 'initialize.Value'; + fixture.componentInstance.path = []; + + // call sore function + fixture.componentInstance.sort('initialize.Value'); + + // expect that + expect (fixture.componentInstance.reverse).toBe(true); + expect (fixture.componentInstance.direction).toBe(fixture.componentInstance.ascUpperLettersFirst); + expect (fixture.componentInstance.sortBy).toBe('initialize.Value'); + expect (fixture.componentInstance.path.length).toBe(2); + expect (fixture.componentInstance.path[0]).toBe('initialize'); + expect (fixture.componentInstance.path[1]).toBe('Value'); + }); + + it ('When sort is called init this.direction to -1', () => { + // init values + fixture.componentInstance.reverse = true; + fixture.componentInstance.direction = 0; + fixture.componentInstance.sortBy = 'initialize.Value'; + fixture.componentInstance.path = []; + + // call sore function + fixture.componentInstance.sort('initialize.Value'); + + // expect that + expect (fixture.componentInstance.reverse).toBe(false); + expect (fixture.componentInstance.direction).toBe(fixture.componentInstance.descLowerLettersFirst); + }); + + it ('When onPropertyChanged is called, event is emitted' , () => { + spyOn(fixture.componentInstance.emitter, 'emit'); + fixture.componentInstance.onPropertyChanged('testProperty'); + expect(fixture.componentInstance.emitter.emit).toHaveBeenCalledWith('testProperty'); + }); + + it ('When onClickPropertyRow is called, selectedPropertyId is updated and event is emitted.' , () => { + const propertyFEModel = new PropertyFEModel(new PropertyBEModel()); + propertyFEModel.name = 'propertyName'; + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(propertyFEModel, 'instanceName'); + + spyOn(fixture.componentInstance.selectPropertyRow, 'emit'); + fixture.componentInstance.onClickPropertyRow(propertyFEModel, 'instanceName'); + + expect (fixture.componentInstance.selectedPropertyId).toBe('propertyName'); + expect (fixture.componentInstance.selectPropertyRow.emit).toHaveBeenCalledWith(propertyRowSelectedEvent); + }); + + it ('When onClickPropertyInnerRow is called, event is emitted.' , () => { + const derivedFEProperty = new DerivedFEProperty(new PropertyBEModel()); + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(derivedFEProperty, 'instanceName'); + spyOn(fixture.componentInstance.selectPropertyRow, 'emit'); + fixture.componentInstance.onClickPropertyInnerRow(derivedFEProperty, 'instanceName'); + + expect (fixture.componentInstance.selectPropertyRow.emit).toHaveBeenCalledWith(propertyRowSelectedEvent); + }); + + it ('When propertyChecked is called, propertiesService.undoDisableRelatedProperties is called and event is emitted.' , () => { + + const propertyFEModel = new PropertyFEModel(new PropertyBEModel()); + propertyFEModel.isSelected = false; + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(propertyFEModel, 'instanceName1'); + + spyOn(fixture.componentInstance.updateCheckedPropertyCount, 'emit'); + fixture.componentInstance.propertyChecked(propertyFEModel); + expect (propertiesServiceMock.undoDisableRelatedProperties).toHaveBeenCalledWith(propertyFEModel, undefined); + expect (fixture.componentInstance.updateCheckedPropertyCount.emit).toHaveBeenCalledWith(false); + }); + + it ('When propertyChecked is called, propertiesService.disableRelatedProperties is called and event is emitted.' , () => { + + const propertyFEModel = new PropertyFEModel(new PropertyBEModel()); + propertyFEModel.isSelected = true; + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(propertyFEModel, 'instanceName1'); + + spyOn(fixture.componentInstance.updateCheckedPropertyCount, 'emit'); + fixture.componentInstance.propertyChecked(propertyFEModel); + expect (propertiesServiceMock.disableRelatedProperties).toHaveBeenCalledWith(propertyFEModel, undefined); + expect (fixture.componentInstance.updateCheckedPropertyCount.emit).toHaveBeenCalledWith(true); + }); + +}); diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.ts b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.ts index 0cc188134f..e499b3786b 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/properties-table.component.ts @@ -8,9 +8,9 @@ * 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. @@ -19,33 +19,33 @@ * ============LICENSE_END========================================================= */ -import { Component, Input, Output, EventEmitter} from "@angular/core"; -import {PropertyFEModel, DerivedFEProperty, InstanceFePropertiesMap} from "app/models"; -import {PropertiesService} from "../../../services/properties.service"; -import {ModalService} from "../../../services/modal.service"; -import { InstanceFeDetails } from "../../../../models/instance-fe-details"; +import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { DerivedFEProperty, InstanceFePropertiesMap, PropertyFEModel } from 'app/models'; +import { InstanceFeDetails } from '../../../../models/instance-fe-details'; +import { PropertiesService } from '../../../services/properties.service'; +import { ModalService } from '../../../services/modal.service'; @Component({ selector: 'properties-table', templateUrl: './properties-table.component.html', styleUrls: ['./properties-table.component.less'] }) -export class PropertiesTableComponent { +export class PropertiesTableComponent implements OnChanges { @Input() fePropertiesMap: InstanceFePropertiesMap; @Input() feInstanceNamesMap: Map<string, InstanceFeDetails>; @Input() selectedPropertyId: string; - @Input() propertyNameSearchText:string; - @Input() searchTerm:string; - @Input() readonly:boolean; - @Input() isLoading:boolean; - @Input() hasDeclareOption:boolean; - @Input() hidePropertyType:boolean; + @Input() propertyNameSearchText: string; + @Input() searchTerm: string; + @Input() readonly: boolean; + @Input() isLoading: boolean; + @Input() hasDeclareOption: boolean; + @Input() hidePropertyType: boolean; @Input() showDelete:boolean; - + @Output('propertyChanged') emitter: EventEmitter<PropertyFEModel> = new EventEmitter<PropertyFEModel>(); @Output() selectPropertyRow: EventEmitter<PropertyRowSelectedEvent> = new EventEmitter<PropertyRowSelectedEvent>(); - @Output() updateCheckedPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>();//only for hasDeclareOption and hasDeclareListOption + @Output() updateCheckedPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>(); // only for hasDeclareOption @Output() updateCheckedChildPropertyCount: EventEmitter<boolean> = new EventEmitter<boolean>();//only for hasDeclareListOption @Output() deleteProperty: EventEmitter<PropertyFEModel> = new EventEmitter<PropertyFEModel>(); private selectedPropertyToDelete: PropertyFEModel; @@ -53,41 +53,48 @@ export class PropertiesTableComponent { sortBy: String; reverse: boolean; direction: number; - path:string[]; + path: string[]; - sort(sortBy){ - this.reverse = (this.sortBy === sortBy) ? !this.reverse : true; - this.direction = this.reverse ? 1 : -1; - this.sortBy = sortBy; - this.path = sortBy.split('.'); + readonly ascUpperLettersFirst = 1; + readonly descLowerLettersFirst = -1; + + constructor(private propertiesService: PropertiesService, private modalService: ModalService ) { } - constructor (private propertiesService:PropertiesService, private modalService: ModalService){ + ngOnChanges(changes: SimpleChanges): void { + if (changes.fePropertiesMap) { + this.sortBy = ''; + this.sort('name'); + } } - - ngOnInit() { + + sort(sortBy) { + this.reverse = (this.sortBy === sortBy) ? !this.reverse : true; + this.direction = this.reverse ? this.ascUpperLettersFirst : this.descLowerLettersFirst; + this.sortBy = sortBy; + this.path = sortBy.split('.'); } onPropertyChanged = (property) => { this.emitter.emit(property); - }; + } // Click on main row (row of propertyFEModel) - onClickPropertyRow = (property:PropertyFEModel, instanceName:string, event?) => { - //event && event.stopPropagation(); + onClickPropertyRow = (property: PropertyFEModel, instanceName: string, event?) => { + // event && event.stopPropagation(); this.selectedPropertyId = property.name; - let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); this.selectPropertyRow.emit(propertyRowSelectedEvent); - }; + } // Click on inner row (row of DerivedFEProperty) - onClickPropertyInnerRow = (property:DerivedFEProperty, instanceName:string) => { - let propertyRowSelectedEvent:PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); + onClickPropertyInnerRow = (property: DerivedFEProperty, instanceName: string) => { + const propertyRowSelectedEvent: PropertyRowSelectedEvent = new PropertyRowSelectedEvent(property, instanceName); this.selectPropertyRow.emit(propertyRowSelectedEvent); } propertyChecked = (prop: PropertyFEModel, childPropName?: string) => { - let isChecked: boolean = (!childPropName)? prop.isSelected : prop.flattenedChildren.find(prop => prop.propertiesName == childPropName).isSelected; + const isChecked: boolean = (!childPropName) ? prop.isSelected : prop.flattenedChildren.find((prop) => prop.propertiesName == childPropName).isSelected; if (!isChecked) { this.propertiesService.undoDisableRelatedProperties(prop, childPropName); @@ -116,11 +123,10 @@ export class PropertiesTableComponent { } export class PropertyRowSelectedEvent { - propertyModel:PropertyFEModel | DerivedFEProperty; - instanceName:string; - constructor ( propertyModel:PropertyFEModel | DerivedFEProperty, instanceName:string ){ + propertyModel: PropertyFEModel | DerivedFEProperty; + instanceName: string; + constructor( propertyModel: PropertyFEModel | DerivedFEProperty, instanceName: string ) { this.propertyModel = propertyModel; this.instanceName = instanceName; } } - diff --git a/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts b/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts index cb8c9a694b..4d968a088c 100644 --- a/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts +++ b/catalog-ui/src/app/ng2/components/logic/properties-table/property-table.module.ts @@ -4,7 +4,6 @@ import {DynamicPropertyComponent} from "./dynamic-property/dynamic-property.comp import {FormsModule} from "@angular/forms"; import {UiElementsModule} from "../../ui/ui-elements.module"; import {CommonModule} from "@angular/common"; -import {HttpModule} from "@angular/http"; import {FilterChildPropertiesPipe} from "./pipes/filterChildProperties.pipe"; import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; import {PropertiesService} from "../../../services/properties.service"; @@ -13,7 +12,6 @@ import {MultilineEllipsisModule} from "../../../shared/multiline-ellipsis/multil @NgModule({ imports: [ FormsModule, - HttpModule, CommonModule, GlobalPipesModule, UiElementsModule, diff --git a/catalog-ui/src/app/ng2/components/logic/select-requirement-or-capability/select-requirement-or-capability.component.ts b/catalog-ui/src/app/ng2/components/logic/select-requirement-or-capability/select-requirement-or-capability.component.ts index 2e3c21c210..d41b580514 100644 --- a/catalog-ui/src/app/ng2/components/logic/select-requirement-or-capability/select-requirement-or-capability.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/select-requirement-or-capability/select-requirement-or-capability.component.ts @@ -9,6 +9,7 @@ import {ComponentInstanceServiceNg2} from "../../../services/component-instance- import {PropertiesUtils} from "app/ng2/pages/properties-assignment/services/properties.utils"; import {Requirement} from "../../../../models/requirement"; import {Capability, RequirementCapabilityModel} from "../../../../models/capability"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; const REQUIREMENT = 'Requirement'; const CAPABILITY = 'Capability'; @@ -24,14 +25,9 @@ export class SelectRequirementOrCapabilityComponent implements OnInit { @Input() optionalRequirementsMap:Dictionary<Requirement[]>; //optional requirement map - key is type, value is array of requirements @Input() optionalCapabilitiesMap:Dictionary<Capability[]>; //optional capabilities map - key is type, value is array of capabilities - @Input() selectedReqOrCapOption:string; // the selection value chosen by the user (options: requirement / capability ) - - @Input() currentComponent:ComponentModel; @Input() componentInstanceId:string; - @Input() selectedReqOrCapModel:RequirementCapabilityModel; - @Output() updateSelectedReqOrCap:EventEmitter<RequirementCapabilityModel> = new EventEmitter<RequirementCapabilityModel>(); types:Array<string> = []; @@ -51,7 +47,8 @@ export class SelectRequirementOrCapabilityComponent implements OnInit { private _loadingCapabilityProperties: Array<Capability>; constructor(private componentInstanceServiceNg2:ComponentInstanceServiceNg2, - private propertiesUtils:PropertiesUtils) { + private propertiesUtils:PropertiesUtils, + private workspaceService: WorkspaceService) { this.selectOptions = [new RadioButtonModel(REQUIREMENT, REQUIREMENT), new RadioButtonModel(CAPABILITY, CAPABILITY)]; this._loadingCapabilityProperties = []; } @@ -171,14 +168,13 @@ export class SelectRequirementOrCapabilityComponent implements OnInit { } } - private setCapabilityProperties = ():void => { let selectedCapability = <Capability>this.selectedReqOrCapModel; if (!selectedCapability.properties) { this.loadingCapabilityProperties = true; if (this._loadingCapabilityProperties.indexOf(selectedCapability) == -1) { this._loadingCapabilityProperties.push(selectedCapability); - this.componentInstanceServiceNg2.getInstanceCapabilityProperties(this.currentComponent, this.componentInstanceId, selectedCapability) + this.componentInstanceServiceNg2.getInstanceCapabilityProperties(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.componentInstanceId, selectedCapability) .subscribe((response: Array<PropertyModel>) => { if (this.selectedReqOrCapModel === selectedCapability) { delete this.loadingCapabilityProperties; diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less index 5830c06972..f4d673d695 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.less @@ -50,7 +50,7 @@ } .operation-name { - .s_1; + // .s_1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts index ebcf9eba22..08e6c36db1 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.component.ts @@ -14,34 +14,38 @@ * permissions and limitations under the License. */ -import {Component, Input, ComponentRef} from '@angular/core'; -import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; -import {ComponentInstanceServiceNg2} from 'app/ng2/services/component-instance-services/component-instance.service'; -import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; -import {ModalService} from 'app/ng2/services/modal.service'; -import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; +import { Component, ComponentRef, Input } from '@angular/core'; import { - ModalModel, ButtonModel, - OperationModel, - Service, - ServiceInstanceObject, - PropertyFEModel, - PropertyBEModel, + CapabilitiesGroup, + Capability, + Component as TopologyTemplate, InputBEModel, InterfaceModel, - CapabilitiesGroup, - Capability + ModalModel, + OperationModel, + PropertyBEModel, + PropertyFEModel } from 'app/models'; -import {ServiceConsumptionCreatorComponent} from 'app/ng2/pages/service-consumption-editor/service-consumption-editor.component'; - +import { ModalComponent } from 'app/ng2/components/ui/modal/modal.component'; +import { ServiceConsumptionCreatorComponent } from 'app/ng2/pages/service-consumption-editor/service-consumption-editor.component'; +import { ComponentInstanceServiceNg2 } from 'app/ng2/services/component-instance-services/component-instance.service'; +import { ComponentServiceNg2 } from 'app/ng2/services/component-services/component.service'; +import { ModalService } from 'app/ng2/services/modal.service'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { Resource } from '../../../../models/components/resource'; +import { FullComponentInstance } from '../../../../models/componentsInstances/fullComponentInstance'; +import { ServiceInstanceObject } from '../../../../models/service-instance-properties-and-interfaces'; +import { ComponentFactory } from '../../../../utils/component-factory'; +import { ComponentType } from '../../../../utils/constants'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; export class ConsumptionInput extends PropertyFEModel{ inputId: string; type: string; source: string; value: any; - constraints: Array<any>; + constraints: any[]; constructor(input?: any) { super(input); @@ -49,19 +53,20 @@ export class ConsumptionInput extends PropertyFEModel{ this.inputId = input.inputId; this.type = input.type; this.source = input.source; - this.value = input.value || ""; + this.value = input.value || ''; this.constraints = input.constraints; } } } +// tslint:disable-next-line:max-classes-per-file export class ConsumptionInputDetails extends ConsumptionInput { name: string; expanded: boolean; assignValueLabel: string; - associatedProps: Array<string>; - associatedInterfaces: Array<any>; - associatedCapabilities: Array<Capability>; + associatedProps: string[]; + associatedInterfaces: any[]; + associatedCapabilities: Capability[]; origVal: string; isValid: boolean; @@ -74,7 +79,7 @@ export class ConsumptionInputDetails extends ConsumptionInput { this.associatedProps = input.associatedProps; this.associatedInterfaces = input.associatedInterfaces; this.associatedCapabilities = input.associatedCapabilities; - this.origVal = input.value || ""; + this.origVal = input.value || ''; this.isValid = input.isValid; } } @@ -84,9 +89,10 @@ export class ConsumptionInputDetails extends ConsumptionInput { } } +// tslint:disable-next-line:max-classes-per-file export class ServiceOperation { operation: OperationModel; - consumptionInputs: Array<ConsumptionInputDetails>; + consumptionInputs: ConsumptionInputDetails[]; constructor(input?: any) { if (input) { @@ -96,24 +102,24 @@ export class ServiceOperation { } } +// tslint:disable-next-line:max-classes-per-file export class InterfaceWithServiceOperation { interfaceId: string; displayName: string; - operationsList: Array<ServiceOperation>; + operationsList: ServiceOperation[]; isExpanded: boolean; constructor(input?: InterfaceModel) { if (input) { this.interfaceId = input.uniqueId; this.displayName = input.displayType(); - this.operationsList = _.map(input.operations, operation => new ServiceOperation({operation: operation})); + this.operationsList = _.map(input.operations, (operation) => new ServiceOperation({operation: operation})); this.isExpanded = true; } } } - - +// tslint:disable-next-line:max-classes-per-file @Component({ selector: 'service-consumption', templateUrl: './service-consumption.component.html', @@ -125,21 +131,23 @@ export class ServiceConsumptionComponent { modalInstance: ComponentRef<ModalComponent>; isLoading: boolean = false; - interfacesList: Array<InterfaceWithServiceOperation>; - operationsGroup: Array<ServiceOperation>; - @Input() parentServiceInputs: Array<InputBEModel> = []; - @Input() parentService: Service; - @Input() selectedService: Service; + interfacesList: InterfaceWithServiceOperation[]; + operationsGroup: ServiceOperation[]; + @Input() parentServiceInputs: InputBEModel[] = []; + @Input() parentService: ComponentMetadata; + @Input() selectedService: TopologyTemplate | FullComponentInstance; @Input() selectedServiceInstanceId: string; - @Input() instancesMappedList: Array<ServiceInstanceObject>; - @Input() instancesCapabilitiesMap: Map<string, Array<Capability>>; + @Input() instancesMappedList: ServiceInstanceObject[]; + @Input() instancesCapabilitiesMap: Map<string, Capability[]>; @Input() readonly: boolean; - selectedInstanceSiblings: Array<ServiceInstanceObject>; - selectedInstancePropertiesList: Array<PropertyBEModel> = []; - selectedInstanceCapabilitisList: Array<Capability> = []; + selectedInstanceSiblings: ServiceInstanceObject[]; + selectedInstancePropertiesList: PropertyBEModel[] = []; + selectedInstanceCapabilitisList: Capability[] = []; - constructor(private ModalServiceNg2: ModalService, private serviceServiceNg2: ServiceServiceNg2, private componentServiceNg2: ComponentServiceNg2, private componentInstanceServiceNg2:ComponentInstanceServiceNg2) {} + constructor(private modalServiceNg2: ModalService, private topologyTemplateService: TopologyTemplateService, + private componentServiceNg2: ComponentServiceNg2, private componentInstanceServiceNg2: ComponentInstanceServiceNg2, + private componentFactory: ComponentFactory) {} ngOnInit() { this.updateSelectedInstancePropertiesAndSiblings(); @@ -147,15 +155,15 @@ export class ServiceConsumptionComponent { } ngOnChanges(changes) { - if(changes.selectedServiceInstanceId && changes.selectedServiceInstanceId.currentValue !== changes.selectedServiceInstanceId.previousValue) { + if (changes.selectedServiceInstanceId && changes.selectedServiceInstanceId.currentValue !== changes.selectedServiceInstanceId.previousValue) { this.selectedServiceInstanceId = changes.selectedServiceInstanceId.currentValue; - if(changes.selectedService && changes.selectedService.currentValue !== changes.selectedService.previousValue) { + if (changes.selectedService && changes.selectedService.currentValue !== changes.selectedService.previousValue) { this.selectedService = changes.selectedService.currentValue; } this.updateSelectedInstancePropertiesAndSiblings(); this.updateSelectedServiceCapabilities(); } - if(changes.instancesMappedList && !_.isEqual(changes.instancesMappedList.currentValue, changes.instancesMappedList.previousValue)) { + if (changes.instancesMappedList && !_.isEqual(changes.instancesMappedList.currentValue, changes.instancesMappedList.previousValue)) { this.updateSelectedInstancePropertiesAndSiblings(); this.updateSelectedServiceCapabilities(); } @@ -163,22 +171,22 @@ export class ServiceConsumptionComponent { updateSelectedInstancePropertiesAndSiblings() { this.interfacesList = []; - let selectedInstanceMetadata: ServiceInstanceObject = _.find(this.instancesMappedList, coInstance => coInstance.id === this.selectedServiceInstanceId); + const selectedInstanceMetadata: ServiceInstanceObject = _.find(this.instancesMappedList, (coInstance) => coInstance.id === this.selectedServiceInstanceId); if (selectedInstanceMetadata) { - _.forEach(selectedInstanceMetadata.interfaces, (interfaceData:InterfaceModel) => { + _.forEach(selectedInstanceMetadata.interfaces, (interfaceData: InterfaceModel) => { this.interfacesList.push(new InterfaceWithServiceOperation(interfaceData)); }); } - this.interfacesList.sort((interf1:InterfaceWithServiceOperation, interf2:InterfaceWithServiceOperation) => interf1.displayName.localeCompare(interf2.displayName)); + this.interfacesList.sort((interf1: InterfaceWithServiceOperation, interf2: InterfaceWithServiceOperation) => interf1.displayName.localeCompare(interf2.displayName)); this.selectedInstancePropertiesList = selectedInstanceMetadata && selectedInstanceMetadata.properties; - this.selectedInstanceSiblings = _.filter(this.instancesMappedList, coInstance => coInstance.id !== this.selectedServiceInstanceId); + this.selectedInstanceSiblings = _.filter(this.instancesMappedList, (coInstance) => coInstance.id !== this.selectedServiceInstanceId); } updateSelectedServiceCapabilities() { this.selectedInstanceCapabilitisList = _.filter( CapabilitiesGroup.getFlattenedCapabilities(this.selectedService.capabilities), - cap => cap.properties && cap.ownerId === this.selectedService.uniqueId + (cap) => cap.properties && cap.ownerId === this.selectedService.uniqueId ); } @@ -186,15 +194,15 @@ export class ServiceConsumptionComponent { currInterface.isExpanded = !currInterface.isExpanded; } - onSelectOperation(event, currInterface:InterfaceWithServiceOperation, opIndex: number) { + onSelectOperation(event, currInterface: InterfaceWithServiceOperation, opIndex: number) { event.stopPropagation(); - if(!this.readonly) { + if (!this.readonly) { this.operationsGroup = currInterface.operationsList; - let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.ModalServiceNg2.closeCurrentModal); - let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createOrUpdateOperationInput, this.getDisabled); - let modalModel: ModalModel = new ModalModel('l', 'Modify Operation Consumption', '', [saveButton, cancelButton], 'standard'); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal( + const cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.modalServiceNg2.closeCurrentModal); + const saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createOrUpdateOperationInput, this.getDisabled); + const modalModel: ModalModel = new ModalModel('l', 'Modify Operation Consumption', '', [saveButton, cancelButton], 'standard'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + this.modalServiceNg2.addDynamicContentToModal( this.modalInstance, ServiceConsumptionCreatorComponent, { @@ -215,11 +223,11 @@ export class ServiceConsumptionComponent { } } - createOrUpdateOperationInput = ():void => { + createOrUpdateOperationInput = (): void => { this.isLoading = true; - let consumptionInputsList:Array<{[id: string]: Array<ConsumptionInput>}> = _.map(this.operationsGroup, (serviceOp) => { - let consumptionInputsArr: Array<any> = []; - if(serviceOp.consumptionInputs) { + const consumptionInputsList: Array<{[id: string]: ConsumptionInput[]}> = _.map(this.operationsGroup, (serviceOp) => { + let consumptionInputsArr: any[] = []; + if (serviceOp.consumptionInputs) { consumptionInputsArr = _.map(serviceOp.consumptionInputs, (input: ConsumptionInputDetails) => { return { inputId: input.inputId, @@ -233,16 +241,24 @@ export class ServiceConsumptionComponent { [serviceOp.operation.uniqueId]: consumptionInputsArr }; }); - this.serviceServiceNg2.createOrUpdateServiceConsumptionInputs(this.parentService,this.selectedServiceInstanceId, consumptionInputsList).subscribe(() => { + this.topologyTemplateService.createOrUpdateServiceConsumptionInputs(this.convertMetaDataToComponent(this.parentService).uniqueId, this.selectedServiceInstanceId, consumptionInputsList) + .subscribe(() => { this.isLoading = false; - }, err=> { + }, (err) => { this.isLoading = false; }); - this.ModalServiceNg2.closeCurrentModal(); - }; + this.modalServiceNg2.closeCurrentModal(); + } - getDisabled = ():boolean => { + getDisabled = (): boolean => { return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); - }; + } + + //TODO remove when workspace page convert to angular5 + convertMetaDataToComponent(componentMetadata: ComponentMetadata) { + const newResource: Resource = this.componentFactory.createEmptyComponent(ComponentType.RESOURCE) as Resource; + newResource.setComponentMetadata(componentMetadata); + return newResource; + } }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts index 8593bef3eb..70b5911779 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts +++ b/catalog-ui/src/app/ng2/components/logic/service-consumption/service-consumption.module.ts @@ -14,11 +14,11 @@ * permissions and limitations under the License. */ -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {ServiceConsumptionComponent} from "./service-consumption.component"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { ServiceConsumptionComponent } from './service-consumption.component'; @NgModule({ declarations: [ @@ -29,11 +29,13 @@ import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; UiElementsModule, TranslateModule ], - exports: [], + exports: [ + ServiceConsumptionComponent + ], entryComponents: [ ServiceConsumptionComponent ], providers: [] }) export class ServiceConsumptionModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less index ae990dc85f..2fccfb414b 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less +++ b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.less @@ -38,7 +38,7 @@ justify-content: space-between; .rule-details { - .s_1; + // .s_1; display: flex; flex: 1; align-items: center; @@ -67,4 +67,13 @@ } } + .w-sdc-designer-sidebar-section-footer { + margin-top: 10px; + text-align: center; + width: 100%; + } + .w-sdc-designer-sidebar-section-footer-action { + width: 180px; + margin-top: 10px; + } }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts index bc3531dc58..fa75a275aa 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts +++ b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.component.ts @@ -13,22 +13,22 @@ * or implied. See the License for the specific language governing * permissions and limitations under the License. */ -import {Component, Input, Output, EventEmitter, ComponentRef} from '@angular/core'; -import {ModalService} from 'app/ng2/services/modal.service'; +import { Component, ComponentRef, EventEmitter, Input, Output } from '@angular/core'; import { - Service, + ButtonModel, ComponentInstance, + InputBEModel, ModalModel, - ButtonModel, PropertyBEModel, - InputBEModel, - ServiceInstanceObject } from 'app/models'; -import {ServiceDependenciesEditorComponent} from 'app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component'; -import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; -import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; -import {TranslateService} from 'app/ng2/shared/translator/translate.service'; -import {ComponentGenericResponse} from 'app/ng2/services/responses/component-generic-response'; +import { ModalComponent } from 'app/ng2/components/ui/modal/modal.component'; +import { ServiceDependenciesEditorComponent } from 'app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component'; +import { ModalService } from 'app/ng2/services/modal.service'; +import { ComponentGenericResponse } from 'app/ng2/services/responses/component-generic-response'; +import { TranslateService } from 'app/ng2/shared/translator/translate.service'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { ServiceInstanceObject } from '../../../../models/service-instance-properties-and-interfaces'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; export class ConstraintObject { servicePropertyName: string; @@ -48,12 +48,13 @@ export class ConstraintObject { } } +// tslint:disable-next-line:max-classes-per-file export class ConstraintObjectUI extends ConstraintObject{ isValidValue: boolean; constructor(input?: any) { super(input); - if(input) { + if (input) { this.isValidValue = input.isValidValue ? input.isValidValue : input.value !== ''; } } @@ -63,7 +64,7 @@ export class ConstraintObjectUI extends ConstraintObject{ } public isValidRule(isStatic) { - let isValidValue = isStatic ? this.isValidValue : true; + const isValidValue = isStatic ? this.isValidValue : true; return this.servicePropertyName != null && this.servicePropertyName !== '' && this.value != null && this.value !== '' && isValidValue; } @@ -75,6 +76,7 @@ export const OPERATOR_TYPES = { LESS_THAN: 'less_than' }; +// tslint:disable-next-line:max-classes-per-file class I18nTexts { static uncheckModalTitle: string; static uncheckModalText: string; @@ -89,21 +91,21 @@ class I18nTexts { static deleteRuleMsg: string; public static translateTexts(translateService) { - I18nTexts.uncheckModalTitle = translateService.translate("SERVICE_DEPENDENCY_UNCHECK_TITLE"); - I18nTexts.uncheckModalText = translateService.translate("SERVICE_DEPENDENCY_UNCHECK_TEXT"); - I18nTexts.modalApprove = translateService.translate("MODAL_APPROVE"); - I18nTexts.modalCancel = translateService.translate("MODAL_CANCEL"); - I18nTexts.modalCreate = translateService.translate("MODAL_CREATE"); - I18nTexts.modalSave = translateService.translate("MODAL_SAVE"); - I18nTexts.modalDelete = translateService.translate("MODAL_DELETE"); - I18nTexts.addRuleTxt = translateService.translate("SERVICE_DEPENDENCY_ADD_RULE"); - I18nTexts.updateRuleTxt = translateService.translate("SERVICE_DEPENDENCY_UPDATE_RULE"); - I18nTexts.deleteRuleTxt = translateService.translate("SERVICE_DEPENDENCY_DELETE_RULE"); - I18nTexts.deleteRuleMsg = translateService.translate("SERVICE_DEPENDENCY_DELETE_RULE_MSG"); + I18nTexts.uncheckModalTitle = translateService.translate('SERVICE_DEPENDENCY_UNCHECK_TITLE'); + I18nTexts.uncheckModalText = translateService.translate('SERVICE_DEPENDENCY_UNCHECK_TEXT'); + I18nTexts.modalApprove = translateService.translate('MODAL_APPROVE'); + I18nTexts.modalCancel = translateService.translate('MODAL_CANCEL'); + I18nTexts.modalCreate = translateService.translate('MODAL_CREATE'); + I18nTexts.modalSave = translateService.translate('MODAL_SAVE'); + I18nTexts.modalDelete = translateService.translate('MODAL_DELETE'); + I18nTexts.addRuleTxt = translateService.translate('SERVICE_DEPENDENCY_ADD_RULE'); + I18nTexts.updateRuleTxt = translateService.translate('SERVICE_DEPENDENCY_UPDATE_RULE'); + I18nTexts.deleteRuleTxt = translateService.translate('SERVICE_DEPENDENCY_DELETE_RULE'); + I18nTexts.deleteRuleMsg = translateService.translate('SERVICE_DEPENDENCY_DELETE_RULE_MSG'); } } - +// tslint:disable-next-line:max-classes-per-file @Component({ selector: 'service-dependencies', templateUrl: './service-dependencies.component.html', @@ -115,56 +117,55 @@ export class ServiceDependenciesComponent { modalInstance: ComponentRef<ModalComponent>; isDependent: boolean; isLoading: boolean; - parentServiceInputs: Array<InputBEModel> = []; - rulesList: Array<ConstraintObject> = []; - operatorTypes: Array<any>; + parentServiceInputs: InputBEModel[] = []; + rulesList: ConstraintObject[] = []; + operatorTypes: any[]; @Input() readonly: boolean; - @Input() compositeService: Service; + @Input() compositeService: ComponentMetadata; @Input() currentServiceInstance: ComponentInstance; - @Input() selectedInstanceSiblings: Array<ServiceInstanceObject>; - @Input() selectedInstanceConstraints: Array<ConstraintObject> = []; - @Input() selectedInstanceProperties: Array<PropertyBEModel> = []; - @Output() updateRulesListEvent:EventEmitter<Array<ConstraintObject>> = new EventEmitter<Array<ConstraintObject>>(); + @Input() selectedInstanceSiblings: ServiceInstanceObject[]; + @Input() selectedInstanceConstraints: ConstraintObject[] = []; + @Input() selectedInstanceProperties: PropertyBEModel[] = []; + @Output() updateRulesListEvent: EventEmitter<ConstraintObject[]> = new EventEmitter<ConstraintObject[]>(); @Output() loadRulesListEvent:EventEmitter<any> = new EventEmitter(); @Output() dependencyStatus = new EventEmitter<boolean>(); - - constructor(private componentServiceNg2: ComponentServiceNg2, private ModalServiceNg2: ModalService, private translateService: TranslateService) { + constructor(private topologyTemplateService: TopologyTemplateService, private modalServiceNg2: ModalService, private translateService: TranslateService) { } ngOnInit() { this.isLoading = false; this.operatorTypes = [ - {label: ">", value: OPERATOR_TYPES.GREATER_THAN}, - {label: "<", value: OPERATOR_TYPES.LESS_THAN}, - {label: "=", value: OPERATOR_TYPES.EQUAL} + {label: '>', value: OPERATOR_TYPES.GREATER_THAN}, + {label: '<', value: OPERATOR_TYPES.LESS_THAN}, + {label: '=', value: OPERATOR_TYPES.EQUAL} ]; - this.componentServiceNg2.getComponentInputsWithProperties(this.compositeService).subscribe((result: ComponentGenericResponse) => { + this.topologyTemplateService.getComponentInputsWithProperties(this.compositeService.componentType, this.compositeService.uniqueId).subscribe((result: ComponentGenericResponse) => { this.parentServiceInputs = result.inputs; }); this.loadRules(); - this.translateService.languageChangedObservable.subscribe(lang => { + this.translateService.languageChangedObservable.subscribe((lang) => { I18nTexts.translateTexts(this.translateService); }); } ngOnChanges(changes) { - if(changes.currentServiceInstance) { + if (changes.currentServiceInstance) { this.currentServiceInstance = changes.currentServiceInstance.currentValue; this.isDependent = this.currentServiceInstance.isDependent(); } - if(changes.selectedInstanceConstraints && changes.selectedInstanceConstraints.currentValue !== changes.selectedInstanceConstraints.previousValue) { + if (changes.selectedInstanceConstraints && changes.selectedInstanceConstraints.currentValue !== changes.selectedInstanceConstraints.previousValue) { this.selectedInstanceConstraints = changes.selectedInstanceConstraints.currentValue; this.loadRules(); } } public openRemoveDependencyModal = (): ComponentRef<ModalComponent> => { - let actionButton: ButtonModel = new ButtonModel(I18nTexts.modalApprove, 'blue', this.onUncheckDependency); - let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'grey', this.onCloseRemoveDependencyModal); - let modalModel: ModalModel = new ModalModel('sm', I18nTexts.uncheckModalTitle, I18nTexts.uncheckModalText, [actionButton, cancelButton]); - return this.ModalServiceNg2.createCustomModal(modalModel); + const actionButton: ButtonModel = new ButtonModel(I18nTexts.modalApprove, 'blue', this.onUncheckDependency); + const cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'grey', this.onCloseRemoveDependencyModal); + const modalModel: ModalModel = new ModalModel('sm', I18nTexts.uncheckModalTitle, I18nTexts.uncheckModalText, [actionButton, cancelButton]); + return this.modalServiceNg2.createCustomModal(modalModel); } loadRules() { @@ -178,60 +179,59 @@ export class ServiceDependenciesComponent { } onUncheckDependency = () => { - this.ModalServiceNg2.closeCurrentModal(); + this.modalServiceNg2.closeCurrentModal(); this.isLoading = true; - let isDepOrig = this.isDependent; - let rulesListOrig = this.rulesList; + const isDepOrig = this.isDependent; + const rulesListOrig = this.rulesList; this.currentServiceInstance.unmarkAsDependent(); this.updateComponentInstance(isDepOrig, rulesListOrig); } onCloseRemoveDependencyModal = () => { this.isDependent = true; - this.ModalServiceNg2.closeCurrentModal(); + this.modalServiceNg2.closeCurrentModal(); } onCheckDependency = () => { - let isDepOrig = this.isDependent; - let rulesListOrig = this.rulesList; + const isDepOrig = this.isDependent; + const rulesListOrig = this.rulesList; this.currentServiceInstance.markAsDependent(); this.rulesList = []; this.updateComponentInstance(isDepOrig, rulesListOrig); } onMarkAsDependent() { - if(!this.currentServiceInstance.isDependent()) { + if (!this.currentServiceInstance.isDependent()) { this.onCheckDependency(); - } - else { + } else { this.openRemoveDependencyModal().instance.open(); } } - updateComponentInstance(isDependent_origVal : boolean, rulesList_orig: Array<ConstraintObject>) { + updateComponentInstance(isDependentOrigVal: boolean, rulesListOrig: ConstraintObject[]) { this.isLoading = true; - this.componentServiceNg2.updateComponentInstance(this.compositeService, this.currentServiceInstance).subscribe((updatedServiceIns: ComponentInstance) => { + this.topologyTemplateService.updateComponentInstance(this.compositeService.uniqueId, this.currentServiceInstance).subscribe((updatedServiceIns: ComponentInstance) => { this.currentServiceInstance = new ComponentInstance(updatedServiceIns); this.isDependent = this.currentServiceInstance.isDependent(); this.dependencyStatus.emit(this.isDependent); - if(this.isDependent) { + if (this.isDependent) { this.loadRulesListEvent.emit(); } this.isLoading = false; - }, err=> { - this.isDependent = isDependent_origVal; - this.rulesList = rulesList_orig; + }, (err) => { + this.isDependent = isDependentOrigVal; + this.rulesList = rulesListOrig; this.isLoading = false; console.log('An error has occurred.'); }); } - onAddRule () { - let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.ModalServiceNg2.closeCurrentModal); - let saveButton: ButtonModel = new ButtonModel(I18nTexts.modalCreate, 'blue', this.createRule, this.getDisabled); - let modalModel: ModalModel = new ModalModel('l', I18nTexts.addRuleTxt, '', [saveButton, cancelButton], 'standard'); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal( + onAddRule() { + const cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.modalServiceNg2.closeCurrentModal); + const saveButton: ButtonModel = new ButtonModel(I18nTexts.modalCreate, 'blue', this.createRule, this.getDisabled); + const modalModel: ModalModel = new ModalModel('l', I18nTexts.addRuleTxt, '', [saveButton, cancelButton], 'standard'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + this.modalServiceNg2.addDynamicContentToModal( this.modalInstance, ServiceDependenciesEditorComponent, { @@ -247,16 +247,16 @@ export class ServiceDependenciesComponent { } onSelectRule(index: number) { - let cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.ModalServiceNg2.closeCurrentModal); - let saveButton: ButtonModel = new ButtonModel(I18nTexts.modalSave, 'blue', () => this.updateRules(), this.getDisabled); - let modalModel: ModalModel = new ModalModel('l', I18nTexts.updateRuleTxt, '', [saveButton, cancelButton], 'standard'); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal( + const cancelButton: ButtonModel = new ButtonModel(I18nTexts.modalCancel, 'outline white', this.modalServiceNg2.closeCurrentModal); + const saveButton: ButtonModel = new ButtonModel(I18nTexts.modalSave, 'blue', () => this.updateRules(), this.getDisabled); + const modalModel: ModalModel = new ModalModel('l', I18nTexts.updateRuleTxt, '', [saveButton, cancelButton], 'standard'); + this.modalInstance = this.modalServiceNg2.createCustomModal(modalModel); + this.modalServiceNg2.addDynamicContentToModal( this.modalInstance, ServiceDependenciesEditorComponent, { serviceRuleIndex: index, - serviceRules: _.map(this.rulesList, rule => new ConstraintObjectUI(rule)), + serviceRules: _.map(this.rulesList, (rule) => new ConstraintObjectUI(rule)), currentServiceName: this.currentServiceInstance.name, operatorTypes: this.operatorTypes, compositeServiceName: this.compositeService.name, @@ -268,40 +268,40 @@ export class ServiceDependenciesComponent { this.modalInstance.instance.open(); } - getDisabled = ():boolean => { + getDisabled = (): boolean => { return !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); - }; + } - createRule = ():void => { - let newRuleToCreate: ConstraintObject = new ConstraintObject(this.modalInstance.instance.dynamicContent.instance.currentRule); + createRule = (): void => { + const newRuleToCreate: ConstraintObject = new ConstraintObject(this.modalInstance.instance.dynamicContent.instance.currentRule); this.isLoading = true; - this.componentServiceNg2.createServiceFilterConstraints( - this.compositeService, - this.currentServiceInstance, + this.topologyTemplateService.createServiceFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, newRuleToCreate ).subscribe( (response) => { this.updateRulesListEvent.emit(response.properties); this.isLoading = false; - }, err=> { + }, (err) => { this.isLoading = false; }); - this.ModalServiceNg2.closeCurrentModal(); - }; + this.modalServiceNg2.closeCurrentModal(); + } - updateRules = ():void => { - let allRulesToUpdate: Array<ConstraintObject> = this.modalInstance.instance.dynamicContent.instance.serviceRulesList.map(rule => new ConstraintObject(rule)); + updateRules = (): void => { + const allRulesToUpdate: ConstraintObject[] = this.modalInstance.instance.dynamicContent.instance.serviceRulesList.map((rule) => new ConstraintObject(rule)); this.isLoading = true; - this.componentServiceNg2.updateServiceFilterConstraints( - this.compositeService, - this.currentServiceInstance, + this.topologyTemplateService.updateServiceFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, allRulesToUpdate ).subscribe((response) => { this.updateRulesListEvent.emit(response.properties); this.isLoading = false; - }, err => { + }, (err) => { this.isLoading = false; }); - this.ModalServiceNg2.closeCurrentModal(); + this.modalServiceNg2.closeCurrentModal(); } getSymbol(constraintOperator) { @@ -312,23 +312,23 @@ export class ServiceDependenciesComponent { } } - onDeleteRule = (index:number) => { + onDeleteRule = (index: number) => { this.isLoading = true; - this.componentServiceNg2.deleteServiceFilterConstraints( - this.compositeService, - this.currentServiceInstance, + this.topologyTemplateService.deleteServiceFilterConstraints( + this.compositeService.uniqueId, + this.currentServiceInstance.uniqueId, index ).subscribe( (response) => { this.updateRulesListEvent.emit(response.properties); this.isLoading = false; - }, err=> { + }, (err) => { this.isLoading = false; }); - this.ModalServiceNg2.closeCurrentModal(); - }; + this.modalServiceNg2.closeCurrentModal(); + } - openDeleteModal = (index:number) => { - this.ModalServiceNg2.createActionModal(I18nTexts.deleteRuleTxt, I18nTexts.deleteRuleMsg, + openDeleteModal = (index: number) => { + this.modalServiceNg2.createActionModal(I18nTexts.deleteRuleTxt, I18nTexts.deleteRuleMsg, I18nTexts.modalDelete, () => this.onDeleteRule(index), I18nTexts.modalCancel).instance.open(); } -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts index 7e66ed99c7..5e2d03d26c 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts +++ b/catalog-ui/src/app/ng2/components/logic/service-dependencies/service-dependencies.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {ServiceDependenciesComponent} from "./service-dependencies.component"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { ServiceDependenciesComponent } from './service-dependencies.component'; @NgModule({ declarations: [ @@ -14,11 +14,13 @@ import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; UiElementsModule, TranslateModule ], - exports: [], + exports: [ + ServiceDependenciesComponent + ], entryComponents: [ ServiceDependenciesComponent ], providers: [] }) export class ServiceDependenciesModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.ts b/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.ts deleted file mode 100644 index e09001fc6c..0000000000 --- a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.ts +++ /dev/null @@ -1,133 +0,0 @@ -import {Component, Input, KeyValueDiffer, IterableDiffers, KeyValueDiffers, DoCheck} from '@angular/core'; -import {Service} from "app/models/components/service"; -import {TranslateService} from "app/ng2/shared/translator/translate.service"; -import {ForwardingPath} from "app/models/forwarding-path"; -import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; - -@Component({ - selector: 'service-path-selector', - templateUrl: './service-path-selector.component.html', - styleUrls:['service-path-selector.component.less'], - providers: [TranslateService] -}) - -export class ServicePathSelectorComponent implements DoCheck { - - defaultSelectedId: string; - hideAllValue: string; - hideAllId: string = '0'; - showAllValue: string; - showAllId: string = '1'; - - paths: Array<ForwardingPath> = []; - dropdownOptions: Array<DropdownValue>; - differ: KeyValueDiffer; - - @Input() service: Service; - @Input() drawPath: Function; - @Input() deletePaths: Function; - @Input() selectedPathId: string; - - constructor(private differs: KeyValueDiffers, private translateService: TranslateService) { - - this.defaultSelectedId = this.hideAllId; - this.convertPathsToDropdownOptions(); - - this.translateService.languageChangedObservable.subscribe(lang => { - this.hideAllValue = this.translateService.translate("SERVICE_PATH_SELECTOR_HIDE_ALL_VALUE"); - this.showAllValue = this.translateService.translate("SERVICE_PATH_SELECTOR_SHOW_ALL_VALUE"); - this.convertPathsToDropdownOptions(); - }); - - } - - ngOnInit(): void { - - this.selectedPathId = this.defaultSelectedId; - this.differ = this.differs.find(this.service.forwardingPaths).create(null); - - } - - ngDoCheck(): void { - - const pathsChanged = this.differ.diff(this.service.forwardingPaths); - - if (pathsChanged) { - let oldPaths = _.cloneDeep(this.paths); - this.populatePathsFromService(); - - if (!(_.isEqual(oldPaths, this.paths))) { - this.convertPathsToDropdownOptions(); - - let temp = this.selectedPathId; - this.selectedPathId = '-1'; - - setTimeout(() => { - this.selectedPathId = temp; - this.onSelectPath(); - }, 0); - } - } - - } - - populatePathsFromService(): void { - - this.paths = []; - let {forwardingPaths} = this.service; - - _.forEach(forwardingPaths, path => { - this.paths.push(path); - }); - this.paths.sort((a:ForwardingPath, b:ForwardingPath)=> { - return a.name.localeCompare(b.name); - }); - - } - - convertPathsToDropdownOptions(): void { - - let result = [ - new DropdownValue(this.hideAllId, this.hideAllValue), - new DropdownValue(this.showAllId, this.showAllValue) - ]; - - _.forEach(this.paths, (value: ForwardingPath) => { - result[result.length] = new DropdownValue(value.uniqueId, value.name); - }); - - this.dropdownOptions = result; - - } - - onSelectPath = (): void => { - - if (this.selectedPathId !== '-1') { - this.deletePaths(); - - switch (this.selectedPathId) { - case this.hideAllId: - break; - - case this.showAllId: - _.forEach(this.paths, path => - this.drawPath(path) - ); - break; - - default: - let path = this.paths.find(path => - path.uniqueId === this.selectedPathId - ); - if (!path) { - this.selectedPathId = this.defaultSelectedId; - this.onSelectPath(); // currently does nothing in default case, but if one day it does, we want the selection to behave accordingly. - break; - } - this.drawPath(path); - break; - } - } - - } -} diff --git a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.html b/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.html deleted file mode 100644 index 2a6a72ab7a..0000000000 --- a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.html +++ /dev/null @@ -1,31 +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. - --> - -<div class='service-path'> - <button class='zoom-icons create-path-button' data-tests-id="pathsMenuBtn" (click)="showServicePathMenu = !showServicePathMenu">...</button> - <div class="service-path-menu" *ngIf="showServicePathMenu"> - <div > - <ul> - <li *ngIf='!isViewOnly'><div class="hand" (click)="onCreateServicePath()" data-tests-id="createPathMenuItem"> - Create Service Flow - </div></li> - <li><div class="hand" (click)="onListServicePath()" data-tests-id="pathsListMenuItem"> - Service Flows List - </div></li> - </ul> - </div> - </div> -</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.less b/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.less deleted file mode 100644 index 777b206714..0000000000 --- a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.less +++ /dev/null @@ -1,51 +0,0 @@ -//@import 'src/assets/styles/variables.less'; -@import './../../../../../assets/styles/variables.less'; -.service-path { - position: relative; - .create-path-button{ - &:extend(.search-bar-button); - width: 30px; - height: 30px; - &:hover { - color: @main_color_a; - } - &:active { - background: @main_color_a; - color: @main_color_p; - } - &:focus { - outline: none; - } - } - .service-path-menu { - border: 1px solid @main_color_o; - border-radius: 0 0 2px 2px; - border-top-color: @main_color_a; - border-top-width: 3px; - - box-sizing: border-box; - box-shadow: 0 2px 4px 0 rgba(0,0,0,0.30); - - background-color: @main_color_p; - - padding: 5px 0; - right: 34px; - position: absolute; - top: 10px; - width: 150px; - font-size: 13px; - font-family: @font-opensans-regular; - - li { - color: @main_color_m; - padding: 0 10px; - line-height: 20px; - &:hover { - cursor: pointer; - color: @main_color_a; - background-color: fade(@main_color_a, 5%); - } - } - - } -} diff --git a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.ts b/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.ts deleted file mode 100644 index d66c5f0132..0000000000 --- a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.component.ts +++ /dev/null @@ -1,84 +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 {Component, Input, ComponentRef} from '@angular/core'; -import {ModalService} from 'app/ng2/services/modal.service'; -import {ModalModel, ButtonModel} from 'app/models'; -import {ServicePathCreatorComponent} from 'app/ng2/pages/service-path-creator/service-path-creator.component'; -import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; -import ServicePathsListComponent from "app/ng2/pages/service-paths-list/service-paths-list.component"; -import {Service} from "app/models/components/service"; - -@Component({ - selector: 'service-path', - templateUrl: './service-path.component.html', - styleUrls: ['service-path.component.less'], - providers: [ModalService] -}) - -export class ServicePathComponent { - showServicePathMenu: boolean = false; - modalInstance: ComponentRef<ModalComponent>; - @Input() service: Service; - @Input() onCreate: Function; - @Input() onSave: Function; - @Input() isViewOnly:boolean; - - constructor(private ModalServiceNg2: ModalService) {} - - onCreateServicePath = ():void => { - this.showServicePathMenu = false; - let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.ModalServiceNg2.closeCurrentModal); - let saveButton: ButtonModel = new ButtonModel('Create', 'blue', this.createPath, this.getDisabled ); - let modalModel: ModalModel = new ModalModel('l', 'Create Service Flow', '', [saveButton, cancelButton], 'standard', true); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, {service: this.service}); - this.modalInstance.instance.open(); - }; - - onListServicePath = ():void => { - this.showServicePathMenu = false; - let cancelButton: ButtonModel = new ButtonModel('Close', 'outline white', this.ModalServiceNg2.closeCurrentModal); - let modalModel: ModalModel = new ModalModel('md', 'Service Flows List','', [cancelButton], 'standard', true); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal(this.modalInstance, ServicePathsListComponent, {service: this.service, - onCreateServicePath: this.onCreateServicePath, onEditServicePath: this.onEditServicePath, isViewOnly: this.isViewOnly}); - this.modalInstance.instance.open(); - }; - - createPath = ():void => { - this.onCreate(this.modalInstance.instance.dynamicContent.instance.createServicePathData()); - this.ModalServiceNg2.closeCurrentModal(); - }; - - onEditServicePath = (id:string):void => { - let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.ModalServiceNg2.closeCurrentModal); - let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createPath, this.getDisabled ); - let modalModel: ModalModel = new ModalModel('l', 'Edit Path', '', [saveButton, cancelButton], 'standard', true); - this.modalInstance = this.ModalServiceNg2.createCustomModal(modalModel); - this.ModalServiceNg2.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, {service: this.service, pathId: id}); - this.modalInstance.instance.open(); - }; - - getDisabled = ():boolean => { - return this.isViewOnly || !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); - }; -} - diff --git a/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.html b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.html new file mode 100644 index 0000000000..127531bfab --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.html @@ -0,0 +1,18 @@ +<form> + <div> + <div class="comment-modal-text" [innerHTML]="message"></div> + <sdc-textarea #comment1 + [(value)]="comment.text" + placeHolder="{{'CONFIRMATION_MODAL_PLACEHOLDER' | translate }}" + [required]="true" + name="comment1" + testId="checkindialog" + [maxLength]="256"> + </sdc-textarea> + <sdc-validation [validateElement]="comment1" (validityChanged)="onValidityChange($event)"> + <sdc-required-validator message="{{ 'VALIDATION_ERROR_REQUIRED' | translate:{'field': 'Comment' } }}"></sdc-required-validator> + <sdc-regex-validator message="{{ 'VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED' | translate}}" + [pattern]="commentValidationPattern"></sdc-regex-validator> + </sdc-validation> + </div> +</form> diff --git a/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.ts b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.ts new file mode 100644 index 0000000000..c66f60b5e7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.component.ts @@ -0,0 +1,25 @@ +/** + * Created by rc2122 on 5/31/2018. + */ +import { Component, Input } from "@angular/core"; +import { ValidationConfiguration } from "app/models"; +import { Subject } from "rxjs/Subject"; + +@Component({ + selector: 'comment-modal', + templateUrl: './comment-modal.component.html', + styleUrls: ['./comment-modal.less'] +}) + +export class CommentModalComponent { + + @Input() message:string; + onValidationChange: Subject<boolean> = new Subject(); + //@Input() showComment:boolean; + private comment = {"text": ''}; + private commentValidationPattern = ValidationConfiguration.validation.validationPatterns.comment; + + private onValidityChange = (isValid: boolean):void => { + this.onValidationChange.next(isValid); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.less b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.less new file mode 100644 index 0000000000..8e20e81115 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/comment-modal/comment-modal.less @@ -0,0 +1,3 @@ +.comment-modal-text { + padding-bottom: 5px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/modals/modals.module.ts b/catalog-ui/src/app/ng2/components/modals/modals.module.ts new file mode 100644 index 0000000000..5aa7f08b60 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/modals.module.ts @@ -0,0 +1,29 @@ +/** + * Created by rc2122 on 5/24/2018. + */ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { CommentModalComponent } from 'app/ng2/components/modals/comment-modal/comment-modal.component'; +import { PopoverModule } from 'app/ng2/components/ui/popover/popover.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { SdcUiComponentsModule } from 'onap-ui-angular'; +import { OnboardingService } from '../../services/onboarding.service'; +import { ImportVSPService } from './onboarding-modal/import-vsp.service'; +import { OnboardingModalComponent } from './onboarding-modal/onboarding-modal.component'; + +@NgModule({ + declarations: [CommentModalComponent, OnboardingModalComponent], + imports: [TranslateModule, + SdcUiComponentsModule, + CommonModule, + PopoverModule, + NgxDatatableModule], + exports: [CommentModalComponent, OnboardingModalComponent], + entryComponents: [CommentModalComponent, OnboardingModalComponent], + providers: [OnboardingService, ImportVSPService], + bootstrap: [] +}) + +export class ModalsModule { +} diff --git a/catalog-ui/src/app/ng2/components/modals/onboarding-modal/import-vsp.service.ts b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/import-vsp.service.ts new file mode 100644 index 0000000000..8e7364660f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/import-vsp.service.ts @@ -0,0 +1,36 @@ +import { Injectable, Inject } from "@angular/core"; +import { OnboardingModalComponent } from "./onboarding-modal.component"; +import { SdcUiServices, SdcUiCommon } from "onap-ui-angular"; +import { Observable, Subject } from "rxjs"; +import { CHANGE_COMPONENT_CSAR_VERSION_FLAG } from "../../../../utils/constants"; +import { CacheService } from "../../../services/cache.service"; + + +@Injectable() +export class ImportVSPService { + + constructor(private modalService: SdcUiServices.ModalService, + private cacheService:CacheService, + @Inject("$state") private $state:ng.ui.IStateService){ + + } + + openOnboardingModal(csarUUID?: string, csarVersion?: string): Observable<any> { + var subject = new Subject<any>(); + const onboardingModalConfig = { + size: SdcUiCommon.ModalSize.xlarge, + title: 'Import VSP', + type: SdcUiCommon.ModalType.custom, + testId: 'sampleTestIdModal1', + } as SdcUiCommon.IModalConfig; + const onboardingModalInstance = this.modalService.openCustomModal(onboardingModalConfig, OnboardingModalComponent, {currentCsarUUID: csarUUID, currentCsarVersion: csarVersion}); + onboardingModalInstance.innerModalContent.instance.closeModalEvent.subscribe( + (result: any) => { + subject.next(result); + onboardingModalInstance.closeModal(); + }, (err) =>{} + ) + return subject.asObservable(); + } +} + diff --git a/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.html b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.html new file mode 100644 index 0000000000..6ba1f428a1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.html @@ -0,0 +1,106 @@ +<div class="onboarding-modal"> + <div class="search-wrapper"> + <span class="sub-title-wrapper"> + <svg-icon class="info-button" + (click) = "openPopover($event, 'ON_BOARDING_GENERAL_INFO')" + [name]="'info-circle-o'" [mode]="'primary'" + [size]="'medium'"></svg-icon> + <span class="sub-title">{{ 'ON_BOARDING_MODAL_SUB_TITLE' | translate }}</span> + </span> + <span class="sdc-filter-bar-wrapper"> + <sdc-filter-bar + [placeHolder]="'Search'" + (keyup)="updateFilter($event)" + [testId]="'onboarding-search'"> + </sdc-filter-bar> + </span> + </div> + <div class="datatable-components-wapper"> + <ngx-datatable #componentsMetadataTable + columnMode="flex" + [headerHeight]="40" + [rowHeight]="35" + [rows]="componentsMetadataList" + [sorts]="[{prop: 'name', dir: 'asc'}]" + (select)='onSelectComponent($event)' + [selectionType]="'single'"> + <ngx-datatable-row-detail [rowHeight]="undefiend"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template > + <div class="onboarding-components-details"> + <span class="row-details-description"> + <div> + <div class="th">VSP Description:</div> + <div>{{row.description}}</div> + </div> + </span> + <span class="row-details-metadata1"> + <div *ngIf="isCsarComponentExists"> + <div class="th">VF'S Meta Data:</div> + <div><span class="th">Name:</span>{{componentFromServer.name}}</div> + <div><span class="th">Lifecycle:</span>{{componentFromServer.lifecycleState}}</div> + <div><span class="th">Creator:</span>{{componentFromServer.creatorFullName}}</div> + </div> + </span> + <span class="row-details-metadata2"> + <div *ngIf="isCsarComponentExists"> + <div class="th"> </div> + <div><span class="th">UUID:</span> {{componentFromServer.uuid}}</div> + <div><span class="th">Version:</span> {{componentFromServer.version}}</div> + <div><span class="th">Modifier:</span> {{componentFromServer.lastUpdaterFullName}}</div> + <div *ngIf="checkNotCertified()"> + <span class="note">Designers cannot update a VSP if the VF is <br>checked out by another user.</span> + </div> + </div> + </span> + <span class="row-details-metadata3"> + <svg-icon class="info-button" + (click) = "openPopover($event, isCsarComponentExists ? 'ON_BOARDING_UPDATE_INFO' : 'ON_BOARDING_IMPORT_INFO')" + [name]="'info-circle-o'" [mode]="'primary'" + [size]="'medium'"></svg-icon> + </span> + <span class="row-details-icon"> + <div> + <sdc-button class="import-update-file-btn" + [text]="isCsarComponentExists ? 'Update VSP' : 'Import VSP'" + [testId]="isCsarComponentExists ? 'update-csar' : 'import-csar'" + [type]="'primary'" + [icon_name]="isCsarComponentExists ? 'sync-o' : 'alert-triangle-o'" + [icon_position]="'left'" + [icon_mode] = "'white'" + [size] = "'medium'" + (click)="importOrUpdateCsar()" + [disabled]="checkNotCertified()" + > + </sdc-button> + <svg-icon class="download-file-btn" sdc-tooltip [tooltip-text]="'Download-csar'" + [mode]="'primary'" [clickable]="true" [name]="'download-o'" + [testId]="'download-csar'" [size]="'medium'" (click)="downloadCsar(row.packageId)"> + </svg-icon> + </div> + </span> + </div> + <sdc-loader [global]="false" [active]="isLoad" [size]="'small'"[relative]="true"></sdc-loader> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column *ngFor="let column of columns" [ngSwitch]="column.prop" [resizeable]="false" [draggable]="false" name={{column.name}} + [flexGrow]="column.flexGrow"> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchCase="'name'"> + <span data-tests-id="csar-row" class="sprite table-arrow" [ngClass]="{'opened': selectedComponent && row.packageId === selectedComponent.packageId}"></span> + {{row[column.prop]}} + </ng-template> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchCase="'categories'"> + <span *ngIf="row[column.prop][0]"> + {{row[column.prop][0].name}} + <span *ngIf="row[column.prop][0].subcategories[0]">{{row[column.prop][0].subcategories[0].name}}</span> + </span> + </ng-template> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchDefault> + {{row[column.prop]}} + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div> +</div> + + + diff --git a/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.less b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.less new file mode 100644 index 0000000000..2e4abda35e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.less @@ -0,0 +1,97 @@ +@import "../../../../../assets/styles/variables"; +@import "../../../../../assets/styles/mixins"; + + +.onboarding-components-details{ + display: flex; +} +.row-details-description, +.row-details-metadata1, +.row-details-metadata2, +.row-details-metadata3{ + .th { .m_14_m; } + flex-basis: 0; + overflow: hidden; + padding: 5px 15px; + white-space: normal; +} +.row-details-description, +.row-details-metadata3 { + border-right: 1px solid @main_color_o; +} + +.row-details-icon { + flex-basis: 0; + overflow: hidden; + padding: 5px 10px; + align-self: center; +} + +.row-details-description { + flex-grow: 19; +} +.row-details-metadata1 { + flex-grow: 26.5; +} +.row-details-metadata2 { + flex-grow: 35; + .note { + color: @func_color_q; + } +} +.row-details-metadata3 { + flex-grow: 8; +} +.info-button{ + cursor: pointer; + float: right; +} +.row-details-icon { + flex-grow: 18; +} +.download-file-btn { + cursor: pointer; + margin-left: 6px; +} + +.import-update-file-btn { + cursor: pointer; +} +.sprite.table-arrow{ + margin-right: 7px; +} +.search-wrapper { + .sdc-filter-bar-wrapper { + flex: 0 0 30%; + } + .sub-title-wrapper { + flex: 0 0 70%; + font-size: 15px; + line-height: 35px; + font-family: OpenSans-Regular, sans-serif; + align-items: center; + display: inline-flex; + .sub-title{ + padding-left: 5px; + } + } + display: flex; + margin-top: 15px; + margin-bottom: 10px; + } + + :host ::ng-deep { + .datatable-row-detail{ + width: 1120px; + } + .datatable-body-row { + cursor: pointer; + } + } + + + + + + + diff --git a/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.spec.ts b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.spec.ts new file mode 100644 index 0000000000..565398b6ad --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.spec.ts @@ -0,0 +1,122 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import { NO_ERRORS_SCHEMA} from "@angular/core"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; + +import {Observable} from "rxjs/Observable"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {SdcUiServices, SdcUiCommon} from "onap-ui-angular"; +import 'rxjs/add/observable/of'; +import {OnboardingService} from "../../../services/onboarding.service"; +import {TranslateService} from "../../../shared/translator/translate.service"; +import {CacheService} from "../../../services/cache.service"; +import {FileUtilsService} from "../../../services/file-utils.service"; +import {onboardingModalVSPMock, onboardingModalUniqueVSPMock, vspFromServerMock} from "../../../../../jest/mocks/onboarding-vsp.mock"; +import {OnboardingModalComponent} from "./onboarding-modal.component"; +import {TranslatePipe} from "../../../shared/translator/translate.pipe"; + +describe('onboarding modal component', () => { + + let fixture: ComponentFixture<OnboardingModalComponent>; + let onboardingServiceMock: Partial<OnboardingService>; + let translateServiceMock: Partial<TranslateService>; + let cacheServiceMock: Partial<CacheService>; + let fileUtilsServiceMock: Partial<FileUtilsService>; + let popoverServiceMock: Partial<SdcUiServices.PopoverService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + + beforeEach( + async(() => { + + onboardingServiceMock = { + getOnboardingComponents: jest.fn().mockImplementation(()=> Observable.of(onboardingModalUniqueVSPMock)), + getComponentFromCsarUuid: jest.fn().mockImplementation(()=> Observable.of(vspFromServerMock)) + }; + + cacheServiceMock = { + set: jest.fn() + }; + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + } + + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [OnboardingModalComponent, TranslatePipe], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: OnboardingService, useValue: onboardingServiceMock }, + { provide: TranslateService, useValue: translateServiceMock }, + { provide: CacheService, useValue: cacheServiceMock }, + { provide: FileUtilsService, useValue: fileUtilsServiceMock }, + { provide: SdcUiServices.PopoverService, useValue: popoverServiceMock }, + { provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + }; + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(OnboardingModalComponent); + }); + }) + ); + + /*it('should match current snapshot of onboarding modal component', () => { + expect(fixture).toMatchSnapshot(); + });*/ + + it('should see exactly 2 vsp in onboarding modal and call initOnboardingComponentsList', () => { + fixture.componentInstance.initOnboardingComponentsList(); + expect(fixture.componentInstance.componentsMetadataList.length).toBe(2); + }); + + it('should see exactly 1 vsp in onboarding modal and call initOnboardingComponentsList', () => { + fixture.componentInstance.currentCsarUUID = "6348841e79a64871ba064ce340a968a4"; + fixture.componentInstance.initOnboardingComponentsList(); + expect(fixture.componentInstance.componentsMetadataList.length).toBe(1); + }); + + it('when get a list of vsp initMaxVersionOfItemsInList will return a list with unique items with the latest versions for each packageId', () => { + onboardingServiceMock.getOnboardingComponents = jest.fn().mockImplementation(() => Observable.of(onboardingModalVSPMock)); + fixture.componentInstance.initOnboardingComponentsList(); + expect(fixture.componentInstance.componentsMetadataList.length).toBe(2); + }); + + it('should filter out 1 vsp when searching and call updateFilter function', () => { + fixture.componentInstance.initOnboardingComponentsList(); + let event = { + target : { + value : 'test new vsp' + } + } + + expect(fixture.componentInstance.componentsMetadataList.length).toBe(2); + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.componentsMetadataList.length).toBe(1); + }); + + it('When select the selected vsp the row details closed and call onSelectComponent function', () => { + fixture.componentInstance.initOnboardingComponentsList(); + fixture.componentInstance.onSelectComponent({selected: []}); + expect(fixture.componentInstance.selectedComponent).toEqual(undefined); + expect(fixture.componentInstance.componentFromServer).toEqual(undefined); + }); + + it('When select vsp a row with its details will be opened and call onSelectComponent function', () => { + fixture.componentInstance.initOnboardingComponentsList(); + fixture.componentInstance.onSelectComponent({selected: onboardingModalVSPMock}); + expect(fixture.componentInstance.selectedComponent).not.toEqual(null); + expect(fixture.componentInstance.componentFromServer).not.toEqual(undefined); + expect(fixture.componentInstance.isCsarComponentExists).toEqual(true); + }); + it('When select new vsp a row with import and download buttons will be opened and call onSelectComponent function', () => { + fixture.componentInstance.initOnboardingComponentsList(); + onboardingServiceMock.getComponentFromCsarUuid.mockImplementation(() => Observable.of(undefined)); + fixture.componentInstance.onSelectComponent({selected: onboardingModalVSPMock}); + expect(fixture.componentInstance.selectedComponent).not.toEqual(null); + expect(fixture.componentInstance.componentFromServer).toEqual(undefined); + expect(fixture.componentInstance.isCsarComponentExists).toEqual(false); + }); +}); diff --git a/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.ts b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.ts new file mode 100644 index 0000000000..2e41716e0b --- /dev/null +++ b/catalog-ui/src/app/ng2/components/modals/onboarding-modal/onboarding-modal.component.ts @@ -0,0 +1,193 @@ +/** + * Created by rc2122 on 6/3/2018. + */ +import { Component, EventEmitter, Input, OnInit, Output, ViewChild } from '@angular/core'; +import * as _ from 'lodash'; +import { SdcUiServices } from 'onap-ui-angular'; +import { ComponentMetadata, IComponentMetadata } from '../../../../models/component-metadata'; +import { IUserProperties } from '../../../../models/user'; + +import { Resource } from '../../../../models/components/resource'; +import { ComponentType } from '../../../../utils/constants'; +import { CacheService } from '../../../services/cache.service'; +import { FileUtilsService } from '../../../services/file-utils.service'; +import { OnboardingService } from '../../../services/onboarding.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; + +export interface ImportVSPdata { + componentCsar: Resource; + previousComponent?: Resource; + type: string; +} + +// tslint:disable-next-line:interface-name +export interface IPoint { + x: number; + y: number; +} + +@Component({ + selector: 'onboarding-modal', + templateUrl: './onboarding-modal.component.html', + styleUrls: ['onboarding-modal.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class OnboardingModalComponent implements OnInit { + @Input() currentCsarUUID: string; + @Input() currentCsarVersion: string; + @ViewChild('componentsMetadataTable') table: any; + @Output() closeModalEvent: EventEmitter<ImportVSPdata> = new EventEmitter<ImportVSPdata>(); + + private columns = [ + {name: 'Name', prop: 'name', flexGrow: 22}, + {name: 'Vendor', prop: 'vendorName', flexGrow: 26}, + {name: 'Category', prop: 'categories', flexGrow: 33}, + {name: 'Version', prop: 'csarVersion', flexGrow: 10}, + {name: 'Type', prop: 'resourceType', flexGrow: 10}, + {name: '#', prop: '', flexGrow: 20} + ]; + private componentsMetadataList: IComponentMetadata[] = []; + private temp: IComponentMetadata[] = []; + private componentFromServer: ComponentMetadata; + private isCsarComponentExists: boolean = false; + private selectedComponent: ComponentMetadata; + private isLoading: boolean; + private user: IUserProperties; + + constructor(private onBoardingService: OnboardingService, + private translateService: TranslateService, + private cacheService: CacheService, + private fileUtilsService: FileUtilsService, + private popoverService: SdcUiServices.PopoverService, + private loaderService: SdcUiServices.LoaderService) { + } + + public ngOnInit(): void { + this.initOnboardingComponentsList(); + this.user = this.cacheService.get('user'); + } + + initMaxVersionOfItemsInList = (onboardingResponse: IComponentMetadata[]): void => { + // Get only the latest version of each item + this.componentsMetadataList = []; + + // group all items according to packageId + const groupByPackageIdItems = _.groupBy(onboardingResponse, 'packageId'); + // Loop on all the groups and push to componentsMetadataList the max version for each package + _.each(groupByPackageIdItems, (items: any): void => { + let maxItem: any = items[0]; + items.forEach((item) => { + if (parseFloat(maxItem.csarVersion) < parseFloat(item.csarVersion)) { + maxItem = item; + } + }); + if (maxItem) { + this.componentsMetadataList.push(maxItem); + } + }); + } + + onSelectComponent({selected}) { + this.table.rowDetail.collapseAllRows(); + if (selected[0] === this.selectedComponent) { + this.selectedComponent = undefined; + this.componentFromServer = undefined; + this.table.rowDetail.toggleExpandRow(null); + return; + } + this.isLoading = true; + this.componentFromServer = undefined; + this.selectedComponent = selected[0]; + this.onBoardingService.getComponentFromCsarUuid(this.selectedComponent.csarUUID).subscribe( + (componentFromServer: ComponentMetadata) => { + this.isLoading = false; + if (componentFromServer) { + this.componentFromServer = componentFromServer; + this.populateRowDetails(true); + } else { + this.populateRowDetails(false); + } + }, (error) => { + this.isLoading = false; + this.populateRowDetails(false); + }); + } + + populateRowDetails(isCsarComponentExists: boolean) { + this.isCsarComponentExists = isCsarComponentExists; + this.table.rowDetail.toggleExpandRow(this.selectedComponent); + } + + importOrUpdateCsar = (): void => { + const selectedComponentConverted = this.onBoardingService.convertMetaDataToComponent(this.selectedComponent); + const componentFromServerConverted = this.componentFromServer ? + this.onBoardingService.convertMetaDataToComponent(this.componentFromServer) : undefined; + const importVSPdata: ImportVSPdata = { + componentCsar: selectedComponentConverted, + previousComponent: componentFromServerConverted, + type: ComponentType.RESOURCE.toLowerCase() + }; + this.closeModalEvent.emit(importVSPdata); + } + + downloadCsar = (packageId: string): void => { + this.isLoading = true; + this.onBoardingService.downloadOnboardingCsar(packageId).subscribe( + (file: any): void => { + this.isLoading = false; + if (file.body) { + this.fileUtilsService.downloadFile(file.body, packageId + '.csar'); + } + }, (): void => { + this.isLoading = false; + } + ); + } + + updateFilter(event) { + const val = event.target.value.toLowerCase(); + + // filter our data + const temp = this.temp.filter((componentMetadata: ComponentMetadata) => { + return !val || + (componentMetadata.name && componentMetadata.name.toLowerCase().indexOf(val) !== -1) || + (componentMetadata.vendorName && componentMetadata.vendorName.toLowerCase().indexOf(val) !== -1) || + (componentMetadata.categories[0] && componentMetadata.categories[0].name.toLowerCase().indexOf(val) !== -1) || + (componentMetadata.categories[0] && componentMetadata.categories[0].subcategories[0] && componentMetadata.categories[0].subcategories[0].name.toLowerCase().indexOf(val) !== -1) || + (componentMetadata.csarVersion && componentMetadata.csarVersion.toLowerCase().indexOf(val) !== -1) || + (componentMetadata.description && componentMetadata.description.toLowerCase().indexOf(val) !== -1); + }); + + // update the rows + this.componentsMetadataList = temp; + } + + checkNotCertified = (): boolean => { + return this.componentFromServer && this.componentFromServer.lifecycleState === 'NOT_CERTIFIED_CHECKOUT' && + this.componentFromServer.lastUpdaterUserId !== this.user.userId; + } + + openPopover = ($event: any, popoverContent): void => { + this.popoverService.createPopOver('', this.translateService.translate(popoverContent), { + x: $event.pageX, + y: $event.pageY + }, 'bottom'); + } + + private initOnboardingComponentsList = (): void => { + this.loaderService.activate(); + this.onBoardingService.getOnboardingComponents().subscribe( + (onboardingResponse: IComponentMetadata[]) => { + this.loaderService.deactivate(); + if (this.currentCsarUUID) { + onboardingResponse = _.filter(onboardingResponse, (input): boolean => { + return (input as ComponentMetadata).csarUUID === this.currentCsarUUID; + }); + } + this.initMaxVersionOfItemsInList(onboardingResponse); + this.temp = [...this.componentsMetadataList]; + }, (error) => { + this.loaderService.deactivate(); + } + ); + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/download-artifact/download-artifact.component.ts b/catalog-ui/src/app/ng2/components/ui/download-artifact/download-artifact.component.ts new file mode 100644 index 0000000000..8f47456a8f --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/download-artifact/download-artifact.component.ts @@ -0,0 +1,145 @@ +import { Component, Input } from "@angular/core"; +import {IFileDownload, Component as TopologyTemplate, ArtifactModel, FullComponentInstance} from "app/models"; +import {EventListenerService} from "app/services"; +import {CacheService} from "app/services-ng2"; +import {EVENTS} from "app/utils"; +import { TopologyTemplateService } from "app/ng2/services/component-services/topology-template.service"; +import { WorkspaceService } from "app/ng2/pages/workspace/workspace.service"; +import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service"; + +@Component({ + selector: 'download-artifact', + template: ` + <svg-icon [mode]="'primary2'" [disabled]="disabled" [clickable]="!disabled" [name]="iconType" [testId]="testId" mode="info" clickable="true" size="medium" (click)="download($event)"></svg-icon> +` +}) +export class DownloadArtifactComponent { + + @Input() showLoader:boolean; + @Input() artifact:ArtifactModel; + @Input() isInstance: boolean; + @Input() downloadIconClass: string; + @Input() componentType: string; + @Input() componentId: string; + @Input() testId: string; + @Input() disabled: boolean; + + public iconType:string; + + private DOWNLOAD_CSS_CLASSES = { + DOWNLOAD_ICON: "download-o", + LOADER_ICON: "spinner" + } + constructor(private cacheService:CacheService, private EventListenerService:EventListenerService, private topologyTemplateService:TopologyTemplateService, + private componentInstanceService: ComponentInstanceServiceNg2, private workspaceService:WorkspaceService) { + + } + + ngOnInit () { + this.iconType = this.DOWNLOAD_CSS_CLASSES.DOWNLOAD_ICON; + this.initDownloadLoader(); + + } + + private initDownloadLoader = ()=> { + //if the artifact is in a middle of download progress register form callBack & change icon from download to loader + if (this.showLoader && this.cacheService.get(this.artifact.uniqueId)) { + this.EventListenerService.registerObserverCallback(EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + this.artifact.uniqueId, this.updateDownloadIcon); + window.setTimeout(():void => { + if (this.cacheService.get(this.artifact.uniqueId)) { + this.iconType = this.DOWNLOAD_CSS_CLASSES.LOADER_ICON; + } + }, 1000); + } + }; + + private updateDownloadIcon = () => { + this.iconType = this.downloadIconClass || this.DOWNLOAD_CSS_CLASSES.DOWNLOAD_ICON; + }; + + public download = (event) => { + event.stopPropagation(); + let onFaild = (response):void => { + console.info('onFaild', response); + this.removeDownloadedFileLoader(); + }; + + let onSuccess = (data:IFileDownload):void => { + this.downloadFile(data); + this.removeDownloadedFileLoader(); + }; + + this.setDownloadedFileLoader(); + + if (this.isInstance) { + this.componentInstanceService.downloadInstanceArtifact(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, this.componentId, this.artifact.uniqueId).subscribe(onSuccess, onFaild); + } else { + this.topologyTemplateService.downloadArtifact(this.componentType, this.componentId, this.artifact.uniqueId).subscribe(onSuccess, onFaild); + } + }; + + private setDownloadedFileLoader = ()=> { + if (this.showLoader) { + //set in cache service thet the artifact is in download progress + this.cacheService.set(this.artifact.uniqueId, true); + this.initDownloadLoader(); + } + }; + + private removeDownloadedFileLoader = ()=> { + if (this.showLoader) { + this.cacheService.set(this.artifact.uniqueId, false); + this.EventListenerService.notifyObservers(EVENTS.DOWNLOAD_ARTIFACT_FINISH_EVENT + this.artifact.uniqueId); + } + }; + + private downloadFile = (file:IFileDownload):void => { + if (file) { + let blob = this.base64toBlob(file.base64Contents, ''); + let fileName = file.artifactName; + this.triggerFileDownload(blob, fileName); + } + }; + + public base64toBlob = (base64Data, contentType):any => { + let byteCharacters = atob(base64Data); + return this.byteCharactersToBlob(byteCharacters, contentType); + }; + + public byteCharactersToBlob = (byteCharacters, contentType):any => { + contentType = contentType || ''; + let sliceSize = 1024; + let bytesLength = byteCharacters.length; + let slicesCount = Math.ceil(bytesLength / sliceSize); + let byteArrays = new Array(slicesCount); + + for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { + let begin = sliceIndex * sliceSize; + let end = Math.min(begin + sliceSize, bytesLength); + + let bytes = new Array(end - begin); + for (let offset = begin, i = 0; offset < end; ++i, ++offset) { + bytes[i] = byteCharacters[offset].charCodeAt(0); + } + byteArrays[sliceIndex] = new Uint8Array(bytes); + } + return new Blob(byteArrays, {type: contentType}); + }; + + public triggerFileDownload = (blob, fileName):void=> { + let url = window.URL.createObjectURL(blob); + let downloadLink = document.createElement("a"); + + downloadLink.setAttribute('href', url); + downloadLink.setAttribute('download', fileName); + document.body.appendChild(downloadLink); + + var clickEvent = new MouseEvent("click", { + "view": window, + "bubbles": true, + "cancelable": true + }); + downloadLink.dispatchEvent(clickEvent); + + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.component.ts b/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.component.ts index 049d40831a..5e3214d888 100644 --- a/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.component.ts @@ -37,11 +37,13 @@ enum DynamicElementComponentCreatorIdentifier { BOOLEAN, SUBNETPOOLID, ENUM, + LIST, DEFAULT } @Component({ selector: 'dynamic-element', + // Span - if constraints not empty template: `<div #target></div>`, styleUrls: ['./dynamic-element.component.less'], entryComponents: [ @@ -61,6 +63,7 @@ export class DynamicElementComponent { @Input() readonly:boolean; @Input() constraints: Array<any>; @Input() path:string;//optional param. used only for for subnetpoolid type + @Input() declared:boolean; @Input() value: any; @Output() valueChange: EventEmitter<any> = new EventEmitter<any>(); @@ -72,16 +75,19 @@ export class DynamicElementComponent { validation = ValidationConfiguration.validation; constructor( + private componentFactoryResolver: ComponentFactoryResolver, private compiler: Compiler, private el: ElementRef) { + + } updateComponent() { if (!this.isViewInitialized) { return; } - + // Factory to create component based on type or other property attributes. const prevElementCreatorIdentifier: DynamicElementComponentCreatorIdentifier = this.elementCreatorIdentifier; switch(true) { @@ -112,7 +118,7 @@ export class DynamicElementComponent { } // In case the dynamic element creator is changed, then destroy old and build new. - if (this.elementCreatorIdentifier !== prevElementCreatorIdentifier) { + if (this.declared || this.elementCreatorIdentifier !== prevElementCreatorIdentifier) { if (this.cmpRef) { this.cmpRef.destroy(); } @@ -149,64 +155,81 @@ export class DynamicElementComponent { } createComponentByIdentifier() { - switch(this.elementCreatorIdentifier) { - case DynamicElementComponentCreatorIdentifier.SUBNETPOOLID: - if(this.name.toUpperCase().indexOf("SUBNETPOOLID") == -1){//if it's an item of subnetpoolid list get the parent name - let pathArray = this.path.split("#"); - this.name = pathArray[pathArray.length - 2]; - } - this.createComponent(UiElementPopoverInputComponent); - break; + // if(!this.constraints || this.declared){ + switch(this.elementCreatorIdentifier) { + case DynamicElementComponentCreatorIdentifier.SUBNETPOOLID: + if(this.name.toUpperCase().indexOf("SUBNETPOOLID") == -1){//if it's an item of subnetpoolid list get the parent name + let pathArray = this.path.split("#"); + this.name = pathArray[pathArray.length - 2]; + } + this.createComponent(UiElementPopoverInputComponent); + break; + case DynamicElementComponentCreatorIdentifier.ENUM: + this.createComponent(UiElementDropDownComponent); + let validVals:Array<DropdownValue> = [...this.getValidValues()].map(val => new DropdownValue(val, val)); + if (this.type === 'float' || this.type === 'integer') { + this.value = this.value && Number(this.value); + validVals = _.map( + validVals, + (val) => new DropdownValue(Number(val.value), val.value) + ); + } + this.cmpRef.instance.values = validVals; + break; + case DynamicElementComponentCreatorIdentifier.INTEGER: + this.createComponent(UiElementIntegerInputComponent); + this.cmpRef.instance.pattern = this.validation.validationPatterns.integer; + break; - case DynamicElementComponentCreatorIdentifier.ENUM: - this.createComponent(UiElementDropDownComponent); - let validVals:Array<DropdownValue> = [...this.getValidValues()].map(val => new DropdownValue(val, val)); - if (this.type === 'float' || this.type === 'integer') { - this.value = this.value && Number(this.value); - validVals = _.map( - validVals, - val => new DropdownValue(Number(val.value), val.value) - ); - } - this.cmpRef.instance.values = validVals; - break; + case DynamicElementComponentCreatorIdentifier.FLOAT: + this.createComponent(UiElementIntegerInputComponent); + this.cmpRef.instance.pattern = /^[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/.source; + break; - case DynamicElementComponentCreatorIdentifier.INTEGER: - this.createComponent(UiElementIntegerInputComponent); - this.cmpRef.instance.pattern = this.validation.validationPatterns.integer; - break; + case DynamicElementComponentCreatorIdentifier.STRING: + this.createComponent(UiElementInputComponent); + break; - case DynamicElementComponentCreatorIdentifier.FLOAT: - this.createComponent(UiElementIntegerInputComponent); - this.cmpRef.instance.pattern = /^[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/.source; - break; + case DynamicElementComponentCreatorIdentifier.BOOLEAN: + this.createComponent(UiElementDropDownComponent); - case DynamicElementComponentCreatorIdentifier.STRING: - this.createComponent(UiElementInputComponent); - break; - - case DynamicElementComponentCreatorIdentifier.BOOLEAN: - this.createComponent(UiElementDropDownComponent); - - // Build drop down values - let tmp = []; - tmp.push(new DropdownValue(true,'TRUE')); - tmp.push(new DropdownValue(false,'FALSE')); - this.cmpRef.instance.values = tmp; - try { - if (typeof this.value === 'string') { - this.value = JSON.parse(this.value); + // Build drop down values + let tmp = []; + tmp.push(new DropdownValue(true,'TRUE')); + tmp.push(new DropdownValue(false,'FALSE')); + this.cmpRef.instance.values = tmp; + try { + if(typeof this.value === 'string'){ + this.value = JSON.parse(this.value); + } + } catch (err) { + this.value = null; } - } catch(err) { - this.value = null; - } - break; + break; - case DynamicElementComponentCreatorIdentifier.DEFAULT: - default: - this.createComponent(UiElementInputComponent); - console.log("ERROR: No ui-models component to handle type: " + this.type); - } + case DynamicElementComponentCreatorIdentifier.DEFAULT: + default: + this.createComponent(UiElementInputComponent); + console.log("ERROR: No ui-models component to handle type: " + this.type); + } + // } + // //There are consraints + // else { + + // this.createComponent(UiElementDropDownComponent); + + // // Build drop down values + // let items = []; + // this.constraints.forEach( (element) => { + // items.push(new DropdownValue(element,element)); + // }); + + // items.push(new DropdownValue(this.value,this.value, true, true)); + // this.cmpRef.instance.values = items; + + + + // } // Subscribe to change event of of ui-models-element-basic and fire event to change the value this.cmpRef.instance.baseEmitter.subscribe((event) => { this.emitter.emit(event); }); diff --git a/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.module.ts b/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.module.ts index 50b22505a9..dc12551e8f 100644 --- a/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/dynamic-element/dynamic-element.module.ts @@ -23,6 +23,12 @@ import {PopoverModule} from "../popover/popover.module"; import {TooltipModule} from "../tooltip/tooltip.module"; import {DynamicElementComponent} from "./dynamic-element.component"; import {FormElementsModule} from "../form-components/form-elements.module"; +// import {SdcUiComponentsModule} from "sdc-ui/lib/angular"; +// import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; +// import {SdcUiComponentsModule} from "onap-ui/lib/angular"; +import { SdcUiComponentsModule } from "onap-ui-angular"; + +import { CommonModule } from '@angular/common'; @NgModule({ declarations: [ @@ -31,7 +37,9 @@ import {FormElementsModule} from "../form-components/form-elements.module"; imports: [ PopoverModule, TooltipModule, - FormElementsModule + FormElementsModule, + SdcUiComponentsModule, + CommonModule ], exports: [ DynamicElementComponent diff --git a/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.html b/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.html index fde7bca6d2..9b678b2790 100644 --- a/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.html +++ b/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.html @@ -14,16 +14,15 @@ ~ limitations under the License. --> -<h1 class="w-sdc-designer-sidebar-section-title" - tooltip="{{titleTooltip}}" - [ngClass]="{'expanded': state == 0, 'collapsed': state == 1}" - (click)="toggleState()"> - {{caption}}<span class="w-sdc-designer-sidebar-section-title-icon"></span> - <ng-content select="header"></ng-content> - <span class="w-sdc-designer-sidebar-section-title-icon"></span> -</h1> +<div class="expand-collapse-container"> + <h1 class="expand-collapse-title" [ngClass]="{'expanded': state == 0, 'collapsed': state == 1}" + tooltip="{{titleTooltip}}" (click)="toggleState()">{{caption}} + <ng-content select="header"></ng-content> + <svg-icon name="caret1-down-o" mode="info" size="small" class="expand-collapse-title-icon"></svg-icon> + </h1> -<div class="expand-collapse-content" [ngClass]="{'visible': state === 0, 'hidden': state === 1}"> - <ng-content></ng-content> - <ng-content select="content"></ng-content> -</div> + <div class="expand-collapse-content" [ngClass]="{'expanded': state === 0, 'collapsed': state === 1}"> + <ng-content></ng-content> + <ng-content select="content"></ng-content> + </div> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.less b/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.less index e5dd2527c1..deda687a09 100644 --- a/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.less +++ b/catalog-ui/src/app/ng2/components/ui/expand-collapse/expand-collapse.component.less @@ -5,3 +5,51 @@ text-decoration: underline; text-align: left; } + + +.expand-collapse-title { + display:flex; + align-items: center; + cursor: pointer; + font-size: 14px; + text-transform: uppercase; + line-height: 32px; + padding: 0 10px 0 20px; + + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + header { + flex: 1; + } + + .expand-collapse-title-icon { + padding-top: 10px; + padding-left: 2px; + transition: transform .3s ease-out; + + } + + &.expanded .expand-collapse-title-icon { + transform: rotate(0); + } + + &.collapsed .expand-collapse-title-icon { + transform: rotate(-180deg); + } + +} + +.expand-collapse-content { + overflow: hidden; + transition: max-height .3s ease-in; + + &.collapsed { + max-height: 0px; + overflow:hidden; + transition: all .3s cubic-bezier(0, 1, 0, 1); + + } + +} diff --git a/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.html b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.html new file mode 100644 index 0000000000..14fc6eefc7 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.html @@ -0,0 +1,6 @@ +<label> + <input type="file" class="i-sdc-dashboard-item-upload-input" + (change)="onFileSelect($event)" + [accept]="extensionsWithDot" + [attr.data-tests-id]="'file-' + testsId" /> +</label> diff --git a/catalog-ui/src/app/ng2/app.component.css b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.less index e69de29bb2..e69de29bb2 100644 --- a/catalog-ui/src/app/ng2/app.component.css +++ b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.less diff --git a/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.ts b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.ts new file mode 100644 index 0000000000..1d1d4cef0d --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/file-opener/file-opener.component.ts @@ -0,0 +1,45 @@ +import {Component, Input, Output, EventEmitter, SimpleChanges} from "@angular/core"; + +@Component({ + selector: 'file-opener', + templateUrl: './file-opener.component.html', + styleUrls: ['./file-opener.component.less'] +}) +export class FileOpenerComponent { + @Input() public testsId: string; + @Input() public extensions: string; + @Output() public onFileUpload: EventEmitter<any>; + + public extensionsWithDot: string; + + constructor() { + this.onFileUpload = new EventEmitter<any>(); + } + + public ngOnChanges(changes:SimpleChanges) { + if (changes.extensions) { + this.extensionsWithDot = this.getExtensionsWithDot(changes.extensions.currentValue); + } + } + + public onFileSelect(event) { + const importFile:any = event.target.files[0]; + const reader = new FileReader(); + reader.readAsBinaryString(importFile); + reader.onload = () => { + this.onFileUpload.emit({ + filename: importFile.name, + filetype: importFile.type, + filesize: importFile.size, + base64: btoa(reader.result) + }); + }; + } + + public getExtensionsWithDot(extensions:string):string { + extensions = extensions || this.extensions || ''; + return extensions.split(',') + .map(ext => '.' + ext.toString()) + .join(','); + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/form-components/checkbox/checkbox.component.ts b/catalog-ui/src/app/ng2/components/ui/form-components/checkbox/checkbox.component.ts index c8da016174..c7b0af6ecf 100644 --- a/catalog-ui/src/app/ng2/components/ui/form-components/checkbox/checkbox.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/form-components/checkbox/checkbox.component.ts @@ -19,7 +19,7 @@ */ import { Component, Input, Output, EventEmitter, ViewEncapsulation } from '@angular/core'; -//import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; +//import { trigger, state, style, transition, animate, keyframes } from '@angular/animations'; @Component({ selector: 'checkbox', diff --git a/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.html b/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.html index 45fd77ee61..9dca01eb61 100644 --- a/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.html +++ b/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.html @@ -15,5 +15,5 @@ --> <select name='{{name}}' [(ngModel)]="value" (change)="onChange()" [ngClass]="{'disabled':readonly}" [attr.data-tests-id]="'value-' + testId"> - <option *ngFor="let ddvalue of values" [ngValue]="ddvalue.label != undefined ? ddvalue.value : ddvalue">{{ddvalue.label||ddvalue}}</option> + <option *ngFor="let ddvalue of values" [ngValue]="ddvalue.label != undefined ? ddvalue.value : ddvalue" [hidden]="ddvalue.hidden" [selected] = "ddvalue.selected">{{ddvalue.label||ddvalue}}</option> </select> diff --git a/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.ts b/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.ts index 03a1fc6040..6db233529b 100644 --- a/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component.ts @@ -24,11 +24,21 @@ import { UiElementBase, UiElementBaseInterface } from './../ui-element-base.comp export class DropdownValue { value:any; label:string; + hidden?: boolean; + selected?: boolean; - constructor(value:any,label:string) { + + constructor(value:any,label:string, hidden: boolean = false, selected: boolean = false) { this.value = value; this.label = label; + this.hidden = hidden; + this.selected = selected; + + // this.hidden = hidden ? true : "hidden": ""; + // this.selected = selected ? true : "selected": ""; } + + } @Component({ @@ -37,8 +47,8 @@ export class DropdownValue { styleUrls: ['./ui-element-dropdown.component.less'], }) export class UiElementDropDownComponent extends UiElementBase implements UiElementBaseInterface { - @Input() - values: DropdownValue[]|string[]; + + @Input() values: DropdownValue[]; constructor() { super(); diff --git a/catalog-ui/src/app/ng2/components/ui/form-components/form-elements.module.ts b/catalog-ui/src/app/ng2/components/ui/form-components/form-elements.module.ts index e5bdf1f557..b35d3ae3f4 100644 --- a/catalog-ui/src/app/ng2/components/ui/form-components/form-elements.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/form-components/form-elements.module.ts @@ -1,19 +1,19 @@ /** * Created by rc2122 on 9/5/2017. */ -import {NgModule} from "@angular/core"; -import {BrowserModule} from "@angular/platform-browser"; -import {FormsModule, ReactiveFormsModule} from "@angular/forms"; -import {UiElementPopoverInputComponent} from "./popover-input/ui-element-popover-input.component"; -import {UiElementIntegerInputComponent} from "./integer-input/ui-element-integer-input.component"; -import {UiElementInputComponent} from "./input/ui-element-input.component"; -import {UiElementDropDownComponent} from "./dropdown/ui-element-dropdown.component"; -import {UiElementBase} from "./ui-element-base.component"; -import {CheckboxModule} from "./checkbox/checkbox.module"; -import {RadioButtonComponent} from "./radio-buttons/radio-buttons.component"; -import {PopoverModule} from "../popover/popover.module"; -import {TooltipModule} from "../tooltip/tooltip.module"; - +import { NgModule } from '@angular/core'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { BrowserModule } from '@angular/platform-browser'; +import { SdcUiComponentsModule } from 'onap-ui-angular/dist'; +import { PopoverModule } from '../popover/popover.module'; +import { TooltipModule } from '../tooltip/tooltip.module'; +import { CheckboxModule } from './checkbox/checkbox.module'; +import { UiElementDropDownComponent } from './dropdown/ui-element-dropdown.component'; +import { UiElementInputComponent } from './input/ui-element-input.component'; +import { UiElementIntegerInputComponent } from './integer-input/ui-element-integer-input.component'; +import { UiElementPopoverInputComponent } from './popover-input/ui-element-popover-input.component'; +import { RadioButtonComponent } from './radio-buttons/radio-buttons.component'; +import { UiElementBase } from './ui-element-base.component'; @NgModule({ imports: [ @@ -22,7 +22,8 @@ import {TooltipModule} from "../tooltip/tooltip.module"; PopoverModule, ReactiveFormsModule, TooltipModule, - CheckboxModule], + CheckboxModule, + SdcUiComponentsModule], declarations: [UiElementDropDownComponent, UiElementInputComponent, @@ -39,4 +40,4 @@ import {TooltipModule} from "../tooltip/tooltip.module"; TooltipModule, CheckboxModule] }) -export class FormElementsModule { }
\ No newline at end of file +export class FormElementsModule { } diff --git a/catalog-ui/src/app/ng2/components/ui/form-components/input/ui-element-input.component.html b/catalog-ui/src/app/ng2/components/ui/form-components/input/ui-element-input.component.html index 85089f12b8..f9cf17f087 100644 --- a/catalog-ui/src/app/ng2/components/ui/form-components/input/ui-element-input.component.html +++ b/catalog-ui/src/app/ng2/components/ui/form-components/input/ui-element-input.component.html @@ -13,19 +13,18 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - -<input - class="value-input" - [ngClass]="{'error': control.invalid, 'disabled':readonly}" - type="text" - [name]="name" - [(ngModel)]="value" - (input)="onChange()" - [attr.maxlength]="validation.propertyValue.max" - [attr.minlength]="validation.propertyValue.min" - [pattern]="pattern" - [formControl]="control" - tooltip="{{value}}" - [readonly]="readonly" - [attr.data-tests-id]="'value-' + testId" - /> +<div sdc-tooltip [tooltip-text]="value"> + <input + class="value-input" + [ngClass]="{'error': control.invalid, 'disabled':readonly}" + type="text" + [name]="name" + [(ngModel)]="value" + (input)="onChange()" + [attr.maxlength]="validation.propertyValue.max" + [attr.minlength]="validation.propertyValue.min" + [pattern]="pattern" + [formControl]="control" + [attr.data-tests-id]="'value-' + testId" + /> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/forms/modal-forms.module.ts b/catalog-ui/src/app/ng2/components/ui/forms/modal-forms.module.ts index 34404e50a5..b401778bc2 100644 --- a/catalog-ui/src/app/ng2/components/ui/forms/modal-forms.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/forms/modal-forms.module.ts @@ -1,15 +1,11 @@ import { NgModule } from "@angular/core"; import { CommonModule } from '@angular/common'; -import { SdcUiComponentsModule } from "sdc-ui/lib/angular"; -import { ValueEditComponent } from './value-edit/value-edit.component'; +import { SdcUiComponentsModule } from "onap-ui-angular"; import { UnsavedChangesComponent } from "./unsaved-changes/unsaved-changes.component"; import { UiElementsModule } from "../ui-elements.module"; - - @NgModule({ declarations: [ - ValueEditComponent, UnsavedChangesComponent ], imports: [ @@ -17,7 +13,7 @@ import { UiElementsModule } from "../ui-elements.module"; SdcUiComponentsModule, UiElementsModule ], - exports: [ValueEditComponent, UnsavedChangesComponent], + exports: [UnsavedChangesComponent], entryComponents: [ UnsavedChangesComponent ], providers: [] diff --git a/catalog-ui/src/app/ng2/components/ui/loader/loader.component.html b/catalog-ui/src/app/ng2/components/ui/loader/loader.component.html index bfb9e1b1ae..00083050d8 100644 --- a/catalog-ui/src/app/ng2/components/ui/loader/loader.component.html +++ b/catalog-ui/src/app/ng2/components/ui/loader/loader.component.html @@ -14,7 +14,7 @@ ~ limitations under the License. --> -<div *ngIf="isVisible" data-tests-id="tlv-loader" [ngClass]="relative ? 'loader-relative' : 'loader-fixed'" +<div *ngIf="isVisible" data-tests-id="loader" [ngClass]="relative ? 'loader-relative' : 'loader-fixed'" [style.top]="offset.top" [style.left]="offset.left" [style.width]="offset.width" [style.height]="offset.height"> <div class="tlv-loader-back" [ngClass]="{'tlv-loader-relative':relative}"></div> <div class="tlv-loader {{size}}"></div> diff --git a/catalog-ui/src/app/ng2/components/ui/loader/loader.component.ts b/catalog-ui/src/app/ng2/components/ui/loader/loader.component.ts index 585c36660e..9658e6b65c 100644 --- a/catalog-ui/src/app/ng2/components/ui/loader/loader.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/loader/loader.component.ts @@ -79,6 +79,13 @@ export class LoaderComponent { width: (parentElement.offsetWidth !== undefined) ? parentElement.offsetWidth + "px" : undefined, height: (parentElement.offsetHeight !== undefined) ? parentElement.offsetHeight + "px" : undefined }; + } else { + this.offset = { + left: '0px', + top: '0px', + width: '100%', + height: '100%' + } } this.isVisible = true; } diff --git a/catalog-ui/src/app/ng2/components/ui/menu/menu-item.component.ts b/catalog-ui/src/app/ng2/components/ui/menu/menu-item.component.ts index 3820573e58..8b2006634e 100644 --- a/catalog-ui/src/app/ng2/components/ui/menu/menu-item.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/menu/menu-item.component.ts @@ -1,5 +1,9 @@ /*- + * ============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 @@ -11,9 +15,9 @@ * 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, Input, Output, EventEmitter } from '@angular/core'; @Component({ diff --git a/catalog-ui/src/app/ng2/components/ui/menu/menu-list.component.ts b/catalog-ui/src/app/ng2/components/ui/menu/menu-list.component.ts index 939599f0bc..290c8d06af 100644 --- a/catalog-ui/src/app/ng2/components/ui/menu/menu-list.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/menu/menu-list.component.ts @@ -1,5 +1,9 @@ /*- + * ============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 @@ -11,9 +15,9 @@ * 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, Input, ContentChildren, SimpleChanges, QueryList } from '@angular/core'; import { MenuItemComponent } from "./menu-item.component"; import { Point } from "app/models"; diff --git a/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.component.ts b/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.component.ts index 1d05b27d68..ab367529d8 100644 --- a/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.component.ts @@ -1,9 +1,9 @@ /** * Created by ob0695 on 11.04.2018. */ -import {Component, Input} from "@angular/core"; -import {UiBaseObject} from "../../../../../models/ui-models/ui-base-object"; -import {IDropDownOption} from "sdc-ui/lib/angular/form-elements/dropdown/dropdown-models"; +import { Component, Input } from "@angular/core"; +import { UiBaseObject } from "../../../../../models/ui-models/ui-base-object"; +import { IDropDownOption } from "onap-ui-angular/dist/form-elements/dropdown/dropdown-models"; @Component({ selector: 'add-elements', diff --git a/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.module.ts b/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.module.ts index a1c34f5686..70ad0a389c 100644 --- a/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/modal/add-elements/add-elements.module.ts @@ -1,14 +1,11 @@ /** * Created by ob0695 on 11.04.2018. */ -import {NgModule} from "@angular/core"; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; -import {AddElementsComponent} from "./add-elements.component"; -import {CommonModule} from "@angular/common"; +import { NgModule } from "@angular/core"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import { AddElementsComponent } from "./add-elements.component"; +import { CommonModule } from "@angular/common"; -/** - * Created by ob0695 on 9.04.2018. - */ @NgModule({ declarations: [ AddElementsComponent diff --git a/catalog-ui/src/app/ng2/components/ui/modal/modal.component.ts b/catalog-ui/src/app/ng2/components/ui/modal/modal.component.ts index 777e9bdc06..2432c3bb37 100644 --- a/catalog-ui/src/app/ng2/components/ui/modal/modal.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/modal/modal.component.ts @@ -36,7 +36,7 @@ export class ModalComponent implements OnInit, OnDestroy { @Input() isMovable: boolean; @Input() input: ModalModel; @Input() dynamicContent: any; - @ViewChild('dynamicContentContainer', { read: ViewContainerRef }) dynamicContentContainer: ViewContainerRef; //Allows for custom component as body instead of simple message. See ModalService.createActionModal for implementation details, and HttpService's catchError() for example. + @ViewChild('dynamicContentContainer', { read: ViewContainerRef }) dynamicContentContainer: ViewContainerRef; //Allows for custom component as body instead of simple message. See ModalService.createActionModal for implementation details, and HttpHelperService's catchError() for example. private modalElement: JQuery; constructor( el: ElementRef ) { diff --git a/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.component.ts b/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.component.ts index 9219a30738..1986e34914 100644 --- a/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.component.ts @@ -20,10 +20,8 @@ * limitations under the License. * ============LICENSE_END========================================================= */ -import { - Component, ElementRef, forwardRef, Inject, Input, trigger, state, style, - transition, animate, ViewChild, ViewContainerRef, ComponentRef -} from "@angular/core"; +import { Component, ElementRef, forwardRef, Inject, Input, ViewChild, ViewContainerRef, ComponentRef} from "@angular/core"; +import {trigger, state, style, transition, animate} from '@angular/animations'; import {StepModel} from "app/models"; import {ModalService} from "../../../services/modal.service"; import {ModalComponent} from "../modal/modal.component"; diff --git a/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.module.ts b/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.module.ts index 3db217d282..81a90b8a9b 100644 --- a/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/multi-steps-wizard/multi-steps-wizard.module.ts @@ -4,7 +4,7 @@ import { NgModule } from "@angular/core"; import {MultiStepsWizardComponent} from "./multi-steps-wizard.component"; import {CommonModule} from "@angular/common"; -import {ConnectionWizardModule} from "../../../pages/connection-wizard/connection-wizard.module"; +import {ConnectionWizardModule} from "app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module"; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.html b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.html new file mode 100644 index 0000000000..277702e336 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.html @@ -0,0 +1,11 @@ +<div class="w-sdc-designer-sidebar-toggle" [class.active]="(withSidebar$ | async)" (click)="toggleSidebarDisplay()"> + <div class="w-sdc-designer-sidebar-toggle-icon sprite-new pointer menu-open-left"></div> +</div> + +<div class="w-sdc-designer-sidebar"> + <ng-content></ng-content> +</div> + + + + diff --git a/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.less b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.less new file mode 100644 index 0000000000..cacb85d6b1 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.less @@ -0,0 +1,55 @@ +@import '../../../../../assets/styles/variables'; + +.w-sdc-designer-sidebar { + background-color:@main_color_p ; + font-family: @font-opensans-regular; + font-size: 13px; + color: #191919; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + bottom: 0; + position: fixed; + right: -302px; + width: 302px; + top: 103px; + transition: right 0.2s; + z-index: 1010; + box-shadow: -7px -3px 6px -8px @main_color_n; +} + +.w-sdc-designer-sidebar-toggle { + background-color: @main_color_p; + border-left: 1px solid @main_color_o; + border-bottom: 1px solid @main_color_o; + height: 21px; + position: absolute; + right: 0; + top: 53px; + width: 17px; + transition: right 0.2s; + z-index: 1005; + box-shadow: -1px 1px 3px 0 @main_color_n; + cursor: pointer; + + &.active { + right: 302px; + .w-sdc-designer-sidebar-toggle-icon{ + transform: rotate(180deg); + } + } +} + +.w-sdc-designer-sidebar-toggle-icon { + margin-left: 6px; + margin-top: 6px; +} + +.w-sdc-designer-sidebar-toggle.active + .w-sdc-designer-sidebar { + right: 0; + display: flex; + flex-direction: column; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.ts b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.ts new file mode 100644 index 0000000000..e9c4a7d354 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/panel-wrapper/panel-wrapper.component.ts @@ -0,0 +1,25 @@ +import {Component} from "@angular/core"; +import {Select, Store} from "@ngxs/store"; +import {Subscription} from "rxjs/Subscription"; +import {GraphState} from "../../../pages/composition/common/store/graph.state"; +import {OnSidebarOpenOrCloseAction} from "../../../pages/composition/common/store/graph.actions"; + +@Component({ + selector: 'panel-wrapper-component', + templateUrl: './panel-wrapper.component.html', + styleUrls: ['./panel-wrapper.component.less'] +}) +export class PanelWrapperComponent { + @Select(GraphState.withSidebar) withSidebar$: boolean; + + tabs: Array<any>; + subscription: Subscription; + + constructor(public store: Store) { + } + + private toggleSidebarDisplay = () => { + // this.withSidebar = !this.withSidebar; + this.store.dispatch(new OnSidebarOpenOrCloseAction()); + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/perfect-scroll-bar/perfect-scrollbar.directive.ts b/catalog-ui/src/app/ng2/components/ui/perfect-scroll-bar/perfect-scrollbar.directive.ts new file mode 100644 index 0000000000..ccc1e2181e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/perfect-scroll-bar/perfect-scrollbar.directive.ts @@ -0,0 +1,51 @@ +import {Directive, Input, ElementRef} from '@angular/core'; +import * as PerfectScrollbar from 'perfect-scrollbar'; + +interface IPerfectScrollbarOptions { + wheelSpeed?: number; + wheelPropagation?: boolean; + minScrollbarLength?: number; + useBothWheelAxes?: boolean; + useKeyboard?: boolean; + suppressScrollX?: boolean; + suppressScrollY?: boolean; + scrollXMarginOffset?: number; + scrollYMarginOffset?: number; + includePadding?: boolean; +} + +@Directive({ + selector: '[perfectScrollbar]' +}) +export class PerfectScrollbarDirective { + @Input() public perfectScrollbarOptions: IPerfectScrollbarOptions; + + private psOptions: IPerfectScrollbarOptions; + private updatingPS: boolean; + + constructor(public elemRef:ElementRef) { + console.log('PSbar: Constructor'); + this.psOptions = Object.assign({}, this.perfectScrollbarOptions); + this.updatingPS = false; + } + + public ngOnInit() { + console.log('PSbar: Initializing'); + PerfectScrollbar.initialize(this.elemRef.nativeElement, this.psOptions); + } + + public ngAfterContentChecked() { + // update perfect-scrollbar after content is checked (updated) - bounced + if (!this.updatingPS) { + this.updatingPS = true; + setTimeout(() => { + this.updatingPS = false; + PerfectScrollbar.update(this.elemRef.nativeElement); + }, 100); + } + } + + public ngOnDestroy() { + PerfectScrollbar.destroy(this.elemRef.nativeElement); + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.html b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.html index cce1c40765..5294c67e31 100644 --- a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.html +++ b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.html @@ -17,6 +17,7 @@ <div class="plugin-frame"> <div class="w-sdc-main-container"> <iframe *ngIf="plugin.isOnline" class="plugin-iframe" [src]="pluginUrl | safeUrlSanitizer"></iframe> - <plugin-not-connected [pluginName]="plugin.pluginId" *ngIf="!plugin.isOnline && isPluginCheckDone"></plugin-not-connected> + <plugin-not-connected [pluginName]="plugin.pluginId" + *ngIf="!plugin.isOnline && isPluginCheckDone"></plugin-not-connected> </div> </div> diff --git a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts index d70c448984..d94f24da28 100644 --- a/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/plugin/plugin-frame.component.ts @@ -38,8 +38,7 @@ export class PluginFrameComponent implements OnInit { } else { this.onLoadingDone.emit(); } - }) - + }); } private initPlugin() { diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html index 88906d96fa..9a280e169d 100644 --- a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.html @@ -19,7 +19,7 @@ [style.left]="left + 'px'" [class.in]="isIn" [class.fade]="animation" - style="display: block" + [style.display] = "displayType" role="popover" [ngClass]="{'hide-arrow':hideArrow}"> <div [hidden]="!closeOnMouseOutside" class="virtual-area"></div> @@ -29,13 +29,11 @@ <span class="close-button" (click)="popover.hide()"></span> </div> <ng-content></ng-content> - <div class="popover-footer"> + <div class="popover-footer" *ngIf="buttons"> <button *ngFor="let buttonName of buttonsNames" - class="tlv-btn {{buttons[buttonName].cssClass}}" + class="tlv-btn {{buttons[buttonName].cssClass}}" [attr.data-tests-id]="'filter-' + buttons[buttonName].text.toLowerCase() + '-button'" [disabled] = "buttons[buttonName].getDisabled && buttons[buttonName].getDisabled()" (click) = "buttons[buttonName].callback()">{{buttons[buttonName].text}}</button> </div> </div> - - diff --git a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts index 6eb36287d0..bea592d2a6 100644 --- a/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/popover/popover-content.component.ts @@ -7,9 +7,9 @@ * 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. @@ -18,25 +18,25 @@ * ============LICENSE_END========================================================= */ -import {Component, Input, Output, AfterViewInit, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, EventEmitter, Renderer } from "@angular/core"; -import {ButtonsModelMap} from "app/models"; -import {PopoverComponent} from "./popover.component"; +import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnDestroy, Output, Renderer, ViewChild } from '@angular/core'; +import { ButtonsModelMap } from 'app/models'; +import { PopoverComponent } from './popover.component'; @Component({ - selector: "popover-content", - templateUrl:'./popover-content.component.html', - styleUrls:['popover-content.component.less'] + selector: 'popover-content', + templateUrl: './popover-content.component.html', + styleUrls: ['popover-content.component.less'] }) export class PopoverContentComponent implements AfterViewInit, OnDestroy { @Input() public title: string; - @Input() public buttons:ButtonsModelMap; + @Input() public buttons: ButtonsModelMap; @Input() content: string; @Input() - placement: "top"|"bottom"|"left"|"right"|"auto"|"auto top"|"auto bottom"|"auto left"|"auto right" = "bottom"; + placement: 'top'|'bottom'|'left'|'right'|'auto'|'auto top'|'auto bottom'|'auto left'|'auto right' = 'bottom'; @Input() animation: boolean = true; @@ -50,52 +50,56 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { @Input() hideArrow: boolean = false; - @ViewChild("popoverDiv") + @ViewChild('popoverDiv') popoverDiv: ElementRef; - buttonsNames:Array<string>; + buttonsNames: string[]; popover: PopoverComponent; onCloseFromOutside = new EventEmitter(); - top: number = -10000; - left: number = -10000; + top: number = 250; + left: number = 300; isIn: boolean = false; - displayType: string = "none"; + displayType: string = 'none'; effectivePlacement: string; - onDocumentMouseDown = (event: any) => { - const element = this.element.nativeElement; - if (!element || !this.popover) return; - if (element.contains(event.target) || this.popover.getElement().contains(event.target)) return; - this.hide(); - this.onCloseFromOutside.emit(undefined); - }; - + listenClickFunc: any; + listenMouseFunc: any; constructor(protected element: ElementRef, protected cdr: ChangeDetectorRef, protected renderer: Renderer) { } - listenClickFunc: any; - listenMouseFunc: any; + onDocumentMouseDown = (event: any) => { + const element = this.element.nativeElement; + if (!element || !this.popover) { return; } + if (element.contains(event.target) || this.popover.getElement().contains(event.target)) { return; } + this.hide(); + this.onCloseFromOutside.emit(undefined); + } ngAfterViewInit(): void { - this.buttonsNames = Object.keys(this.buttons); - if (this.closeOnClickOutside) - this.listenClickFunc = this.renderer.listenGlobal("document", "mousedown", (event: any) => this.onDocumentMouseDown(event)); - if (this.closeOnMouseOutside) - this.listenMouseFunc = this.renderer.listenGlobal("document", "mouseover", (event: any) => this.onDocumentMouseDown(event)); + if ( this.buttons ) { + this.buttonsNames = Object.keys(this.buttons); + } + if (this.closeOnClickOutside) { + this.listenClickFunc = this.renderer.listenGlobal('document', 'mousedown', (event: any) => this.onDocumentMouseDown(event)); + } + if (this.closeOnMouseOutside) { + this.listenMouseFunc = this.renderer.listenGlobal('document', 'mouseover', (event: any) => this.onDocumentMouseDown(event)); + } - this.show(); this.cdr.detectChanges(); } ngOnDestroy() { - if (this.closeOnClickOutside) + if (this.closeOnClickOutside) { this.listenClickFunc(); - if (this.closeOnMouseOutside) + } + if (this.closeOnMouseOutside) { this.listenMouseFunc(); + } } // ------------------------------------------------------------------------- @@ -103,11 +107,12 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { // ------------------------------------------------------------------------- show(): void { - if (!this.popover || !this.popover.getElement()) + if (!this.popover || !this.popover.getElement()) { return; + } const p = this.positionElements(this.popover.getElement(), this.popoverDiv.nativeElement, this.placement); - this.displayType = "block"; + this.displayType = 'block'; this.top = p.top; this.left = p.left; this.isIn = true; @@ -121,8 +126,7 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { } hideFromPopover() { - this.top = -10000; - this.left = -10000; + this.displayType = 'none'; this.isIn = true; } @@ -131,56 +135,56 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { // ------------------------------------------------------------------------- protected positionElements(hostEl: HTMLElement, targetEl: HTMLElement, positionStr: string, appendToBody: boolean = false): { top: number, left: number } { - let positionStrParts = positionStr.split("-"); + const positionStrParts = positionStr.split('-'); let pos0 = positionStrParts[0]; - let pos1 = positionStrParts[1] || "center"; - let hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); - let targetElWidth = targetEl.offsetWidth; - let targetElHeight = targetEl.offsetHeight; + const pos1 = positionStrParts[1] || 'center'; + const hostElPos = appendToBody ? this.offset(hostEl) : this.position(hostEl); + const targetElWidth = targetEl.offsetWidth; + const targetElHeight = targetEl.offsetHeight; this.effectivePlacement = pos0 = this.getEffectivePlacement(pos0, hostEl, targetEl); - let shiftWidth: any = { - center: function (): number { + const shiftWidth: any = { + center(): number { return hostElPos.left + hostElPos.width / 2 - targetElWidth / 2; }, - left: function (): number { + left(): number { return hostElPos.left; }, - right: function (): number { + right(): number { return hostElPos.left + hostElPos.width - targetElWidth; } }; - let shiftHeight: any = { - center: function (): number { + const shiftHeight: any = { + center(): number { return hostElPos.top + hostElPos.height / 2 - targetElHeight / 2; }, - top: function (): number { + top(): number { return hostElPos.top; }, - bottom: function (): number { + bottom(): number { return hostElPos.top + hostElPos.height - targetElHeight; } }; let targetElPos: { top: number, left: number }; switch (pos0) { - case "right": + case 'right': targetElPos = { top: shiftHeight[pos1](), left: hostElPos.left + hostElPos.width }; break; - case "left": + case 'left': targetElPos = { top: shiftHeight[pos1](), left: hostElPos.left - targetElWidth }; break; - case "bottom": + case 'bottom': targetElPos = { top: hostElPos.top + hostElPos.height, left: shiftWidth[pos1]() @@ -228,18 +232,20 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { } protected getStyle(nativeEl: HTMLElement, cssProp: string): string { - if ((nativeEl as any).currentStyle) // IE + if ((nativeEl as any).currentStyle) { // IE return (nativeEl as any).currentStyle[cssProp]; + } - if (window.getComputedStyle) + if (window.getComputedStyle) { return (window.getComputedStyle as any)(nativeEl)[cssProp]; + } // finally try and get inline style return (nativeEl.style as any)[cssProp]; } protected isStaticPositioned(nativeEl: HTMLElement): boolean { - return (this.getStyle(nativeEl, "position") || "static" ) === "static"; + return (this.getStyle(nativeEl, 'position') || 'static' ) === 'static'; } protected parentOffsetEl(nativeEl: HTMLElement): any { @@ -251,26 +257,26 @@ export class PopoverContentComponent implements AfterViewInit, OnDestroy { } protected getEffectivePlacement(placement: string, hostElement: HTMLElement, targetElement: HTMLElement): string { - const placementParts = placement.split(" "); - if (placementParts[0] !== "auto") { + const placementParts = placement.split(' '); + if (placementParts[0] !== 'auto') { return placement; } const hostElBoundingRect = hostElement.getBoundingClientRect(); - const desiredPlacement = placementParts[1] || "bottom"; + const desiredPlacement = placementParts[1] || 'bottom'; - if (desiredPlacement === "top" && hostElBoundingRect.top - targetElement.offsetHeight < 0) { - return "bottom"; + if (desiredPlacement === 'top' && hostElBoundingRect.top - targetElement.offsetHeight < 0) { + return 'bottom'; } - if (desiredPlacement === "bottom" && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) { - return "top"; + if (desiredPlacement === 'bottom' && hostElBoundingRect.bottom + targetElement.offsetHeight > window.innerHeight) { + return 'top'; } - if (desiredPlacement === "left" && hostElBoundingRect.left - targetElement.offsetWidth < 0) { - return "right"; + if (desiredPlacement === 'left' && hostElBoundingRect.left - targetElement.offsetWidth < 0) { + return 'right'; } - if (desiredPlacement === "right" && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) { - return "left"; + if (desiredPlacement === 'right' && hostElBoundingRect.right + targetElement.offsetWidth > window.innerWidth) { + return 'left'; } return desiredPlacement; diff --git a/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.html b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.html new file mode 100644 index 0000000000..fba99ca8b5 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.html @@ -0,0 +1,12 @@ +<div class="sdc-element-icon"> + <svg-icon [ngClass]="{'service-proxy': elementType === 'ServiceProxy'}" + [type]="elementIcon.type" + [name]="elementIcon.iconName" + [mode]="elementIcon.color" + [size]="elementIcon.size" + [backgroundShape]="elementIcon.shape" + [backgroundColor]="elementIcon.backgroundColor"></svg-icon> + <span *ngIf="uncertified" class="uncertified-icon-wapper"> + <svg-icon class="element-non-certified" [name]="'alert-circle'" [mode]="'error'"></svg-icon> + </span> +</div> diff --git a/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.less b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.less new file mode 100644 index 0000000000..7fd5cbd53e --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.less @@ -0,0 +1,25 @@ +@import "./../../../../../assets/styles/override"; +.service-proxy { + /deep/.svg-icon.bg-type-circle { + border: 2px solid @sdcui_color_blue; + } +} + +.element-non-certified { + position: absolute; + top: 0.4px; + left: 0.4px; +} +.sdc-element-icon { + position: relative; +} + +.uncertified-icon-wapper{ + height: 17px; + width: 17px; + background-color: white; + border-radius: 50%; + position: absolute; + top: -2px; + left: -1px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.ts b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.ts new file mode 100644 index 0000000000..baadbd8e02 --- /dev/null +++ b/catalog-ui/src/app/ng2/components/ui/sdc-element-icon/sdc-element-icon.component.ts @@ -0,0 +1,69 @@ +import {Component, Input, OnInit} from "@angular/core"; +import {ComponentType, SdcElementType, ResourceType} from "../../../../utils/constants"; + + +export class ElementIcon { + iconName: string; + color: string; + backgroundColor: string; + type: string + shape: string; + size: string; + + constructor(name?: string, type?:string, backgroundColor?:string, color?:string, shape?: string, size?:string) { + this.iconName = name || 'default'; + this.type = type || 'resource_24'; + this.backgroundColor = backgroundColor || 'primary'; + this.color = color || "white"; + this.shape = shape || "circle"; + this.size = size || "x_large"; + } +} + +@Component({ + selector: 'sdc-element-icon', + templateUrl: './sdc-element-icon.component.html', + styleUrls: ['./sdc-element-icon.component.less'] +}) +export class SdcElementIconComponent { + + @Input() iconName: string; + @Input() elementType: string; + @Input() uncertified: boolean = false; + + public elementIcon; + + private createIconForDisplay = () => { + switch (this.elementType) { + + case ComponentType.SERVICE: + this.elementIcon = new ElementIcon(this.iconName, "services_24", "lightBlue"); + break; + case ComponentType.SERVICE_PROXY: + this.elementIcon = new ElementIcon(this.iconName, "services_24", "white", "primary"); + break; + case ResourceType.CONFIGURATION: + this.elementIcon = new ElementIcon(this.iconName, "resources_24", "purple", "white", 'circle', "medium"); + break; + case SdcElementType.GROUP: + this.elementIcon = new ElementIcon("group", "resources_24", "blue", 'white', 'rectangle'); + break; + case SdcElementType.POLICY: + this.elementIcon = new ElementIcon("policy", "resources_24", "darkBlue2", 'white', 'rectangle'); + break; + case ResourceType.CP: + case ResourceType.VL: + this.elementIcon = new ElementIcon(this.iconName, "resources_24", "purple", '', '', 'medium'); + break; + default: + this.elementIcon = new ElementIcon(this.iconName, "resources_24", "purple"); + } + } + + ngOnChanges():void { + this.createIconForDisplay(); + } +} + + + diff --git a/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.html b/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.html index 8682473d14..1f952c46f5 100644 --- a/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.html +++ b/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.html @@ -1,21 +1,5 @@ -<!-- - ~ 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="search-bar-container {{class}}"> - <input class="search-bar-input" type="text" [placeholder]="placeholder" [(ngModel)]="searchQuery" (ngModelChange)="searchQueryChange($event)"/> - <span class="clear-search-x" *ngIf="searchQuery" (click)="clearSearchQuery()">x</span> - <button class="search-bar-button" (click)="searchButtonClick()"></button> +<div class="sdc-search-bar-container {{class}}"> + <input class="sdc-search-bar-input" type="text" [placeholder]="placeholder" [(ngModel)]="searchQuery" (ngModelChange)="searchQueryChange($event)"/> + <span class="sdc-clear-search-x" *ngIf="searchQuery" (click)="clearSearchQuery()">x</span> + <button class="sdc-search-bar-button" (click)="searchButtonClick()"></button> </div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.less b/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.less index 751fceec35..c09951a284 100644 --- a/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.less +++ b/catalog-ui/src/app/ng2/components/ui/search-bar/search-bar.component.less @@ -1,9 +1,9 @@ -.search-bar-container { +.sdc-search-bar-container { display:flex; border-radius: 4px; box-shadow: 0px 2px 3.88px 0.12px rgba(0, 0, 0, 0.29); - .search-bar-input { + .sdc-search-bar-input { border: 1px solid #cdcdcd; border-radius: 4px; border-right:none; @@ -14,9 +14,11 @@ color: #5a5a5a; font-size: 1em; font-style: italic; + + } - .clear-search-x { + .sdc-clear-search-x { position:absolute; right:40px; top:5px; @@ -29,7 +31,7 @@ } } - .search-bar-button { + .sdc-search-bar-button { background: url('../../../../../assets/styles/images/sprites/sprite-global.png') no-repeat -206px -1275px; background-color: rgba(234, 234, 234, 0.88); width: 30px; diff --git a/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.html b/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.html index 82dbbf33d3..da9b2c9e24 100644 --- a/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.html +++ b/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.html @@ -14,9 +14,9 @@ ~ limitations under the License. --> -<div class="search-with-autocomplete-container {{searchBarClass}}" [class.autocomplete-visible]="autoCompleteValues && autoCompleteValues.length" [class.active]="searchQuery && searchQuery.length"> +<div class="sdc-search-with-autocomplete-container {{searchBarClass}}" [class.sdc-autocomplete-visible]="autoCompleteValues && autoCompleteValues.length" [class.active]="searchQuery && searchQuery.length"> <search-bar [placeholder]="searchPlaceholder" [searchQuery]="searchQuery" (searchButtonClicked)="updateSearch($event)" (searchChanged)="searchChange($event)"></search-bar> - <div class="autocomplete-results"> - <div *ngFor="let item of autoCompleteValues" class="autocomplete-result-item" (click)="updateSearch(item)">{{item}}</div> + <div class="sdc-autocomplete-results"> + <div *ngFor="let item of autoCompleteValues" class="sdc-autocomplete-result-item" (click)="updateSearch(item)">{{item}}</div> </div> </div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.less b/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.less index f1830846d3..9cd312adcb 100644 --- a/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.less +++ b/catalog-ui/src/app/ng2/components/ui/search-with-autocomplete/search-with-autocomplete.component.less @@ -1,13 +1,13 @@ -.search-with-autocomplete-container{ - &.autocomplete-visible { +.sdc-search-with-autocomplete-container{ + &.sdc-autocomplete-visible { - .search-bar-input { + .sdc-search-bar-input { border-bottom-left-radius: 0; } - .search-bar-button { + .sdc-search-bar-button { border-bottom-right-radius: 0; } - .autocomplete-results { + .sdc-autocomplete-results { border: solid 1px #d2d2d2; border-top:none; border-bottom-left-radius: 4px; @@ -20,7 +20,7 @@ overflow-y: scroll; } - .autocomplete-result-item { + .sdc-autocomplete-result-item { color:#5a5a5a; padding: 5px 0; cursor:pointer; diff --git a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.ts b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.ts index f4b410347b..cabac121f7 100644 --- a/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/tabs/tabs.component.ts @@ -21,7 +21,7 @@ import { Component, ContentChildren, QueryList, AfterContentInit, Input, Output, EventEmitter } from '@angular/core'; import { Tab } from './tab/tab.component'; import { ViewEncapsulation } from '@angular/core'; -import { trigger, state, style, transition, animate, keyframes } from '@angular/core'; +import { trigger, state, style, transition, animate, keyframes } from '@angular/animations'; export {Tab}; diff --git a/catalog-ui/src/app/ng2/components/ui/tile/tile.module.ts b/catalog-ui/src/app/ng2/components/ui/tile/sdc-tile.module.ts index 55b34400d6..5dec67e495 100644 --- a/catalog-ui/src/app/ng2/components/ui/tile/tile.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/tile/sdc-tile.module.ts @@ -3,13 +3,13 @@ import { BrowserModule } from '@angular/platform-browser'; import { TileComponent } from './tile.component'; import { GlobalPipesModule } from "../../../pipes/global-pipes.module"; import { TooltipModule } from "../tooltip/tooltip.module"; -import {MultilineEllipsisModule} from "../../../shared/multiline-ellipsis/multiline-ellipsis.module"; +import {SdcUiComponentsModule} from "onap-ui-angular"; @NgModule({ - imports: [BrowserModule, GlobalPipesModule, TooltipModule, MultilineEllipsisModule], + imports: [BrowserModule, GlobalPipesModule, SdcUiComponentsModule, TooltipModule], declarations: [TileComponent], exports: [TileComponent], entryComponents: [TileComponent] }) -export class TileModule { } +export class SdcTileModule { } diff --git a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.html b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.html index 018384db1f..239053e9f8 100644 --- a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.html +++ b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.html @@ -23,15 +23,21 @@ <div class='sdc-tile-content' data-tests-id="dashboard-Elements" (click)="tileClicked()"> <div class='sdc-tile-content-icon'> - <div [ngClass]="[component.iconSprite, component.icon]" [ngClass]="{'sprite-resource-icons': component.isResource(), 'sprite-services-icons': component.isService()}" + <div [ngClass]="[component.iconSprite, component.icon]" + [ngClass]="{'sprite-resource-icons': component.isResource(), 'sprite-services-icons': component.isService()}" [attr.data-tests-id]="component.name"></div> </div> <div class='sdc-tile-content-info'> - <div class="sdc-tile-info-line title" [attr.data-tests-id]="component.name | resourceName" [tooltip]="component.name | resourceName" [tooltipDisabled]="!hasEllipsis"> - <multiline-ellipsis className="w-sdc-tile-multiline-ellipsis" [lines]="3" (hasEllipsisChanged)="hasEllipsis = $event">{{component.name | resourceName}}</multiline-ellipsis> + <div class="sdc-tile-info-line title" [attr.data-tests-id]="component.name | resourceName" + [tooltip]="component.name | resourceName" [tooltipDisabled]="!hasEllipsis"> + <multiline-ellipsis className="w-sdc-tile-multiline-ellipsis" [lines]="3" + (hasEllipsisChanged)="hasEllipsis = $event">{{component.name | resourceName}} + </multiline-ellipsis> + </div> + <div class="sdc-tile-info-line subtitle" [attr.data-tests-id]="component.name+'Version'">V + {{component.version}} </div> - <div class="sdc-tile-info-line subtitle" [attr.data-tests-id]="component.name+'Version'">V {{component.version}}</div> </div> </div> @@ -42,3 +48,32 @@ </div> </div> + +<!--<sdc-tile>--> +<!--<sdc-tile-header>--> +<!--<div [ngClass]="{'blue': component.isService(), 'purple': component.isResource()}">{{component.isResource() ?--> +<!--component.getComponentSubType(): 'S' }}--> +<!--</div>--> +<!--</sdc-tile-header>--> +<!--<sdc-tile-content>--> +<!--<div class='sdc-tile-content-icon blue'>--> +<!--<svg-icon--> +<!--[type]="catalogIcon.type"--> +<!--[name]="catalogIcon.iconName"--> +<!--[size]="catalogIcon.size"--> +<!--[mode]="catalogIcon.color"--> +<!--[backgroundShape]="catalogIcon.shape"--> +<!--[backgroundColor]="catalogIcon.backgroundColor"--> +<!--[disabled]="false"--> +<!-->--> +<!--</svg-icon>--> +<!--</div>--> +<!--<div class="sdc-tile-content-info">--> +<!--<span class="sdc-tile-info-line title">{{component.name | resourceName}}</span>--> +<!--<div class="sdc-tile-info-line subtitle">{{'V'+ component.version}}</div>--> +<!--</div>--> +<!--</sdc-tile-content>--> +<!--<sdc-tile-footer>--> +<!--<span class="sdc-tile-footer-cell">{{component.getStatus(sdcMenu)}}</span>--> +<!--</sdc-tile-footer>--> +<!--</sdc-tile>-->
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.less b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.less index e69de29bb2..febf46e99c 100644 --- a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.less +++ b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.less @@ -0,0 +1,15 @@ + + +//This was done as a fix for the wrong dispaly that was in every ui-tile. +//All other styles located in mail.less + +.sdc-tile { + display: block; + + .sdc-tile-content { + overflow: visible; + height: 80%; + .sdc-tile-content-icon { + } + } +} diff --git a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.ts b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.ts index b6f63584be..f5d9e88934 100644 --- a/catalog-ui/src/app/ng2/components/ui/tile/tile.component.ts +++ b/catalog-ui/src/app/ng2/components/ui/tile/tile.component.ts @@ -1,6 +1,8 @@ import {Component, Input, Output, Inject, EventEmitter} from '@angular/core'; import {Component as ComponentModel} from 'app/models'; import {SdcMenuToken, IAppMenu} from "../../../config/sdc-menu.config"; +import {ElementIcon} from "../sdc-element-icon/sdc-element-icon.component"; +import {ComponentType, ResourceType, SdcElementType} from "../../../../utils/constants"; @Component({ selector: 'ui-tile', @@ -10,14 +12,42 @@ import {SdcMenuToken, IAppMenu} from "../../../config/sdc-menu.config"; export class TileComponent { @Input() public component: ComponentModel; @Output() public onTileClick: EventEmitter<ComponentModel>; - + public catalogIcon: ElementIcon; public hasEllipsis: boolean; - constructor(@Inject(SdcMenuToken) public sdcMenu:IAppMenu) { + constructor(@Inject(SdcMenuToken) public sdcMenu: IAppMenu) { this.onTileClick = new EventEmitter<ComponentModel>(); this.hasEllipsis = false; } + ngOnInit(): void { + switch (this.component.componentType) { + + case ComponentType.SERVICE: + if (this.component.icon === 'defaulticon') { + this.catalogIcon = new ElementIcon(this.component.icon, "services_60", 'lightBlue', 'white'); + } else { + this.catalogIcon = new ElementIcon(this.component.icon, "services_60", '', 'lightBlue'); + } + break; + case ComponentType.RESOURCE: + switch (this.component.getComponentSubType()) { + case ResourceType.CP: + case ResourceType.VL: + this.catalogIcon = new ElementIcon(this.component.icon, "resources_24", "purple", "white", "circle", 'medium'); + break; + default: + if (this.component.icon === 'defaulticon') { + this.catalogIcon = new ElementIcon(this.component.icon, "resources_60", "purple", "white", "circle", 'x_large'); + } else { + this.catalogIcon = new ElementIcon(this.component.icon, "resources_60", '', "error"); + } + + } + + } + } + public tileClicked() { this.onTileClick.emit(this.component); } diff --git a/catalog-ui/src/app/ng2/components/ui/ui-elements.module.ts b/catalog-ui/src/app/ng2/components/ui/ui-elements.module.ts index e905db73a6..cdb173cb12 100644 --- a/catalog-ui/src/app/ng2/components/ui/ui-elements.module.ts +++ b/catalog-ui/src/app/ng2/components/ui/ui-elements.module.ts @@ -18,82 +18,89 @@ * ============LICENSE_END========================================================= */ -import { NgModule } from '@angular/core'; -import { NavbarModule } from "./navbar/navbar.module"; -import { DynamicElementModule } from "./dynamic-element/dynamic-element.module"; -import { FormElementsModule } from "./form-components/form-elements.module"; -import { LoaderComponent } from "./loader/loader.component"; -import { ModalModule } from "./modal/modal.module"; -import { PopoverModule } from "./popover/popover.module"; -import { SearchBarComponent } from "./search-bar/search-bar.component"; -import { SearchWithAutoCompleteComponent } from "./search-with-autocomplete/search-with-autocomplete.component"; -import { PalettePopupPanelComponent } from "./palette-popup-panel/palette-popup-panel.component"; -import { ZoneContainerComponent } from "./canvas-zone/zone-container.component"; -import { ZoneInstanceComponent } from "./canvas-zone/zone-instance/zone-instance.component"; -import { PaletteAnimationComponent } from "./palette-animation/palette-animation.component" -import { TabModule } from "./tabs/tabs.module"; -import { TooltipModule } from "./tooltip/tooltip.module"; -import { CommonModule } from "@angular/common"; -import { FormsModule } from "@angular/forms"; -import { BrowserModule } from "@angular/platform-browser"; -import { MultiStepsWizardModule } from "./multi-steps-wizard/multi-steps-wizard.module"; -import { MenuListModule } from "./menu/menu-list.module"; -import { MenuListNg2Module } from "../downgrade-wrappers/menu-list-ng2/menu-list-ng2.module"; -import { ExpandCollapseComponent } from './expand-collapse/expand-collapse.component'; -import { SdcUiComponentsModule } from "sdc-ui/lib/angular"; -import { TileModule } from "./tile/tile.module"; +import {NgModule} from '@angular/core'; +import {NavbarModule} from "./navbar/navbar.module"; +import {DynamicElementModule} from "./dynamic-element/dynamic-element.module"; +import {FormElementsModule} from "./form-components/form-elements.module"; +import {LoaderComponent} from "./loader/loader.component"; +import {ModalModule} from "./modal/modal.module"; +import {PopoverModule} from "./popover/popover.module"; +import {SearchBarComponent} from "./search-bar/search-bar.component"; +import {SearchWithAutoCompleteComponent} from "./search-with-autocomplete/search-with-autocomplete.component"; +import { PaletteAnimationComponent } from "app/ng2/pages/composition/palette/palette-animation/palette-animation.component"; +import {TabModule} from "./tabs/tabs.module"; +import {TooltipModule} from "./tooltip/tooltip.module"; +import {CommonModule} from "@angular/common"; +import {FormsModule} from "@angular/forms"; +import {BrowserModule} from "@angular/platform-browser"; +import {MultiStepsWizardModule} from "./multi-steps-wizard/multi-steps-wizard.module"; +import {MenuListModule} from "./menu/menu-list.module"; +import {MenuListNg2Module} from "../downgrade-wrappers/menu-list-ng2/menu-list-ng2.module"; +import {ExpandCollapseComponent} from './expand-collapse/expand-collapse.component'; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {SdcTileModule} from "./tile/sdc-tile.module"; +import {PerfectScrollbarDirective} from "./perfect-scroll-bar/perfect-scrollbar.directive"; +import {FileOpenerComponent} from "./file-opener/file-opener.component"; +import {DownloadArtifactComponent} from "app/ng2/components/ui/download-artifact/download-artifact.component"; +import {SdcElementIconComponent} from "./sdc-element-icon/sdc-element-icon.component"; +import {PanelWrapperComponent} from "./panel-wrapper/panel-wrapper.component"; @NgModule({ - declarations: [ - LoaderComponent, - SearchBarComponent, - SearchWithAutoCompleteComponent, - PalettePopupPanelComponent, - ZoneContainerComponent, - ZoneInstanceComponent, - PaletteAnimationComponent, - ExpandCollapseComponent -], - - imports: [ - SdcUiComponentsModule, - BrowserModule, - FormsModule, - CommonModule, - DynamicElementModule, - NavbarModule, - FormElementsModule, - ModalModule, - PopoverModule, - TabModule, - TooltipModule, - MultiStepsWizardModule, - MenuListModule, - MenuListNg2Module, - TileModule - ], - exports: [ - LoaderComponent, - MultiStepsWizardModule, - SearchBarComponent, - SearchWithAutoCompleteComponent, - PalettePopupPanelComponent, - ZoneContainerComponent, - ZoneInstanceComponent, - DynamicElementModule, - NavbarModule, - FormElementsModule, - ModalModule, - PopoverModule, - TabModule, - TooltipModule, - MenuListModule, - MenuListNg2Module, - PaletteAnimationComponent, - ExpandCollapseComponent, - TileModule - ], - entryComponents: [SearchWithAutoCompleteComponent, PalettePopupPanelComponent, ZoneContainerComponent, ZoneInstanceComponent, PaletteAnimationComponent] + declarations: [ + LoaderComponent, + SearchBarComponent, + SearchWithAutoCompleteComponent, + PaletteAnimationComponent, + ExpandCollapseComponent, + PerfectScrollbarDirective, + FileOpenerComponent, + SdcElementIconComponent, + DownloadArtifactComponent, + PanelWrapperComponent + ], + + imports: [ + SdcUiComponentsModule, + BrowserModule, + FormsModule, + CommonModule, + DynamicElementModule, + NavbarModule, + FormElementsModule, + ModalModule, + PopoverModule, + TabModule, + TooltipModule, + MultiStepsWizardModule, + MenuListModule, + MenuListNg2Module, + SdcTileModule + ], + exports: [ + LoaderComponent, + MultiStepsWizardModule, + SearchBarComponent, + SearchWithAutoCompleteComponent, + DynamicElementModule, + NavbarModule, + FormElementsModule, + ModalModule, + PopoverModule, + TabModule, + TooltipModule, + MenuListModule, + MenuListNg2Module, + PaletteAnimationComponent, + ExpandCollapseComponent, + SdcTileModule, + PerfectScrollbarDirective, + SdcElementIconComponent, + FileOpenerComponent, + DownloadArtifactComponent, + PanelWrapperComponent + ], + entryComponents: [SearchWithAutoCompleteComponent, SdcElementIconComponent, PaletteAnimationComponent] }) -export class UiElementsModule {} +export class UiElementsModule { +} diff --git a/catalog-ui/src/app/ng2/config/sdc-config.config.ts b/catalog-ui/src/app/ng2/config/sdc-config.config.ts index b10a87882f..76e81b2333 100644 --- a/catalog-ui/src/app/ng2/config/sdc-config.config.ts +++ b/catalog-ui/src/app/ng2/config/sdc-config.config.ts @@ -13,13 +13,12 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. -- */ - - import {Provider, OpaqueToken} from "@angular/core"; +import {Provider, InjectionToken} from "@angular/core"; import {getSdcConfig, ISdcConfig} from "./sdc-config.config.factory"; export { ISdcConfig }; -export const SdcConfigToken = new OpaqueToken('SdcConfigToken'); +export const SdcConfigToken = new InjectionToken('SdcConfigToken'); export const SdcConfig:Provider = { provide: SdcConfigToken, diff --git a/catalog-ui/src/app/ng2/config/sdc-menu.config.ts b/catalog-ui/src/app/ng2/config/sdc-menu.config.ts index 44c2d288b5..9dd83e52ea 100644 --- a/catalog-ui/src/app/ng2/config/sdc-menu.config.ts +++ b/catalog-ui/src/app/ng2/config/sdc-menu.config.ts @@ -15,13 +15,13 @@ -- */ -import {Provider, OpaqueToken} from "@angular/core"; +import {Provider, InjectionToken} from "@angular/core"; import {getSdcMenu} from "./sdc-menu.config.factory"; import {IAppMenu} from "app/models"; export { IAppMenu }; -export const SdcMenuToken = new OpaqueToken('SdcMenuToken'); +export const SdcMenuToken = new InjectionToken('SdcMenuToken'); export const SdcMenu:Provider = { provide: SdcMenuToken, diff --git a/catalog-ui/src/app/ng2/http-interceptor/headers-interceptor.ts b/catalog-ui/src/app/ng2/http-interceptor/headers-interceptor.ts new file mode 100644 index 0000000000..00e2fd8fcd --- /dev/null +++ b/catalog-ui/src/app/ng2/http-interceptor/headers-interceptor.ts @@ -0,0 +1,61 @@ +import { HttpErrorResponse, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http'; +import { Injectable, Injector } from '@angular/core'; +import { SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { ButtonType } from 'onap-ui-angular/dist/common'; +import { Observable } from 'rxjs/Observable'; +import { ServerErrorResponse } from '../../models/server-error-response'; +import { Cookie2Service } from '../services/cookie.service'; +import { HttpHelperService } from '../services/http-hepler.service'; +import { TranslateService } from '../shared/translator/translate.service'; + +@Injectable() +export class HeadersInterceptor implements HttpInterceptor { + + constructor(private injector: Injector, private cookieService: Cookie2Service, private httpHelperService: HttpHelperService) {} + + intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> { + let authReq = req.clone({ headers: req.headers.set(this.cookieService.getUserIdSuffix(), this.cookieService.getUserId()) + .set('Content-Type', 'application/json; charset=UTF-8') + .set(this.cookieService.getUserIdSuffix(), this.cookieService.getUserId()) + .set(this.cookieService.getUserIdSuffix(), this.cookieService.getUserId()) + }); + + const uuidValue = this.httpHelperService.getUuidValue(authReq.url); + if (uuidValue !== '') { + authReq = authReq.clone({ headers: authReq.headers.set(this.cookieService.getUserIdSuffix(), this.cookieService.getUserId())}); + } + return next.handle(authReq).do( + + (event: HttpEvent<any>) => { /* Do Nothing */ }, + + (err: any) => { + if (err instanceof HttpErrorResponse) { + const errorResponse: ServerErrorResponse = new ServerErrorResponse(err); + const modalService = this.injector.get(SdcUiServices.ModalService); + const translateService = this.injector.get(TranslateService); + + const errorDetails = { + 'Error Code': errorResponse.messageId, + 'Status Code': errorResponse.status + }; + + if (errorResponse.ecompRequestId) { + errorDetails['Transaction ID'] = errorResponse.ecompRequestId; + } + + if (errorResponse.messageId === 'POL5005') { + // Session and Role expiration special handling + modalService.openWarningModal( + 'Warning', + translateService.translate('ERROR_MODAL_TEXT', errorResponse), + 'warn-modal', + [ ] ); + } else { + modalService.openErrorDetailModal('Error', errorResponse.message, 'error-modal', errorDetails); + } + + return Observable.throwError(err); + } + }); + } +} diff --git a/catalog-ui/src/app/ng2/http-interceptor/index.ts b/catalog-ui/src/app/ng2/http-interceptor/index.ts new file mode 100644 index 0000000000..57eb238890 --- /dev/null +++ b/catalog-ui/src/app/ng2/http-interceptor/index.ts @@ -0,0 +1,7 @@ +import { HTTP_INTERCEPTORS } from '@angular/common/http'; +import { HeadersInterceptor } from './headers-interceptor'; +import IStateService = angular.ui.IStateService; + +export const httpInterceptorProviders = [ + { provide: HTTP_INTERCEPTORS, useClass: HeadersInterceptor, multi: true }, +]; diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-models/ui-component-to-upgrade.ts b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-models/ui-component-to-upgrade.ts index 97fb71e210..17e5ea7ef1 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-models/ui-component-to-upgrade.ts +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-models/ui-component-to-upgrade.ts @@ -21,7 +21,7 @@ export class ServiceContainerToUpgradeUiObject extends UiBaseObject { this.icon = componentToUpgrade.icon; this.version = componentToUpgrade.version; this.isAlreadyUpgrade = true; - this.isLock = componentToUpgrade.state === ComponentState.CERTIFICATION_IN_PROGRESS || componentToUpgrade.state === ComponentState.NOT_CERTIFIED_CHECKOUT; + this.isLock = componentToUpgrade.state === ComponentState.NOT_CERTIFIED_CHECKOUT; this.vspInstances = []; } diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component.html b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component.html index f77c3410a6..c1e9529869 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component.html +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component.html @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <div class="components-to-upgrade-list-item"> <div class="component-to-upgrade-data"> <div class="component-to-upgrade-icon small sprite-services-icons {{upgradedComponent.icon}}"></div> diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component.html b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component.html index b97e41444c..5c49735a81 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component.html +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component.html @@ -13,8 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - <div class="components-to-upgrade-list-item "> <div class="component-to-upgrade-data"> <sdc-checkbox class="component-to-upgrade-checkbox" diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.component.ts b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.component.ts index 9ae73497ef..613caa4b8d 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.component.ts +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.component.ts @@ -29,8 +29,7 @@ import {AutomatedUpgradeService} from "./automated-upgrade.service"; @Component({ selector: 'upgrade-vsp', templateUrl: './automated-upgrade.component.html', - styleUrls: ['./automated-upgrade.component.less'], - providers: [TranslateService] + styleUrls: ['./automated-upgrade.component.less'] }) export class AutomatedUpgradeComponent { diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.module.ts b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.module.ts index 19f6412071..8a4e8fb660 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.module.ts +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.module.ts @@ -2,16 +2,15 @@ * Created by ob0695 on 4/18/2018. */ import { NgModule } from "@angular/core"; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; -import {CommonModule} from "@angular/common"; -import {AutomatedUpgradeStatusComponent} from "./automated-upgrade-status/automated-upgrade-status.component"; -import {AutomatedUpgradeComponent} from "./automated-upgrade.component"; -import {UpgradeListItemComponent} from "./automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component"; -import {UpgradeListItemStatusComponent} from "./automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component"; -import {TranslateService} from "../../shared/translator/translate.service"; -import {UpgradeListItemInnerContent} from "./automated-upgrade-ui-components/list-item-inner-content/list-item-inner-content.component"; -import {UpgradeLineItemComponent} from "./automated-upgrade-ui-components/upgrade-line-item/upgrade-line-item.component"; -import {UpgradeListItemOrderPipe} from "./automated-upgrade-ui-components/list-item-order-pipe/list-item-order-pipe"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import { CommonModule } from "@angular/common"; +import { AutomatedUpgradeStatusComponent } from "./automated-upgrade-status/automated-upgrade-status.component"; +import { AutomatedUpgradeComponent } from "./automated-upgrade.component"; +import { UpgradeListItemComponent } from "./automated-upgrade-ui-components/upgrade-list-item/upgrade-list-item.component"; +import { UpgradeListItemStatusComponent } from "./automated-upgrade-ui-components/upgrade-list-item-status/upgrade-list-status-item.component"; +import { UpgradeListItemInnerContent } from "./automated-upgrade-ui-components/list-item-inner-content/list-item-inner-content.component"; +import { UpgradeLineItemComponent } from "./automated-upgrade-ui-components/upgrade-line-item/upgrade-line-item.component"; +import { UpgradeListItemOrderPipe } from "./automated-upgrade-ui-components/list-item-order-pipe/list-item-order-pipe"; @NgModule({ declarations: [ @@ -27,8 +26,7 @@ import {UpgradeListItemOrderPipe} from "./automated-upgrade-ui-components/list-i exports: [], entryComponents: [ AutomatedUpgradeComponent, AutomatedUpgradeStatusComponent - ], - providers: [TranslateService] + ] }) export class AutomatedUpgradeModule { }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.service.ts b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.service.ts index 0acfececaa..14ca7f0947 100644 --- a/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.service.ts +++ b/catalog-ui/src/app/ng2/pages/automated-upgrade/automated-upgrade.service.ts @@ -1,19 +1,15 @@ -import {SdcUiComponents} from "sdc-ui/lib/angular"; -import {Injectable, Inject} from "@angular/core"; -import {IModalConfig} from "sdc-ui/lib/angular/modals/models/modal-config"; -import {AutomatedUpgradeComponent} from "./automated-upgrade.component"; -import {Component} from "../../../models/components/component"; -import {ComponentServiceNg2} from "../../services/component-services/component.service"; -import {GeneralStatus, ComponentType} from "../../../utils/constants"; -import {IDependenciesServerResponse} from "../../services/responses/dependencies-server-response"; -import {AutomatedUpgradeStatusComponent} from "./automated-upgrade-status/automated-upgrade-status.component"; -import {AutomatedUpgradeStatusResponse} from "../../services/responses/automated-upgrade-response"; +import { SdcUiComponents, SdcUiCommon, SdcUiServices } from "onap-ui-angular"; +import { Injectable, ComponentRef } from "@angular/core"; +import { AutomatedUpgradeComponent } from "./automated-upgrade.component"; +import { Component } from "../../../models/components/component"; +import { ComponentServiceNg2 } from "../../services/component-services/component.service"; +import { GeneralStatus, ComponentType } from "../../../utils/constants"; +import { IDependenciesServerResponse } from "../../services/responses/dependencies-server-response"; +import { AutomatedUpgradeStatusComponent } from "./automated-upgrade-status/automated-upgrade-status.component"; +import { AutomatedUpgradeStatusResponse } from "../../services/responses/automated-upgrade-response"; +import { TranslateService, ITranslateArgs } from "../../shared/translator/translate.service"; +import { ServiceContainerToUpgradeUiObject, AllottedResourceInstanceUiObject, VspInstanceUiObject } from "./automated-upgrade-models/ui-component-to-upgrade"; import Dictionary = _.Dictionary; -import {TranslateService, ITranslateArgs} from "../../shared/translator/translate.service"; -import { - ServiceContainerToUpgradeUiObject, - AllottedResourceInstanceUiObject, VspInstanceUiObject -} from "./automated-upgrade-models/ui-component-to-upgrade"; export interface IAutomatedUpgradeRequestObj { serviceId:string; @@ -30,8 +26,9 @@ export class AutomatedUpgradeService { private vspComponent:Component; private uiComponentsToUpgrade:Array<ServiceContainerToUpgradeUiObject>; private componentType:string; + private modalInstance: ComponentRef<SdcUiComponents.ModalComponent>; - constructor(private modalService:SdcUiComponents.ModalService, + constructor(private modalService:SdcUiServices.ModalService, private componentService:ComponentServiceNg2, private translateService:TranslateService) { } @@ -69,21 +66,21 @@ export class AutomatedUpgradeService { } private disabledAllModalButtons = ():void => { - this.modalService.getCurrentInstance().innerModalContent.instance.disabled = true; - this.modalService.getCurrentInstance().buttons[0].show_spinner = true; - this.modalService.getCurrentInstance().buttons[1].disabled = true; + this.modalInstance.instance.innerModalContent.instance.disabled = true; + this.modalInstance.instance.buttons[0].show_spinner = true; + this.modalInstance.instance.buttons[1].disabled = true; } public changeUpgradeButtonState = (isDisabled:boolean):void => { - if (this.modalService.getCurrentInstance().buttons[0].disabled !== isDisabled) { - this.modalService.getCurrentInstance().buttons[0].disabled = isDisabled; + if (this.modalInstance.instance.buttons[0].disabled !== isDisabled) { + this.modalInstance.instance.buttons[0].disabled = isDisabled; } } //TODO We will need to replace this function after sdc-ui modal new design, this is just a workaround public automatedUpgrade = ():void => { - let selectedServices = this.modalService.getCurrentInstance().innerModalContent.instance.selectedComponentsToUpgrade; + let selectedServices = this.modalInstance.instance.innerModalContent.instance.selectedComponentsToUpgrade; this.disabledAllModalButtons(); this.componentService.automatedUpgrade(this.vspComponent.componentType, this.vspComponent.uniqueId, this.convertToServerRequest(selectedServices)).subscribe((automatedUpgradeStatus:any) => { @@ -105,11 +102,11 @@ export class AutomatedUpgradeService { }); let statusModalTitle = this.getTextByComponentType("_UPGRADE_STATUS_TITLE"); - this.modalService.getCurrentInstance().setTitle(statusModalTitle); - this.modalService.getCurrentInstance().getButtons().splice(0, 1); // Remove the upgrade button - this.modalService.getCurrentInstance().buttons[0].disabled = false; // enable close again - this.modalService.getCurrentInstance().innerModalContent.destroy(); - this.modalService.createInnnerComponent(AutomatedUpgradeStatusComponent, { + this.modalInstance.instance.setTitle(statusModalTitle); + this.modalInstance.instance.getButtons().splice(0, 1); // Remove the upgrade button + this.modalInstance.instance.buttons[0].disabled = false; // enable close again + this.modalInstance.instance.innerModalContent.destroy(); + this.modalService.createInnnerComponent(this.modalInstance, AutomatedUpgradeStatusComponent, { upgradedComponentsList: upgradedComponent, upgradeStatusMap: statusMap, statusText: this.getStatusText(statusMap) @@ -250,10 +247,10 @@ export class AutomatedUpgradeService { let modalTitle = this.getTextByComponentType("_UPGRADE_TITLE"); let certificationText = isAfterCertification ? this.getTextByComponentType("_CERTIFICATION_STATUS_TEXT", {resourceName: this.vspComponent.name}) : undefined; - let upgradeVspModalConfig:IModalConfig = { + let upgradeVspModalConfig = { title: modalTitle, size: "md", - type: "custom", + type: SdcUiCommon.ModalType.custom, testId: "upgradeVspModal", buttons: [ { @@ -266,10 +263,11 @@ export class AutomatedUpgradeService { }, {text: 'CLOSE', size: 'sm', closeModal: true, type: 'secondary'} - ] - }; + ] as SdcUiCommon.IModalButtonComponent[] + } as SdcUiCommon.IModalConfig; - this.modalService.openCustomModal(upgradeVspModalConfig, AutomatedUpgradeComponent, { + this.modalInstance = this.modalService.openModal(upgradeVspModalConfig); + this.modalService.createInnnerComponent(this.modalInstance, AutomatedUpgradeComponent, { componentsToUpgrade: this.uiComponentsToUpgrade, informationText: informationalText, certificationStatusText: certificationText diff --git a/catalog-ui/src/app/ng2/pages/catalog/__snapshots__/catalog.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/catalog/__snapshots__/catalog.component.spec.ts.snap new file mode 100644 index 0000000000..d6091cd599 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/__snapshots__/catalog.component.spec.ts.snap @@ -0,0 +1,164 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`catalog component should match current snapshot of catalog component 1`] = ` +<catalog + $state={[Function Object]} + cacheService={[Function Object]} + catalogService={[Function Object]} + componentShouldReload={[Function Function]} + defaultFilterParams={[Function Object]} + getTestIdForCheckboxByText={[Function Function]} + initCatalogData={[Function Function]} + initLeftSwitch={[Function Function]} + initScopeMembers={[Function Function]} + isDefaultFilter={[Function Function]} + loaderService={[Function Object]} + resourceNamePipe={[Function Object]} + sdcConfig={[Function Object]} + sdcMenu={[Function Object]} + updateCatalogItems={[Function Function]} +> + <div + class="sdc-catalog-container" + > + <div + class="w-sdc-main-container" + > + <div + class="i-sdc-designer-leftbar-section-left-switch-header" + > + <div + class="i-sdc-designer-leftbar-section-left-switch-header-text" + > + + </div> + <div + class="i-sdc-designer-leftbar-section-left-switch-header-icon sprite-new arrow-up-small" + > + Â + </div> + + </div> + <div + class="sdc-catalog-body-container w-sdc-left-sidebar i-sdc-designer-left-sidebar" + perfectscrollbar="" + > + <div + class="sdc-catalog-leftbar-container" + > + <div + class="sdc-catalog-type-filter-container" + > + <div + class="i-sdc-designer-leftbar-section-title pointer" + > + <span + class="i-sdc-designer-leftbar-section-title-icon" + /> + <span + class="i-sdc-designer-leftbar-section-title-text" + data-tests-id="typeFilterTitle" + > + Type + </span> + </div> + <div + class="i-sdc-designer-leftbar-section-content" + > + <sdc-checklist /> + </div> + </div> + <div + class="sdc-catalog-categories-filter-container" + > + <div + class="i-sdc-designer-leftbar-section-title pointer" + > + <span + class="i-sdc-designer-leftbar-section-title-icon" + /> + <span + class="i-sdc-designer-leftbar-section-title-text" + data-tests-id="categoriesFilterTitle" + > + Categories + </span> + </div> + <div + class="i-sdc-designer-leftbar-section-content" + > + <sdc-checklist /> + </div> + </div> + <div + class="sdc-catalog-status-filter-container" + > + <div + class="i-sdc-designer-leftbar-section-title pointer" + > + <span + class="i-sdc-designer-leftbar-section-title-icon" + /> + <span + class="i-sdc-designer-leftbar-section-title-text" + data-tests-id="statusFilterTitle" + > + Status + </span> + </div> + <div + class="i-sdc-designer-leftbar-section-content" + > + <sdc-checklist /> + </div> + </div> + </div> + </div> + <div + class="w-sdc-main-right-container w-sdc-catalog-main" + infinitescroll="" + > + <div + class="catalog-top-bar" + > + <div + class="w-sdc-dashboard-catalog-items-header" + /> + <div + class="catalog-top-right-bar" + > + <span + class="w-sdc-dashboard-catalog-header-order1" + > + + </span> + Â Â + <a + class="w-sdc-dashboard-catalog-sort" + data-tests-id="sort-by-last-update" + > + + </a> + Â + + <a + class="w-sdc-dashboard-catalog-sort" + data-tests-id="sort-by-alphabetical" + > + + </a> + Â + + </div> + </div> + <div + class="catalog-elements-list" + > + + </div> + </div> + </div> + <top-nav /> + </div> +</catalog> +`; diff --git a/catalog-ui/src/app/ng2/pages/catalog/catalog.component.html b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.html new file mode 100644 index 0000000000..4a13bee973 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.html @@ -0,0 +1,101 @@ +<div class="sdc-catalog-container"> + <div class="w-sdc-main-container"> + <div class="i-sdc-designer-leftbar-section-left-switch-header" + (click)="showCatalogSelector=!showCatalogSelector"> + <div class="i-sdc-designer-leftbar-section-left-switch-header-text"> + {{selectedCatalogItem.title}} + </div> + <div class="i-sdc-designer-leftbar-section-left-switch-header-icon sprite-new arrow-up-small"> </div> + + <div class="sdc-catalog-selector-wrapper" *ngIf="showCatalogSelector"> + <div class="sdc-catalog-selector-item" + *ngFor="let leftSwitchItem of catalogSelectorItems" + (click)="selectLeftSwitchItem(leftSwitchItem)"> + <span>{{leftSwitchItem.title}}</span> + </div> + </div> + </div> + + <!-- LEFT SIDE --> + <div perfectScrollbar class="sdc-catalog-body-container w-sdc-left-sidebar i-sdc-designer-left-sidebar"> + <div class="sdc-catalog-leftbar-container"> + <div class="sdc-catalog-type-filter-container"> + <div class="i-sdc-designer-leftbar-section-title pointer" + (click)="sectionClick('type')" + [ngClass]="{'expanded': expandedSection.indexOf('type') !== -1}"> + <span class="i-sdc-designer-leftbar-section-title-icon"></span> + <span class="i-sdc-designer-leftbar-section-title-text" + data-tests-id="typeFilterTitle">Type</span> + </div> + <div class="i-sdc-designer-leftbar-section-content"> + <sdc-checklist [checklistModel]="typesChecklistModel" [testId]="'checklist-type'" + (checkedChange)="gui.onComponentTypeClick()"></sdc-checklist> + </div> + </div> + + <div class="sdc-catalog-categories-filter-container"> + <div class="i-sdc-designer-leftbar-section-title pointer" (click)="sectionClick('category')" + [ngClass]="{'expanded': expandedSection.indexOf('category') !== -1}"> + <span class="i-sdc-designer-leftbar-section-title-icon"></span> + <span class="i-sdc-designer-leftbar-section-title-text" data-tests-id="categoriesFilterTitle">Categories</span> + </div> + <div class="i-sdc-designer-leftbar-section-content"> + <sdc-checklist [checklistModel]="categoriesChecklistModel" [testId]="'checklist-category'" + (checkedChange)="gui.onCategoryClick()"></sdc-checklist> + </div> + </div> + + <!-- STATUS --> + <div class="sdc-catalog-status-filter-container"> + <div class="i-sdc-designer-leftbar-section-title pointer" (click)="sectionClick('status')" + [ngClass]="{'expanded': expandedSection.indexOf('status') !== -1}"> + <span class="i-sdc-designer-leftbar-section-title-icon"></span> + <span class="i-sdc-designer-leftbar-section-title-text" + data-tests-id="statusFilterTitle">Status</span> + </div> + + <div class="i-sdc-designer-leftbar-section-content"> + <sdc-checklist [checklistModel]="statusChecklistModel" [testId]="'checklist-status'" + (checkedChange)="gui.onStatusClick()"></sdc-checklist> + </div> + </div> + + </div> + </div> + + <!-- RIGHT SIDE --> + <div class="w-sdc-main-right-container w-sdc-catalog-main" infiniteScroll + (infiniteScroll)="raiseNumberOfElementToDisplay()" [infiniteScrollDistance]="100"> + <!-- HEADER --> + <div class="catalog-top-bar"> + <div class="w-sdc-dashboard-catalog-items-header" + [innerHTML]="getNumOfElements(catalogFilteredItems.length)"> + + </div> + <div class="catalog-top-right-bar"> + <span class="w-sdc-dashboard-catalog-header-order1">{{'SORT_CAPTION'|translate}}</span> + <a class="w-sdc-dashboard-catalog-sort" data-tests-id="sort-by-last-update" + [ngClass]="{'blue' : sortBy==='lastUpdateDate'}" + (click)="order('lastUpdateDate')">{{'SORT_BY_UPDATE_DATE'|translate}}</a> + <span *ngIf="sortBy === 'lastUpdateDate'" class="w-sdc-catalog-sort-arrow" + [ngClass]="{'down': reverse, 'up':!reverse}"></span> + <a class="w-sdc-dashboard-catalog-sort" data-tests-id="sort-by-alphabetical" + [ngClass]="{'blue' : sortBy!=='lastUpdateDate'}" + (click)="order('resourceName')">{{'SORT_ALPHABETICAL'|translate}}</a> + <span *ngIf="sortBy !== 'lastUpdateDate'" class="w-sdc-catalog-sort-arrow" + [ngClass]="{'down': reverse, 'up':!reverse}"></span> + </div> + </div> + + <div class='catalog-elements-list'> + <!-- Tile new --> + <ui-tile *ngFor="let component of catalogFilteredSlicedItems" + [component]="component" (onTileClick)="goToComponent(component)"></ui-tile> + <!-- Tile new --> + </div> + </div> + </div> + + <top-nav [topLvlSelectedIndex]="1" [searchTerm]="search.filterTerm" + (searchTermChange)="gui.changeFilterTerm($event)" [version]="version"></top-nav> +</div> diff --git a/catalog-ui/src/app/ng2/pages/catalog/catalog.component.less b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.less new file mode 100644 index 0000000000..036db8d94d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.less @@ -0,0 +1,113 @@ +@import '../../../../assets/styles/variables'; +@import '../../../../assets/styles/mixins'; + +.w-sdc-catalog-main { + height: 100%; + overflow-y: scroll; + padding: 10px 50px; + + .catalog-top-bar { + display: flex; + justify-content:space-between; + padding: 0px 10px; + } + + .catalog-elements-list { + display: flex; + flex-wrap: wrap; + flex-direction: row; + } + +} + + +.i-sdc-designer-left-sidebar { + margin-top: 43px; +} + +.sdc-catalog-selector-item { + text-transform: none; + line-height: 40px; + font-family: OpenSans-Bold, sans-serif; + font-size: 14px; + color: #000000; + background-color: #ffffff; + padding-left: 20px; +} + +.sdc-catalog-selector-wrapper { + position: absolute; + left: 0px; + top: 42px; + width: 241px; + height: auto; + cursor: pointer; + opacity: 1; + z-index: 1000; + box-shadow: 1px 2px 3px #b1b1b1; +} + +.sdc-catalog-selector-item { + text-transform: none; + line-height: 40px; + font-family: OpenSans-Bold, sans-serif; + font-size: 14px; + color: @main_color_l; + background-color: @main_color_p; + padding-left: 20px; +} + +.sdc-catalog-selector-item:hover { + color: @main_color_a; + background-color: @tlv_color_v; +} + +.i-sdc-designer-leftbar-section-left-switch-header { + text-transform: uppercase; + .l_14_m; + line-height: 40px; + width: 243px; + + font-family: OpenSans-Bold, sans-serif; + font-size: 14px; + + color: @main_color_a; + background-color: @tlv_color_t; + border: solid 1px fade(@main_color_t, 40%); + cursor: pointer; + opacity: 1; + z-index: 9999; + position: relative; +} + +.i-sdc-designer-leftbar-section-left-switch-header-text { + display: inline-block; + width: 180px; + margin-left: 20px; +} + +.i-sdc-designer-leftbar-section-left-switch-header-icon { + display: inline-block; + vertical-align: middle; +} + +.w-sdc-dashboard-catalog-items-header { + color: @main_color_m; + font-family: OpenSans-Regular, sans-serif; + font-size: 12px; + display: inline-block; + font-style: normal; size: 12px; + margin-left: 11px; + font-weight: normal; + b { + font-family: OpenSans-Bold, sans-serif; + color: @main_color_l; + font-weight: bold; + } +} + +.w-sdc-dashboard-catalog-header-order1 { + font-style: normal; + font-size: 12px; + font-weight: 800; +} diff --git a/catalog-ui/src/app/ng2/pages/catalog/catalog.component.spec.ts b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.spec.ts new file mode 100644 index 0000000000..ff27ec77fd --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.spec.ts @@ -0,0 +1,649 @@ + +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import {ConfigureFn, configureTests} from "../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import { CacheService} from "../../../../app/services-ng2"; +import {CatalogComponent} from "./catalog.component"; +import { SdcUiServices } from "onap-ui-angular"; +import { SdcConfigToken } from "../../config/sdc-config.config"; +import { SdcMenuToken} from "../../config/sdc-menu.config"; +import { ResourceNamePipe } from "../../pipes/resource-name.pipe"; +import { CatalogService } from "../../services/catalog.service"; +import {TranslatePipe} from "../../shared/translator/translate.pipe"; +import {TranslateService} from "../../shared/translator/translate.service"; +import {Observable} from "rxjs"; +import {LoaderService} from "onap-ui-angular/dist/loader/loader.service"; +import {categoriesElements} from "../../../../jest/mocks/categories.mock"; +import {sdcMenu} from "../../../../jest/mocks/sdc-menu.mock"; +import {IEntityFilterObject} from "../../pipes/entity-filter.pipe"; + + + + + +describe('catalog component', () => { + + let fixture: ComponentFixture<CatalogComponent>; + + //Data variables + let catalogSelectorItemsMock; + let checkListModelMock; + let filterParamsMock; + let checkboxesFilterMock; + let checkboxesFilterKeysMock; + + + //Service variables + let stateServiceMock; + let cacheServiceMock: Partial<CacheService>; + let loaderServiceMock: Partial<LoaderService>; + let catalogServiceMock: Partial<CatalogService>; + + + beforeEach( + + async(() => { + console.info = jest.fn(); + catalogSelectorItemsMock = [ + { + value: 0, + title: 'Active Items', + header: 'Active' + }, + { + value: 1, + title: 'Archive', + header: 'Archived' + } + ]; + checkListModelMock = { + checkboxes: [ + {label: "VF", disabled: false, isChecked: false, testId: "checkbox-vf", value: "Resource.VF"}, + {label: "VFC", disabled: false, isChecked: false, testId: "checkbox-vfc", value: "Resource.VFC", + subLevelChecklist: {checkboxes:[{label: "VFD", disabled: false, isChecked: false, testId: "checkbox-vfd", value: "Resource.VFD"}], + selectedValues: ["Resource.VFD"]} + }, + {label: "CR", disabled: false, isChecked: false, testId: "checkbox-cr", value: "Resource.CR", + subLevelChecklist: { checkboxes:[{label: "VF", disabled: false, isChecked: false, testId: "checkbox-vf", value: "Resource.VF"}], + selectedValues: []} + }], + selectedValues: ["Resource.VF"] + } + filterParamsMock = { + active: true, + categories: ["resourceNewCategory.allotted resource.allotted resource", "resourceNewCategory.allotted resource.contrail route", "resourceNewCategory.application l4+.application server"], + components: ["Resource.VF", "Resource.VFC"], + order: ["lastUpdateDate", true], + statuses: ["inDesign"], + term: "Vf" + } + checkboxesFilterMock = { + selectedCategoriesModel: ["serviceNewCategory.network l4+", "resourceNewCategory.allotted resource.allotted resource"], + selectedComponentTypes: ["Resource.VF", "Resource.VFC"], + selectedResourceSubTypes: ["VF", "VFC"], + selectedStatuses: ["NOT_CERTIFIED_CHECKOUT", "NOT_CERTIFIED_CHECKIN"] + }; + checkboxesFilterKeysMock = { + categories:{_main: ["serviceNewCategory.network l4+"]}, + componentTypes: { Resource: ["Resource.VF", "Resource.VFC"], _main: ["Resource.VFC"]}, + statuses: {_main: ["inDesign"]} + } + + stateServiceMock = { + go: jest.fn(), + current: jest.fn() + }; + cacheServiceMock = { + get: jest.fn().mockImplementation(()=> categoriesElements), + set: jest.fn(), + contains: jest.fn().mockImplementation(()=> true) + }; + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + catalogServiceMock = { + //TODO create mock function of archive + getCatalog: jest.fn().mockImplementation(()=> Observable.of(categoriesElements)), + getArchiveCatalog: jest.fn().mockImplementation(()=> Observable.of(categoriesElements)) + }; + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [CatalogComponent, TranslatePipe], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: SdcConfigToken, useValue: {}}, + {provide: SdcMenuToken, useValue: sdcMenu}, + {provide: "$state", useValue: stateServiceMock }, + {provide: CacheService, useValue: cacheServiceMock }, + {provide: CatalogService, useValue: catalogServiceMock }, + {provide: ResourceNamePipe, useValue: {}}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock }, + {provide: TranslateService, useValue: {}} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(CatalogComponent); + }); + }) + ); + + + it('should match current snapshot of catalog component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it ('should call on catalog component onInit' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.initGui = jest.fn(); + component.componentInstance.initLeftSwitch = jest.fn(); + component.componentInstance.initScopeMembers = jest.fn(); + component.componentInstance.loadFilterParams = jest.fn(); + component.componentInstance.initCatalogData = jest.fn(); + component.componentInstance.ngOnInit(); + expect(component.componentInstance.initGui).toHaveBeenCalled(); + expect(component.componentInstance.initLeftSwitch).toHaveBeenCalled(); + expect(component.componentInstance.initScopeMembers).toHaveBeenCalled(); + expect(component.componentInstance.loadFilterParams).toHaveBeenCalled(); + expect(component.componentInstance.initCatalogData).toHaveBeenCalled(); + }); + + it ('should call on catalog component initLeftSwitch' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.initLeftSwitch(); + expect(component.componentInstance.showCatalogSelector).toEqual(false); + expect(component.componentInstance.catalogSelectorItems).toEqual(catalogSelectorItemsMock); + expect(component.componentInstance.selectedCatalogItem).toEqual(catalogSelectorItemsMock[0]); + }); + + it ('should call on catalog component initCatalogData and selectedCatalogItem is archive ' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.getArchiveCatalogItems = jest.fn(); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[1]; + component.componentInstance.initCatalogData(); + expect(component.componentInstance.getArchiveCatalogItems).toHaveBeenCalled(); + }); + + it ('should call on catalog component initCatalogData and selectedCatalogItem is active ' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.getActiveCatalogItems = jest.fn(); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[0]; + component.componentInstance.initCatalogData(); + expect(component.componentInstance.getActiveCatalogItems).toHaveBeenCalled(); + }); + + it ('should call on catalog component initScopeMembers' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.makeSortedCategories = jest.fn().mockImplementation(()=> categoriesElements); + component.componentInstance.initCategoriesMap = jest.fn(); + component.componentInstance.initCheckboxesFilter = jest.fn(); + component.componentInstance.initCheckboxesFilterKeys = jest.fn(); + component.componentInstance.buildCheckboxLists = jest.fn(); + component.componentInstance.initScopeMembers(); + expect(component.componentInstance.numberOfItemToDisplay).toEqual(0); + expect(component.componentInstance.categories).toEqual(categoriesElements); + expect(component.componentInstance.confStatus).toEqual(component.componentInstance.sdcMenu.statuses); + expect(component.componentInstance.expandedSection).toEqual( ["type", "category", "status"]); + expect(component.componentInstance.catalogItems).toEqual([]); + expect(component.componentInstance.search).toEqual({FilterTerm: ""}); + expect(component.componentInstance.initCategoriesMap).toHaveBeenCalled(); + expect(component.componentInstance.initCheckboxesFilter).toHaveBeenCalled(); + expect(component.componentInstance.initCheckboxesFilterKeys).toHaveBeenCalled(); + expect(component.componentInstance.buildCheckboxLists).toHaveBeenCalled(); + expect(component.componentInstance.version).toEqual(categoriesElements); + expect(component.componentInstance.sortBy).toEqual('lastUpdateDate'); + expect(component.componentInstance.reverse).toEqual(true); + }); + + it ('should call on catalog component buildCheckboxLists ' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.buildChecklistModelForTypes = jest.fn(); + component.componentInstance.buildChecklistModelForCategories = jest.fn(); + component.componentInstance.buildChecklistModelForStatuses = jest.fn(); + component.componentInstance.buildCheckboxLists(); + expect(component.componentInstance.buildChecklistModelForTypes).toHaveBeenCalled(); + expect(component.componentInstance.buildChecklistModelForCategories).toHaveBeenCalled(); + expect(component.componentInstance.buildChecklistModelForStatuses).toHaveBeenCalled(); + }); + + it ('should call on catalog component getTestIdForCheckboxByText ' , () => { + const component = TestBed.createComponent(CatalogComponent); + let testId = component.componentInstance.getTestIdForCheckboxByText("catalog filter"); + expect(testId).toEqual("checkbox-catalogfilter"); + }); + + it ('should call on catalog component selectLeftSwitchItem with active catalog' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[1]; + component.componentInstance.getActiveCatalogItems = jest.fn(); + component.componentInstance.changeFilterParams = jest.fn(); + component.componentInstance.selectLeftSwitchItem(catalogSelectorItemsMock[0]); + expect(component.componentInstance.getActiveCatalogItems).toBeCalledWith(true); + expect(component.componentInstance.changeFilterParams).toBeCalledWith({"active": true}); + }); + + it ('should call on catalog component selectLeftSwitchItem with archive catalog' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[0]; + component.componentInstance.getArchiveCatalogItems = jest.fn(); + component.componentInstance.changeFilterParams = jest.fn(); + component.componentInstance.selectLeftSwitchItem(catalogSelectorItemsMock[1]); + expect(component.componentInstance.getArchiveCatalogItems).toBeCalledWith(true); + expect(component.componentInstance.changeFilterParams).toBeCalledWith({"active": false}); + }); + + it ('should call on catalog component buildChecklistModelForTypes' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.buildChecklistModelForTypes(); + expect(component.componentInstance.componentTypes).toEqual({ Resource: ['VF', 'VFC', 'CR', 'PNF', 'CP', 'VL'], + Service: null}) + expect(component.componentInstance.typesChecklistModel.checkboxes.length).toEqual(2); + }); + + it ('should call on catalog component buildChecklistModelForCategories' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.categories = categoriesElements; + component.componentInstance.buildChecklistModelForCategories(); + expect(component.componentInstance.categoriesChecklistModel.checkboxes).not.toEqual(null); + }); + + it ('should call on catalog component buildChecklistModelForStatuses' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.categories = categoriesElements; + component.componentInstance.confStatus = sdcMenu.statuses; + component.componentInstance.buildChecklistModelForStatuses(); + expect(component.componentInstance.statusChecklistModel.checkboxes.length).toEqual(3); + }); + + it ('should call on catalog component initCheckboxesFilter' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.initCheckboxesFilter(); + expect(component.componentInstance.checkboxesFilter.selectedComponentTypes).toEqual([]); + expect(component.componentInstance.checkboxesFilter.selectedResourceSubTypes).toEqual([]); + expect(component.componentInstance.checkboxesFilter.selectedCategoriesModel).toEqual([]); + expect(component.componentInstance.checkboxesFilter.selectedStatuses).toEqual([]); + }); + + it ('should call on catalog component initCheckboxesFilterKeys' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.initCheckboxesFilterKeys(); + expect(component.componentInstance.checkboxesFilterKeys.componentTypes).toEqual({ _main: [] }); + expect(component.componentInstance.checkboxesFilterKeys.categories).toEqual({ _main: [] }); + expect(component.componentInstance.checkboxesFilterKeys.statuses).toEqual({ _main: [] }); + }); + + it ('should call on catalog component initCategoriesMap' , () => { + const component = TestBed.createComponent(CatalogComponent); + const categoriesMap = component.componentInstance.initCategoriesMap(categoriesElements); + expect(categoriesMap["resourceNewCategory.allotted resource.allotted resource"].parent.name).toEqual("Allotted Resource"); + expect(categoriesMap["resourceNewCategory.generic"].category.uniqueId).toEqual("resourceNewCategory.generic"); + expect(categoriesMap["serviceNewCategory.voip call control"].category.name).toEqual("VoIP Call Control"); + + }); + + + it ('should call on catalog component selectLeftSwitchItem with active and selectedCatalogItem equal to archived' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.getActiveCatalogItems = jest.fn(); + component.componentInstance.changeFilterParams = jest.fn(); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[1] + component.componentInstance.selectLeftSwitchItem(catalogSelectorItemsMock[0]); + expect(component.componentInstance.getActiveCatalogItems).toHaveBeenCalledWith(true); + expect(component.componentInstance.changeFilterParams).toHaveBeenCalledWith({active: true}) + }); + + it ('should call on catalog component selectLeftSwitchItem with archived and selectedCatalogItem equal to active' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.getArchiveCatalogItems = jest.fn(); + component.componentInstance.changeFilterParams = jest.fn(); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[0] + component.componentInstance.selectLeftSwitchItem(catalogSelectorItemsMock[1]); + expect(component.componentInstance.getArchiveCatalogItems).toBeCalledWith(true); + expect(component.componentInstance.changeFilterParams).toHaveBeenCalledWith({active: false}) + }); + + it ('should call on catalog component sectionClick with section contains in expandedSection' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.expandedSection = ["type", "category", "status"]; + component.componentInstance.sectionClick("type"); + expect(component.componentInstance.expandedSection).toEqual(["category", "status"]) + }); + + it ('should call on catalog component sectionClick with section not contains in expandedSection' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.expandedSection = ["type", "category", "status"]; + component.componentInstance.sectionClick("newItem"); + expect(component.componentInstance.expandedSection).toEqual(["type", "category", "status", "newItem"]) + }); + + it ('should call on catalog component makeFilterParamsFromCheckboxes with selected values' , () => { + const component = TestBed.createComponent(CatalogComponent); + expect(component.componentInstance.makeFilterParamsFromCheckboxes(checkListModelMock)).toEqual(["Resource.VF", "Resource.VFD"]) + }); + + it ('should call on catalog component order with resourceName value' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.changeFilterParams = jest.fn(); + component.componentInstance.filterParams = filterParamsMock + component.componentInstance.order("resourceName"); + expect(component.componentInstance.changeFilterParams).toHaveBeenCalledWith( {"order": ["resourceName", false]}) + }); + + it ('should call on catalog component goToComponent' , () => { + const component = TestBed.createComponent(CatalogComponent); + const componentMock = { uniqueId: "d3e80fed-12f6-4f29-aeb1-771050e5db72", componentType: "RESOURCE"} + component.componentInstance.goToComponent(componentMock); + expect(stateServiceMock.go).toHaveBeenCalledWith('workspace.general', {id: componentMock.uniqueId, type: componentMock.componentType.toLowerCase()}) + + }); + + it ('should call on catalog component getNumOfElements for active catalog' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.selectedCatalogItem = catalogSelectorItemsMock[0] + expect(component.componentInstance.getNumOfElements(3)).toEqual("3 <b>Active</b> Elements found") + + }); + + it ('should call on catalog component raiseNumberOfElementToDisplay with empty catalogFilteredItems' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.catalogFilteredItems = [] + component.componentInstance.raiseNumberOfElementToDisplay(true); + expect(component.componentInstance.numberOfItemToDisplay).toEqual(NaN); + expect(component.componentInstance.catalogFilteredSlicedItems).toEqual([]); + }); + + it ('should call on catalog component raiseNumberOfElementToDisplay with full catalogFilteredItems' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.catalogFilteredItems = [1 , 2 , 3, 4, 5, 6] + component.componentInstance.numberOfItemToDisplay = 2; + component.componentInstance.raiseNumberOfElementToDisplay(false); + expect(component.componentInstance.numberOfItemToDisplay).toEqual(6); + expect(component.componentInstance.catalogFilteredSlicedItems).toEqual([1 , 2 , 3, 4, 5, 6]); + }); + + it ('should call on catalog component componentShouldReload return false' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.isDefaultFilter = jest.fn().mockImplementation(() => false); + cacheServiceMock.get.mockImplementation(()=> "mockConstructor"); + let componentShouldReload = component.componentInstance.componentShouldReload(); + expect(component.componentInstance.cacheService.get()).toEqual(component.componentInstance.$state.current.name); + expect(component.componentInstance.cacheService.contains()).toEqual(true); + expect(component.componentInstance.isDefaultFilter).toHaveBeenCalled(); + expect(componentShouldReload).toEqual(false); + }); + + it ('should call on catalog component componentShouldReload return true' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.isDefaultFilter = jest.fn(); + let componentShouldReload = component.componentInstance.componentShouldReload(); + expect(component.componentInstance.cacheService.get()).not.toEqual(component.componentInstance.$state.current.name); + expect(component.componentInstance.cacheService.contains()).toEqual(true); + expect(componentShouldReload).toEqual(true); + }); + + it ('should call on catalog component getActiveCatalogItems with true' , () => { + const component = TestBed.createComponent(CatalogComponent); + let resp = component.componentInstance.cacheService.get(); + component.componentInstance.updateCatalogItems = jest.fn().mockImplementation((resp) => {}); + component.componentInstance.getActiveCatalogItems(true); + expect(component.componentInstance.loaderService.activate).toHaveBeenCalled(); + expect(component.componentInstance.updateCatalogItems).toHaveBeenCalledWith(resp); + expect(component.componentInstance.loaderService.deactivate).toHaveBeenCalled(); + expect(component.componentInstance.cacheService.set).toHaveBeenCalledWith('breadcrumbsComponentsState', "mockConstructor"); + expect(component.componentInstance.cacheService.set).toHaveBeenCalledWith('breadcrumbsComponents', categoriesElements); + expect(component.componentInstance.catalogService.getCatalog).toHaveBeenCalled(); + }); + + it ('should call on catalog component getActiveCatalogItems with false' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.componentShouldReload = jest.fn(); + component.componentInstance.updateCatalogItems = jest.fn().mockImplementation((resp) => {}); + component.componentInstance.getActiveCatalogItems(false); + expect(component.componentInstance.componentShouldReload).toHaveBeenCalled(); + let resp = component.componentInstance.cacheService.get(); + expect(component.componentInstance.updateCatalogItems).toHaveBeenCalledWith(resp); + }); + + it ('should call on catalog component getActiveCatalogItems with true observable return error' , () => { + const component = TestBed.createComponent(CatalogComponent); + catalogServiceMock.getCatalog.mockImplementation(()=> Observable.throwError('error')); + component.componentInstance.getActiveCatalogItems(true); + expect(component.componentInstance.loaderService.activate).toHaveBeenCalled(); + expect(console.info).toHaveBeenCalledWith('Failed to load catalog CatalogViewModel::getActiveCatalogItems'); + expect(component.componentInstance.loaderService.deactivate).toHaveBeenCalled(); + expect(component.componentInstance.catalogService.getCatalog).toHaveBeenCalled(); + }); + + it ('should call on catalog component getArchiveCatalogItems with true' , () => { + const component = TestBed.createComponent(CatalogComponent); + const resp = component.componentInstance.cacheService.get(); + component.componentInstance.updateCatalogItems = jest.fn().mockImplementation((resp) => {}); + component.componentInstance.getArchiveCatalogItems(true); + expect(component.componentInstance.loaderService.activate).toHaveBeenCalled(); + expect(component.componentInstance.catalogService.getArchiveCatalog).toHaveBeenCalled(); + expect(component.componentInstance.cacheService.set).toHaveBeenCalledWith('archiveComponents', categoriesElements); + expect(component.componentInstance.loaderService.deactivate).toHaveBeenCalled(); + expect(component.componentInstance.updateCatalogItems).toHaveBeenCalledWith(resp) + }); + + it ('should call on catalog component getArchiveCatalogItems with false' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.updateCatalogItems = jest.fn().mockImplementation((resp) => Observable.of()); + component.componentInstance.getArchiveCatalogItems(false); + expect(component.componentInstance.cacheService.contains).toHaveBeenCalled(); + expect(component.componentInstance.cacheService.get).toHaveBeenCalled(); + let resp = component.componentInstance.cacheService.get(); + expect(component.componentInstance.updateCatalogItems).toHaveBeenCalledWith(resp); + }); + + it ('should call on catalog component getArchiveCatalogItems with true observable return error' , () => { + const component = TestBed.createComponent(CatalogComponent); + catalogServiceMock.getArchiveCatalog.mockImplementation(()=> Observable.throwError('error')); + component.componentInstance.getArchiveCatalogItems(true); + expect(component.componentInstance.loaderService.activate).toHaveBeenCalled(); + expect(component.componentInstance.catalogService.getArchiveCatalog).toHaveBeenCalled(); + expect(component.componentInstance.loaderService.deactivate).toHaveBeenCalled(); + expect(console.info).toHaveBeenCalledWith('Failed to load catalog CatalogViewModel::getArchiveCatalogItems'); + }); + + it ('should call on catalog component updateCatalogItems' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.filterCatalogItems = jest.fn(); + component.componentInstance.addFilterTermToComponent = jest.fn(); + component.componentInstance.updateCatalogItems([1, 2, 3]); + expect(component.componentInstance.catalogItems).toEqual([1, 2, 3]); + expect(component.componentInstance.addFilterTermToComponent).toHaveBeenCalled(); + expect(component.componentInstance.filterCatalogItems).toHaveBeenCalled(); + }); + + it ('should call on catalog component applyFilterParamsToView' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.initCheckboxesFilter = jest.fn(); + component.componentInstance.filterCatalogCategories = jest.fn(); + component.componentInstance.applyFilterParamsComponents = jest.fn(); + component.componentInstance.applyFilterParamsCategories = jest.fn(); + component.componentInstance.applyFilterParamsStatuses = jest.fn(); + component.componentInstance.applyFilterParamsOrder = jest.fn(); + component.componentInstance.applyFilterParamsTerm = jest.fn(); + component.componentInstance.filterCatalogItems = jest.fn(); + component.componentInstance.applyFilterParamsToView(filterParamsMock); + expect(component.componentInstance.initCheckboxesFilter).toHaveBeenCalled(); + expect(component.componentInstance.filterCatalogCategories).toHaveBeenCalled(); + expect(component.componentInstance.applyFilterParamsComponents).toHaveBeenCalledWith(filterParamsMock); + expect(component.componentInstance.applyFilterParamsCategories).toHaveBeenCalledWith(filterParamsMock); + expect(component.componentInstance.applyFilterParamsStatuses).toHaveBeenCalledWith(filterParamsMock); + expect(component.componentInstance.applyFilterParamsOrder).toHaveBeenCalledWith(filterParamsMock); + expect(component.componentInstance.applyFilterParamsTerm).toHaveBeenCalledWith(filterParamsMock); + expect(component.componentInstance.filterCatalogItems).toHaveBeenCalled(); + }); + + it ('should call on catalog component filterCatalogCategories' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.makeFilteredCategories = jest.fn(); + component.componentInstance.buildChecklistModelForCategories = jest.fn(); + component.componentInstance.categories = categoriesElements; + component.componentInstance.checkboxesFilter = {selectedComponentTypes: ["firstType", "secondType"]}; + component.componentInstance.filterCatalogCategories(); + expect(component.componentInstance.makeFilteredCategories).toHaveBeenCalledWith(categoriesElements, ["firstType", "secondType"]); + expect(component.componentInstance.buildChecklistModelForCategories).toHaveBeenCalled(); + }); + + it ('should call on catalog component filterCatalogItems' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.makeFilteredItems = jest.fn().mockImplementation(() => [1,2,3]); + component.componentInstance.raiseNumberOfElementToDisplay = jest.fn(); + component.componentInstance.catalogItems = ["firstComponent", "secondComponent"]; + component.componentInstance.checkboxesFilter = {}; + component.componentInstance.search = {}; + component.componentInstance.sortBy = ""; + component.componentInstance.reverse = true; + component.componentInstance.numberOfItemToDisplay = 2; + // component.componentInstance.catalogFilteredItems = component.componentInstance.makeFilteredItems(); + component.componentInstance.filterCatalogItems(); + expect(component.componentInstance.makeFilteredItems).toHaveBeenCalledWith(["firstComponent", "secondComponent"], {}, {}, "",true); + expect(component.componentInstance.raiseNumberOfElementToDisplay).toHaveBeenCalledWith(true); + expect(component.componentInstance.catalogFilteredSlicedItems).toEqual([1,2]); + }); + + it ('should call on catalog component applyFilterParamsToCheckboxes' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToCheckboxes(checkListModelMock, ["Resource.CR", "Resource.VFD", "Resource.VF"]); + expect(checkListModelMock.selectedValues).toEqual(["Resource.VF","Resource.CR"]); + expect(checkListModelMock.checkboxes[1].subLevelChecklist.selectedValues).toEqual(["Resource.VFD"]); + expect(checkListModelMock.checkboxes[2].subLevelChecklist.selectedValues).toEqual(["Resource.VF"]) + }); + + it ('should call on catalog component applyFilterParamsComponents and filterParams.active equal true' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToCheckboxes = jest.fn(); + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.checkboxesFilter = checkboxesFilterMock; + component.componentInstance.catalogSelectorItems = catalogSelectorItemsMock; + component.componentInstance.typesChecklistModel = checkListModelMock; + component.componentInstance.applyFilterParamsComponents(filterParamsMock); + expect(component.componentInstance.applyFilterParamsToCheckboxes).toHaveBeenCalledWith(checkListModelMock, filterParamsMock.components); + expect(component.componentInstance.checkboxesFilter.selectedComponentTypes).toEqual(["Resource.VFC"]); + expect(component.componentInstance.checkboxesFilter.selectedResourceSubTypes).toEqual(["VF", "VFC"]); + expect(component.componentInstance.selectedCatalogItem).toEqual(catalogSelectorItemsMock[0]); + }); + + it ('should call on catalog component applyFilterParamsComponents and filterParams.active equal false' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToCheckboxes = jest.fn(); + filterParamsMock.active = false; + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.checkboxesFilter = checkboxesFilterMock; + component.componentInstance.catalogSelectorItems = catalogSelectorItemsMock; + component.componentInstance.typesChecklistModel = checkListModelMock; + component.componentInstance.applyFilterParamsComponents(filterParamsMock); + expect(component.componentInstance.applyFilterParamsToCheckboxes).toHaveBeenCalledWith(checkListModelMock, filterParamsMock.components); + expect(component.componentInstance.checkboxesFilter.selectedComponentTypes).toEqual(["Resource.VFC"]); + expect(component.componentInstance.checkboxesFilter.selectedResourceSubTypes).toEqual(["VF", "VFC"]); + expect(component.componentInstance.selectedCatalogItem).toEqual(catalogSelectorItemsMock[1]); + }); + + it ('should call on catalog component applyFilterParamsCategories' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToCheckboxes = jest.fn(); + component.componentInstance.categoriesChecklistModel = checkListModelMock; + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.checkboxesFilter = checkboxesFilterMock; + component.componentInstance.applyFilterParamsCategories(filterParamsMock); + expect(component.componentInstance.applyFilterParamsToCheckboxes).toHaveBeenCalledWith(checkListModelMock, filterParamsMock.categories); + expect(component.componentInstance.checkboxesFilter.selectedCategoriesModel).toEqual(["serviceNewCategory.network l4+"]); + }); + + it ('should call on catalog component applyFilterParamsStatuses' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToCheckboxes = jest.fn(); + + component.componentInstance.statusChecklistModel = checkListModelMock; + component.componentInstance.checkboxesFilterKeys = checkboxesFilterKeysMock; + component.componentInstance.checkboxesFilter = checkboxesFilterMock; + component.componentInstance.confStatus = sdcMenu.statuses; + component.componentInstance.applyFilterParamsStatuses(filterParamsMock); + expect(component.componentInstance.applyFilterParamsToCheckboxes).toHaveBeenCalledWith(checkListModelMock, filterParamsMock.statuses); + expect(component.componentInstance.checkboxesFilter.selectedStatuses).toEqual(["NOT_CERTIFIED_CHECKOUT", "NOT_CERTIFIED_CHECKIN"]); + }); + + it ('should call on catalog component applyFilterParamsOrder' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsOrder(filterParamsMock); + expect(component.componentInstance.sortBy).toEqual("lastUpdateDate"); + expect(component.componentInstance.reverse).toEqual( true); + }); + + it ('should call on catalog component applyFilterParamsTerm' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsTerm(filterParamsMock); + expect(component.componentInstance.search.filterTerm).toEqual("Vf"); + }); + + // it ('should call on catalog component loadFilterParams' , () => { + // const component = TestBed.createComponent(CatalogComponent); + // component.componentInstance.$state = {params: {}}; + // component.componentInstance.loadFilterParams(); + // }); + + it ('should call on catalog component changeFilterParams' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToView = jest.fn(); + component.componentInstance.filterParams = { active: true, categories: [], components: [], order: ["lastUpdateDate", true], statuses: [], term: ""}; + component.componentInstance.$state.go = jest.fn().mockImplementation(() => Promise.resolve({ json: () => [] })); + const newParams = {"filter.active": true, "filter.categories": "resourceNewCategory.allotted resource.allotted resource,resourceNewCategory.allotted resource.contrail route,resourceNewCategory.application l4+.application server", "filter.components": "Resource.VF,Resource.VFC", "filter.order": "-lastUpdateDate", "filter.statuses": "inDesign", "filter.term": "Vf"} + component.componentInstance.changeFilterParams(filterParamsMock); + expect(component.componentInstance.filterParams).toEqual(filterParamsMock); + expect(component.componentInstance.$state.go).toHaveBeenCalledWith('.',newParams, {location: 'replace', notify: false}); + expect(component.componentInstance.applyFilterParamsToView).toHaveBeenCalledWith(filterParamsMock); + }); + + it ('should call on catalog component changeFilterParams and rebuild equal true' , () => { + const component = TestBed.createComponent(CatalogComponent); + component.componentInstance.applyFilterParamsToView = jest.fn(); + component.componentInstance.makeFilterParamsFromCheckboxes = jest.fn(); + component.componentInstance.buildCheckboxLists = jest.fn(); + component.componentInstance.filterParams = { active: true, categories: [], components: [], order: ["lastUpdateDate", true], statuses: [], term: ""}; + component.componentInstance.$state.go = jest.fn().mockImplementation(() => Promise.resolve({ json: () => [] })); + const newParams = {"filter.active": true, "filter.categories": "resourceNewCategory.allotted resource.allotted resource,resourceNewCategory.allotted resource.contrail route,resourceNewCategory.application l4+.application server", "filter.components": "Resource.VF,Resource.VFC", "filter.order": "-lastUpdateDate", "filter.statuses": "inDesign", "filter.term": "Vf"} + component.componentInstance.typesChecklistModel = checkListModelMock; + component.componentInstance.categoriesChecklistModel = checkListModelMock; + component.componentInstance.statusChecklistModel = checkListModelMock; + component.componentInstance.changeFilterParams(filterParamsMock, true); + expect(component.componentInstance.filterParams).toEqual(filterParamsMock); + expect(component.componentInstance.$state.go).toHaveBeenCalledWith('.',newParams, {location: 'replace', notify: false}); + //expect(component.componentInstance.makeFilterParamsFromCheckboxes).toHaveBeenCalledWith(component.componentInstance.typesChecklistModel); + //expect(component.componentInstance.buildCheckboxLists).toHaveBeenCalled(); + expect(component.componentInstance.applyFilterParamsToView).toHaveBeenCalledWith(filterParamsMock); + }); + + it ('should call on catalog component makeFilteredCategories' , () => { + const component = TestBed.createComponent(CatalogComponent); + const categoryMock = [{"name":"Network L1-3","normalizedName":"network l1-3","uniqueId":"serviceNewCategory.network l1-3","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}]; + cacheServiceMock.get.mockImplementation(()=> categoryMock); + const resp = component.componentInstance.makeFilteredCategories(categoriesElements, checkboxesFilterMock.selectedComponentTypes); + expect(component.componentInstance.cacheService.get).toHaveBeenCalledWith("resourceCategories"); + expect(resp).toEqual(categoryMock); + }); + + it ('should call on catalog component makeFilteredCategories return unique elements' , () => { + const component = TestBed.createComponent(CatalogComponent); + const categoryMock = [{"name":"Network L1-3","normalizedName":"network l1-3","uniqueId":"serviceNewCategory.network l1-3","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}, + {"name":"Network L1-3","normalizedName":"network l1-3","uniqueId":"serviceNewCategory.network l1-3","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}, + {"name":"Network Service","normalizedName":"network service","uniqueId":"serviceNewCategory.network service","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}]; + const categoryUniqueMock = [{"name":"Network L1-3","normalizedName":"network l1-3","uniqueId":"serviceNewCategory.network l1-3","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}, + {"name":"Network Service","normalizedName":"network service","uniqueId":"serviceNewCategory.network service","icons":["network_l_1-3"],"subcategories":null,"version":null,"ownerId":null,"empty":false,"type":null}]; + cacheServiceMock.get.mockImplementation(()=> categoryMock); + checkboxesFilterMock.selectedComponentTypes = ["SERVICE", "Resource.VF"]; + const resp = component.componentInstance.makeFilteredCategories(categoriesElements, checkboxesFilterMock.selectedComponentTypes); + expect(component.componentInstance.cacheService.get).toHaveBeenCalledWith("resourceCategories"); + expect(resp).toEqual(categoryUniqueMock); + }); + + +}); diff --git a/catalog-ui/src/app/ng2/pages/catalog/catalog.component.ts b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.ts new file mode 100644 index 0000000000..527764862a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/catalog.component.ts @@ -0,0 +1,666 @@ +/*- + * ============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 as NgComponent, Inject } from '@angular/core'; +import { SdcUiCommon, SdcUiServices } from "onap-ui-angular"; +import { CacheService, CatalogService } from "app/services-ng2"; +import { SdcConfigToken, ISdcConfig } from "../../config/sdc-config.config"; +import { SdcMenuToken, IAppMenu } from "../../config/sdc-menu.config"; +import { Component, ICategoryBase, IMainCategory, ISubCategory, IConfigStatuses, ICatalogSelector, CatalogSelectorTypes } from "app/models"; +import { ResourceNamePipe } from "../../pipes/resource-name.pipe"; +import { EntityFilterPipe, IEntityFilterObject, ISearchFilter} from "../../pipes/entity-filter.pipe"; + +interface Gui { + onComponentSubTypesClick:Function; + onComponentTypeClick:Function; + onCategoryClick:Function; + onStatusClick:Function; + changeFilterTerm:Function; +} + +interface IFilterParams { + components: string[]; + categories: string[]; + statuses: (string)[]; + order: [string, boolean]; + term: string; + active: boolean; +} + +interface ICheckboxesFilterMap { + [key: string]: Array<string>; + _main: Array<string>; +} + +interface ICheckboxesFilterKeys { + componentTypes: ICheckboxesFilterMap; + categories: ICheckboxesFilterMap; + statuses: ICheckboxesFilterMap; +} + +interface ICategoriesMap { + [key: string]: { + category: ICategoryBase, + parent: ICategoryBase + } +} + +@NgComponent({ + selector: 'catalog', + templateUrl: './catalog.component.html', + styleUrls:['./catalog.component.less'] +}) +export class CatalogComponent { + public checkboxesFilter:IEntityFilterObject; + public checkboxesFilterKeys:ICheckboxesFilterKeys; + public gui:Gui; + public categories:Array<IMainCategory>; + public filteredCategories:Array<IMainCategory>; + public confStatus:IConfigStatuses; + public componentTypes:{[key:string]: Array<string>}; + public catalogItems:Array<Component>; + public catalogFilteredItems:Array<Component>; + public catalogFilteredSlicedItems:Array<Component>; + public expandedSection:Array<string>; + public version:string; + public sortBy:string; + public reverse:boolean; + public filterParams:IFilterParams; + public search:ISearchFilter; + + //this is for UI paging + public numberOfItemToDisplay:number; + + public selectedCatalogItem: ICatalogSelector; + public catalogSelectorItems: Array<ICatalogSelector>; + public showCatalogSelector: boolean; + + public typesChecklistModel: SdcUiCommon.ChecklistModel; + public categoriesChecklistModel: SdcUiCommon.ChecklistModel; + public statusChecklistModel: SdcUiCommon.ChecklistModel; + + private defaultFilterParams:IFilterParams = { + components: [], + categories: [], + statuses: [], + order: ['lastUpdateDate', true], + term: '', + active: true + }; + private categoriesMap:ICategoriesMap; + + constructor( + @Inject(SdcConfigToken) private sdcConfig:ISdcConfig, + @Inject(SdcMenuToken) public sdcMenu:IAppMenu, + @Inject("$state") private $state:ng.ui.IStateService, + private cacheService:CacheService, + private catalogService:CatalogService, + private resourceNamePipe:ResourceNamePipe, + private loaderService: SdcUiServices.LoaderService + ) {} + + ngOnInit(): void { + this.initGui(); + this.initLeftSwitch(); + this.initScopeMembers(); + this.loadFilterParams(); + this.initCatalogData(); // Async task to get catalog from server. + } + + private initLeftSwitch = ():void => { + this.showCatalogSelector = false; + + this.catalogSelectorItems = [ + {value: CatalogSelectorTypes.Active, title: "Active Items", header: "Active"}, + {value: CatalogSelectorTypes.Archive, title: "Archive", header: "Archived"} + ]; + // set active items is default + this.selectedCatalogItem = this.catalogSelectorItems[0]; + }; + + private initCatalogData = ():void => { + if(this.selectedCatalogItem.value === CatalogSelectorTypes.Archive){ + this.getArchiveCatalogItems(); + } else { + this.getActiveCatalogItems(); + } + }; + + + private initScopeMembers = ():void => { + this.numberOfItemToDisplay = 0; + this.categories = this.makeSortedCategories(this.cacheService.get('serviceCategories').concat(this.cacheService.get('resourceCategories'))) + .map((cat) => <IMainCategory>cat); + this.confStatus = this.sdcMenu.statuses; + this.expandedSection = ["type", "category", "status"]; + this.catalogItems = []; + this.search = {FilterTerm: ""}; + this.categoriesMap = this.initCategoriesMap(); + this.initCheckboxesFilter(); + this.initCheckboxesFilterKeys(); + this.buildCheckboxLists(); + + this.version = this.cacheService.get('version'); + this.sortBy = 'lastUpdateDate'; + this.reverse = true; + }; + + private buildCheckboxLists() { + this.buildChecklistModelForTypes(); + this.buildChecklistModelForCategories(); + this.buildChecklistModelForStatuses(); + } + + private getTestIdForCheckboxByText = ( text: string ):string => { + return 'checkbox-' + text.toLowerCase().replace(/ /g, ''); + } + + private buildChecklistModelForTypes() { + this.componentTypes = { + Resource: ['VF', 'VFC', 'CR', 'PNF', 'CP', 'VL'], + Service: null + }; + this.typesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.componentTypes._main, + Object.keys(this.componentTypes).map((ct) => { + let subChecklist = null; + if (this.componentTypes[ct]) { + this.checkboxesFilterKeys.componentTypes[ct] = this.checkboxesFilterKeys.componentTypes[ct] || []; + subChecklist = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.componentTypes[ct], + this.componentTypes[ct].map((st) => { + const stKey = [ct, st].join('.'); + const testId = this.getTestIdForCheckboxByText(st); + return new SdcUiCommon.ChecklistItemModel(st, false, this.checkboxesFilterKeys.componentTypes[ct].indexOf(stKey) !== -1, null, testId, stKey); + }) + ); + } + const testId = this.getTestIdForCheckboxByText(ct); + return new SdcUiCommon.ChecklistItemModel(ct, false, this.checkboxesFilterKeys.componentTypes._main.indexOf(ct) !== -1, subChecklist, testId, ct); + }) + ); + } + + private buildChecklistModelForCategories() { + this.categoriesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories._main, + (this.filteredCategories || this.categories).map((cat) => { + this.checkboxesFilterKeys.categories[cat.uniqueId] = this.checkboxesFilterKeys.categories[cat.uniqueId] || []; + const subCategoriesChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories[cat.uniqueId], + (cat.subcategories || []).map((scat) => { + this.checkboxesFilterKeys.categories[scat.uniqueId] = this.checkboxesFilterKeys.categories[scat.uniqueId] || []; + const groupingsChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.categories[scat.uniqueId], + (scat.groupings || []).map(gcat => + new SdcUiCommon.ChecklistItemModel(gcat.name, false, this.checkboxesFilterKeys.categories[scat.uniqueId].indexOf(gcat.uniqueId) !== -1, null, this.getTestIdForCheckboxByText(gcat.uniqueId), gcat.uniqueId)) + ); + return new SdcUiCommon.ChecklistItemModel(scat.name, false, this.checkboxesFilterKeys.categories[cat.uniqueId].indexOf(scat.uniqueId) !== -1, groupingsChecklistModel, this.getTestIdForCheckboxByText(scat.uniqueId), scat.uniqueId); + }) + ); + return new SdcUiCommon.ChecklistItemModel(cat.name, false, this.checkboxesFilterKeys.categories._main.indexOf(cat.uniqueId) !== -1, subCategoriesChecklistModel, this.getTestIdForCheckboxByText(cat.uniqueId), cat.uniqueId); + }) + ); + } + + private buildChecklistModelForStatuses() { + // For statuses checklist model, use the statuses keys as values. On applying filtering map the statuses keys to statuses values. + this.statusChecklistModel = new SdcUiCommon.ChecklistModel(this.checkboxesFilterKeys.statuses._main, + Object.keys(this.confStatus).map((sKey) => new SdcUiCommon.ChecklistItemModel( + this.confStatus[sKey].name, + false, + this.checkboxesFilterKeys.statuses._main.indexOf(sKey) !== -1, + null, + this.getTestIdForCheckboxByText(sKey), + sKey)) + ); + } + + private initCheckboxesFilter() { + // Checkboxes filter init + this.checkboxesFilter = <IEntityFilterObject>{}; + this.checkboxesFilter.selectedComponentTypes = []; + this.checkboxesFilter.selectedResourceSubTypes = []; + this.checkboxesFilter.selectedCategoriesModel = []; + this.checkboxesFilter.selectedStatuses = []; + } + + private initCheckboxesFilterKeys() { + // init checkboxes filter keys (for checklists values): + this.checkboxesFilterKeys = <ICheckboxesFilterKeys>{}; + this.checkboxesFilterKeys.componentTypes = { _main: [] }; + this.checkboxesFilterKeys.categories = { _main: [] }; + this.checkboxesFilterKeys.statuses = { _main: [] }; + } + + private initCategoriesMap(categoriesList?:(ICategoryBase)[], parentCategory:ICategoryBase=null): ICategoriesMap { + categoriesList = (categoriesList) ? categoriesList : this.categories; + + // Init categories map + return categoriesList.reduce((acc, cat) => { + acc[cat.uniqueId] = { + category: cat, + parent: parentCategory + }; + const catChildren = ((<IMainCategory>cat).subcategories) + ? (<IMainCategory>cat).subcategories + : (((<ISubCategory>cat).groupings) + ? (<ISubCategory>cat).groupings + : null); + if (catChildren) { + Object.assign(acc, this.initCategoriesMap(catChildren, cat)); + } + return acc; + }, <ICategoriesMap>{}); + } + + public selectLeftSwitchItem(item: ICatalogSelector): void { + if (this.selectedCatalogItem.value !== item.value) { + this.selectedCatalogItem = item; + switch (item.value) { + case CatalogSelectorTypes.Active: + this.getActiveCatalogItems(true); + break; + + case CatalogSelectorTypes.Archive: + this.getArchiveCatalogItems(true); + break; + } + this.changeFilterParams({active: (item.value === CatalogSelectorTypes.Active)}); + } + } + + public sectionClick(section: string): void { + let index: number = this.expandedSection.indexOf(section); + if (index !== -1) { + this.expandedSection.splice(index, 1); + } else { + this.expandedSection.push(section); + } + } + + + private makeFilterParamsFromCheckboxes(checklistModel:SdcUiCommon.ChecklistModel): Array<string> { + return checklistModel.checkboxes.reduce((acc, chbox) => { + if (checklistModel.selectedValues.indexOf(chbox.value) !== -1) { + acc.push(chbox.value); + } else if (chbox.subLevelChecklist) { // else, if checkbox is not checked, then try to get values from sub checklists + acc.push(...this.makeFilterParamsFromCheckboxes(chbox.subLevelChecklist)); + } + return acc; + }, []); + } + + //default sort by descending last update. default for alphabetical = ascending + public order(sortBy: string): void { + this.changeFilterParams({ + order: (this.filterParams.order[0] === sortBy) + ? [sortBy, !this.filterParams.order[1]] + : [sortBy, sortBy === 'lastUpdateDate'] + }); + } + + + public goToComponent(component: Component): void { + this.$state.go('workspace.general', {id: component.uniqueId, type: component.componentType.toLowerCase()}); + } + + + // Will print the number of elements found in catalog + public getNumOfElements(num:number):string { + if (!num || num === 0) { + return `No <b>${this.selectedCatalogItem.header}</b> Elements found`; + } else if (num === 1) { + return `1 <b>${this.selectedCatalogItem.header}</b> Element found`; + } else { + return num + ` <b>${this.selectedCatalogItem.header}</b> Elements found`; + } + } + + public initGui(): void { + this.gui = <Gui>{}; + + /** + * Select | unselect sub resource when resource is clicked | unclicked. + * @param type + */ + this.gui.onComponentTypeClick = (): void => { + this.changeFilterParams({ + components: this.makeFilterParamsFromCheckboxes(this.typesChecklistModel) + }); + }; + + this.gui.onCategoryClick = (): void => { + this.changeFilterParams({ + categories: this.makeFilterParamsFromCheckboxes(this.categoriesChecklistModel) + }); + }; + + this.gui.onStatusClick = (statusChecklistItem: SdcUiCommon.ChecklistItemModel) => { + this.changeFilterParams({ + statuses: this.makeFilterParamsFromCheckboxes(this.statusChecklistModel) + }); + }; + + this.gui.changeFilterTerm = (filterTerm: string) => { + this.changeFilterParams({ + term: filterTerm + }); + }; + } + + public raiseNumberOfElementToDisplay(recalculate:boolean = false): void { + const scrollPageAmount = 35; + if (!this.catalogFilteredItems) { + this.numberOfItemToDisplay = 0; + } else if (this.catalogFilteredItems.length > this.numberOfItemToDisplay || recalculate) { + let fullPagesAmount = Math.ceil(this.numberOfItemToDisplay / scrollPageAmount) * scrollPageAmount; + if (!recalculate || fullPagesAmount === 0) { //TODO trigger infiniteScroll to check bottom and fire onBottomHit by itself (sdc-ui) + fullPagesAmount += scrollPageAmount; + } + this.numberOfItemToDisplay = Math.min(this.catalogFilteredItems.length, fullPagesAmount); + this.catalogFilteredSlicedItems = this.catalogFilteredItems.slice(0, this.numberOfItemToDisplay); + } + } + + private isDefaultFilter = (): boolean => { + return angular.equals(this.defaultFilterParams, this.filterParams); + } + + private componentShouldReload = ():boolean => { + let breadcrumbsValid: boolean = (this.$state.current.name === this.cacheService.get('breadcrumbsComponentsState') && this.cacheService.contains('breadcrumbsComponents')); + return !breadcrumbsValid || this.isDefaultFilter(); + } + + private getActiveCatalogItems(forceReload?: boolean): void { + if (forceReload || this.componentShouldReload()) { + this.loaderService.activate(); + + let onSuccess = (followedResponse:Array<Component>):void => { + this.updateCatalogItems(followedResponse); + this.loaderService.deactivate(); + this.cacheService.set('breadcrumbsComponentsState', this.$state.current.name); //catalog + this.cacheService.set('breadcrumbsComponents', followedResponse); + + }; + + let onError = ():void => { + console.info('Failed to load catalog CatalogViewModel::getActiveCatalogItems'); + this.loaderService.deactivate(); + }; + this.catalogService.getCatalog().subscribe(onSuccess, onError); + } else { + let cachedComponents = this.cacheService.get('breadcrumbsComponents'); + this.updateCatalogItems(cachedComponents); + } + } + + private getArchiveCatalogItems(forceReload?: boolean): void { + if(forceReload || !this.cacheService.contains("archiveComponents")) { + this.loaderService.activate(); + let onSuccess = (followedResponse:Array<Component>):void => { + this.cacheService.set("archiveComponents", followedResponse); + this.loaderService.deactivate(); + this.updateCatalogItems(followedResponse); + }; + + let onError = ():void => { + console.info('Failed to load catalog CatalogViewModel::getArchiveCatalogItems'); + this.loaderService.deactivate(); + }; + + this.catalogService.getArchiveCatalog().subscribe(onSuccess, onError); + } else { + let archiveCache = this.cacheService.get("archiveComponents"); + this.updateCatalogItems(archiveCache); + } + } + + private updateCatalogItems = (items:Array<Component>):void => { + this.catalogItems = items; + this.catalogItems.forEach(this.addFilterTermToComponent); + this.filterCatalogItems(); + } + + private applyFilterParamsToView(filterParams:IFilterParams) { + // reset checkboxes filter + this.initCheckboxesFilter(); + + this.filterCatalogCategories(); + + this.applyFilterParamsComponents(filterParams); + this.applyFilterParamsCategories(filterParams); + this.applyFilterParamsStatuses(filterParams); + this.applyFilterParamsOrder(filterParams); + this.applyFilterParamsTerm(filterParams); + + // do filters when filter params are changed: + this.filterCatalogItems(); + } + + private filterCatalogCategories() { + this.filteredCategories = this.makeFilteredCategories(this.categories, this.checkboxesFilter.selectedComponentTypes); + this.buildChecklistModelForCategories(); + } + + private filterCatalogItems() { + this.catalogFilteredItems = this.makeFilteredItems(this.catalogItems, this.checkboxesFilter, this.search, this.sortBy, this.reverse); + this.raiseNumberOfElementToDisplay(true); + this.catalogFilteredSlicedItems = this.catalogFilteredItems.slice(0, this.numberOfItemToDisplay); + } + + private applyFilterParamsToCheckboxes(checklistModel:SdcUiCommon.ChecklistModel, filterParamsList:Array<string>) { + checklistModel.checkboxes.forEach((chbox) => { + // if checkbox is checked, then add it to selected values if not there, and select all sub checkboxes + if (filterParamsList.indexOf(chbox.value) !== -1 && checklistModel.selectedValues.indexOf(chbox.value) === -1) { + checklistModel.selectedValues.push(chbox.value); + if (chbox.subLevelChecklist) { + this.applyFilterParamsToCheckboxes(chbox.subLevelChecklist, chbox.subLevelChecklist.checkboxes.map((subchbox) => subchbox.value)); + } + } else if ( chbox.subLevelChecklist ) { + this.applyFilterParamsToCheckboxes(chbox.subLevelChecklist, filterParamsList); + } + }); + } + + private applyFilterParamsComponents(filterParams:IFilterParams) { + this.applyFilterParamsToCheckboxes(this.typesChecklistModel, filterParams.components); + this.checkboxesFilter.selectedComponentTypes = this.checkboxesFilterKeys.componentTypes._main; + Object.keys(this.checkboxesFilterKeys.componentTypes).forEach((chKey) => { + if (chKey !== '_main') { + this.checkboxesFilter['selected' + chKey + 'SubTypes'] = this.checkboxesFilterKeys.componentTypes[chKey].map((st) => st.substr(chKey.length + 1)); + } + }); + + let selectedCatalogIndex = filterParams.active ? CatalogSelectorTypes.Active : CatalogSelectorTypes.Archive; + this.selectedCatalogItem = this.catalogSelectorItems[selectedCatalogIndex]; + } + + private applyFilterParamsCategories(filterParams:IFilterParams) { + this.applyFilterParamsToCheckboxes(this.categoriesChecklistModel, filterParams.categories); + this.checkboxesFilter.selectedCategoriesModel = <Array<string>>_.flatMap(this.checkboxesFilterKeys.categories); + } + + private applyFilterParamsStatuses(filterParams: IFilterParams) { + this.applyFilterParamsToCheckboxes(this.statusChecklistModel, filterParams.statuses); + this.checkboxesFilter.selectedStatuses = _.reduce(_.flatMap(this.checkboxesFilterKeys.statuses), (stats, st:string) => [...stats, ...this.confStatus[st].values], []); + } + + private applyFilterParamsOrder(filterParams: IFilterParams) { + this.sortBy = filterParams.order[0]; + this.reverse = filterParams.order[1]; + } + + private applyFilterParamsTerm(filterParams: IFilterParams) { + this.search = { + filterTerm: filterParams.term + }; + } + + private loadFilterParams() { + const params = this.$state.params; + this.filterParams = angular.copy(this.defaultFilterParams); + Object.keys(params).forEach((k) => { + if (!angular.isUndefined(params[k])) { + let newVal; + let paramsChecklist: SdcUiCommon.ChecklistModel = null; + let filterKey = k.substr('filter.'.length); + switch (k) { + case 'filter.components': + paramsChecklist = paramsChecklist || this.typesChecklistModel; + case 'filter.categories': + paramsChecklist = paramsChecklist || this.categoriesChecklistModel; + case 'filter.statuses': + paramsChecklist = paramsChecklist || this.statusChecklistModel; + + // for those cases above - split param by comma and make reduced checklist values for filter params (url) + newVal = _.uniq(params[k].split(',')); + break; + case 'filter.order': + newVal = params[k].startsWith('-') ? [params[k].substr(1), true] : [params[k], false]; + break; + case 'filter.term': + newVal = params[k]; + break; + case 'filter.active': + newVal = (params[k] === "true" || params[k] === true)? true : false; + break; + default: + // unknown filter key + filterKey = null; + } + if (filterKey) { + this.filterParams[filterKey] = newVal; + } + } + }); + // re-set filter params with valid values, and then re-build checklists + this.changeFilterParams(this.filterParams, true); + } + + private changeFilterParams(changedFilterParams, rebuild:boolean = false) { + const newParams = {}; + Object.keys(changedFilterParams).forEach((k) => { + let newVal; + switch (k) { + case 'components': + case 'categories': + case 'statuses': + newVal = changedFilterParams[k] && changedFilterParams[k].length ? changedFilterParams[k].join(',') : null; + break; + case 'order': + newVal = (changedFilterParams[k][1] ? '-' : '') + changedFilterParams[k][0]; + break; + case 'term': + newVal = changedFilterParams[k] ? changedFilterParams[k] : null; + break; + case 'active': + newVal = (changedFilterParams[k] === "true" || changedFilterParams[k] === true); + break; + default: + return; + } + this.filterParams[k] = changedFilterParams[k]; + newParams['filter.' + k] = newVal; + }); + this.$state.go('.', newParams, {location: 'replace', notify: false}).then(() => { + if (rebuild) { + // fix the filter params to only valid values for checkboxes + this.changeFilterParams({ + components: this.makeFilterParamsFromCheckboxes(this.typesChecklistModel), + categories: this.makeFilterParamsFromCheckboxes(this.categoriesChecklistModel), + statuses: this.makeFilterParamsFromCheckboxes(this.statusChecklistModel) + }); + // rebuild the checkboxes to show selected + this.buildCheckboxLists(); + } + }); + this.applyFilterParamsToView(this.filterParams); + } + + private makeFilteredCategories(categories:Array<IMainCategory>, selectedTypes:Array<string>=[]): Array<IMainCategory> { + let filteredCategories = categories.slice(); + + const filteredMainTypes = selectedTypes.reduce((acc, st) => { + const mainType = st.split('.')[0]; + if (acc.indexOf(mainType) === -1) { + acc.push(mainType); + } + return acc; + }, []); + + // filter by selected types + if (filteredMainTypes.length) { + const filteredTypesCategories = filteredMainTypes.reduce((acc, mainType: string) => { + acc.push(...this.cacheService.get(mainType.toLowerCase() + 'Categories')); + return acc; + }, []); + + filteredCategories = _.intersectionBy(filteredCategories, filteredTypesCategories, c => c.uniqueId); + } + + return filteredCategories; + } + + private makeSortedCategories(categories:Array<IMainCategory|ISubCategory|ICategoryBase>, sortBy?:any): Array<IMainCategory|ISubCategory|ICategoryBase> { + sortBy = (sortBy !== undefined) ? sortBy : ['name']; + let sortedCategories = categories.map(cat => Object.assign({}, cat)); // copy each object in the array + sortedCategories = _.sortBy(sortedCategories, sortBy); + + // inner sort of subcategories and groupings + sortedCategories.forEach((cat) => { + if ('subcategories' in cat && cat['subcategories'] && cat['subcategories'].length > 0) { + cat['subcategories'] = this.makeSortedCategories(cat['subcategories'], sortBy); + } + if ('groupings' in cat && cat['groupings'] && cat['groupings'].length > 0) { + cat['groupings'] = this.makeSortedCategories(cat['groupings'], sortBy); + } + }); + + return sortedCategories; + } + + private addFilterTermToComponent(component:Component) { + component.filterTerm = component.name + ' ' + component.description + ' ' + component.tags.toString() + ' ' + component.version; + component.filterTerm = component.filterTerm.toLowerCase(); + } + + private makeFilteredItems(catalogItems:Array<Component>, filter:IEntityFilterObject, search:ISearchFilter, sortBy:string, reverse:boolean) { + let filteredComponents:Array<Component> = catalogItems; + + // common entity filter + // -------------------------------------------------------------------------- + filter = Object.assign({ search }, filter); // add search to entity filter object + filteredComponents = EntityFilterPipe.transform(filteredComponents, filter); + + // sort + // -------------------------------------------------------------------------- + if (sortBy) { + switch (sortBy) { + case 'resourceName': + filteredComponents = _.sortBy(filteredComponents, cat => this.resourceNamePipe.transform(cat.name)); + break; + default: + filteredComponents = _.sortBy(filteredComponents, [sortBy]); + } + if (reverse) { + _.reverse(filteredComponents); + } + } + + return filteredComponents; + } +} diff --git a/catalog-ui/src/app/ng2/pages/catalog/catalog.module.ts b/catalog-ui/src/app/ng2/pages/catalog/catalog.module.ts new file mode 100644 index 0000000000..5ef8de01e3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/catalog/catalog.module.ts @@ -0,0 +1,33 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { CatalogComponent } from "./catalog.component"; +import { LayoutModule } from "../../components/layout/layout.module"; +import { UiElementsModule } from "../../components/ui/ui-elements.module"; +import { GlobalPipesModule } from "../../pipes/global-pipes.module"; +import { TranslateModule } from "../../shared/translator/translate.module"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import {SdcTileModule} from "../../components/ui/tile/sdc-tile.module"; + +@NgModule({ + declarations: [ + CatalogComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + LayoutModule, + UiElementsModule, + GlobalPipesModule, + TranslateModule, + SdcTileModule + ], + exports: [ + CatalogComponent + ], + entryComponents: [ + CatalogComponent + ], + providers: [] +}) +export class CatalogModule { +} diff --git a/catalog-ui/src/app/ng2/pages/composition/common/common-graph-data.service.ts b/catalog-ui/src/app/ng2/pages/composition/common/common-graph-data.service.ts new file mode 100644 index 0000000000..d4caa5e9ed --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/common/common-graph-data.service.ts @@ -0,0 +1,64 @@ +import {Injectable} from "@angular/core"; +import 'rxjs/add/observable/forkJoin'; +import {ComponentInstance} from "../../../../models/componentsInstances/componentInstance"; +import {SelectedComponentType} from "./store/graph.actions"; +import {RelationshipModel} from "../../../../models/graph/relationship"; + +@Injectable() +export class CommonGraphDataService { + + public componentInstances: Array<ComponentInstance>; + public componentInstancesRelations: RelationshipModel[]; + public selectedComponentType: SelectedComponentType; + + constructor() { + } + + //------------------------ RELATIONS ---------------------------------// + public setRelations = (componentInstancesRelations: RelationshipModel[]) => { + this.componentInstancesRelations = this.componentInstancesRelations; + } + + public getRelations = (): RelationshipModel[] => { + return this.componentInstancesRelations; + } + + public addRelation = (componentInstancesRelations: RelationshipModel) => { + this.componentInstancesRelations.push(componentInstancesRelations); + } + + public deleteRelation(relationToDelete: RelationshipModel) { + this.componentInstancesRelations = _.filter(this.componentInstancesRelations, (relationship: RelationshipModel) => { + return relationship.relationships[0].relation.id !== relationToDelete.relationships[0].relation.id; + }); + } + + //---------------------------- COMPONENT INSTANCES ------------------------------------// + public getComponentInstances = (): Array<ComponentInstance> => { + return this.componentInstances; + } + + public addComponentInstance = (instance: ComponentInstance) => { + return this.componentInstances.push(instance); + } + + public updateComponentInstances = (componentInstances: ComponentInstance[]) => { + _.unionBy(this.componentInstances, componentInstances, 'uniqueId'); + } + + public updateInstance = (instance: ComponentInstance) => { + this.componentInstances = this.componentInstances.map(componentInstance => instance.uniqueId === componentInstance.uniqueId? instance : componentInstance); + } + + public deleteComponentInstance(instanceToDelete: string) { + this.componentInstances = _.filter(this.componentInstances, (instance: ComponentInstance) => { + return instance.uniqueId !== instanceToDelete; + }); + } + + //----------------------------SELECTED COMPONENT -----------------------// + + public setSelectedComponentType = (selectedType: SelectedComponentType) => { + this.selectedComponentType = selectedType; + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/common/store/graph.actions.ts b/catalog-ui/src/app/ng2/pages/composition/common/store/graph.actions.ts new file mode 100644 index 0000000000..9bd5d0db62 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/common/store/graph.actions.ts @@ -0,0 +1,33 @@ +export enum SelectedComponentType { + COMPONENT_INSTANCE = "COMPONENT_INSTANCE", + GROUP = "GROUP", + POLICY = "POLICY", + TOPOLOGY_TEMPLATE = "TOPOLOGY_TEMPLATE" +} + +export class UpdateSelectedComponentAction { + static readonly type = '[COMPOSITION] UpdateSelectedComponent'; + + constructor(public payload: {uniqueId?: string, type?: string}) { + } +} + +export class SetSelectedComponentAction { + static readonly type = '[COMPOSITION] SetSelectedComponent'; + + constructor(public payload: {component?: any, type?: SelectedComponentType}) { + } +} + +export class OnSidebarOpenOrCloseAction { + static readonly type = '[COMPOSITION] OnSidebarOpenOrCloseAction'; + + constructor() { + } +} + +export class TogglePanelLoadingAction { + static readonly type = '[COMPOSITION] TogglePanelLoading'; + constructor(public payload: { isLoading: boolean}) { + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/common/store/graph.state.ts b/catalog-ui/src/app/ng2/pages/composition/common/store/graph.state.ts new file mode 100644 index 0000000000..d58bb446df --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/common/store/graph.state.ts @@ -0,0 +1,170 @@ +import { Action, Selector, State, StateContext} from '@ngxs/store'; +import { + OnSidebarOpenOrCloseAction, + SelectedComponentType, + SetSelectedComponentAction, + TogglePanelLoadingAction +} from "./graph.actions"; +import { PolicyInstance, GroupInstance, Component as TopologyTemplate, ComponentInstance, LeftPaletteComponent, FullComponentInstance} from "app/models"; +import { TopologyTemplateService } from "app/ng2/services/component-services/topology-template.service"; +import { tap } from "rxjs/operators"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import {GroupsService} from "../../../../services/groups.service"; +import {PoliciesService} from "../../../../services/policies.service"; +import {WorkspaceService} from "../../../workspace/workspace.service"; + +export class CompositionStateModel { + + isViewOnly?: boolean; + panelLoading?: boolean; + selectedComponentType?: SelectedComponentType; + selectedComponent?: PolicyInstance | GroupInstance | TopologyTemplate | ComponentInstance; + withSidebar?: boolean; +} + +@State<CompositionStateModel>({ + name: 'composition', + defaults: { + withSidebar: true + } +}) +export class GraphState { + + constructor(private topologyTemplateService: TopologyTemplateService, + private compositionService: CompositionService, + private policiesService:PoliciesService, private groupsService:GroupsService, + private workspaceService: WorkspaceService) {} + + @Action(SetSelectedComponentAction) + setSelectedComponent({dispatch, getState, patchState}:StateContext<CompositionStateModel>, action: SetSelectedComponentAction) { + + const state:CompositionStateModel = getState(); + + patchState({ panelLoading: true }); + + if(action.payload.component instanceof ComponentInstance){ + let originComponent = this.compositionService.getOriginComponentById(action.payload.component.getComponentUid()); + if(!originComponent) { + return this.topologyTemplateService.getFullComponent(action.payload.component.originType, action.payload.component.getComponentUid()) + .pipe(tap(resp => { + this.compositionService.addOriginComponent(resp); + this.compositionService.setSelectedComponentType(SelectedComponentType.COMPONENT_INSTANCE); + patchState({ + selectedComponent: new FullComponentInstance(action.payload.component, resp), + selectedComponentType: action.payload.type, + panelLoading: false + }); + }, err => { + patchState({ + panelLoading: false + }) + } + )); + } else { + patchState({ + selectedComponent: new FullComponentInstance(action.payload.component, originComponent), + selectedComponentType: action.payload.type, + panelLoading: false + }); + } + } else if (action.payload.component instanceof PolicyInstance) { + let topologyTemplate = this.workspaceService.metadata; + return this.policiesService.getSpecificPolicy(topologyTemplate.componentType, topologyTemplate.uniqueId, action.payload.component.uniqueId).pipe(tap(resp => + { + this.compositionService.updatePolicy(resp); + patchState({ + selectedComponent: resp, + selectedComponentType: action.payload.type, + panelLoading: false + }) + }, err => { + patchState({ + panelLoading: false + }) + } + )); + + } else if (action.payload.component instanceof GroupInstance) { + let topologyTemplate = this.workspaceService.metadata; + return this.groupsService.getSpecificGroup(topologyTemplate.componentType, topologyTemplate.uniqueId, action.payload.component.uniqueId).pipe(tap(resp => { + this.compositionService.updateGroup(resp); + patchState({ + selectedComponent: resp, + selectedComponentType: action.payload.type, + panelLoading: false + }); + }, err => { + patchState({ + panelLoading: false + }) + } + )); + } else { //TopologyTemplate + patchState({ + selectedComponent: action.payload.component, + selectedComponentType: action.payload.type, + panelLoading: false + }) + } + } + + + // @Action(UpdateSelectedComponentNameAction) + // UpdateSelectedComponentNameAction({patchState}:StateContext<CompositionStateModel>, action: UpdateSelectedComponentNameAction) { + + // switch(action.payload.type){ + // case SelectedComponentType.COMPONENT_INSTANCE: + // this.store.dispatch(new UpdateComponentInstancesAction([action.payload.component])); + // break; + // case SelectedComponentType.POLICY: + // this.store.dispatch(new UpdatePolicyNameAction(action.payload.uniqueId, action.payload.newName)); + // break; + // case SelectedComponentType.GROUP: + // this.store.dispatch(new UpdateGroupInstancesAction) + + // } + // if(action.payload.type === SelectedComponentType.COMPONENT_INSTANCE){ + + // } + + // } + + @Selector() + static getSelectedComponent(state:CompositionStateModel) { + return state.selectedComponent; + } + + @Selector() + static getSelectedComponentId(state:CompositionStateModel) { + return state.selectedComponent.uniqueId; + } + + @Selector() + static getSelectedComponentType(state:CompositionStateModel) { + return state.selectedComponentType; + } + + + @Action(OnSidebarOpenOrCloseAction) + onSidebarOpenOrCloseAction({getState, setState}:StateContext<CompositionStateModel>) { + const state:CompositionStateModel = getState(); + + setState({ + ...state, + withSidebar: !state.withSidebar + }); + } + + @Action(TogglePanelLoadingAction) + TogglePanelLoading({patchState}:StateContext<CompositionStateModel>, action: TogglePanelLoadingAction) { + + patchState({ + panelLoading: action.payload.isLoading + }); + } + + @Selector() static withSidebar(state):boolean { + return state.withSidebar; + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/composition-page.component.html b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.html new file mode 100644 index 0000000000..e1851d5c0c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.html @@ -0,0 +1,8 @@ +<div class="workspace-composition-page"> + <div class="composition-graph"> + <composition-palette></composition-palette> + <app-palette-popup-panel></app-palette-popup-panel> + <composition-graph dndDropzone [dndAllowExternal]=true [topologyTemplate]="topologyTemplate" [testId]="'canvas'"></composition-graph> + <ng2-composition-panel [topologyTemplate]="topologyTemplate"></ng2-composition-panel> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/composition-page.component.less b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.less new file mode 100644 index 0000000000..a80333e2be --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.less @@ -0,0 +1,26 @@ +@import "./../../../../assets/styles/override"; +.workspace-composition-page { + height:100%; + display: block; + text-align: left; + align-items: left; + padding: 0; + + .composition-graph { + height:100%; + background-color: @sdcui_color_white; + bottom: 0; + display:flex; + flex-direction: row; + + .view-mode{ + background-color: #f8f8f8; + border:0; + } + } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/composition-page.component.ts b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.ts new file mode 100644 index 0000000000..ed1b82e1df --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/composition-page.component.ts @@ -0,0 +1,47 @@ +/*- + * ============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, Inject, OnDestroy, OnInit } from '@angular/core'; +import { Component as TopologyTemplate } from 'app/models'; +import * as Constants from 'constants'; +import { EventListenerService } from '../../../services/event-listener-service'; +import { EVENTS } from '../../../utils'; + +@Component({ + templateUrl: './composition-page.component.html', + styleUrls: ['composition-page.component.less'] +}) +export class CompositionPageComponent implements OnInit, OnDestroy { + + private topologyTemplate: TopologyTemplate; + + constructor(@Inject('$stateParams') private stateParams, private eventListenerService: EventListenerService) { + this.topologyTemplate = stateParams.component; + } + + ngOnInit(): void { + this.eventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, (comp) => { + this.topologyTemplate = comp; + }); + } + + ngOnDestroy(): void { + this.eventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/composition-page.module.ts b/catalog-ui/src/app/ng2/pages/composition/composition-page.module.ts new file mode 100644 index 0000000000..d0ca05b2be --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/composition-page.module.ts @@ -0,0 +1,30 @@ +/** + * Created by ob0695 on 6/4/2018. + */ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {CompositionGraphModule} from "./graph/composition-graph.module"; +import {CompositionPageComponent} from "./composition-page.component"; +import {NgxsModule} from "@ngxs/store"; +import {PaletteModule} from "./palette/palette.module"; +import {PalettePopupPanelComponent} from "./palette/palette-popup-panel/palette-popup-panel.component"; +import { CompositionPanelModule } from "app/ng2/pages/composition/panel/composition-panel.module"; +import {CompositionService} from "./composition.service"; +import {DndModule} from "ngx-drag-drop"; +import {GraphState} from "./common/store/graph.state"; + +@NgModule({ + declarations: [CompositionPageComponent, PalettePopupPanelComponent], + imports: [CommonModule, + CompositionGraphModule, + CompositionPanelModule, + PaletteModule, + DndModule, + NgxsModule.forFeature([ + GraphState])], + exports: [CompositionPageComponent], + entryComponents: [CompositionPageComponent], + providers: [CompositionService] +}) +export class CompositionPageModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/composition.service.ts b/catalog-ui/src/app/ng2/pages/composition/composition.service.ts new file mode 100644 index 0000000000..e5e9d2dca8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/composition.service.ts @@ -0,0 +1,59 @@ +import {Injectable} from "@angular/core"; +import 'rxjs/add/observable/forkJoin'; +import {Component, PropertiesGroup, AttributesGroup, PolicyInstance} from "app/models"; +import {GroupInstance} from "app/models/graph/zones/group-instance"; +import {CommonGraphDataService} from "./common/common-graph-data.service"; +import {ForwardingPath} from "../../../models/forwarding-path"; +import {SelectedComponentType} from "./common/store/graph.actions"; + +@Injectable() +export class CompositionService extends CommonGraphDataService{ + + public originComponents: Array<Component>; //This contains the full data set after specifically requesting it. The uniqueId matches the 'componentUid' in the componentInstances array + public componentInstancesProperties:PropertiesGroup; + public componentInstancesAttributes:AttributesGroup; + public groupInstances: GroupInstance[]; + public policies: PolicyInstance[]; + public forwardingPaths: { [key:string]:ForwardingPath }; + public selectedComponentType: SelectedComponentType; + + //---------------------------- COMPONENT INSTANCES ------------------------------------// + + public getOriginComponentById = (uniqueId:string):Component => { + return this.originComponents && this.originComponents.find(instance => instance.uniqueId === uniqueId); + } + + public addOriginComponent = (originComponent:Component) => { + if(!this.originComponents) this.originComponents = []; + if(!this.getOriginComponentById(originComponent.uniqueId)){ + this.originComponents.push(originComponent); + } + } + + + public updateGroup = (instance: GroupInstance) => { + this.groupInstances = this.groupInstances.map(group => instance.uniqueId === group.uniqueId? instance : group); + } + + public updatePolicy = (instance: PolicyInstance) => { + this.policies = this.policies.map(policy => instance.uniqueId === policy.uniqueId? instance : policy); + } + + //---------------------------- POLICIES---------------------------------// + public addPolicyInstance = (instance: PolicyInstance) => { + return this.policies.push(instance); + } + + + //---------------------------- POLICIES---------------------------------// + public addGroupInstance = (instance: GroupInstance) => { + return this.groupInstances.push(instance); + } + + + //----------------------------SELECTED COMPONENT -----------------------// + + public setSelectedComponentType = (selectedType: SelectedComponentType) => { + this.selectedComponentType = selectedType; + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.html b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.html new file mode 100644 index 0000000000..4a163ee24b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.html @@ -0,0 +1 @@ +<div class="sdc-deployment-graph-wrapper"></div> diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.less b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.less new file mode 100644 index 0000000000..9b80fcd651 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.less @@ -0,0 +1,13 @@ +.sdc-deployment-graph-wrapper { + height: 100%; + width: 100%; + + ::ng-deep canvas { + /*canvas z-index is initialized to 999 while top-nav z-Index is 10, which makes top-nav disappear, so z-Index must be overwritten here*/ + z-index: 10 !important; + } + } + +::ng-deep .sdc-workspace-container .w-sdc-main-right-container .w-sdc-main-container-body-content.deploy-body-content{ + padding: 0px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.spec.ts new file mode 100644 index 0000000000..823086fbbf --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.spec.ts @@ -0,0 +1,92 @@ +import {async, ComponentFixture} from '@angular/core/testing'; +import 'jest-dom/extend-expect'; +import {DeploymentGraphComponent} from "./deployment-graph.component"; +import {DeploymentGraphService} from "./deployment-graph.service"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import * as cytoscape from "cytoscape/dist/cytoscape" +import {AngularJSBridge} from "../../../../services/angular-js-bridge-service"; +import {NodesFactory} from "../../../../models/graph/nodes/nodes-factory"; +import {CommonGraphUtils} from "../graph/common/common-graph-utils"; +import {groupsMock} from "../../../../../jest/mocks/groups.mock"; +import {Module} from "../../../../models/modules/base-module"; +import {ComponentInstance} from "../../../../models/componentsInstances/componentInstance"; +import {componentInstancesMock} from "../../../../../jest/mocks/component-instance.mock"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../../workspace/workspace.service"; +import {SdcConfigToken} from "../../../config/sdc-config.config"; +import {CompositionGraphLinkUtils} from "../graph/utils"; + +describe('DeploymentGraphComponent', () => { + + let fixture: ComponentFixture<DeploymentGraphComponent>; + let deploymentGraphServiceMock: Partial<DeploymentGraphService>; + let nodeFactoryServiceMock: Partial<NodesFactory>; + let commonGraphUtilsServiceMock: Partial<CommonGraphUtils>; + let angularJsBridgeServiceMock: Partial<AngularJSBridge>; + let sdcConfigTokenMock: Partial<AngularJSBridge>; + + beforeEach( + async(() => { + + deploymentGraphServiceMock = { + modules: <Array<Module>>groupsMock, + componentInstances: <Array<ComponentInstance>>componentInstancesMock + } + + nodeFactoryServiceMock = { + createModuleNode: jest.fn().mockResolvedValue(() => { + }), + createNode: jest.fn().mockResolvedValue(() => { + }) + } + + commonGraphUtilsServiceMock = { + addNodeToGraph: jest.fn(), + addComponentInstanceNodeToGraph: jest.fn() + } + + sdcConfigTokenMock = { + imagePath: '' + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [DeploymentGraphComponent], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: DeploymentGraphService, useValue: deploymentGraphServiceMock}, + {provide: NodesFactory, useValue: nodeFactoryServiceMock}, + {provide: TopologyTemplateService, useValue: {}}, + {provide: WorkspaceService, useValue: {}}, + {provide: CommonGraphUtils, useValue: commonGraphUtilsServiceMock}, + {provide: CompositionGraphLinkUtils, useValue: {}}, + {provide: AngularJSBridge, useValue: angularJsBridgeServiceMock}, + {provide: SdcConfigToken, useValue: SdcConfigToken} + ] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(DeploymentGraphComponent); + }); + }) + ); + + it('expected deployment graph component to be defined', () => { + expect(fixture).toBeDefined(); + }); + + + it('expected to addNodeToGraph to haveBeenCalled 6 times out of 7 cause one of the instances have no parent module', () => { + fixture.componentInstance._cy = cytoscape({ + zoomingEnabled: false, + selectionType: 'single', + }); + jest.spyOn(fixture.componentInstance, 'findInstanceModule'); + fixture.componentInstance.initGraphComponentInstances(); + expect(fixture.componentInstance.findInstanceModule).toHaveBeenCalledTimes(7); + expect(commonGraphUtilsServiceMock.addComponentInstanceNodeToGraph).toHaveBeenCalledTimes(6); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.ts b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.ts new file mode 100644 index 0000000000..143a759960 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.component.ts @@ -0,0 +1,127 @@ +import {Component, ElementRef, Inject, OnInit} from "@angular/core"; +import {DeploymentGraphService} from "./deployment-graph.service"; +import '@bardit/cytoscape-expand-collapse'; +import * as _ from "lodash"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../../workspace/workspace.service"; +import {NodesFactory} from "../../../../models/graph/nodes/nodes-factory"; +import {CommonGraphUtils} from "../graph/common/common-graph-utils"; +import {ISdcConfig, SdcConfigToken} from "../../../config/sdc-config.config"; +import {Module} from "../../../../models/modules/base-module"; +import {ComponentInstance} from "../../../../models/componentsInstances/componentInstance"; +import {ComponentGenericResponse} from "../../../services/responses/component-generic-response"; +import {ComponentInstanceFactory} from "../../../../utils/component-instance-factory"; +import {ModulesNodesStyle} from "../graph/common/style/module-node-style"; +import {ComponentInstanceNodesStyle} from "../graph/common/style/component-instances-nodes-style"; +import {CompositionGraphLinkUtils} from "../graph/utils/composition-graph-links-utils"; + +@Component({ + selector: 'deployment-graph', + templateUrl: './deployment-graph.component.html', + styleUrls: ['./deployment-graph.component.less'] +}) + +export class DeploymentGraphComponent implements OnInit { + constructor(private elRef: ElementRef, + private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private deploymentService: DeploymentGraphService, + private commonGraphUtils: CommonGraphUtils, + private nodeFactory: NodesFactory, + private commonGraphLinkUtils: CompositionGraphLinkUtils, + @Inject(SdcConfigToken) private sdcConfig: ISdcConfig) { + + } + + public _cy: Cy.Instance; + + ngOnInit(): void { + this.topologyTemplateService.getDeploymentGraphData(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId).subscribe((response: ComponentGenericResponse) => { + this.deploymentService.componentInstances = response.componentInstances; + this.deploymentService.componentInstancesRelations = response.componentInstancesRelations; + this.deploymentService.modules = response.modules; + this.loadGraph(); + }); + } + + public findInstanceModule = (groupsArray: Array<Module>, componentInstanceId: string): string => { + let parentGroup: Module = _.find(groupsArray, (group: Module) => { + return _.find(_.values(group.members), (member: string) => { + return member === componentInstanceId; + }); + }); + return parentGroup ? parentGroup.uniqueId : ""; + }; + + public initGraphModules = () => { + if (this.deploymentService.modules) { // Init module nodes + _.each(this.deploymentService.modules, (groupModule: Module) => { + let moduleNode = this.nodeFactory.createModuleNode(groupModule); + this.commonGraphUtils.addNodeToGraph(this._cy, moduleNode); + }); + } + } + + public initGraphComponentInstances = () => { + _.each(this.deploymentService.componentInstances, (instance: ComponentInstance) => { // Init component instance nodes + let componentInstanceNode = this.nodeFactory.createNode(instance); + componentInstanceNode.parent = this.findInstanceModule(this.deploymentService.modules, instance.uniqueId); + if (componentInstanceNode.parent) { // we are not drawing instances that are not a part of a module + this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, componentInstanceNode); + } + }); + } + + public handleEmptyModule = () => { + // This is a special functionality to pass the cytoscape default behavior - we can't create Parent module node without children's + // so we must add an empty dummy child node + _.each(this._cy.nodes('[?isGroup]'), (moduleNode: Cy.CollectionFirstNode) => { + if (!moduleNode.isParent()) { + let dummyInstance = ComponentInstanceFactory.createEmptyComponentInstance(); + let componentInstanceNode = this.nodeFactory.createNode(dummyInstance); + componentInstanceNode.parent = moduleNode.id(); + let dummyNode = this.commonGraphUtils.addNodeToGraph(this._cy, componentInstanceNode, moduleNode.position()); + dummyNode.addClass('dummy-node'); + } + }) + } + + public initGraphNodes = (): void => { + this.initGraphModules(); + this.initGraphComponentInstances(); + this.handleEmptyModule(); + }; + + private loadGraph = () => { + + let graphEl = this.elRef.nativeElement.querySelector('.sdc-deployment-graph-wrapper'); + this._cy = cytoscape({ + container: graphEl, + style: ComponentInstanceNodesStyle.getCompositionGraphStyle().concat(ModulesNodesStyle.getModuleGraphStyle()), + zoomingEnabled: false, + selectionType: 'single', + + }); + + //adding expand collapse extension + this._cy.expandCollapse({ + layoutBy: { + name: "grid", + animate: true, + randomize: false, + fit: true + }, + fisheye: false, + undoable: false, + expandCollapseCueSize: 18, + expandCueImage: this.sdcConfig.imagesPath + '/assets/styles/images/resource-icons/' + 'closeModule.png', + collapseCueImage: this.sdcConfig.imagesPath + '/assets/styles/images/resource-icons/' + 'openModule.png', + expandCollapseCueSensitivity: 2, + cueOffset: -20 + }); + + this.initGraphNodes(); //creating instances nodes + this.commonGraphLinkUtils.initGraphLinks(this._cy, this.deploymentService.componentInstancesRelations); + this._cy.collapseAll(); + }; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.module.ts b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.module.ts new file mode 100644 index 0000000000..91f97db8c3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.module.ts @@ -0,0 +1,15 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {DeploymentGraphComponent} from "./deployment-graph.component"; + +@NgModule({ + declarations: [DeploymentGraphComponent], + imports: [CommonModule], + exports: [DeploymentGraphComponent], + entryComponents: [DeploymentGraphComponent], + providers: [ + + ] +}) +export class DeploymentGraphModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.service.ts b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.service.ts new file mode 100644 index 0000000000..7ec346c20b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/deployment/deployment-graph.service.ts @@ -0,0 +1,8 @@ +import {Injectable} from "@angular/core"; +import 'rxjs/add/observable/forkJoin'; +import {CommonGraphDataService} from "../common/common-graph-data.service"; +import {Module} from "../../../../models/modules/base-module"; +@Injectable() +export class DeploymentGraphService extends CommonGraphDataService { + public modules:Array<Module>; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.html new file mode 100644 index 0000000000..a8645dc5f0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.html @@ -0,0 +1,23 @@ +<div class="canvas-search-component" [ngClass]="{'results-shown': autoCompleteResults.length}" + [class.canvas-search-visible]="autoCompleteValues && autoCompleteValues.length" [attr.data-tests-id]="testId"> + <div class="canvas-search-bar-container" [attr.data-tests-id]="testId" + [class.active]="searchQuery && searchQuery.length"> + <sdc-search-bar class="canvas-search-bar" + [placeHolder]="placeholder" + (onSearchClicked)="onSearchClicked($event)" + [size]="'medium'" + [value]="searchQuery" + (valueChange)="onSearchQueryChanged($event)"> + </sdc-search-bar> + <svg-icon class="canvas-clear-search" + [name]="'close'" + [clickable]="true" + [mode]="'secondary'" + [size]="'small'" + (click)="onClearSearch()"> + </svg-icon> + </div> + <dropdown-results *ngIf="autoCompleteResults && autoCompleteResults.length" [options]="autoCompleteResults" + (onItemSelected)="onItemSelected($event)"></dropdown-results> +</div> + diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.less new file mode 100644 index 0000000000..247f2a3913 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.less @@ -0,0 +1,42 @@ +.canvas-search-component { + + .canvas-search-bar-container { + display:flex; + border-radius: 4px; + align-items: center; + box-shadow: 0px 2px 3.88px 0.12px rgba(0, 0, 0, 0.29); + + /deep/.sdc-search-bar .search-bar-container .search-button { + border: solid 1px #d2d2d2; + } + + /deep/.sdc-input__input { + width: 250px; + transition: all 0.4s; + } + + .canvas-clear-search { + position: absolute; + right: 45px; + } + } + + &:not(:hover):not(.canvas-search-visible):not(.active) { + border-radius: 0; + box-shadow: none; + + /deep/.sdc-input__input:not(:focus) { + border: none; + padding: 0px; + width: 0px; + } + .canvas-clear-search { + display: none; + } + } +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.ts new file mode 100644 index 0000000000..c1a45a9a4b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.component.ts @@ -0,0 +1,25 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {AutoCompleteComponent} from "onap-ui-angular/dist/autocomplete/autocomplete.component"; + +@Component({ + selector: 'canvas-search', + templateUrl: './canvas-search.component.html', + styleUrls: ['./canvas-search.component.less'] +}) +export class CanvasSearchComponent extends AutoCompleteComponent { + + @Output() public searchButtonClicked: EventEmitter<string> = new EventEmitter<string>(); + @Output() public onSelectedItem: EventEmitter<string> = new EventEmitter<string>(); + + public onSearchClicked = (searchText:string)=> { + this.searchButtonClicked.emit(searchText); + } + + public onItemSelected = (selectedItem) => { + this.searchQuery = selectedItem.value; + this.autoCompleteResults = []; + this.searchButtonClicked.emit(this.searchQuery); + this.onSelectedItem.emit(selectedItem); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.module.ts new file mode 100644 index 0000000000..6df06067a6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-search/canvas-search.module.ts @@ -0,0 +1,30 @@ +import {SdcUiComponentsModule} from "onap-ui-angular"; +import { NgModule } from "@angular/core"; +import {CanvasSearchComponent} from "./canvas-search.component"; +import {CommonModule} from "@angular/common"; +import {BrowserAnimationsModule} from "@angular/platform-browser/animations"; +import {HttpClientModule} from "@angular/common/http"; +import {BrowserModule} from "@angular/platform-browser"; +import {AutocompletePipe} from "onap-ui-angular/dist/autocomplete/autocomplete.pipe"; + +@NgModule({ + declarations: [ + CanvasSearchComponent + ], + imports: [ + CommonModule, + BrowserModule, + HttpClientModule, + BrowserAnimationsModule, + SdcUiComponentsModule, + ], + exports: [ + CanvasSearchComponent + ], + entryComponents: [ + CanvasSearchComponent + ], + providers: [AutocompletePipe] +}) +export class CanvasSearchModule { +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/__snapshots__/zone-container.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/__snapshots__/zone-container.component.spec.ts.snap new file mode 100644 index 0000000000..d4e2a7a359 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/__snapshots__/zone-container.component.spec.ts.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`ZoneContainerComponent should match current snapshot of palette element component 1`] = ` +<zone-container + backgroundClick={[Function EventEmitter]} + backgroundClicked={[Function Function]} + minimize={[Function EventEmitter]} + unminifyZone={[Function Function]} +> + <div> + <div + class="sdc-canvas-zone__header" + > + <div + class="sdc-canvas-zone__title" + > + + <span + class="sdc-canvas-zone__counter" + > + + </span> + </div> + <span + class="sdc-canvas-zone__state-button" + > + – + </span> + </div> + <div + class="sdc-canvas-zone__container" + /> + </div> +</zone-container> +`; diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.html index 50a93fac3c..d6343a4a4f 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.html @@ -15,14 +15,16 @@ --> -<div class="sdc-canvas-zone {{class}}-zone" [class.minimized]="minimized" [class.hidden]="!visible" (click)="backgroundClicked()"> - <div class="sdc-canvas-zone__header" (click)="unminifyZone(); $event.stopPropagation();" > - <div class="sdc-canvas-zone__title">{{title}} +<div class="sdc-canvas-zone {{class}}-zone" [class.minimized]="minimized" [class.hidden]="!visible" + (click)="backgroundClicked()"> + <div class="sdc-canvas-zone__header" (click)="unminifyZone(); $event.stopPropagation();"> + <div class="sdc-canvas-zone__title">{{title}} <span class="sdc-canvas-zone__counter">{{count}}</span> </div> <span class="sdc-canvas-zone__state-button">–</span> </div> - <div class="sdc-canvas-zone__container" #scrollDiv > + <div class="sdc-canvas-zone__container" #scrollDiv> <ng-content></ng-content> </div> -</div>
\ No newline at end of file +</div> + diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.less index 02880a9202..827786cc49 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.less @@ -3,12 +3,12 @@ max-height:186px; display:flex; flex-direction:column; - align-self: flex-end; color:white; font-family:OpenSans-Regular, sans-serif; transition: width .2s ease-in-out, max-height .2s ease-in-out .1s; position:relative; bottom:0px; + margin-right: 5px; .sdc-canvas-zone__header { background: #5A5A5A; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.spec.ts new file mode 100644 index 0000000000..c432054492 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.spec.ts @@ -0,0 +1,46 @@ +import {async, ComponentFixture} from '@angular/core/testing'; +import {By} from '@angular/platform-browser'; +import {ConfigureFn, configureTests} from '../../../../../../jest/test-config.helper'; +import 'jest-dom/extend-expect'; +import {ZoneInstanceType} from '../../../../../../app/models/graph/zones/zone-instance'; +import {ZoneContainerComponent} from './zone-container.component'; + + +describe('ZoneContainerComponent', () => { + let fixture: ComponentFixture<ZoneContainerComponent>; + + beforeEach( + async(() => { + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [ZoneContainerComponent] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(ZoneContainerComponent); + }); + }) + ); + + + it('should match current snapshot of palette element component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should have a group-zone class when the ZoneInstanceType is GROUP', + () => { + fixture.componentInstance.type = ZoneInstanceType.GROUP; + fixture.detectChanges(); + const compiled = fixture.debugElement.query(By.css('.sdc-canvas-zone')); + expect(compiled.nativeElement).toHaveClass('group-zone'); + }); + + it('should have a policy-zone class when the ZoneInstanceType is POLICY', + () => { + fixture.componentInstance.type = ZoneInstanceType.POLICY; + fixture.detectChanges(); + const compiled = fixture.debugElement.query(By.css('.sdc-canvas-zone')); + expect(compiled.nativeElement).toHaveClass('policy-zone'); + }); +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.ts index 4059ad6cae..4757c1f36d 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-container.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-container.component.ts @@ -1,5 +1,5 @@ import { Component, Input, Output, ViewEncapsulation, EventEmitter, OnInit } from '@angular/core'; -import { ZoneInstanceType } from '../../../../models/graph/zones/zone-instance'; +import { ZoneInstanceType } from 'app/models/graph/zones/zone-instance'; @Component({ selector: 'zone-container', diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.html index 728764c427..d97be69e34 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.html @@ -12,15 +12,16 @@ * See the License for the specific language governing permissions and * limitations under the License. --> - - -<div tooltip="{{zoneInstance.instanceData.name}}" #currentComponent - class="zone-instance mode-{{zoneInstance.mode}}" [class.locked]="activeInstanceMode > MODE.HOVER" [class.hiding]="hidden" + + +<div #currentComponent class="zone-instance mode-{{zoneInstance.mode}}" [class.locked]="activeInstanceMode > MODE.HOVER" + [class.hiding]="hidden" (mouseenter)="setMode(MODE.HOVER)" (mouseleave)="setMode(MODE.NONE)" (click)="setMode(MODE.SELECTED, $event)"> - <div *ngIf="zoneInstance.handle" class="target-handle {{zoneInstance.handle}}" (click)="tagHandleClicked($event)"></div> + <div class="zone-instance__body" sdc-tooltip tooltip-text="{{zoneInstance.instanceData.name}}" [attr.data-tests-id]="zoneInstance.instanceData.name"> + <div *ngIf="zoneInstance.handle" class="target-handle {{zoneInstance.handle}}" + (click)="tagHandleClicked($event)"></div> <div *ngIf="!isViewOnly" class="zone-instance__handle" (click)="setMode(MODE.TAG, $event)">+</div> - <div class="zone-instance__body"> - <div class="zone-instance__body-content">{{zoneInstance.assignments.length || defaultIconText}}</div> - </div> - <div class="zone-instance__name">{{zoneInstance.instanceData.name}}</div> -</div>
\ No newline at end of file + <div class="zone-instance__body-content">{{zoneInstance.assignments.length || defaultIconText}}</div> + </div> + <div class="zone-instance__name">{{zoneInstance.instanceData.name}}</div> +</div> diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.less index b562c08514..c34b8e149a 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.less @@ -1,7 +1,7 @@ -@import '../../../../../../assets/styles/variables'; +@import '../../../../../../../assets/styles/variables'; .zone-instance { - position:relative; + width:76px; margin:5px; opacity:1; @@ -9,8 +9,8 @@ .zone-instance__handle { display:none; position:absolute; - right:4px; - top:10px; + left: 31px; + top: 8px; width:22px; height:22px; cursor:pointer; @@ -21,6 +21,7 @@ } .zone-instance__body { + position:relative; margin:0 auto; width:43px; height:43px; @@ -55,16 +56,16 @@ height:18px; display:block; top: -4px; - right: 10px; + right: -6px; background-size: 100% 100%; - cursor: url("../../../../../../assets/styles/images/canvas-tagging-icons/policy_2.svg"), pointer; + cursor: url("../../../../../../../assets/styles/images/canvas-tagging-icons/policy_2.svg"), pointer; &.tagged-policy { - background-image: url('../../../../../../assets/styles/images/canvas-tagging-icons/policy_added.svg'); + background-image: url('../../../../../../../assets/styles/images/canvas-tagging-icons/policy_added.svg'); } &.tag-available { - background-image: url('../../../../../../assets/styles/images/canvas-tagging-icons/indication.svg'); + background-image: url('../../../../../../../assets/styles/images/canvas-tagging-icons/indication.svg'); } } @@ -99,8 +100,8 @@ &.mode-3 .zone-instance__handle { width:24px; height:24px; - right:3px; - top:9px; + right:-6px; + top:7px; display:block; background-image: linear-gradient(-140deg, #009E98 0%, #97D648 100%); border: 2px solid @main_color_p; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.spec.ts new file mode 100644 index 0000000000..f5a5f6f546 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.spec.ts @@ -0,0 +1,132 @@ +import { ComponentFixture, TestBed, async } from '@angular/core/testing'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { SimpleChanges } from '@angular/core'; +import { PoliciesService } from 'app/ng2/services/policies.service'; +import { GroupsService } from 'app/ng2/services/groups.service'; +import { EventListenerService } from 'app/services'; +import { Store } from '@ngxs/store'; +import { CompositionService } from 'app/ng2/pages/composition/composition.service'; +import { ZoneInstanceComponent } from './zone-instance.component'; +import { ZoneInstanceType, ZoneInstance, ZoneInstanceMode, ZoneInstanceAssignmentType, IZoneInstanceAssignment } from "app/models"; +import { PolicyInstance } from "app/models/graph/zones/policy-instance"; +import { Subject, of } from 'rxjs'; +import { _throw } from 'rxjs/observable/throw'; + +describe('ZoneInstanceComponent', () => { + let component: ZoneInstanceComponent; + let fixture: ComponentFixture<ZoneInstanceComponent>; + + let createPolicyInstance = () => { + let policy = new PolicyInstance(); + policy.targets = {COMPONENT_INSTANCES: [], GROUPS: []}; + return new ZoneInstance(policy, '', ''); + } + + beforeEach(() => { + const policiesServiceStub = {updateZoneInstanceAssignments : jest.fn()}; + const groupsServiceStub = {}; + const eventListenerServiceStub = {}; + const storeStub = {}; + const compositionServiceStub = {}; + TestBed.configureTestingModule({ + schemas: [NO_ERRORS_SCHEMA], + declarations: [ZoneInstanceComponent], + providers: [ + { provide: PoliciesService, useValue: policiesServiceStub }, + { provide: GroupsService, useValue: groupsServiceStub }, + { provide: EventListenerService, useValue: eventListenerServiceStub }, + { provide: Store, useValue: storeStub }, + { provide: CompositionService, useValue: compositionServiceStub } + ] + }).compileComponents().then(() => { + fixture = TestBed.createComponent(ZoneInstanceComponent); + component = fixture.componentInstance; + }); + }); + + it('can load instance', async((done) => { + component.zoneInstance = <ZoneInstance>{type : ZoneInstanceType.POLICY, instanceData: {name: 'test policy'}, assignments: []}; + component.forceSave = new Subject<Function>(); + fixture.detectChanges(); + expect(component).toBeTruthy(); + })); + + + it('if another instance is already tagging, i cannot change my mode', ()=> { + component.zoneInstance = <ZoneInstance>{ mode: ZoneInstanceMode.NONE }; + component.isActive = false; + component.activeInstanceMode = ZoneInstanceMode.TAG; + component.setMode(ZoneInstanceMode.SELECTED); + expect(component.zoneInstance.mode).toBe(ZoneInstanceMode.NONE); + }); + + it('if i am active(selected) and NOT in tag mode, I can set another mode', ()=> { + component.isActive = true; + component.zoneInstance = <ZoneInstance>{ mode: ZoneInstanceMode.SELECTED }; + jest.spyOn(component.modeChange, 'emit'); + component.setMode(ZoneInstanceMode.NONE); + expect(component.modeChange.emit).toHaveBeenCalledWith({instance: component.zoneInstance, newMode: ZoneInstanceMode.NONE }); + }); + + it('if i am active and in tag mode and i try to set mode other than tag, I am not allowed', ()=> { + component.isActive = true; + component.zoneInstance = <ZoneInstance>{ mode: ZoneInstanceMode.TAG }; + component.setMode(ZoneInstanceMode.SELECTED); + expect(component.zoneInstance.mode).toBe(ZoneInstanceMode.TAG); + }); + + it('if i am active and in tag mode and click tag again and no changes, does NOT call save, but DOES turn tagging off', ()=> { + component.isActive = true; + component.zoneInstance = createPolicyInstance(); + component.zoneService = component.policiesService; + component.zoneInstance.mode = ZoneInstanceMode.TAG; + jest.spyOn(component.zoneService, 'updateZoneInstanceAssignments'); + jest.spyOn(component.modeChange, 'emit'); + + component.setMode(ZoneInstanceMode.TAG); + + expect(component.zoneService.updateZoneInstanceAssignments).not.toHaveBeenCalled(); + expect(component.modeChange.emit).toHaveBeenCalledWith({instance: component.zoneInstance, newMode: ZoneInstanceMode.NONE }); + + }); + it('if i am active and in tag mode and click tag again and HAVE changes, calls save AND turns tagging off', ()=> { + component.isActive = true; + component.zoneInstance = createPolicyInstance(); + component.zoneService = component.policiesService; + component.zoneInstance.mode = ZoneInstanceMode.TAG; + component.zoneInstance.assignments.push(<IZoneInstanceAssignment>{uniqueId: '123', type: ZoneInstanceAssignmentType.COMPONENT_INSTANCES}); + jest.spyOn(component.zoneService, 'updateZoneInstanceAssignments').mockReturnValue(of(true)); + jest.spyOn(component.modeChange, 'emit'); + + component.setMode(ZoneInstanceMode.TAG); + + expect(component.zoneService.updateZoneInstanceAssignments).toHaveBeenCalled(); + expect(component.modeChange.emit).toHaveBeenCalledWith({instance: component.zoneInstance, newMode: ZoneInstanceMode.NONE }); + }); + + it('on save error, temporary assignment list is reverted to saved assignments', ()=> { + component.isActive = true; + component.zoneInstance = createPolicyInstance(); + component.zoneService = component.policiesService; + component.zoneInstance.mode = ZoneInstanceMode.TAG; + component.zoneInstance.assignments.push(<IZoneInstanceAssignment>{uniqueId: '123', type: ZoneInstanceAssignmentType.COMPONENT_INSTANCES}); + jest.spyOn(component.zoneService, 'updateZoneInstanceAssignments').mockReturnValue(_throw({status: 404})); + + component.setMode(ZoneInstanceMode.TAG); + + expect(component.zoneInstance.assignments.length).toEqual(0); + }); + + it('on save success, all changes are saved to zoneInstance.savedAssignments', ()=> { + component.isActive = true; + component.zoneInstance = createPolicyInstance(); + component.zoneService = component.policiesService; + component.zoneInstance.mode = ZoneInstanceMode.TAG; + component.zoneInstance.assignments.push(<IZoneInstanceAssignment>{uniqueId: '123', type: ZoneInstanceAssignmentType.COMPONENT_INSTANCES}); + jest.spyOn(component.zoneService, 'updateZoneInstanceAssignments').mockReturnValue(of(true)); + + component.setMode(ZoneInstanceMode.TAG); + + expect(component.zoneInstance.instanceData.getSavedAssignments().length).toEqual(1); + }); +}); diff --git a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts index 3c2dd45db5..1b1363e576 100644 --- a/catalog-ui/src/app/ng2/components/ui/canvas-zone/zone-instance/zone-instance.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zone-instance/zone-instance.component.ts @@ -3,12 +3,17 @@ import { ZoneInstance, ZoneInstanceMode, ZoneInstanceType, IZoneInstanceAssignment } from 'app/models/graph/zones/zone-instance'; -import { PoliciesService } from '../../../../services/policies.service'; -import { GroupsService } from '../../../../services/groups.service'; -import { IZoneService } from "../../../../../models/graph/zones/zone"; +import { PoliciesService } from 'app/ng2/services/policies.service'; +import { GroupsService } from 'app/ng2/services/groups.service'; +import { IZoneService } from "app/models/graph/zones/zone"; import { EventListenerService } from 'app/services'; -import { GRAPH_EVENTS } from '../../../../../utils'; -import { Subject, Observable } from 'rxjs'; +import { GRAPH_EVENTS } from 'app/utils'; +import { Subject } from 'rxjs'; +import { Store } from "@ngxs/store"; +import { CompositionService } from "app/ng2/pages/composition/composition.service"; +import { PolicyInstance } from "app/models"; +import {SelectedComponentType, SetSelectedComponentAction} from "../../../common/store/graph.actions"; + @Component({ selector: 'zone-instance', @@ -33,7 +38,7 @@ export class ZoneInstanceComponent implements OnInit { private MODE = ZoneInstanceMode; private zoneService:IZoneService; - constructor(private policiesService:PoliciesService, private groupsService:GroupsService, private eventListenerService:EventListenerService){} + constructor(private policiesService:PoliciesService, private groupsService:GroupsService, private eventListenerService:EventListenerService, private compositionService:CompositionService, private store:Store){} ngOnInit(){ if(this.zoneInstance.type == ZoneInstanceType.POLICY){ @@ -41,9 +46,11 @@ export class ZoneInstanceComponent implements OnInit { } else { this.zoneService = this.groupsService; } - this.forceSave.subscribe((afterSaveFunction:Function) => { - this.setMode(ZoneInstanceMode.TAG, null, afterSaveFunction); - }) + if(this.forceSave) { + this.forceSave.subscribe((afterSaveFunction:Function) => { + this.setMode(ZoneInstanceMode.TAG, null, afterSaveFunction); + }) + } } ngOnChanges(changes:SimpleChanges) { @@ -53,7 +60,9 @@ export class ZoneInstanceComponent implements OnInit { } ngOnDestroy() { - this.forceSave.unsubscribe(); + if(this.forceSave) { + this.forceSave.unsubscribe(); + } } private setMode = (mode:ZoneInstanceMode, event?:any, afterSaveCallback?:Function):void => { @@ -79,11 +88,17 @@ export class ZoneInstanceComponent implements OnInit { this.zoneService.updateZoneInstanceAssignments(this.zoneInstance.parentComponentType, this.zoneInstance.parentComponentID, this.zoneInstance.instanceData.uniqueId, this.zoneInstance.assignments).subscribe( (success) => { this.zoneInstance.instanceData.setSavedAssignments(this.zoneInstance.assignments); - if(this.zoneInstance.type === ZoneInstanceType.POLICY){ + + if(this.zoneInstance.instanceData instanceof PolicyInstance){ + this.compositionService.updatePolicy(this.zoneInstance.instanceData); this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, this.zoneInstance.instanceData); + this.store.dispatch(new SetSelectedComponentAction({component: this.zoneInstance.instanceData, type: SelectedComponentType.POLICY})); } else { + this.compositionService.updateGroup(this.zoneInstance.instanceData); this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, this.zoneInstance.instanceData); + this.store.dispatch(new SetSelectedComponentAction({component: this.zoneInstance.instanceData, type: SelectedComponentType.GROUP})); } + this.assignmentSaveComplete.emit(true); if(afterSaveCallback) afterSaveCallback(); }, (error) => { @@ -94,12 +109,15 @@ export class ZoneInstanceComponent implements OnInit { if(afterSaveCallback) afterSaveCallback(); } this.modeChange.emit({newMode: ZoneInstanceMode.NONE, instance: this.zoneInstance}); + // this.store.dispatch(new unsavedChangesActions.RemoveUnsavedChange(this.zoneInstance.instanceData.uniqueId)); + } else { this.modeChange.emit({newMode: mode, instance: this.zoneInstance}); + if(mode == ZoneInstanceMode.TAG){ + // this.store.dispatch(new unsavedChangesActions.AddUnsavedChange(this.zoneInstance.instanceData.uniqueId)); + } } - - } private tagHandleClicked = (event:Event) => { diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zones-module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zones-module.ts new file mode 100644 index 0000000000..3287c01f5a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/canvas-zone/zones-module.ts @@ -0,0 +1,15 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { ZoneContainerComponent } from "./zone-container.component"; +import { ZoneInstanceComponent } from "./zone-instance/zone-instance.component"; +import { SdcUiComponentsModule } from "onap-ui-angular"; + +@NgModule({ + declarations: [ZoneContainerComponent, ZoneInstanceComponent], + imports: [CommonModule, SdcUiComponentsModule], + entryComponents: [ZoneContainerComponent, ZoneInstanceComponent], + exports: [ZoneContainerComponent, ZoneInstanceComponent], + providers: [] +}) +export class ZoneModules { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/common/common-graph-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/common/common-graph-utils.ts new file mode 100644 index 0000000000..bfc540e97e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/common/common-graph-utils.ts @@ -0,0 +1,304 @@ +/*- + * ============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 { + CommonNodeBase, + Relationship, + CompositionCiNodeBase +} from "app/models"; +import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link"; +import {Requirement, Capability} from "app/models"; +import {Injectable} from "@angular/core"; + + + +@Injectable() +export class CommonGraphUtils { + + constructor() { + + } + + public safeApply = (scope:ng.IScope, fn:any) => { //todo remove to general utils + let phase = scope.$root.$$phase; + if (phase == '$apply' || phase == '$digest') { + if (fn && (typeof(fn) === 'function')) { + fn(); + } + } else { + scope.$apply(fn); + } + }; + + /** + * Draw node on the graph + * @param cy + * @param compositionGraphNode + * @param position + * @returns {CollectionElements} + */ + public addNodeToGraph(cy:Cy.Instance, compositionGraphNode:CommonNodeBase, position?:Cy.Position):Cy.CollectionElements { + + let node = cy.add(<Cy.ElementDefinition> { + group: 'nodes', + position: position, + data: compositionGraphNode, + classes: compositionGraphNode.classes + }); + + this.initNodeTooltip(node); + return node; + }; + + /** + * The function will create a component instance node by the componentInstance position. + * If the node is UCPE the function will create all cp lan&wan for the ucpe + * @param cy + * @param compositionGraphNode + * @returns {Cy.CollectionElements} + */ + public addComponentInstanceNodeToGraph(cy:Cy.Instance, compositionGraphNode:CompositionCiNodeBase):Cy.CollectionElements { + + let nodePosition = { + x: +compositionGraphNode.componentInstance.posX, + y: +compositionGraphNode.componentInstance.posY + }; + + let node = this.addNodeToGraph(cy, compositionGraphNode, nodePosition); + return node; + }; + + /** + * Add service path link to graph - only draw the link + * @param cy + * @param link + */ + public insertServicePathLinkToGraph = (cy:Cy.Instance, link:CompositionCiServicePathLink) => { + let linkElement = cy.add({ + group: 'edges', + data: link, + classes: link.classes + }); + this.initServicePathTooltip(linkElement, link); + }; + + /** + * Returns function for the link tooltip content + * @param {Relationship} linkRelation + * @param {Requirement} requirement + * @param {Capability} capability + * @returns {() => string} + * @private + */ + private _getLinkTooltipContent(linkRelation:Relationship, requirement?:Requirement, capability?:Capability):string { + return '<div class="line">' + + '<span class="req-cap-label">R: </span>' + + '<span>' + (requirement ? requirement.getTitle() : linkRelation.relation.requirement) + '</span>' + + '</div>' + + '<div class="line">' + + '<div class="sprite-new link-tooltip-arrow"></div>' + + '<span class="req-cap-label">C: </span>' + + '<span>' + (capability ? capability.getTitle() : linkRelation.relation.capability) + '</span>' + + '</div>'; + } + + /** + * This function will init qtip tooltip on the link + * @param linkElement - the link we want the tooltip to apply on, + * @param link + * @param getLinkRequirementCapability + * link - the link obj + */ + public initLinkTooltip(linkElement:Cy.CollectionElements, link:Relationship, getLinkRequirementCapability:Function) { + const content = () => this._getLinkTooltipContent(link); // base tooltip content without owner names + const render = (event, api) => { + // on render (called once at first show), get the link requirement and capability and change to full tooltip content (with owner names) + getLinkRequirementCapability().then((linkReqCap) => { + const fullContent = () => this._getLinkTooltipContent(link, linkReqCap.requirement, linkReqCap.capability); + api.set('content.text', fullContent); + }); + }; + linkElement.qtip(this.prepareInitTooltipData({content, events: {render}})); + }; + + /** + * + * @param linkElement + * @param link + */ + public initServicePathTooltip(linkElement:Cy.CollectionElements, link:CompositionCiServicePathLink) { + let content = function () { + return '<div class="line">' + + '<div>' + link.pathName + '</div>' + + '</div>'; + }; + linkElement.qtip(this.prepareInitTooltipData({content})); + }; + + private prepareInitTooltipData(options?:Object) { + return _.merge({ + position: { + my: 'top center', + at: 'bottom center', + adjust: {x: 0, y: 0}, + effect: false + }, + style: { + classes: 'qtip-dark qtip-rounded qtip-custom link-qtip', + tip: { + width: 16, + height: 8 + } + }, + show: { + event: 'mouseover', + delay: 1000 + }, + hide: {event: 'mouseout mousedown'}, + includeLabels: true, + events: {} + }, options); + } + + public HTMLCoordsToCytoscapeCoords(cytoscapeBoundingBox:Cy.Extent, mousePos:Cy.Position):Cy.Position { + return {x: mousePos.x + cytoscapeBoundingBox.x1, y: mousePos.y + cytoscapeBoundingBox.y1} + }; + + + public getCytoscapeNodePosition = (cy:Cy.Instance, event:DragEvent | MouseEvent):Cy.Position => { + let targetOffset = $(event.target).offset(); + if(event instanceof DragEvent) { + targetOffset = $('canvas').offset(); + } + + let x = (event.pageX - targetOffset.left) / cy.zoom(); + let y = (event.pageY - targetOffset.top) / cy.zoom(); + + return this.HTMLCoordsToCytoscapeCoords(cy.extent(), { + x: x, + y: y + }); + }; + + + public getNodePosition(node:Cy.CollectionFirstNode):Cy.Position { + let nodePosition = node.relativePoint(); + if (node.data().isUcpe) { //UCPEs use bounding box and not relative point. + nodePosition = {x: node.boundingbox().x1, y: node.boundingbox().y1}; + } + + return nodePosition; + } + + /** + * Generic function that can be used for any html elements overlaid on canvas + * Returns the html position of a node on canvas, including left palette and header offsets. Option to pass in additional offset to add to return position. + * @param node + * @param additionalOffset + * @returns {Cy.Position} + + public getNodePositionWithOffset = (node:Cy.CollectionFirstNode, additionalOffset?:Cy.Position): Cy.Position => { + if(!additionalOffset) additionalOffset = {x: 0, y:0}; + + let nodePosition = node.renderedPosition(); + let posWithOffset:Cy.Position = { + x: nodePosition.x + GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET + additionalOffset.x, + y: nodePosition.y + GraphUIObjects.COMPOSITION_HEADER_OFFSET + additionalOffset.y + }; + return posWithOffset; + };*/ + + /** + * return true/false if first node contains in second - this used in order to verify is node is entirely inside ucpe + * @param firstBox + * @param secondBox + * @returns {boolean} + */ + public isFirstBoxContainsInSecondBox(firstBox:Cy.BoundingBox, secondBox:Cy.BoundingBox) { + + return firstBox.x1 > secondBox.x1 && firstBox.x2 < secondBox.x2 && firstBox.y1 > secondBox.y1 && firstBox.y2 < secondBox.y2; + + }; + + /** + * + * @param cy + * @param node + * @returns {Array} + */ + public getLinkableNodes(cy:Cy.Instance, node:Cy.CollectionFirstNode):Array<CompositionCiNodeBase> { + let compatibleNodes = []; + _.each(cy.nodes(), (tempNode)=> { + if (this.nodeLocationsCompatible(node, tempNode)) { + compatibleNodes.push(tempNode.data()); + } + }); + return compatibleNodes; + } + + /** + * Checks whether node locations are compatible in reference to UCPEs. + * Returns true if both nodes are in UCPE or both nodes out, or one node is UCPEpart. + * @param node1 + * @param node2 + */ + public nodeLocationsCompatible(node1:Cy.CollectionFirstNode, node2:Cy.CollectionFirstNode) { + return (this.isFirstBoxContainsInSecondBox(node1.boundingbox(), node2.boundingbox())); + } + + /** + * This function will init qtip tooltip on the node + * @param node - the node we want the tooltip to apply on + */ + public initNodeTooltip(node:Cy.CollectionNodes) { + + let opts = { + content: function () { + return this.data('name'); + }, + position: { + my: 'top center', + at: 'bottom center', + adjust: {x: 0, y: -5} + }, + style: { + classes: 'qtip-dark qtip-rounded qtip-custom', + tip: { + width: 16, + height: 8 + } + }, + show: { + event: 'mouseover', + delay: 1000 + }, + hide: {event: 'mouseout mousedown'}, + includeLabels: true + }; + + if (node.data().isUcpePart) { //fix tooltip positioning for UCPE-cps + opts.position.adjust = {x: 0, y: 20}; + } + + node.qtip(opts); + }; +} + diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/common/image-creator.service.ts b/catalog-ui/src/app/ng2/pages/composition/graph/common/image-creator.service.ts new file mode 100644 index 0000000000..2be92c782b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/common/image-creator.service.ts @@ -0,0 +1,92 @@ +/*- + * ============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========================================================= + */ +'use strict'; +import {Injectable} from "@angular/core"; + +export interface ICanvasImage { + src: string; + width: number + height: number; + x: number; + y: number; +} + +@Injectable() +export class ImageCreatorService { + + private _canvas:HTMLCanvasElement; + + constructor() { + this._canvas = <HTMLCanvasElement>$('<canvas>')[0]; + this._canvas.setAttribute('style', 'display:none'); + + let body = document.getElementsByTagName('body')[0]; + body.appendChild(this._canvas); + } + + /** + * Create an image composed of different image layers + * @param canvasImages + * @param canvasWidth + * @param canvasHeight + * returns a PROMISE + */ + getMultiLayerBase64Image(canvasImages: ICanvasImage[], canvasWidth?:number, canvasHeight?:number):Promise<string> { + + var promise = new Promise<string>((resolve, reject) => { + if(canvasImages && canvasImages.length === 0){ + return null; + } + + //If only width was set, use it for height, otherwise use first canvasImage height + canvasHeight = canvasHeight || canvasImages[0].height; + canvasWidth = canvasWidth || canvasImages[0].width; + + const images = []; + let imagesLoaded = 0; + const onImageLoaded = () => { + imagesLoaded++; + if(imagesLoaded < canvasImages.length){ + return; + } + this._canvas.setAttribute('width', (canvasWidth * 4).toString()); + this._canvas.setAttribute('height', (canvasHeight * 4).toString()); + const canvasCtx = this._canvas.getContext('2d'); + canvasCtx.scale(4,4); + canvasCtx.clearRect(0, 0, this._canvas.width, this._canvas.height); + images.forEach((image, index) => { + const canvasImage = canvasImages[index]; + canvasCtx.drawImage(image, canvasImage.x, canvasImage.y, canvasImage.width, canvasImage.height); + }); + + let base64Image = this._canvas.toDataURL(); + resolve(base64Image) + }; + canvasImages.forEach(canvasImage => { + let image = new Image(); + image.onload = onImageLoaded; + image.src = canvasImage.src; + images.push(image); + }); + }); + + return promise; + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.spec.ts new file mode 100644 index 0000000000..54b3dbed24 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.spec.ts @@ -0,0 +1,37 @@ +import {async} from "@angular/core/testing"; +import {ComponentInstanceNodesStyle} from "./component-instances-nodes-style"; + + +describe('component instance nodes style component', () => { + + beforeEach( + async(() => { + const createElement = document.createElement.bind(document); + document.createElement = (tagName) => { + if (tagName === 'canvas') { + return { + getContext: () => ({ + font: "", + measureText: (x) => ({width: x.length}) + }), + }; + } + return createElement(tagName); + }; + }) + ); + + it('verify getGraphDisplayName for String.length smaller than 67 chars', () => { + let inputString = 'SomeText'; + let expectedRes = inputString; + let res = ComponentInstanceNodesStyle.getGraphDisplayName(inputString); + expect(res).toBe(expectedRes); + }); + + it('verify getGraphDisplayName for String.length greater than 67 chars', () => { + let inputString = 'AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFFFGGGGGGGGGG12345678'; + let expectedRes = 'AAAAAAAAAABBBBBBBBBBCCCCCCCCCCDDDDDDDDDDEEEEEEEEEEFFFFFFFFF...'; + let res = ComponentInstanceNodesStyle.getGraphDisplayName(inputString); + expect(res).toBe(expectedRes); + }); +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.ts b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.ts new file mode 100644 index 0000000000..cc9cac16e6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/component-instances-nodes-style.ts @@ -0,0 +1,362 @@ +/*- + * ============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 { GraphColors, GraphUIObjects} from "app/utils/constants"; +import constant = require("lodash/constant"); +import {ImagesUrl} from "app/utils/constants"; +import {AngularJSBridge} from "app/services/angular-js-bridge-service"; +import { CanvasHandleTypes } from "app/utils"; +/** + * Created by obarda on 12/18/2016. + */ +export class ComponentInstanceNodesStyle { + + public static getCompositionGraphStyle = ():Array<Cy.Stylesheet> => { + return [ + { + selector: 'core', + css: { + 'shape': 'rectangle', + 'active-bg-size': 0, + 'selection-box-color': 'rgb(0, 159, 219)', + 'selection-box-opacity': 0.2, + 'selection-box-border-color': '#009fdb', + 'selection-box-border-width': 1 + + } + }, + { + selector: 'node', + css: { + 'font-family': 'OpenSans-Regular,sans-serif', + + 'font-size': 14, + 'events': 'yes', + 'text-events': 'yes', + 'text-border-width': 15, + 'text-border-color': GraphColors.NODE_UCPE, + 'text-margin-y': 5 + } + }, + { + selector: '.vf-node', + css: { + 'background-color': 'transparent', + 'shape': 'rectangle', + 'label': 'data(displayName)', + 'background-image': 'data(img)', + 'width': GraphUIObjects.DEFAULT_RESOURCE_WIDTH, + 'height': GraphUIObjects.DEFAULT_RESOURCE_WIDTH, + 'background-opacity': 0, + "background-width": GraphUIObjects.DEFAULT_RESOURCE_WIDTH, + "background-height": GraphUIObjects.DEFAULT_RESOURCE_WIDTH, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'background-fit': 'cover', + 'background-clip': 'node', + 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + + { + selector: '.service-node', + css: { + 'background-color': 'transparent', + 'label': 'data(displayName)', + 'events': 'yes', + 'text-events': 'yes', + 'background-image': 'data(img)', + 'width': 64, + 'height': 64, + "border-width": 0, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'background-opacity': 0, + 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + { + selector: '.cp-node', + css: { + 'background-color': 'rgb(255,255,255)', + 'shape': 'rectangle', + 'label': 'data(displayName)', + 'background-image': 'data(img)', + 'background-width': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'background-height': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'width': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE, + 'height': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE/2, + 'background-position-x': GraphUIObjects.HANDLE_SIZE / 2, + 'background-position-y': GraphUIObjects.HANDLE_SIZE / 2, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'background-opacity': 0, + 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + { + selector: '.vl-node', + css: { + 'background-color': 'rgb(255,255,255)', + 'shape': 'rectangle', + 'label': 'data(displayName)', + 'background-image': 'data(img)', + 'background-width': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'background-height': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'background-position-x': GraphUIObjects.HANDLE_SIZE / 2, + 'background-position-y': GraphUIObjects.HANDLE_SIZE / 2, + 'width': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE, + 'height': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE / 2, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'background-opacity': 0, + 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + { + selector: '.ucpe-cp', + css: { + 'background-color': GraphColors.NODE_UCPE_CP, + 'background-width': 15, + 'background-height': 15, + 'width': 15, + 'height': 15, + 'text-halign': 'center', + 'overlay-opacity': 0, + 'label': 'data(displayName)', + 'text-valign': 'data(textPosition)', + 'text-margin-y': (ele:Cy.Collection) => { + return (ele.data('textPosition') == 'top') ? -5 : 5; + }, + 'font-size': 12 + } + }, + { + selector: '.ucpe-node', + css: { + 'background-fit': 'cover', + 'padding-bottom': 0, + 'padding-top': 0 + } + }, + { + selector: '.simple-link', + css: { + 'width': 1, + 'line-color': GraphColors.BASE_LINK, + 'target-arrow-color': '#3b7b9b', + 'target-arrow-shape': 'triangle', + 'curve-style': 'bezier', + 'control-point-step-size': 30 + } + }, + { + selector: '.vl-link', + css: { + 'width': 3, + 'line-color': GraphColors.VL_LINK, + 'curve-style': 'bezier', + 'control-point-step-size': 30 + } + }, + { + selector: '.vl-link-1', + css: { + 'width': 3, + 'line-color': GraphColors.ACTIVE_LINK, + 'curve-style': 'unbundled-bezier', + 'target-arrow-color': '#3b7b9b', + 'target-arrow-shape': 'triangle', + 'control-point-step-size': 30 + } + }, + { + selector: '.ucpe-host-link', + css: { + 'width': 0 + } + }, + { + selector: '.not-certified-link', + css: { + 'width': 1, + 'line-color': GraphColors.NOT_CERTIFIED_LINK, + 'curve-style': 'bezier', + 'control-point-step-size': 30, + 'line-style': 'dashed', + 'target-arrow-color': '#3b7b9b', + 'target-arrow-shape': 'triangle' + + } + }, + + { + selector: '.service-path-link', + css: { + 'width': 2, + 'line-color': GraphColors.SERVICE_PATH_LINK, + 'target-arrow-color': GraphColors.SERVICE_PATH_LINK, + 'target-arrow-shape': 'triangle', + 'curve-style': 'bezier', + 'control-point-step-size': 30 + } + }, + { + selector: '.not-certified', + css: { + 'shape': 'rectangle', + 'background-image': (ele:Cy.Collection) => { + // return ele.data().setUncertifiedImageBgStyle(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE);//Change name to setUncertifiedImageBgStyle?? + return ele.data().initUncertifiedImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE); + }, + 'border-width': 0 + } + }, + { + selector: '.dependent', + css: { + 'shape': 'rectangle', + 'background-image': (ele:Cy.Collection) => { + return ele.data().initDependentImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE) + }, + 'border-width': 0 + } + }, + { + selector: '.dependent.not-certified', + css: { + 'shape': 'rectangle', + 'background-image': (ele:Cy.Collection) => { + return ele.data().initUncertifiedDependentImage(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE) + }, + 'border-width': 0 + } + }, + { + selector: 'node:selected', + css: { + "border-width": 2, + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'shape': 'rectangle' + } + }, + { + selector: 'edge:selected', + css: { + 'line-color': GraphColors.ACTIVE_LINK + + } + }, + { + selector: 'edge:active', + css: { + 'overlay-opacity': 0 + } + }, { + selector: '.configuration-node', + css: { + 'background-color': 'rgb(255,255,255)', + 'shape': 'rectangle', + 'label': 'data(displayName)', + 'background-image': 'data(img)', + 'background-width': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'background-height': GraphUIObjects.SMALL_RESOURCE_WIDTH, + 'background-position-x': GraphUIObjects.HANDLE_SIZE / 2, + 'background-position-y': GraphUIObjects.HANDLE_SIZE / 2, + 'width': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE, + 'height': GraphUIObjects.SMALL_RESOURCE_WIDTH + GraphUIObjects.HANDLE_SIZE/2, + 'text-valign': 'bottom', + 'text-halign': 'center', + 'background-opacity': 0, + 'overlay-color': GraphColors.NODE_BACKGROUND_COLOR, + 'overlay-opacity': 0 + } + }, + { + selector: '.archived', + css: { + 'shape': 'rectangle', + 'background-image': (ele:Cy.Collection) => { + return ele.data().setArchivedImageBgStyle(ele, GraphUIObjects.NODE_OVERLAP_MIN_SIZE); //Change name to setArchivedImageBgStyle ?? + }, + "border-width": 0 + } + } + ] + } + + public static getAddEdgeHandle = () => { + return { + + single: false, + type: CanvasHandleTypes.ADD_EDGE, + imageUrl: AngularJSBridge.getAngularConfig().imagesPath + ImagesUrl.CANVAS_PLUS_ICON, + lineColor: '#27a337', + lineWidth: 2, + lineStyle: 'dashed' + + } + } + + public static getTagHandle = () => { + return { + single: false, + type: CanvasHandleTypes.TAG_AVAILABLE, + imageUrl: AngularJSBridge.getAngularConfig().imagesPath + ImagesUrl.CANVAS_TAG_ICON, + } + } + + public static getTaggedPolicyHandle = () => { + return { + single: false, + type: CanvasHandleTypes.TAGGED_POLICY, + imageUrl: AngularJSBridge.getAngularConfig().imagesPath + ImagesUrl.CANVAS_POLICY_TAGGED_ICON, + } + } + + public static getTaggedGroupHandle = () => { + return { + single: false, + type: CanvasHandleTypes.TAGGED_GROUP, + imageUrl: AngularJSBridge.getAngularConfig().imagesPath + ImagesUrl.CANVAS_GROUP_TAGGED_ICON, + } + } + + public static getGraphDisplayName(name:string):string { + let context = document.createElement("canvas").getContext("2d"); + context.font = "13px Arial"; + + if (67 < context.measureText(name).width) { + let newLen = name.length - 3; + let newName = name.substring(0, newLen); + + while (59 < (context.measureText(newName).width)) { + newName = newName.substring(0, (--newLen)); + } + return newName + '...'; + } + return name; + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/common/style/module-node-style.ts b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/module-node-style.ts new file mode 100644 index 0000000000..bf71e1c868 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/common/style/module-node-style.ts @@ -0,0 +1,103 @@ +/*- + * ============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 {GraphColors} from "app/utils"; +export class ModulesNodesStyle { + + public static getModuleGraphStyle = ():Array<Cy.Stylesheet> => { + + return [ + { + selector: '.cy-expand-collapse-collapsed-node', + css: { + 'background-image': 'data(img)', + 'width': 34, + 'height': 32, + 'background-opacity': 0, + 'shape': 'rectangle', + 'label': 'data(displayName)', + 'events': 'yes', + 'text-events': 'yes', + 'text-valign': 'bottom', + 'text-halign': 'center', + 'text-margin-y': 5, + 'border-opacity': 0 + } + }, + { + selector: '.module-node', + css: { + 'background-color': 'transparent', + 'background-opacity': 0, + "border-width": 2, + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'border-style': 'dashed', + 'label': 'data(displayName)', + 'events': 'yes', + 'text-events': 'yes', + 'text-valign': 'bottom', + 'text-halign': 'center', + 'text-margin-y': 8 + } + }, + { + selector: 'node:selected', + css: { + "border-opacity": 0 + } + }, + { + selector: '.simple-link:selected', + css: { + 'line-color': GraphColors.BASE_LINK, + } + }, + { + selector: '.vl-link:selected', + css: { + 'line-color': GraphColors.VL_LINK, + } + }, + { + selector: '.cy-expand-collapse-collapsed-node:selected', + css: { + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'border-opacity': 1, + 'border-style': 'solid', + 'border-width': 2 + } + }, + { + selector: '.module-node:selected', + css: { + "border-color": GraphColors.NODE_SELECTED_BORDER_COLOR, + 'border-opacity': 1 + } + }, + { + selector: '.dummy-node', + css: { + 'width': 20, + 'height': 20 + } + }, + ] + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html new file mode 100644 index 0000000000..5a0ca3e43f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.html @@ -0,0 +1,57 @@ +<div class="sdc-composition-graph-wrapper {{zoneTagMode}}" + [ngClass]="{'with-sidebar': withSidebar$ | async, 'view-only':isViewOnly$ | async}"> +</div> + +<div class="sdc-composition-menu" [ngClass]="{'with-sidebar': withSidebar$ | async}"> + + <service-path-selector + *ngIf="topologyTemplate.isService() && compositionService.forwardingPaths" + [drawPath]="drawPathOnCy" + [deletePaths]="deletePathsOnCy" + [selectedPathId]="selectedPathId"> + </service-path-selector> + + <canvas-search *ngIf="componentInstanceNames" class="composition-search" + [placeholder]="'Type to search'" + [data]="componentInstanceNames" + (searchChanged)="getAutoCompleteValues($event)" + (searchButtonClicked)="highlightSearchMatches($event)"> + </canvas-search> + + <!--<service-path class="zoom-icons"--> + <!--*ngIf="!(isViewOnly$ | async) && topologyTemplate.isService()"--> + <!--[service]="topologyTemplate"--> + <!--[onCreate]="createOrUpdateServicePath">--> + <!--</service-path>--> + + <svg-icon *ngIf="!(isViewOnly$ | async) && topologyTemplate.isService()" class="zoom-icons" [mode]="'primary2'" [size]="'medium'" [backgroundShape]="'rectangle'" + [backgroundColor]="'silver'" [name]="'browse'" [clickable]="true" [testId]="'pathsMenuBtn'" + (click)="openServicePathMenu($event)"></svg-icon> + <svg-icon class="zoom-icons" [mode]="'primary2'" [size]="'medium'" [backgroundShape]="'rectangle'" + [backgroundColor]="'silver'" [name]="'expand-o'" [clickable]="true" + (click)="zoomAllWithoutSidebar()"></svg-icon> + <svg-icon class="zoom-icons" [mode]="'primary2'" [size]="'medium'" [backgroundShape]="'rectangle'" + [backgroundColor]="'silver'" [name]="'plus'" [clickable]="true" + (click)="zoom(true)"></svg-icon> + <svg-icon class="zoom-icons" [mode]="'primary2'" [size]="'medium'" [backgroundShape]="'rectangle'" + [backgroundColor]="'silver'" [name]="'minus'" [clickable]="true" + (click)="zoom(false)"></svg-icon> +</div> + +<div class="sdc-canvas-zones__wrapper {{zoneTagMode}}" [ngClass]="{'with-sidebar': withSidebar$ | async}"> + <zone-container *ngFor="let zone of zones" [title]="zone.title" [type]="zone.type" [count]="zone.instances.length" + [visible]="zone.visible" [minimized]="zone.minimized" (minimize)="zoneMinimizeToggle(zone.type)" + (backgroundClick)="zoneBackgroundClicked()"> + <zone-instance *ngFor="let instance of zone.instances" [hidden]="instance.hidden" + [zoneInstance]="instance" [defaultIconText]="zone.defaultIconText" + [isActive]="activeZoneInstance == instance" + [activeInstanceMode]="activeZoneInstance && activeZoneInstance.mode" + [isViewOnly]="isViewOnly$ | async" + [forceSave]="instance.forceSave" + (modeChange)="zoneInstanceModeChanged($event.newMode, $event.instance, zone.type)" + (tagHandleClick)="zoneInstanceTagged($event)" + (assignmentSaveStart)="zoneAssignmentSaveStart()" + (assignmentSaveComplete)="zoneAssignmentSaveComplete($event)"> + </zone-instance> + </zone-container> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.less new file mode 100644 index 0000000000..b3e5ef3a0c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.less @@ -0,0 +1,93 @@ +:host(composition-graph) { + flex: 1; + padding-top: 53px; +} + +.composition { + .custom-modal { + /* Hack solution to hide canvas tooltips under modals */ + z-index: 20000 !important; + } +} + +.sdc-composition-graph-wrapper { + height: 100%; + width: 100%; + + &.with-sidebar { + width: calc(~'100% - 300px'); + } +} + +.view-only { + background-color: rgb(248, 248, 248); +} + +.sdc-canvas-zones__wrapper { + position: absolute; + bottom: 10px; + right: 12px; + display: flex; + transition: right 0.2s; + + &.with-sidebar { + right: 310px; + } + + ng2-zone-container { + display: flex; + margin-left: 10px; + } +} + +.group-tagging { + cursor: url("../../../../../assets/styles/images/canvas-tagging-icons/group_1.svg"), pointer; +} + +.group-tagging-hover { + cursor: url("../../../../../assets/styles/images/canvas-tagging-icons/group_2.svg"), pointer; +} + +.policy-tagging { + cursor: url("../../../../../assets/styles/images/canvas-tagging-icons/policy_1.svg"), pointer; +} + +.policy-tagging-hover { + cursor: url("../../../../../assets/styles/images/canvas-tagging-icons/policy_2.svg"), pointer; +} + +//Canvas menu +.sdc-composition-menu { + position: absolute; + right: 18px; + top: 53px; + transition: right 0.2s; + display: flex; + flex-direction: column; + align-items: flex-end; + margin-right: 10px; + pointer-events: none; + + & > * { + pointer-events: all; + } + + &.with-sidebar { + right: 320px; + } + + .composition-search { + margin-top: 12px; + } + + .zoom-icons { + border: solid 1px #d2d2d2; + border-radius: 2px; + box-shadow: 0px 2px 3.88px 0.12px rgba(0, 0, 0, 0.29); + margin-top: 10px; + + /deep/ .svg-icon { + box-sizing: content-box; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.spec.ts new file mode 100644 index 0000000000..9a15ecba69 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.spec.ts @@ -0,0 +1,354 @@ +import {NO_ERRORS_SCHEMA} from '@angular/core'; +import {async, ComponentFixture} from '@angular/core/testing'; +import {SdcUiServices} from 'onap-ui-angular'; +import 'rxjs/add/observable/of'; +import {ConfigureFn, configureTests} from '../../../../../jest/test-config.helper'; +import {CompositionGraphComponent} from "./composition-graph.component"; +import {WorkspaceService} from "../../workspace/workspace.service"; +import {ComponentInstance, GroupInstance, NodesFactory, ZoneInstance, ZoneInstanceMode} from "../../../../models"; +import {EventListenerService} from "../../../../services"; +import { + CompositionGraphGeneralUtils, + CompositionGraphNodesUtils, + CompositionGraphZoneUtils, + MatchCapabilitiesRequirementsUtils, ServicePathGraphUtils +} from "./utils"; +import {CompositionGraphLinkUtils} from "./utils/composition-graph-links-utils"; +import {ConnectionWizardService} from "./connection-wizard/connection-wizard.service"; +import {CommonGraphUtils} from "./common/common-graph-utils"; +import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {ComponentInstanceServiceNg2} from "../../../services/component-instance-services/component-instance.service"; +import {CompositionService} from "../composition.service"; +import {ModalService} from '../../../services/modal.service'; +import {Store} from '@ngxs/store'; +import {PoliciesService} from '../../../services/policies.service'; +import {GroupsService} from '../../../services/groups.service'; +import {PolicyInstance} from "../../../../models/graph/zones/policy-instance"; +import {ZoneInstanceType} from "../../../../models/graph/zones/zone-instance"; +import {GRAPH_EVENTS} from "../../../../utils/constants"; +import * as cytoscape from "cytoscape"; +import {ComponentMetadata} from "../../../../models/component-metadata"; +import {Zone} from "../../../../models/graph/zones/zone"; +import {SelectedComponentType, SetSelectedComponentAction} from "../common/store/graph.actions"; + +describe('composition graph component', () => { + + let fixture: ComponentFixture<CompositionGraphComponent>; + let instance: CompositionGraphComponent; + let eventServiceMock: Partial<EventListenerService>; + let compositionGraphZoneUtils: Partial<CompositionGraphZoneUtils>; + let generalGraphUtils: Partial<CompositionGraphGeneralUtils>; + let workspaceServiceMock: Partial<WorkspaceService>; + let policyService: Partial<PoliciesService>; + let storeStub; + let compositionGraphLinkUtils: Partial<CompositionGraphLinkUtils>; + let nodesGraphUtils: Partial<CompositionGraphNodesUtils>; + + let createPolicyInstance = () => { + let policy = new PolicyInstance(); + policy.targets = {COMPONENT_INSTANCES: [], GROUPS: []}; + return new ZoneInstance(policy, '', ''); + } + + beforeEach( + async(() => { + + eventServiceMock = { + notifyObservers: jest.fn(), + unRegisterObserver: jest.fn() + } + + compositionGraphZoneUtils = { + endCyTagMode: jest.fn(), + showZoneTagIndications: jest.fn(), + hideZoneTagIndications: jest.fn(), + hideGroupZoneIndications: jest.fn(), + showGroupZoneIndications: jest.fn(), + startCyTagMode: jest.fn() + } + + workspaceServiceMock = { + metadata: <ComponentMetadata>{ + uniqueId: 'service_unique_id', + componentType: 'SERVICE' + } + } + + compositionGraphLinkUtils = { + handleLinkClick: jest.fn(), + getModifyLinkMenu: jest.fn() + } + + storeStub = { + dispatch: jest.fn() + } + policyService = { + getSpecificPolicy: jest.fn() + } + + generalGraphUtils = { + zoomGraphTo: jest.fn() + } + + nodesGraphUtils = { + onNodesPositionChanged: jest.fn() + } + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [CompositionGraphComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: NodesFactory, useValue: {}}, + {provide: EventListenerService, useValue: eventServiceMock}, + {provide: CompositionGraphZoneUtils, useValue: compositionGraphZoneUtils}, + {provide: CompositionGraphGeneralUtils, useValue: generalGraphUtils}, + {provide: CompositionGraphLinkUtils, useValue: compositionGraphLinkUtils}, + {provide: CompositionGraphNodesUtils, useValue: nodesGraphUtils}, + {provide: ConnectionWizardService, useValue: {}}, + {provide: CommonGraphUtils, useValue: {}}, + {provide: CompositionGraphPaletteUtils, useValue: {}}, + {provide: TopologyTemplateService, useValue: {}}, + {provide: ComponentInstanceServiceNg2, useValue: {}}, + {provide: MatchCapabilitiesRequirementsUtils, useValue: {}}, + {provide: CompositionService, useValue: {}}, + {provide: SdcUiServices.LoaderService, useValue: {}}, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: SdcUiServices.NotificationsService, useValue: {}}, + {provide: SdcUiServices.simplePopupMenuService, useValue: {}}, + {provide: ServicePathGraphUtils, useValue: {}}, + {provide: ModalService, useValue: {}}, + {provide: PoliciesService, useValue: policyService}, + {provide: GroupsService, useValue: {}}, + {provide: Store, useValue: storeStub}, + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(CompositionGraphComponent); + instance = fixture.componentInstance; + instance._cy = cytoscape({}); + }); + }) + ); + + it('composition graph component should be defined', () => { + expect(fixture).toBeDefined(); + }); + + describe('on zone instance mode changed', () => { + let newZoneInstance: ZoneInstance; + + beforeEach( + async(() => { + newZoneInstance = createPolicyInstance(); + instance.zoneTagMode = null; + instance.zones = []; + instance.zones[ZoneInstanceType.POLICY] = new Zone('Policies', 'P', ZoneInstanceType.POLICY); + instance.zones[ZoneInstanceType.GROUP] = new Zone('Groups', 'G', ZoneInstanceType.GROUP); + instance.activeZoneInstance = createPolicyInstance(); + })) + + it('zone instance in tag mode and we want to turn tag mode off', () => { + instance.zoneTagMode = 'some_zone_id'; + instance.activeZoneInstance = newZoneInstance; + instance.zoneInstanceModeChanged(ZoneInstanceMode.NONE, newZoneInstance, ZoneInstanceType.POLICY); + expect(instance.eventListenerService.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_CANVAS_TAG_END, newZoneInstance); + expect(instance.activeZoneInstance.mode).toBe(ZoneInstanceMode.SELECTED) + }) + + it('we are not in tag mode and policy instance mode changed to NONE - group and zone tag indication need to be removed', () => { + instance.zoneInstanceModeChanged(ZoneInstanceMode.NONE, newZoneInstance, ZoneInstanceType.POLICY); + expect(instance.compositionGraphZoneUtils.hideZoneTagIndications).toHaveBeenCalledWith(instance._cy); + expect(instance.compositionGraphZoneUtils.hideGroupZoneIndications).toHaveBeenCalledWith(instance.zones[ZoneInstanceType.GROUP].instances); + }) + + it('we are not in tag mode and active zone instance gets hover/none - we dont actually change mode', () => { + let newMode = ZoneInstanceMode.SELECTED; + instance.zoneInstanceModeChanged(newMode, newZoneInstance, ZoneInstanceType.POLICY); + expect(newZoneInstance.mode).toBe(newMode); + }) + + it('we are not in tag mode and zone instance mode changed to HOVER mode', () => { + instance.zoneInstanceModeChanged(ZoneInstanceMode.HOVER, newZoneInstance, ZoneInstanceType.POLICY); + expect(instance.compositionGraphZoneUtils.showZoneTagIndications).toHaveBeenCalledWith(instance._cy, newZoneInstance); + expect(instance.compositionGraphZoneUtils.showGroupZoneIndications).toHaveBeenCalledWith(instance.zones[ZoneInstanceType.GROUP].instances, newZoneInstance); + expect(instance.eventListenerService.notifyObservers).not.toHaveBeenCalled(); + }) + + it('we are not in tag mode and mode changed to SELECTED', () => { + instance.zoneInstanceModeChanged(ZoneInstanceMode.SELECTED, newZoneInstance, ZoneInstanceType.POLICY); + expect(instance.compositionGraphZoneUtils.showZoneTagIndications).toHaveBeenCalledWith(instance._cy, newZoneInstance); + expect(instance.compositionGraphZoneUtils.showGroupZoneIndications).toHaveBeenCalledWith(instance.zones[ZoneInstanceType.GROUP].instances, newZoneInstance); + expect(instance.activeZoneInstance).toBe(newZoneInstance); + expect(instance.eventListenerService.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, newZoneInstance); + expect(instance.store.dispatch).toHaveBeenCalledWith(new SetSelectedComponentAction({ + component: newZoneInstance.instanceData, + type: SelectedComponentType[ZoneInstanceType[newZoneInstance.type]] + })); + expect(instance.eventListenerService.notifyObservers).not.toHaveBeenCalledWith(GRAPH_EVENTS.ON_CANVAS_TAG_START, ZoneInstanceType.POLICY); + }) + + + it('we are not in tag mode and and zone instance mode changed to TAG', () => { + instance.zoneInstanceModeChanged(ZoneInstanceMode.TAG, newZoneInstance, ZoneInstanceType.POLICY); + expect(instance.compositionGraphZoneUtils.showZoneTagIndications).toHaveBeenCalledWith(instance._cy, newZoneInstance); + expect(instance.compositionGraphZoneUtils.showGroupZoneIndications).toHaveBeenCalledWith(instance.zones[ZoneInstanceType.GROUP].instances, newZoneInstance); + expect(instance.activeZoneInstance).toBe(newZoneInstance); + expect(instance.eventListenerService.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, newZoneInstance); + expect(instance.store.dispatch).toHaveBeenCalledWith(new SetSelectedComponentAction({ + component: newZoneInstance.instanceData, + type: SelectedComponentType[ZoneInstanceType[newZoneInstance.type]] + })); + expect(instance.compositionGraphZoneUtils.startCyTagMode).toHaveBeenCalledWith(instance._cy); + expect(instance.eventListenerService.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_CANVAS_TAG_START, ZoneInstanceType.POLICY); + }) + }) + + it('unset active zone instance', () => { + instance.activeZoneInstance = createPolicyInstance(); + instance.unsetActiveZoneInstance(); + expect(instance.activeZoneInstance).toBeNull(); + expect(instance.zoneTagMode).toBeNull(); + }) + + it('zone background clicked - we are not in tag mode and active zone instance exist', () => { + instance.activeZoneInstance = createPolicyInstance(); + jest.spyOn(instance, 'unsetActiveZoneInstance'); + jest.spyOn(instance, 'selectTopologyTemplate'); + instance.zoneBackgroundClicked(); + expect(instance.unsetActiveZoneInstance).toHaveBeenCalled(); + expect(instance.selectTopologyTemplate).toHaveBeenCalled(); + }) + + it('zone background clicked - we are not in tag mode and no active zone instance exist', () => { + jest.spyOn(instance, 'unsetActiveZoneInstance'); + jest.spyOn(instance, 'selectTopologyTemplate'); + instance.zoneBackgroundClicked(); + expect(instance.unsetActiveZoneInstance).not.toHaveBeenCalled(); + expect(instance.selectTopologyTemplate).not.toHaveBeenCalled(); + }) + + it('on zoom in', () => { + jest.spyOn(instance, 'zoom'); + instance.zoom(true); + expect(instance.generalGraphUtils.zoomGraphTo).toHaveBeenCalledWith(instance._cy, instance._cy.zoom() + .1); + }) + + it('on zoom out', () => { + jest.spyOn(instance, 'zoom'); + instance.zoom(false); + expect(instance.generalGraphUtils.zoomGraphTo).toHaveBeenCalledWith(instance._cy, instance._cy.zoom() - .1); + }) + + describe('cytoscape tap end event have been called', () => { + + it('canvas background was clicked while zone instance in tag mode, zone instance still selected in tag mode)', () => { + let event = <Cy.EventObject>{cyTarget: instance._cy}; + instance.zoneTagMode = 'instance_in_tag' + instance.onTapEnd(event); + expect(instance.zoneTagMode).toBe('instance_in_tag'); + }) + + it('canvas background was clicked and no zone instance selected, topology template is now selected', () => { + let event = <Cy.EventObject>{cyTarget: instance._cy}; + jest.spyOn(instance, 'selectTopologyTemplate'); + instance.onTapEnd(event); + expect(instance.selectTopologyTemplate).toHaveBeenCalled(); + expect(instance.eventListenerService.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED); + }) + + it('canvas background was clicked and zone instance was selected, topology template is now selected and zone instance is unselected', () => { + let event = <Cy.EventObject>{cyTarget: instance._cy}; + instance.activeZoneInstance = createPolicyInstance(); + jest.spyOn(instance, 'selectTopologyTemplate'); + jest.spyOn(instance, 'unsetActiveZoneInstance'); + instance.onTapEnd(event); + expect(instance.selectTopologyTemplate).toHaveBeenCalled(); + expect(instance.unsetActiveZoneInstance).toHaveBeenCalled(); + }) + + + it('canvas background was clicked and zone instance was selected, topology template is now selected and zone instance is unselected', () => { + let event = <Cy.EventObject>{cyTarget: instance._cy}; + instance.activeZoneInstance = createPolicyInstance(); + jest.spyOn(instance, 'selectTopologyTemplate'); + jest.spyOn(instance, 'unsetActiveZoneInstance'); + instance.onTapEnd(event); + expect(instance.selectTopologyTemplate).toHaveBeenCalled(); + expect(instance.unsetActiveZoneInstance).toHaveBeenCalled(); + }) + + it('on simple edge clicked, open link menu and handle link click', () => { + let event = <Cy.EventObject>{ + cyTarget: [{ + isEdge: jest.fn().mockReturnValue(true), + data: jest.fn().mockReturnValue({type: 'simple'}) + } + }]; + instance.openModifyLinkMenu = jest.fn(); + instance.onTapEnd(event); + expect(instance.compositionGraphLinkUtils.handleLinkClick).toHaveBeenCalledWith(instance._cy, event); + expect(instance.openModifyLinkMenu).toHaveBeenCalled(); + }) + + it('on service path edge clicked, no menu is opened', () => { + let event = <Cy.EventObject>{ + cyTarget: [{ + isEdge: jest.fn().mockReturnValue(true), + data: jest.fn().mockReturnValue({type: 'service-path-link'}) + }] + }; + instance.openModifyLinkMenu = jest.fn(); + instance.onTapEnd(event); + expect(instance.compositionGraphLinkUtils.handleLinkClick).toHaveBeenCalledWith(instance._cy, event); + expect(instance.openModifyLinkMenu).not.toHaveBeenCalled(); + }) + + it('on drop after drag event (position has changed), call onNodesPositionChanged to update node position', () => { + let event = <Cy.EventObject>{ + cyTarget: [{ + isEdge: jest.fn().mockReturnValue(false), + position: jest.fn().mockReturnValue({x:2.11, y:2.44}) + }] + }; + instance.currentlyClickedNodePosition = <Cy.Position>{x:2.33, y:2.44}; + instance.onTapEnd(event); + let nodesMoved: Cy.CollectionNodes = instance._cy.$(':grabbed'); + expect(instance.nodesGraphUtils.onNodesPositionChanged).toHaveBeenCalledWith(instance._cy, instance.topologyTemplate, nodesMoved); + + }) + + it('on node clicked (position not changed) while zone instance selected, unset active zone and call set selected instance', () => { + let event = <Cy.EventObject>{ + cyTarget: [{ + isEdge: jest.fn().mockReturnValue(false), + position: jest.fn().mockReturnValue({x:2.11, y:2.44}), + data: jest.fn().mockReturnValue({componentInstance: new ComponentInstance()}) + }], + }; + instance.currentlyClickedNodePosition = <Cy.Position>{x:2.11, y:2.44}; + instance.activeZoneInstance = createPolicyInstance(); + jest.spyOn(instance, 'unsetActiveZoneInstance'); + jest.spyOn(instance, 'selectComponentInstance'); + instance.onTapEnd(event); + expect(instance.unsetActiveZoneInstance).toHaveBeenCalled(); + expect(instance.selectComponentInstance).toHaveBeenCalledWith(event.cyTarget[0].data().componentInstance); + }) + }) + + it('initial view mode will turn off all cytoscape events', () => { + jest.spyOn(instance, 'isViewOnly').mockReturnValue(true); + jest.spyOn(instance._cy, 'off'); + instance.initViewMode(); + expect(instance._cy.off).toHaveBeenCalledWith('drag'); + expect(instance._cy.off).toHaveBeenCalledWith('handlemouseout'); + expect(instance._cy.off).toHaveBeenCalledWith('handlemouseover'); + expect(instance._cy.off).toHaveBeenCalledWith('canvasredraw'); + expect(instance._cy.off).toHaveBeenCalledWith('handletagclick'); + + }) +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts new file mode 100644 index 0000000000..69ca3faaf5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.component.ts @@ -0,0 +1,768 @@ +/** + * Created by ob0695 on 4/24/2018. + */ +import { AfterViewInit, Component, ElementRef, HostBinding, Input } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { + ButtonModel, + Component as TopologyTemplate, + ComponentInstance, + CompositionCiNodeBase, + ConnectRelationModel, + GroupInstance, + LeftPaletteComponent, + LinkMenu, + Match, + ModalModel, + NodesFactory, + Point, + PolicyInstance, + PropertyBEModel, + Relationship, + StepModel, + Zone, + ZoneInstance, + ZoneInstanceAssignmentType, + ZoneInstanceMode, + ZoneInstanceType +} from 'app/models'; +import { ForwardingPath } from 'app/models/forwarding-path'; +import { CompositionCiServicePathLink } from 'app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link'; +import { UIZoneInstanceObject } from 'app/models/ui-models/ui-zone-instance-object'; +import { CompositionService } from 'app/ng2/pages/composition/composition.service'; +import { CommonGraphUtils } from 'app/ng2/pages/composition/graph/common/common-graph-utils'; +import { ComponentInstanceNodesStyle } from 'app/ng2/pages/composition/graph/common/style/component-instances-nodes-style'; +import { ConnectionPropertiesViewComponent } from 'app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component'; +import { ConnectionWizardHeaderComponent } from 'app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component'; +import { ConnectionWizardService } from 'app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service'; +import { FromNodeStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component'; +import { PropertiesStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component'; +import { ToNodeStepComponent } from 'app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { ComponentInstanceServiceNg2 } from 'app/ng2/services/component-instance-services/component-instance.service'; +import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service'; +import { ModalService } from 'app/ng2/services/modal.service'; +import { ComponentGenericResponse } from 'app/ng2/services/responses/component-generic-response'; +import { ServiceGenericResponse } from 'app/ng2/services/responses/service-generic-response'; +import { WorkspaceState } from 'app/ng2/store/states/workspace.state'; +import { EventListenerService } from 'app/services'; +import { ComponentInstanceFactory, EVENTS, SdcElementType } from 'app/utils'; +import { ComponentType, GRAPH_EVENTS, GraphColors, DEPENDENCY_EVENTS } from 'app/utils/constants'; +import * as _ from 'lodash'; +import { DndDropEvent } from 'ngx-drag-drop/ngx-drag-drop'; +import { SdcUiServices } from 'onap-ui-angular'; +import { NotificationSettings } from 'onap-ui-angular/dist/notifications/utilities/notification.config'; +import { menuItem } from 'onap-ui-angular/dist/simple-popup-menu/menu-data.interface'; +import { CytoscapeEdgeEditation } from '../../../../../third-party/cytoscape.js-edge-editation/CytoscapeEdgeEditation.js'; +import { SelectedComponentType, SetSelectedComponentAction } from '../common/store/graph.actions'; +import { GraphState } from '../common/store/graph.state'; +import { + CompositionGraphGeneralUtils, + CompositionGraphNodesUtils, + CompositionGraphZoneUtils, + MatchCapabilitiesRequirementsUtils +} from './utils'; +import { CompositionGraphLinkUtils } from './utils/composition-graph-links-utils'; +import { CompositionGraphPaletteUtils } from './utils/composition-graph-palette-utils'; +import { ServicePathGraphUtils } from './utils/composition-graph-service-path-utils'; + +declare const window: any; + +@Component({ + selector: 'composition-graph', + templateUrl: './composition-graph.component.html', + styleUrls: ['./composition-graph.component.less'] +}) + +export class CompositionGraphComponent implements AfterViewInit { + + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + @Select(GraphState.withSidebar) withSidebar$: boolean; + @Input() topologyTemplate: TopologyTemplate; + @HostBinding('attr.data-tests-id') dataTestId: string; + @Input() testId: string; + + // tslint:disable:variable-name + private _cy: Cy.Instance; + private zoneTagMode: string; + private activeZoneInstance: ZoneInstance; + private zones: Zone[]; + private currentlyClickedNodePosition: Cy.Position; + private dragElement: JQuery; + private dragComponent: ComponentInstance; + private componentInstanceNames: string[]; + private topologyTemplateId: string; + private topologyTemplateType: string; + + constructor(private elRef: ElementRef, + private nodesFactory: NodesFactory, + private eventListenerService: EventListenerService, + private compositionGraphZoneUtils: CompositionGraphZoneUtils, + private generalGraphUtils: CompositionGraphGeneralUtils, + private compositionGraphLinkUtils: CompositionGraphLinkUtils, + private nodesGraphUtils: CompositionGraphNodesUtils, + private connectionWizardService: ConnectionWizardService, + private commonGraphUtils: CommonGraphUtils, + private modalService: ModalService, + private compositionGraphPaletteUtils: CompositionGraphPaletteUtils, + private topologyTemplateService: TopologyTemplateService, + private componentInstanceService: ComponentInstanceServiceNg2, + private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils, + private store: Store, + private compositionService: CompositionService, + private loaderService: SdcUiServices.LoaderService, + private workspaceService: WorkspaceService, + private notificationService: SdcUiServices.NotificationsService, + private simplePopupMenuService: SdcUiServices.simplePopupMenuService, + private servicePathGraphUtils: ServicePathGraphUtils) { + } + + ngOnInit() { + this.dataTestId = this.testId; + this.topologyTemplateId = this.workspaceService.metadata.uniqueId; + this.topologyTemplateType = this.workspaceService.metadata.componentType; + + this.store.dispatch(new SetSelectedComponentAction({ + component: this.topologyTemplate, + type: SelectedComponentType.TOPOLOGY_TEMPLATE + })); + this.eventListenerService.registerObserverCallback(EVENTS.ON_CHECKOUT, () => { + this.loadGraphData(); + }); + this.loadCompositionData(); + } + + ngAfterViewInit() { + this.loadGraph(); + } + + ngOnDestroy() { + this._cy.destroy(); + _.forEach(GRAPH_EVENTS, (event) => { + this.eventListenerService.unRegisterObserver(event); + }); + this.eventListenerService.unRegisterObserver(EVENTS.ON_CHECKOUT); + this.eventListenerService.unRegisterObserver(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE); + } + + public isViewOnly = (): boolean => { + return this.store.selectSnapshot((state) => state.workspace.isViewOnly); + } + + public zoom = (zoomIn: boolean): void => { + const currentZoom: number = this._cy.zoom(); + if (zoomIn) { + this.generalGraphUtils.zoomGraphTo(this._cy, currentZoom + .1); + } else { + this.generalGraphUtils.zoomGraphTo(this._cy, currentZoom - .1); + } + } + + public zoomAllWithoutSidebar = () => { + setTimeout(() => { // wait for sidebar changes to take effect before zooming + this.generalGraphUtils.zoomAll(this._cy); + }); + } + + public getAutoCompleteValues = (searchTerm: string) => { + if (searchTerm.length > 1) { // US requirement: only display search results after 2nd letter typed. + const nodes: Cy.CollectionNodes = this.nodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm); + this.componentInstanceNames = _.map(nodes, (node) => node.data('name')); + } else { + this.componentInstanceNames = []; + } + } + + public highlightSearchMatches = (searchTerm: string) => { + this.nodesGraphUtils.highlightMatchingNodesByName(this._cy, searchTerm); + const matchingNodes: Cy.CollectionNodes = this.nodesGraphUtils.getMatchingNodesByName(this._cy, searchTerm); + this.generalGraphUtils.zoomAll(this._cy, matchingNodes); + } + + public onDrop = (dndEvent: DndDropEvent) => { + this.compositionGraphPaletteUtils.addNodeFromPalette(this._cy, dndEvent); + } + + public openServicePathMenu = ($event): void => { + + const menuConfig: menuItem[] = []; + if (!this.isViewOnly()) { + menuConfig.push({ + text: 'Create Service Flow', + action: () => this.servicePathGraphUtils.onCreateServicePath() + }); + } + menuConfig.push({ + text: 'Service Flows List', + type: '', + action: () => this.servicePathGraphUtils.onListServicePath() + }); + const popup = this.simplePopupMenuService.openBaseMenu(menuConfig, { + x: $event.x, + y: $event.y + }); + + } + + public deletePathsOnCy = () => { + this.servicePathGraphUtils.deletePathsFromGraph(this._cy); + } + + public drawPathOnCy = (data: ForwardingPath) => { + this.servicePathGraphUtils.drawPath(this._cy, data); + } + + public onTapEnd = (event: Cy.EventObject) => { + if (this.zoneTagMode) { + return; + } + if (event.cyTarget === this._cy) { // On Background clicked + if (this._cy.$('node:selected').length === 0) { // if the background click but not dragged + if (this.activeZoneInstance) { + this.unsetActiveZoneInstance(); + this.selectTopologyTemplate(); + } else { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_GRAPH_BACKGROUND_CLICKED); + this.selectTopologyTemplate(); + } + + } + } else if (event.cyTarget[0].isEdge()) { // and Edge clicked + this.compositionGraphLinkUtils.handleLinkClick(this._cy, event); + if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) { + return; + } + this.openModifyLinkMenu(this.compositionGraphLinkUtils.getModifyLinkMenu(event.cyTarget[0], event), event); + } else { // On Node clicked + + this._cy.nodes(':grabbed').style({'overlay-opacity': 0}); + + const newPosition = event.cyTarget[0].position(); + // node position changed (drop after drag event) - we need to update position + if (this.currentlyClickedNodePosition.x !== newPosition.x || this.currentlyClickedNodePosition.y !== newPosition.y) { + const nodesMoved: Cy.CollectionNodes = this._cy.$(':grabbed'); + this.nodesGraphUtils.onNodesPositionChanged(this._cy, this.topologyTemplate, nodesMoved); + } else { + if (this.activeZoneInstance) { + this.unsetActiveZoneInstance(); + } + this.selectComponentInstance(event.cyTarget[0].data().componentInstance); + } + } + } + + private registerCytoscapeGraphEvents() { + + this._cy.on('addedgemouseup', (event, data) => { + const connectRelationModel: ConnectRelationModel = this.compositionGraphLinkUtils.onLinkDrawn(this._cy, data.source, data.target); + if (connectRelationModel != null) { + this.connectionWizardService.setRelationMenuDirectiveObj(connectRelationModel); + this.connectionWizardService.selectedMatch = null; + + const steps: StepModel[] = []; + const fromNodeName: string = connectRelationModel.fromNode.componentInstance.name; + const toNodeName: string = connectRelationModel.toNode.componentInstance.name; + steps.push(new StepModel(fromNodeName, FromNodeStepComponent)); + steps.push(new StepModel(toNodeName, ToNodeStepComponent)); + steps.push(new StepModel('Properties', PropertiesStepComponent)); + const wizardTitle = 'Connect: ' + fromNodeName + ' to ' + toNodeName; + const modalInstance = this.modalService.createMultiStepsWizard(wizardTitle, steps, this.createLinkFromMenu, ConnectionWizardHeaderComponent); + modalInstance.instance.open(); + } + }); + + this._cy.on('tapstart', 'node', (event: Cy.EventObject) => { + this.currentlyClickedNodePosition = angular.copy(event.cyTarget[0].position()); // update node position on drag + }); + + this._cy.on('drag', 'node', (event: Cy.EventObject) => { + if (event.cyTarget.data().componentSubType !== SdcElementType.POLICY && event.cyTarget.data().componentSubType !== SdcElementType.GROUP) { + event.cyTarget.style({'overlay-opacity': 0.24}); + if (this.generalGraphUtils.isValidDrop(this._cy, event.cyTarget)) { + event.cyTarget.style({'overlay-color': GraphColors.NODE_BACKGROUND_COLOR}); + } else { + event.cyTarget.style({'overlay-color': GraphColors.NODE_OVERLAPPING_BACKGROUND_COLOR}); + } + } + }); + + this._cy.on('handlemouseover', (event, payload) => { + // no need to add opacity while we are dragging and hovering othe nodes- or if opacity was already calculated for these nodes + if (payload.node.grabbed() || this._cy.scratch('_edge_editation_highlights') === true) { + return; + } + + if (this.zoneTagMode) { + this.zoneTagMode = this.zones[this.activeZoneInstance.type].getHoverTagModeId(); + return; + } + + const nodesData = this.nodesGraphUtils.getAllNodesData(this._cy.nodes()); + const nodesLinks = this.generalGraphUtils.getAllCompositionCiLinks(this._cy); + const instance = payload.node.data().componentInstance; + const filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodesToComponentInstance(instance, nodesData, nodesLinks); + this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, this._cy); + this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, this._cy, payload.node.data()); + + this._cy.scratch()._edge_editation_highlights = true; + }); + + this._cy.on('handlemouseout', () => { + if (this.zoneTagMode) { + this.zoneTagMode = this.zones[this.activeZoneInstance.type].getTagModeId(); + return; + } + if (this._cy.scratch('_edge_editation_highlights') === true) { + this._cy.removeScratch('_edge_editation_highlights'); + this._cy.emit('hidehandles'); + this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy); + } + }); + + this._cy.on('tapend', (event: Cy.EventObject) => { + this.onTapEnd(event); + }); + + this._cy.on('boxselect', 'node', (event: Cy.EventObject) => { + this.unsetActiveZoneInstance(); + this.selectComponentInstance(event.cyTarget.data().componentInstance); + }); + + this._cy.on('canvasredraw', (event: Cy.EventObject) => { + if (this.zoneTagMode) { + this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, this.activeZoneInstance); + } + }); + + this._cy.on('handletagclick', (event: Cy.EventObject, eventData: any) => { + this.compositionGraphZoneUtils.handleTagClick(this._cy, this.activeZoneInstance, eventData.nodeId); + }); + } + + private initViewMode() { + + if (this.isViewOnly()) { + // remove event listeners + this._cy.off('drag'); + this._cy.off('handlemouseout'); + this._cy.off('handlemouseover'); + this._cy.off('canvasredraw'); + this._cy.off('handletagclick'); + this._cy.edges().unselectify(); + } + } + + private saveChangedCapabilityProperties = (): Promise<PropertyBEModel[]> => { + return new Promise<PropertyBEModel[]>((resolve) => { + const capabilityPropertiesBE: PropertyBEModel[] = this.connectionWizardService.changedCapabilityProperties.map((prop) => { + prop.value = prop.getJSONValue(); + const propBE = new PropertyBEModel(prop); + propBE.parentUniqueId = this.connectionWizardService.selectedMatch.relationship.relation.capabilityOwnerId; + return propBE; + }); + if (capabilityPropertiesBE.length > 0) { + // if there are capability properties to update, then first update capability properties and then resolve promise + this.componentInstanceService + .updateInstanceCapabilityProperties( + this.topologyTemplate, + this.connectionWizardService.selectedMatch.toNode, + this.connectionWizardService.selectedMatch.capability, + capabilityPropertiesBE + ) + .subscribe((response) => { + console.log('Update resource instance capability properties response: ', response); + this.connectionWizardService.changedCapabilityProperties = []; + resolve(capabilityPropertiesBE); + }); + } else { + // no capability properties to update, immediately resolve promise + resolve(capabilityPropertiesBE); + } + }); + } + + private loadCompositionData = () => { + this.loaderService.activate(); + this.topologyTemplateService.getComponentCompositionData(this.topologyTemplateId, this.topologyTemplateType).subscribe((response: ComponentGenericResponse) => { + if (this.topologyTemplateType === ComponentType.SERVICE) { + this.compositionService.forwardingPaths = (response as ServiceGenericResponse).forwardingPaths; + } + this.compositionService.componentInstances = response.componentInstances; + this.compositionService.componentInstancesRelations = response.componentInstancesRelations; + this.compositionService.groupInstances = response.groupInstances; + this.compositionService.policies = response.policies; + this.loadGraphData(); + this.loaderService.deactivate(); + }, (error) => { this.loaderService.deactivate(); }); + } + + private loadGraph = () => { + const graphEl = this.elRef.nativeElement.querySelector('.sdc-composition-graph-wrapper'); + this.initGraph(graphEl); + this.zones = this.compositionGraphZoneUtils.createCompositionZones(); + this.registerCytoscapeGraphEvents(); + this.registerCustomEvents(); + this.initViewMode(); + } + + private initGraphNodes() { + + setTimeout(() => { + const handles = new CytoscapeEdgeEditation(); + handles.init(this._cy); + if (!this.isViewOnly()) { // Init nodes handle extension - enable dynamic links + handles.initNodeEvents(); + handles.registerHandle(ComponentInstanceNodesStyle.getAddEdgeHandle()); + } + handles.registerHandle(ComponentInstanceNodesStyle.getTagHandle()); + handles.registerHandle(ComponentInstanceNodesStyle.getTaggedPolicyHandle()); + handles.registerHandle(ComponentInstanceNodesStyle.getTaggedGroupHandle()); + }, 0); + + _.each(this.compositionService.componentInstances, (instance) => { + const compositionGraphNode: CompositionCiNodeBase = this.nodesFactory.createNode(instance); + this.commonGraphUtils.addComponentInstanceNodeToGraph(this._cy, compositionGraphNode); + }); + + } + + private loadGraphData = () => { + this.initGraphNodes(); + this.compositionGraphLinkUtils.initGraphLinks(this._cy, this.compositionService.componentInstancesRelations); + this.compositionGraphZoneUtils.initZoneInstances(this.zones); + setTimeout(() => { // Need setTimeout so that angular canvas changes will take effect before resize & center + this.generalGraphUtils.zoomAllWithMax(this._cy, 1); + }); + this.componentInstanceNames = _.map(this._cy.nodes(), (node) => node.data('name')); + } + + private initGraph(graphEl: JQuery) { + + this._cy = cytoscape({ + container: graphEl, + style: ComponentInstanceNodesStyle.getCompositionGraphStyle(), + zoomingEnabled: true, + maxZoom: 1.2, + minZoom: .1, + userZoomingEnabled: false, + userPanningEnabled: true, + selectionType: 'single', + boxSelectionEnabled: true, + autolock: this.isViewOnly(), + autoungrabify: this.isViewOnly() + }); + + // Testing Bridge that allows Cypress tests to select a component on canvas not via DOM + if (window.Cypress) { + window.testBridge = this.createCanvasTestBridge(); + } + } + + private createCanvasTestBridge(): any { + return { + selectComponentInstance: (componentName: string) => { + const matchingNodesByName = this.nodesGraphUtils.getMatchingNodesByName(this._cy, componentName); + const component = new ComponentInstance(matchingNodesByName.first().data().componentInstance); + this.selectComponentInstance(component); + } + }; + } + + // -------------------------------------------- ZONES---------------------------------------------------------// + private zoneMinimizeToggle = (zoneType: ZoneInstanceType): void => { + this.zones[zoneType].minimized = !this.zones[zoneType].minimized; + } + + private zoneInstanceModeChanged = (newMode: ZoneInstanceMode, instance: ZoneInstance, zoneId: ZoneInstanceType): void => { + if (this.zoneTagMode) { // we're in tag mode. + if (instance === this.activeZoneInstance && newMode === ZoneInstanceMode.NONE) { // we want to turn tag mode off. + this.zoneTagMode = null; + this.activeZoneInstance.mode = ZoneInstanceMode.SELECTED; + this.compositionGraphZoneUtils.endCyTagMode(this._cy); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_END, instance); + + } + } else { + // when active zone instance gets hover/none, don't actually change mode, just show/hide indications + if (instance !== this.activeZoneInstance || (instance === this.activeZoneInstance && newMode > ZoneInstanceMode.HOVER)) { + instance.mode = newMode; + } + + if (newMode === ZoneInstanceMode.NONE) { + this.compositionGraphZoneUtils.hideZoneTagIndications(this._cy); + if (this.zones[ZoneInstanceType.GROUP]) { + this.compositionGraphZoneUtils.hideGroupZoneIndications(this.zones[ZoneInstanceType.GROUP].instances); + } + } + if (newMode >= ZoneInstanceMode.HOVER) { + this.compositionGraphZoneUtils.showZoneTagIndications(this._cy, instance); + if (instance.type === ZoneInstanceType.POLICY && this.zones[ZoneInstanceType.GROUP]) { + this.compositionGraphZoneUtils.showGroupZoneIndications(this.zones[ZoneInstanceType.GROUP].instances, instance); + } + } + if (newMode >= ZoneInstanceMode.SELECTED) { + this._cy.$('node:selected').unselect(); + if (this.activeZoneInstance && this.activeZoneInstance !== instance && newMode >= ZoneInstanceMode.SELECTED) { + this.activeZoneInstance.mode = ZoneInstanceMode.NONE; + } + this.activeZoneInstance = instance; + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_ZONE_INSTANCE_SELECTED, instance); + this.store.dispatch(new SetSelectedComponentAction({ + component: instance.instanceData, + type: SelectedComponentType[ZoneInstanceType[instance.type]] + })); + } + if (newMode === ZoneInstanceMode.TAG) { + this.compositionGraphZoneUtils.startCyTagMode(this._cy); + this.zoneTagMode = this.zones[zoneId].getTagModeId(); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CANVAS_TAG_START, zoneId); + } + } + } + + private zoneInstanceTagged = (taggedInstance: ZoneInstance) => { + this.activeZoneInstance.addOrRemoveAssignment(taggedInstance.instanceData.uniqueId, ZoneInstanceAssignmentType.GROUPS); + const newHandle: string = this.compositionGraphZoneUtils.getCorrectHandleForNode(taggedInstance.instanceData.uniqueId, this.activeZoneInstance); + taggedInstance.showHandle(newHandle); + } + + private unsetActiveZoneInstance = (): void => { + if (this.activeZoneInstance) { + this.activeZoneInstance.mode = ZoneInstanceMode.NONE; + this.activeZoneInstance = null; + this.zoneTagMode = null; + } + } + + private selectComponentInstance = (componentInstance: ComponentInstance) => { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_NODE_SELECTED, componentInstance); + this.store.dispatch(new SetSelectedComponentAction({ + component: componentInstance, + type: SelectedComponentType.COMPONENT_INSTANCE + })); + } + + private selectTopologyTemplate = () => { + this.store.dispatch(new SetSelectedComponentAction({ + component: this.topologyTemplate, + type: SelectedComponentType.TOPOLOGY_TEMPLATE + })); + } + + private zoneBackgroundClicked = (): void => { + if (!this.zoneTagMode && this.activeZoneInstance) { + this.unsetActiveZoneInstance(); + this.selectTopologyTemplate(); + } + } + + private zoneAssignmentSaveStart = () => { + this.loaderService.activate(); + } + + private zoneAssignmentSaveComplete = (success: boolean) => { + this.loaderService.deactivate(); + if (!success) { + this.notificationService.push(new NotificationSettings('error', 'Update Failed', 'Error')); + } + } + + private deleteZoneInstance = (deletedInstance: UIZoneInstanceObject) => { + if (deletedInstance.type === ZoneInstanceType.POLICY) { + this.compositionService.policies = this.compositionService.policies.filter((policy) => policy.uniqueId !== deletedInstance.uniqueId); + } else if (deletedInstance.type === ZoneInstanceType.GROUP) { + this.compositionService.groupInstances = this.compositionService.groupInstances.filter((group) => group.uniqueId !== deletedInstance.uniqueId); + } + // remove it from zones + this.zones[deletedInstance.type].removeInstance(deletedInstance.uniqueId); + if (deletedInstance.type === ZoneInstanceType.GROUP && !_.isEmpty(this.zones[ZoneInstanceType.POLICY])) { + this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(deletedInstance.uniqueId, [this.zones[ZoneInstanceType.POLICY]], ZoneInstanceAssignmentType.GROUPS); + } + this.selectTopologyTemplate(); + } + // -------------------------------------------------------------------------------------------------------------// + + private registerCustomEvents() { + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_GROUP_INSTANCE_UPDATE, (groupInstance: GroupInstance) => { + this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(this.zones, groupInstance); + this.notificationService.push(new NotificationSettings('success', 'Group Updated', 'Success')); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_POLICY_INSTANCE_UPDATE, (policyInstance: PolicyInstance) => { + this.compositionGraphZoneUtils.findAndUpdateZoneInstanceData(this.zones, policyInstance); + this.notificationService.push(new NotificationSettings('success', 'Policy Updated', 'Success')); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, (leftPaletteComponent: LeftPaletteComponent) => { + this.compositionGraphPaletteUtils.onComponentHoverIn(leftPaletteComponent, this._cy); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_ADD_ZONE_INSTANCE_FROM_PALETTE, + (component: TopologyTemplate, paletteComponent: LeftPaletteComponent, startPosition: Point) => { + + const zoneType: ZoneInstanceType = this.compositionGraphZoneUtils.getZoneTypeForPaletteComponent(paletteComponent.categoryType); + this.compositionGraphZoneUtils.showZone(this.zones[zoneType]); + + this.loaderService.activate(); + this.compositionGraphZoneUtils.createZoneInstanceFromLeftPalette(zoneType, paletteComponent.type).subscribe((zoneInstance: ZoneInstance) => { + this.loaderService.deactivate(); + this.compositionGraphZoneUtils.addInstanceToZone(this.zones[zoneInstance.type], zoneInstance, true); + this.compositionGraphZoneUtils.createPaletteToZoneAnimation(startPosition, zoneType, zoneInstance); + }, (error) => { + this.loaderService.deactivate(); + }); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT, () => { + this._cy.emit('hidehandles'); + this.matchCapabilitiesRequirementsUtils.resetFadedNodes(this._cy); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_START, (dragElement, dragComponent) => { + this.dragElement = dragElement; + this.dragComponent = ComponentInstanceFactory.createComponentInstanceFromComponent(dragComponent); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, (position: Point) => { + const draggedElement = document.getElementById('draggable_element'); + draggedElement.className = this.compositionGraphPaletteUtils.isDragValid(this._cy, position) ? 'valid-drag' : 'invalid-drag'; + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DROP, (event: DndDropEvent) => { + this.onDrop(event); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_COMPONENT_INSTANCE_NAME_CHANGED, (component: ComponentInstance) => { + const selectedNode = this._cy.getElementById(component.uniqueId); + selectedNode.data().componentInstance.name = component.name; + selectedNode.data('name', component.name); // used for tooltip + selectedNode.data('displayName', selectedNode.data().getDisplayName()); // abbreviated + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, (componentInstanceId: string) => { + const nodeToDelete = this._cy.getElementById(componentInstanceId); + this.nodesGraphUtils.deleteNode(this._cy, this.topologyTemplate, nodeToDelete); + this.selectTopologyTemplate(); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_ZONE_INSTANCE, (deletedInstance: UIZoneInstanceObject) => { + this.deleteZoneInstance(deletedInstance); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, (componentInstanceId: string) => { + if (!_.isEmpty(this.zones)) { + this.compositionGraphZoneUtils.updateTargetsOrMembersOnCanvasDelete(componentInstanceId, this.zones, ZoneInstanceAssignmentType.COMPONENT_INSTANCES); + } + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_DELETE_EDGE, (releaseLoading: boolean, linksToDelete: Cy.CollectionEdges) => { + this.compositionGraphLinkUtils.deleteLink(this._cy, this.topologyTemplate, releaseLoading, linksToDelete); + }); + + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_VERSION_CHANGED, (component: ComponentInstance) => { + // Remove everything from graph and reload it all + this._cy.elements().remove(); + this.loadCompositionData(); + setTimeout(() => { this._cy.getElementById(component.uniqueId).select(); }, 1000); + this.selectComponentInstance(component); + }); + this.eventListenerService.registerObserverCallback(DEPENDENCY_EVENTS.ON_DEPENDENCY_CHANGE, (ischecked: boolean) => { + if (ischecked) { + this._cy.$('node:selected').addClass('dependent'); + } else { + // due to defect in cytoscape, just changing the class does not replace the icon, and i need to revert to original icon with no markings. + this._cy.$('node:selected').removeClass('dependent'); + this._cy.$('node:selected').style({'background-image': this._cy.$('node:selected').data('originalImg')}); + } + }); + } + private createLinkFromMenu = (): void => { + this.saveChangedCapabilityProperties().then(() => { + this.compositionGraphLinkUtils.createLinkFromMenu(this._cy, this.connectionWizardService.selectedMatch); + }); + } + + private deleteRelation = (link: Cy.CollectionEdges) => { + // if multiple edges selected, delete the VL itself so edges get deleted automatically + if (this._cy.$('edge:selected').length > 1) { + this.nodesGraphUtils.deleteNode(this._cy, this.topologyTemplate, this._cy.$('node:selected')); + } else { + this.compositionGraphLinkUtils.deleteLink(this._cy, this.topologyTemplate, true, link); + } + } + + private viewRelation = (link: Cy.CollectionEdges) => { + + const linkData = link.data(); + const sourceNode: CompositionCiNodeBase = link.source().data(); + const targetNode: CompositionCiNodeBase = link.target().data(); + const relationship: Relationship = linkData.relation.relationships[0]; + + this.compositionGraphLinkUtils.getRelationRequirementCapability(relationship, sourceNode.componentInstance, targetNode.componentInstance).then((objReqCap) => { + const capability = objReqCap.capability; + const requirement = objReqCap.requirement; + + this.connectionWizardService.connectRelationModel = new ConnectRelationModel(sourceNode, targetNode, []); + this.connectionWizardService.selectedMatch = new Match(requirement, capability, true, linkData.source, linkData.target); + this.connectionWizardService.selectedMatch.relationship = relationship; + + const title = `Connection Properties`; + const saveButton: ButtonModel = new ButtonModel('Save', 'blue', () => { + this.saveChangedCapabilityProperties().then(() => { + this.modalService.closeCurrentModal(); + }); + }); + const cancelButton: ButtonModel = new ButtonModel('Cancel', 'white', () => { + this.modalService.closeCurrentModal(); + }); + const modal = new ModalModel('xl', title, '', [saveButton, cancelButton]); + const modalInstance = this.modalService.createCustomModal(modal); + this.modalService.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent); + modalInstance.instance.open(); + + new Promise((resolve) => { + if (!this.connectionWizardService.selectedMatch.capability.properties) { + this.componentInstanceService.getInstanceCapabilityProperties(this.topologyTemplateType, this.topologyTemplateId, linkData.target, capability) + .subscribe(() => { + resolve(); + }, () => { /* do nothing */ }); + } else { + resolve(); + } + }).then(() => { + this.modalService.addDynamicContentToModal(modalInstance, ConnectionPropertiesViewComponent); + }); + }, () => { /* do nothing */ }); + } + + private openModifyLinkMenu = (linkMenuObject: LinkMenu, $event) => { + + const menuConfig: menuItem[] = [{ + text: 'View', + iconName: 'eye-o', + iconType: 'common', + iconMode: 'secondary', + iconSize: 'small', + type: '', + action: () => this.viewRelation(linkMenuObject.link as Cy.CollectionEdges) + }]; + + if (!this.isViewOnly()) { + menuConfig.push({ + text: 'Delete', + iconName: 'trash-o', + iconType: 'common', + iconMode: 'secondary', + iconSize: 'small', + type: '', + action: () => this.deleteRelation(linkMenuObject.link as Cy.CollectionEdges) + }); + } + this.simplePopupMenuService.openBaseMenu(menuConfig, { + x: $event.originalEvent.x, + y: $event.originalEvent.y + }); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts new file mode 100644 index 0000000000..e58d160c4d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/composition-graph.module.ts @@ -0,0 +1,55 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {CompositionGraphComponent} from "./composition-graph.component"; +import {ZoneModules} from "./canvas-zone/zones-module"; +import {CompositionGraphZoneUtils} from "./utils/composition-graph-zone-utils"; +import {CompositionGraphGeneralUtils} from "./utils/composition-graph-general-utils"; +import {CommonGraphUtils} from "./common/common-graph-utils"; +import {LinksFactory} from "app/models/graph/graph-links/links-factory"; +import {NodesFactory} from "app/models/graph/nodes/nodes-factory"; +import {ImageCreatorService} from "./common/image-creator.service"; +import {MatchCapabilitiesRequirementsUtils} from "./utils/match-capability-requierment-utils"; +import {CompositionGraphNodesUtils} from "./utils/composition-graph-nodes-utils"; +import {ConnectionWizardService} from "app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service"; +import {CompositionGraphPaletteUtils} from "./utils/composition-graph-palette-utils"; +import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; +import {DndModule} from "ngx-drag-drop"; +import { MenuListNg2Module } from "app/ng2/components/downgrade-wrappers/menu-list-ng2/menu-list-ng2.module"; +import { UiElementsModule } from "app/ng2/components/ui/ui-elements.module"; +import {ServicePathSelectorModule} from "./service-path-selector/service-path-selector.module"; +import {SdcUiComponentsModule, SdcUiServices} from "onap-ui-angular"; +import {CanvasSearchModule} from "./canvas-search/canvas-search.module"; +import {CompositionGraphLinkUtils, ServicePathGraphUtils} from "./utils"; + + +@NgModule({ + declarations: [CompositionGraphComponent], + imports: [CommonModule, + ServicePathSelectorModule, + SdcUiComponentsModule, + MenuListNg2Module, + UiElementsModule, + ZoneModules, + CanvasSearchModule, + DndModule], + exports: [CompositionGraphComponent], + entryComponents: [CompositionGraphComponent], + providers: [ + CompositionGraphZoneUtils, + CompositionGraphGeneralUtils, + MatchCapabilitiesRequirementsUtils, + CompositionGraphNodesUtils, + CompositionGraphLinkUtils, + CommonGraphUtils, + NodesFactory, + LinksFactory, + ImageCreatorService, + ConnectionWizardService, + CompositionGraphPaletteUtils, + QueueServiceUtils, + SdcUiServices.simplePopupMenuService, + ServicePathGraphUtils + ] +}) +export class CompositionGraphModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.html index b24e469554..b24e469554 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.html diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.less index 07f9aa2135..07f9aa2135 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.less diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.ts index 5abb879013..5abb879013 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-properties-view/connection-properties-view.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-properties-view/connection-properties-view.component.ts diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.html index 7e7e82d85f..7e7e82d85f 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.html diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.less index 72fa6e813f..d8bab288d3 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.less @@ -1,5 +1,5 @@ -@import '../../../../../assets/styles/sprite-proxy-services-icons'; -@import '../../../../../assets/styles/variables'; +@import '../../../../../../../assets/styles/sprite-proxy-services-icons'; +@import '../../../../../../../assets/styles/variables'; .header-main-container{ background-color: #f8f8f8; width: 100%; diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.ts index f5bc3b7ca4..f5bc3b7ca4 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard-header/connection-wizard-header.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard-header/connection-wizard-header.component.ts diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts index 6b4b4128c1..80464dc970 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.module.ts @@ -3,9 +3,9 @@ import {NgModule} from "@angular/core"; import {FromNodeStepComponent} from "./from-node-step/from-node-step.component"; import {PropertiesStepComponent} from "./properties-step/properties-step.component"; import {ConnectionWizardService} from "./connection-wizard.service"; -import {SelectRequirementOrCapabilityModule} from "../../components/logic/select-requirement-or-capability/select-requirement-or-capability.module"; -import {PropertyTableModule} from "../../components/logic/properties-table/property-table.module"; -import {FormElementsModule} from "../../components/ui/form-components/form-elements.module"; +import {SelectRequirementOrCapabilityModule} from "../../../../components/logic/select-requirement-or-capability/select-requirement-or-capability.module"; +import {PropertyTableModule} from "../../../../components/logic/properties-table/property-table.module"; +import {FormElementsModule} from "../../../../components/ui/form-components/form-elements.module"; import {ConnectionWizardHeaderComponent} from "./connection-wizard-header/connection-wizard-header.component"; import {ConnectionPropertiesViewComponent} from "./connection-properties-view/connection-properties-view.component"; import {BrowserModule} from "@angular/platform-browser"; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service.spec.ts new file mode 100644 index 0000000000..8a5c5fcefb --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service.spec.ts @@ -0,0 +1,85 @@ +import {TestBed} from "@angular/core/testing"; +import {WorkspaceService} from "../../../../pages/workspace/workspace.service"; +import { ConnectionWizardService } from "app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service"; +import { ConnectRelationModel, Match, Requirement, Capability } from "app/models"; +import { Mock } from "ts-mockery/dist"; + +describe('Connection Wizard Service', () => { + + let service: ConnectionWizardService; + + const connectRelationModelMock = Mock.of<ConnectRelationModel>({ + possibleRelations: [ + Mock.of<Match>({isFromTo: true, requirement: Mock.of<Requirement>({uniqueId: 'requirement1', capability: "cap1"}), capability: Mock.of<Capability>({uniqueId: 'capability1', type: 'othertype'})}), + Mock.of<Match>({isFromTo: true, requirement: Mock.of<Requirement>({uniqueId: 'requirement2', capability: "cap1"}), capability: Mock.of<Capability>({uniqueId: 'capability2', type: 'tosca'})}), + Mock.of<Match>({isFromTo: true, requirement: Mock.of<Requirement>({uniqueId: 'requirement3', capability: "cap1"}), capability: Mock.of<Capability>({uniqueId: 'capability3', type: 'tosca'})}), + Mock.of<Match>({isFromTo: true, requirement: Mock.of<Requirement>({uniqueId: 'requirement4', capability: "cap1"}), capability: Mock.of<Capability>({uniqueId: 'capability2', type: 'tosca'})}), + Mock.of<Match>({isFromTo: true, requirement: Mock.of<Requirement>({uniqueId: 'requirement5', capability: "cap2"}), capability: Mock.of<Capability>({uniqueId: 'capability1', type: 'tosca'})}), + Mock.of<Match>({isFromTo: false, requirement: Mock.of<Requirement>({uniqueId: 'requirement6', capability: "cap2"}), capability: Mock.of<Capability>({uniqueId: 'capability2', type: 'tosca'})}), + Mock.of<Match>({isFromTo: false, requirement: Mock.of<Requirement>({uniqueId: 'requirement7', capability: "cap2"}), capability: Mock.of<Capability>({uniqueId: 'capability1', type: 'othertype'})}) + ] + }); + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [ConnectionWizardService, + {provide: WorkspaceService, useValue: {}} + ] + }); + + service = TestBed.get(ConnectionWizardService); + service.connectRelationModel = connectRelationModelMock; + }); + + describe('getOptionalRequirementsByInstanceUniqueId', () => { + it('if no capability to match is sent in and isFromTo is true, ALL isFromTo==true requirements are returned', () => { + const requirements = service.getOptionalRequirementsByInstanceUniqueId(true); + expect(requirements['cap1'].length).toBe(4); + expect(requirements['cap2'].length).toBe(1); + }); + + it('if no capability to match is sent in and isFromTo is false, ALL isFromTo==false requirements are returned', () => { + const requirements = service.getOptionalRequirementsByInstanceUniqueId(false); + expect(requirements['cap1']).toBeUndefined(); + expect(requirements['cap2'].length).toBe(2); + }); + + it('if capability to match IS sent in and isFromTo is true, matches with the same uniqueID and isFromTo==true are returned', () => { + const capability = Mock.of<Capability>({uniqueId: 'capability1'}); + const requirements = service.getOptionalRequirementsByInstanceUniqueId(true, capability); + expect(requirements['cap1'].length).toBe(1); + expect(requirements['cap2'].length).toBe(1); + }); + + it('if capability to match IS sent in and isFromTo is false, requirements with the same uniqueID and isFromTo==false are returned', () => { + const capability = Mock.of<Capability>({uniqueId: 'capability1'}); + const requirements = service.getOptionalRequirementsByInstanceUniqueId(false, capability); + expect(requirements['cap1']).toBeUndefined(); + expect(requirements['cap2'].length).toBe(1); + }); + }) + + describe('getOptionalCapabilitiesByInstanceUniqueId', () => { + it('if requirement to match IS sent in and isFromTo is true, matches with the same uniqueID and isFromTo==true are returned', () => { + const requirement = Mock.of<Requirement>({uniqueId: 'requirement1'}); + const capabilities = service.getOptionalCapabilitiesByInstanceUniqueId(true, requirement); + expect(capabilities['othertype'].length).toBe(1); + expect(capabilities['tosca']).toBeUndefined(); + }); + + it('if no requirement to match is sent in and isFromTo is true, a UNIQUE list of all capabilities with isFromTo==true are returned', () => { + const capabilities = service.getOptionalCapabilitiesByInstanceUniqueId(true); + expect(capabilities['othertype'].length).toBe(1); + expect(capabilities['tosca'].length).toBe(2); + }); + + it('if no requirement to match is sent in and isFromTo is false, all capabilities with isFromTo==false are returned', () => { + const capabilities = service.getOptionalCapabilitiesByInstanceUniqueId(false); + expect(capabilities['othertype'].length).toBe(1); + expect(capabilities['tosca'].length).toBe(1); + }); + }); + +}); + diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service.ts index af8dcb4956..2eb5428f61 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/connection-wizard.service.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/connection-wizard.service.ts @@ -1,20 +1,23 @@ import * as _ from "lodash"; -import {ConnectRelationModel} from "../../../models/graph/connectRelationModel"; +import {ConnectRelationModel} from "app/models/graph/connectRelationModel"; import {Injectable} from "@angular/core"; import { Requirement, Capability} from "app/models"; import {Dictionary} from "lodash"; import {Match, Component, PropertyFEModel} from "app/models"; +import {Store} from "@ngxs/store"; +import {WorkspaceService} from "../../../workspace/workspace.service"; @Injectable() export class ConnectionWizardService { connectRelationModel:ConnectRelationModel; - currentComponent:Component; selectedMatch:Match; changedCapabilityProperties:PropertyFEModel[]; - constructor() { + + constructor(private workspaceService: WorkspaceService) { this.changedCapabilityProperties = []; + } public setRelationMenuDirectiveObj = (connectRelationModel:ConnectRelationModel) => { diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/__snapshots__/from-node-step.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/__snapshots__/from-node-step.component.spec.ts.snap new file mode 100644 index 0000000000..739ce3d8fe --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/__snapshots__/from-node-step.component.spec.ts.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`from-node-step component should match current snapshot 1`] = ` +<from-node-step + connectWizardService={[Function Object]} + preventBack={[Function Function]} + preventNext={[Function Function]} + updateSelectedReqOrCap={[Function Function]} +> + <select-requirement-or-capability /> +</from-node-step> +`; diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.html index 1cb3df735c..0a70069748 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.html @@ -17,7 +17,6 @@ <select-requirement-or-capability [optionalRequirementsMap]="optionalRequirementsMap" [optionalCapabilitiesMap]="optionalCapabilitiesMap" [selectedReqOrCapModel]="connectWizardService.selectedMatch && (connectWizardService.selectedMatch.isFromTo ? connectWizardService.selectedMatch.requirement : connectWizardService.selectedMatch.capability)" - [currentComponent]="connectWizardService.currentComponent" [componentInstanceId]="connectWizardService.connectRelationModel.fromNode.componentInstance.uniqueId" (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)"> </select-requirement-or-capability>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.spec.ts new file mode 100644 index 0000000000..59ff72adda --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.spec.ts @@ -0,0 +1,114 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { Capability, Match } from 'app/models'; +import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper'; +import { Requirement } from '../../../../../../models/requirement'; +import { ConnectionWizardService } from '../connection-wizard.service'; +import { FromNodeStepComponent } from './from-node-step.component'; + +describe('from-node-step component', () => { + + let fixture: ComponentFixture<FromNodeStepComponent>; + let connectionWizardServiceMockWithoutSelectedMatch: Partial<ConnectionWizardService>; + let connectionWizardServiceMockWithSelectedMatch: Partial<ConnectionWizardService>; + + const connectionWizardServiceMockSelectedMatchWithRequirements = {requirement: 'val'}; + + connectionWizardServiceMockWithoutSelectedMatch = { + getOptionalRequirementsByInstanceUniqueId: jest.fn().mockReturnValue(5), + getOptionalCapabilitiesByInstanceUniqueId: jest.fn().mockReturnValue(10), + + connectRelationModel: { + fromNode: { + componentInstance: { + uniqueId : 'testUniqueID' + } + } + } + }; + + connectionWizardServiceMockWithSelectedMatch = { + selectedMatch: connectionWizardServiceMockSelectedMatchWithRequirements, + getOptionalRequirementsByInstanceUniqueId: jest.fn().mockReturnValue(5), + getOptionalCapabilitiesByInstanceUniqueId: jest.fn().mockReturnValue(10) + }; + + let expectedConnectionWizardServiceMock = connectionWizardServiceMockWithoutSelectedMatch; + + beforeEach( + async(() => { + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [FromNodeStepComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: ConnectionWizardService, useValue: expectedConnectionWizardServiceMock} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(FromNodeStepComponent); + }); + }) + ); + + + it('should match current snapshot', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('preventBack return true - always', () => { + fixture.componentInstance.ngOnInit(); + const result = fixture.componentInstance.preventBack(); + expect(result).toEqual(true); + }); + + it('preventNext return true since selectedMatch does not exist in connectionWizardServiceMock', () => { + fixture.componentInstance.ngOnInit(); + const result = fixture.componentInstance.preventNext(); + expect(result).toEqual(true); + }); + + it('preventNext return false since to selectedMatch or selectedMatch.capability & selectedMatch.requirement does exist in connectionWizardServiceMock', () => { + fixture.componentInstance.connectWizardService = connectionWizardServiceMockWithSelectedMatch; + fixture.componentInstance.ngOnInit(); + const result = fixture.componentInstance.preventNext(); + expect(result).toEqual(false); + }); + + it('updateSelectedReqOrCap is called with instance of requirement, the selectMatch will be set to an Instance of Match of type Requirement', () => { + const requirement = new Requirement(); + fixture.componentInstance.updateSelectedReqOrCap(requirement); + const expectedSelectedMatch = fixture.componentInstance.connectWizardService.selectedMatch; + + expect(expectedSelectedMatch).toBeInstanceOf(Match); + expect(expectedSelectedMatch.capability).toBe(null); + expect(expectedSelectedMatch.fromNode).toBe('testUniqueID'); + expect(expectedSelectedMatch.isFromTo).toBe(true); + expect(expectedSelectedMatch.toNode).toBe(null); + expect(expectedSelectedMatch.requirement).toBeInstanceOf(Requirement); + }); + + it('updateSelectedReqOrCap is called with instance of capability, the selectMatch will be set to an Instance of Match of type Capability', () => { + const capability = new Capability(); + fixture.componentInstance.updateSelectedReqOrCap(capability); + const expectedSelectedMatch = fixture.componentInstance.connectWizardService.selectedMatch; + + expect(expectedSelectedMatch).toBeInstanceOf(Match); + expect(expectedSelectedMatch.requirement).toBe(null); + expect(expectedSelectedMatch.fromNode).toBe(null); + expect(expectedSelectedMatch.isFromTo).toBe(false); + expect(expectedSelectedMatch.toNode).toBe('testUniqueID'); + expect(expectedSelectedMatch.capability).toBeInstanceOf(Capability); + }); + + it('updateSelectedReqOrCap is called with null, the selectMatch will be set to null', () => { + fixture.componentInstance.updateSelectedReqOrCap(null); + const expectedSelectedMatch = fixture.componentInstance.connectWizardService.selectedMatch; + + expect(expectedSelectedMatch).toBe(null); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.ts index 054d38b063..cffd58c9ea 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/from-node-step/from-node-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/from-node-step/from-node-step.component.ts @@ -1,11 +1,10 @@ -import {Component, OnInit, Inject, forwardRef} from "@angular/core"; -import {IStepComponent} from "../../../../models/wizard-step"; -import {Dictionary} from "lodash"; -import { Match} from "app/models"; -import {ConnectionWizardService} from "../connection-wizard.service"; -import {Requirement} from "../../../../models/requirement"; -import {Capability} from "../../../../models/capability"; -import {PropertyModel} from "../../../../models/properties"; +import { Component, forwardRef, Inject, OnInit } from '@angular/core'; +import { Match } from 'app/models'; +import { Capability } from 'app/models/capability'; +import { Requirement } from 'app/models/requirement'; +import { IStepComponent } from 'app/models/wizard-step'; +import { Dictionary } from 'lodash'; +import { ConnectionWizardService } from '../connection-wizard.service'; @Component({ selector: 'from-node-step', @@ -14,31 +13,31 @@ import {PropertyModel} from "../../../../models/properties"; export class FromNodeStepComponent implements IStepComponent, OnInit{ - constructor(@Inject(forwardRef(() => ConnectionWizardService)) public connectWizardService: ConnectionWizardService) {} - optionalRequirementsMap: Dictionary<Requirement[]>; optionalCapabilitiesMap: Dictionary<Capability[]>; - ngOnInit(){ + constructor(@Inject(forwardRef(() => ConnectionWizardService)) public connectWizardService: ConnectionWizardService) {} + + ngOnInit() { this.optionalRequirementsMap = this.connectWizardService.getOptionalRequirementsByInstanceUniqueId(true); this.optionalCapabilitiesMap = this.connectWizardService.getOptionalCapabilitiesByInstanceUniqueId(false); } - preventNext = ():boolean => { + preventNext = (): boolean => { return !this.connectWizardService.selectedMatch || (!this.connectWizardService.selectedMatch.capability && !this.connectWizardService.selectedMatch.requirement); } - preventBack = ():boolean => { + preventBack = (): boolean => { return true; } - private updateSelectedReqOrCap = (selected:Requirement|Capability):void => { - if(!selected){ + private updateSelectedReqOrCap = (selected: Requirement|Capability): void => { + if (!selected) { this.connectWizardService.selectedMatch = null; - } else if(selected instanceof Requirement){ + } else if (selected instanceof Requirement) { this.connectWizardService.selectedMatch = new Match(<Requirement>selected, null, true, this.connectWizardService.connectRelationModel.fromNode.componentInstance.uniqueId, null); - } else{ - this.connectWizardService.selectedMatch = new Match(null,<Capability>selected , false, null, this.connectWizardService.connectRelationModel.fromNode.componentInstance.uniqueId); + } else { + this.connectWizardService.selectedMatch = new Match(null, <Capability>selected , false, null, this.connectWizardService.connectRelationModel.fromNode.componentInstance.uniqueId); } } diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.html index 293ebf9822..a8177595a5 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.html @@ -13,8 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - <div class="title"> <span class="capability-name"> {{(connectWizardService.selectedMatch.capability && connectWizardService.selectedMatch.capability.getTitle()) || connectWizardService.selectedMatch.relationship.relation.capability}} diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.less index 8e9e07c0d5..c8ad4d38d2 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.less @@ -1,4 +1,4 @@ -@import '../../../../../assets/styles/variables'; +@import '../../../../../../../assets/styles/variables'; .title{ margin-bottom: 20px; .capability-name-label{ diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.ts index 946d1858dc..2c12e0daed 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/properties-step/properties-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/properties-step/properties-step.component.ts @@ -7,10 +7,10 @@ import {Component, Inject, forwardRef} from '@angular/core'; import {IStepComponent} from "app/models" import {ConnectionWizardService} from "../connection-wizard.service"; -import {PropertyFEModel} from "../../../../models/properties-inputs/property-fe-model"; -import {InstanceFePropertiesMap} from "../../../../models/properties-inputs/property-fe-map"; -import {PropertiesUtils} from "../../properties-assignment/services/properties.utils"; -import {ComponentInstanceServiceNg2} from "../../../services/component-instance-services/component-instance.service"; +import {PropertyFEModel} from "app/models/properties-inputs/property-fe-model"; +import {InstanceFePropertiesMap} from "app/models/properties-inputs/property-fe-map"; +import {PropertiesUtils} from "app/ng2/pages/properties-assignment/services/properties.utils"; +import { ComponentInstanceServiceNg2 } from "app/ng2/services/component-instance-services/component-instance.service"; @Component({ selector: 'properties-step', diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/__snapshots__/to-node-step.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/__snapshots__/to-node-step.component.spec.ts.snap new file mode 100644 index 0000000000..ea587bce71 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/__snapshots__/to-node-step.component.spec.ts.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`to-node-step component should match current snapshot 1`] = ` +<to-node-step + connectWizardService={[Function Object]} + optionalCapabilitiesMap={[Function Object]} + optionalRequirementsMap={[Function Object]} + preventBack={[Function Function]} + preventNext={[Function Function]} + updateSelectedReqOrCap={[Function Function]} +> + <select-requirement-or-capability /> +</to-node-step> +`; diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.html index 775a1a7fc2..4892b7fadc 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.html @@ -13,13 +13,10 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - <select-requirement-or-capability [optionalRequirementsMap]="optionalRequirementsMap" [optionalCapabilitiesMap]="optionalCapabilitiesMap" [selectedReqOrCapModel]="connectWizardService.selectedMatch.isFromTo ? connectWizardService.selectedMatch.capability : connectWizardService.selectedMatch.requirement" [selectedReqOrCapOption]="displayRequirementsOrCapabilities" - [currentComponent]="connectWizardService.currentComponent" [componentInstanceId]="connectWizardService.connectRelationModel.toNode.componentInstance.uniqueId" (updateSelectedReqOrCap)="updateSelectedReqOrCap($event)"> </select-requirement-or-capability>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.spec.ts new file mode 100644 index 0000000000..9d453f21dd --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.spec.ts @@ -0,0 +1,71 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {ToNodeStepComponent} from "./to-node-step.component"; +import {ConnectionWizardService} from "../connection-wizard.service"; +import {ConfigureFn, configureTests} from "../../../../../../../jest/test-config.helper"; +import {Match} from "../../../../../../models/graph/match-relation"; + + +describe('to-node-step component', () => { + + let fixture: ComponentFixture<ToNodeStepComponent>; + let connectionWizardServiceMock: Partial<ConnectionWizardService>; + + beforeEach( + async(() => { + + connectionWizardServiceMock = { + // selectedMatch: new Match(null, null, true, '',''), + selectedMatch: { + isFromTo: false + }, + getOptionalRequirementsByInstanceUniqueId: jest.fn().mockReturnValue(5), + getOptionalCapabilitiesByInstanceUniqueId: jest.fn().mockReturnValue(10) + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [ToNodeStepComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: ConnectionWizardService, useValue: connectionWizardServiceMock} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(ToNodeStepComponent); + }); + }) + ); + + + it('should match current snapshot', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should test the ngOnInit with isFromTo = false', () => { + const component = TestBed.createComponent(ToNodeStepComponent); + let service = TestBed.get(ConnectionWizardService); + service.selectedMatch.isFromTo = false; + component.componentInstance.ngOnInit(); + expect(component.componentInstance.displayRequirementsOrCapabilities).toEqual("Requirement"); + expect(connectionWizardServiceMock.getOptionalRequirementsByInstanceUniqueId).toHaveBeenCalledWith(false, connectionWizardServiceMock.selectedMatch.capability); + expect(component.componentInstance.optionalRequirementsMap).toEqual(5); + expect(component.componentInstance.optionalCapabilitiesMap).toEqual({}); + }); + + + it('should test the ngOnInit with isFromTo = true', () => { + const component = TestBed.createComponent(ToNodeStepComponent); + let service = TestBed.get(ConnectionWizardService); + service.selectedMatch.isFromTo = true; + component.componentInstance.ngOnInit(); + expect(component.componentInstance.displayRequirementsOrCapabilities).toEqual("Capability"); + expect(connectionWizardServiceMock.getOptionalCapabilitiesByInstanceUniqueId).toHaveBeenCalledWith(true, connectionWizardServiceMock.selectedMatch.requirement); + expect(component.componentInstance.optionalCapabilitiesMap).toEqual(10); + expect(component.componentInstance.optionalRequirementsMap).toEqual({}); + }); + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.ts index ea3b129c7b..67dc381284 100644 --- a/catalog-ui/src/app/ng2/pages/connection-wizard/to-node-step/to-node-step.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/connection-wizard/to-node-step/to-node-step.component.ts @@ -2,10 +2,10 @@ import {Component, forwardRef, Inject} from '@angular/core'; import {IStepComponent} from "app/models" import {Dictionary} from "lodash"; import {ConnectionWizardService} from "../connection-wizard.service"; -import {Match} from "../../../../models/graph/match-relation"; -import {Requirement} from "../../../../models/requirement"; -import {Capability} from "../../../../models/capability"; -import {PropertyModel} from "../../../../models/properties"; +import {Match} from "app/models/graph/match-relation"; +import {Requirement} from "app/models/requirement"; +import {Capability} from "app/models/capability"; +import {PropertyModel} from "app/models/properties"; @Component({ selector: 'to-node-step', diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/__snapshots__/link-row.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/__snapshots__/link-row.component.spec.ts.snap new file mode 100644 index 0000000000..094f41bd84 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/__snapshots__/link-row.component.spec.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`artifact form component should match current snapshot of artifact form component 1`] = ` +<link-row + source={[Function Array]} + srcCP={[Function Array]} + target={[Function Array]} + targetCP={[Function Array]} +> + <ui-element-dropdown + class="cell link-selector" + data-tests-id="linkSrc" + /><ui-element-dropdown + class="cell link-selector" + data-tests-id="linkSrcCP" + /><ui-element-dropdown + class="cell link-selector" + data-tests-id="linkTarget" + /><ui-element-dropdown + class="cell link-selector" + data-tests-id="linkTargetCP" + /><div + class="cell remove" + data-tests-id="removeLnk" + > + + </div> +</link-row> +`; diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.html index 0abdda1cc6..0abdda1cc6 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.html diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.less index beec9bd567..2a1d0d98c8 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.less @@ -1,4 +1,4 @@ -@import './../../../../../assets/styles/variables.less'; +@import './../../../../../../../assets/styles/variables.less'; .remove { display: flex; align-items: center; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.spec.ts new file mode 100644 index 0000000000..5cbad6ea5d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.spec.ts @@ -0,0 +1,478 @@ +import {async, ComponentFixture} from "@angular/core/testing"; +import {CacheService} from "../../../../../services/cache.service"; +import {ConfigureFn, configureTests} from "../../../../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {LinkRowComponent} from "./link-row.component"; +import {DropdownValue} from "../../../../../components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {MapItemData, ServicePathMapItem} from "../../../../../../models/graph/nodes-and-links-map"; + +describe('artifact form component', () => { + + let fixture: ComponentFixture<LinkRowComponent>; + let cacheServiceMock: Partial<CacheService>; + + beforeEach( + async(() => { + + + cacheServiceMock = { + contains: jest.fn(), + remove: jest.fn(), + set: jest.fn(), + get: jest.fn() + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [LinkRowComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [] + , + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(LinkRowComponent); + }); + }) + ); + + + it('should match current snapshot of artifact form component', () => { + expect(fixture).toMatchSnapshot(); + }); + + + it('ngOnChanges() -> in case data exist -> call to parseInitialData()' ,() => { + // init values / mock functions + let data = 'something'; + fixture.componentInstance.parseInitialData = jest.fn(); + fixture.componentInstance.data = data; + + // call to the tested function + fixture.componentInstance.ngOnChanges(); + + // expect that + expect(fixture.componentInstance.parseInitialData).toHaveBeenCalledWith(data); + }); + + it('onSourceSelected() -> in case id -> srcCP, link.fromCP, link.toNode, link.toCP, target, targetCP should be updated accordingly' ,() => { + // init values / mock functions + let id = 'id'; + let data = 'data'; + let link = { + fromCP:'testVal', + toNode:'testVal', + toCP:'testVal' + } + let target = ['val1', 'val2']; + let targetCP = ['val1', 'val2']; + + fixture.componentInstance.findOptions = jest.fn(); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => 'dummyConvertedVal'); + fixture.componentInstance.data = data; + fixture.componentInstance.link = link; + fixture.componentInstance.target = target; + fixture.componentInstance.targetCP = targetCP; + + // call to the tested function + fixture.componentInstance.onSourceSelected(id); + + // expect that + expect(fixture.componentInstance.findOptions).toHaveBeenCalledWith(data, id); + expect(fixture.componentInstance.srcCP).toBe('dummyConvertedVal'); + expect(fixture.componentInstance.link.fromCP).toBe(''); + expect(fixture.componentInstance.link.toNode).toBe(''); + expect(fixture.componentInstance.link.toCP).toBe(''); + expect(fixture.componentInstance.target.length).toBe(0); + expect(fixture.componentInstance.targetCP.length).toBe(0); + }); + + it('onSourceSelected() -> in case id undefined -> No Change to srcCP, link.fromCP, link.toNode, link.toCP, target, targetCP' ,() => { + // init values / mock functions + let id; + let data = 'data'; + let link = { + fromCP:'testVal', + toNode:'testVal', + toCP:'testVal' + } + let target = ['val1', 'val2']; + let targetCP = ['val1', 'val2']; + + fixture.componentInstance.findOptions = jest.fn(); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => 'dummyConvertedVal'); + fixture.componentInstance.data = data; + fixture.componentInstance.link = link; + fixture.componentInstance.target = target; + fixture.componentInstance.targetCP = targetCP; + + // call to the tested function + fixture.componentInstance.onSourceSelected(id); + + // expect that + expect(fixture.componentInstance.link.fromCP).toBe(link.fromCP); + expect(fixture.componentInstance.link.toNode).toBe(link.toNode); + expect(fixture.componentInstance.link.toCP).toBe(link.toCP); + expect(fixture.componentInstance.target.length).toBe(2); + expect(fixture.componentInstance.target[0]).toBe('val1') + expect(fixture.componentInstance.targetCP.length).toBe(2); + expect(fixture.componentInstance.targetCP[1]).toBe('val2'); + }); + + it('onSrcCPSelected() -> in case id -> Verify target, link.fromCPOriginId, link.toNode, link.toCP, targetCP.length' ,() => { + // init values / mock functions + let id = 'id'; + let link = { + fromNode:'testVal', + toCPOriginId: 'initValue_ShouldBeChanged' + }; + let option1 = { + id: 'something' + }; + let option2 = { + id: 'id', + data: {"ownerId":1} + }; + + fixture.componentInstance.link = link; + fixture.componentInstance.findOptions = jest.fn(() => [option1, option2]); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => 'dummyConvertedVal'); + + // call to the tested function + fixture.componentInstance.onSrcCPSelected(id); + + // expect that + expect(fixture.componentInstance.target).toBe('dummyConvertedVal'); + expect(fixture.componentInstance.link.fromCPOriginId).toBe(option2.data.ownerId); + expect(fixture.componentInstance.link.toNode).toBe(''); + expect(fixture.componentInstance.link.toCP).toBe(''); + expect(fixture.componentInstance.targetCP.length).toBe(0); + + }); + + it('onSrcCPSelected() -> in case id undefined -> Verify target, link.fromCPOriginId, link.toNode, link.toCP, targetCP.length' ,() => { + // init values / mock functions + let id; + + let targetInput:Array<DropdownValue> = [{value:'Value', label:'Label', hidden:true, selected:true}]; + + let linkInput = { + fromCPOriginId:'expectedLinkFromCPOriginId', + toNode:'expectedLinkToNode', + toCP:'expectedLinkToCP', + // Link Object + canEdit:true, + canRemove:true, + isFirst:true, + // ForwardingPathLink Object + ownerId:'', + fromNode:'', + fromCP:'', + toCPOriginId:'' + } + + fixture.componentInstance.target = targetInput; + fixture.componentInstance.link = linkInput; + fixture.componentInstance.targetCP = targetInput; + + + // call to the tested function + fixture.componentInstance.onSrcCPSelected(id); + + // expect that + expect(fixture.componentInstance.target).toBe(targetInput); + expect(fixture.componentInstance.link.fromCPOriginId).toBe('expectedLinkFromCPOriginId'); + expect(fixture.componentInstance.link.toNode).toBe('expectedLinkToNode'); + expect(fixture.componentInstance.link.toCP).toBe('expectedLinkToCP'); + expect(fixture.componentInstance.targetCP.length).toBe(1); + }); + + it('onTargetSelected() -> in case id -> Verify targetCP & link.toCP' ,() => { + // init values / mock functions + let id = 'id'; + let link = { + toCP:'testVal' + } + let targetCP = ['val1', 'val2']; + + fixture.componentInstance.findOptions = jest.fn(); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => 'dummyConvertedVal'); + fixture.componentInstance.link = link; + fixture.componentInstance.targetCP = targetCP; + + // call to the tested function + fixture.componentInstance.onTargetSelected(id); + + // expect that + expect(fixture.componentInstance.targetCP).toBe('dummyConvertedVal'); + expect(fixture.componentInstance.link.toCP).toBe(''); + + }); + + it('onTargetSelected() -> in case id undefined -> Verify targetCP & link.toCP' ,() => { + // init values / mock functions + let id; + let link = { + toCP:'toCP_testVal' + } + let targetCP = ['val1', 'val2']; + + fixture.componentInstance.findOptions = jest.fn(); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => 'dummyConvertedVal'); + fixture.componentInstance.link = link; + fixture.componentInstance.targetCP = targetCP; + + // call to the tested function + fixture.componentInstance.onTargetSelected(id); + + // expect that + expect(fixture.componentInstance.targetCP.length).toBe(2); + expect(fixture.componentInstance.targetCP).toEqual(['val1', 'val2']); + expect(fixture.componentInstance.link.toCP).toBe('toCP_testVal'); + }); + + it('onTargetCPSelected() -> in case id -> Validate toCPOriginId' ,() => { + // init values / mock functions + let id = 'id'; + let link = { + toNode:'testVal', + toCPOriginId: 'initValue_ShouldBeChanged' + }; + let option1 = { + id: 'something' + }; + let option2 = { + id: 'id', + data: {"ownerId":1} + }; + fixture.componentInstance.link = link; + fixture.componentInstance.findOptions = jest.fn(() => [option1, option2]); + + // call to the tested function + fixture.componentInstance.onTargetCPSelected(id); + + // expect that + expect(fixture.componentInstance.link.toCPOriginId).toBe(option2.data.ownerId); + }); + + it('onTargetCPSelected() -> in case id undefined -> Validate toCPOriginId' ,() => { + // init values / mock functions + let id; + let link = { + toNode:'testVal', + toCPOriginId: 'initValue_ShouldRemain' + }; + let option1 = { + id: 'something' + }; + let option2 = { + id: 'id', + data: {"ownerId":1} + }; + fixture.componentInstance.link = link; + fixture.componentInstance.findOptions = jest.fn(() => [option1, option2]); + + // call to the tested function + fixture.componentInstance.onTargetCPSelected(id); + + // expect that + expect(fixture.componentInstance.link.toCPOriginId).toBe('initValue_ShouldRemain'); + }); + + + it('findOptions() -> in case item.data.options -> Validate return item.data.options' ,() => { + // init values / mock functions + const innerMapItemData1: MapItemData = { id: 'innerMapItemData1_id', name: 'innerMapItemData1_name', options: []}; + const innerServicePathItem: ServicePathMapItem = { id: 'innerServicePathItem_id', data: innerMapItemData1 }; + const mapItemData1: MapItemData = { id: 'mapItemData1_id', name: 'mapItemData1_name', options: [innerServicePathItem]}; + + const servicePathItem: ServicePathMapItem = { id: 'servicePathItem_id', data: mapItemData1 }; + const arrServicePathItems: ServicePathMapItem[] = [servicePathItem]; + + let nodeOrCPId: string = servicePathItem.id; + + // call to the tested function + let res = fixture.componentInstance.findOptions(arrServicePathItems, nodeOrCPId); + + // expect that + expect(res).toEqual([innerServicePathItem]); + }); + + it('findOptions() -> in case NOT item || item.data || item.data.options -> Validate return null' ,() => { + // init values / mock functions + let item = [{ + // data: { + data:{ + name:'data_name', + id: 'data_id' + }, + name:'name', + id: 'id' + // } + }]; + let items: Array<ServicePathMapItem> = item; + let nodeOrCPId: string = 'someString'; + + // call to the tested function + let res = fixture.componentInstance.findOptions(items, nodeOrCPId); + + // expect that + expect(res).toBe(null); + }); + + it('convertValuesToDropDownOptions() -> Verify that the result is sorted' ,() => { + // init values / mock functions + const mapItemData1: MapItemData = { id: 'Z_ID', name: 'Z_NAME'}; + const servicePathItem1: ServicePathMapItem = { id: 'Z_servicePathItem_id', data: mapItemData1 }; + + const mapItemData2: MapItemData = { id: 'A_ID', name: 'A_NAME'}; + const servicePathItem2: ServicePathMapItem = { id: 'A_servicePathItem_id', data: mapItemData2 }; + + const mapItemData3: MapItemData = { id: 'M_ID', name: 'M_NAME'}; + const servicePathItem3: ServicePathMapItem = { id: 'M_servicePathItem_id', data: mapItemData3 }; + + const arrServicePathItems: ServicePathMapItem[] = [servicePathItem1, servicePathItem2, servicePathItem3]; + + // call to the tested function + let res = fixture.componentInstance.convertValuesToDropDownOptions(arrServicePathItems); + + // expect that + expect(res.length).toBe(3); + expect(res[0].value).toBe("A_servicePathItem_id"); + expect(res[0].label).toBe("A_NAME"); + expect(res[1].value).toBe("M_servicePathItem_id"); + expect(res[1].label).toBe("M_NAME"); + expect(res[2].value).toBe("Z_servicePathItem_id"); + expect(res[2].label).toBe("Z_NAME"); + + }); + + it('parseInitialData() -> link.fromNode Exist => Verify srcCP' ,() => { + // init values / mock functions + + //Simulate Array<ServicePathMapItem to pass to the function + const mapItemData1: MapItemData = { id: 'mapItemID', name: 'mapItemName'}; + const servicePathItem1: ServicePathMapItem = { id: 'servicePathItemId', data: mapItemData1 }; + const arrServicePathItems: ServicePathMapItem[] = [servicePathItem1]; + + //Simulate link + let link = { + fromNode:'testVal' + }; + fixture.componentInstance.link = link; + + //Simulate the response from convertValuesToDropDownOptions() + const value = "expected_id_fromNode"; + const label = "expected_label_fromNode" + let result:Array<DropdownValue> = []; + result[0] = new DropdownValue(value, label); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => result); + + //Simulate the response from findOptions() + const innerMapItemData1: MapItemData = { id: 'innerMapItemData1_id', name: 'innerMapItemData1_name', options: []}; + const options: ServicePathMapItem = { id: 'innerServicePathItem_id', data: innerMapItemData1 }; + fixture.componentInstance.findOptions = jest.fn(() => options); + + + // call to the tested function + fixture.componentInstance.parseInitialData(arrServicePathItems); + + // expect that + expect(fixture.componentInstance.srcCP.length).toBe(1); + expect(fixture.componentInstance.srcCP[0]).toEqual({ + "value": value, + "label": label, + "hidden": false, + "selected": false + }); + }); + + it('parseInitialData() -> link.fromNode & link.fromCP Exist => Verify srcCP' ,() => { + // init values / mock functions + + //Simulate Array<ServicePathMapItem to pass to the function + const mapItemData1: MapItemData = { id: 'mapItemID', name: 'mapItemName'}; + const servicePathItem1: ServicePathMapItem = { id: 'servicePathItemId', data: mapItemData1 }; + const arrServicePathItems: ServicePathMapItem[] = [servicePathItem1]; + + //Simulate link + let link = { + fromNode:'testVal', + fromCP: 'testVal' + }; + fixture.componentInstance.link = link; + + //Simulate the response from convertValuesToDropDownOptions() + const value = "expected_id_fromNode_and_fromCP"; + const label = "expected_label_fromNode_and_fromCP" + let result:Array<DropdownValue> = []; + result[0] = new DropdownValue(value, label); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => result); + + //Simulate the response from findOptions() + const innerMapItemData1: MapItemData = { id: 'innerMapItemData1_id', name: 'innerMapItemData1_name', options: []}; + const options: ServicePathMapItem = { id: 'innerServicePathItem_id', data: innerMapItemData1 }; + fixture.componentInstance.findOptions = jest.fn(() => options); + + + // call to the tested function + fixture.componentInstance.parseInitialData(arrServicePathItems); + + // expect that + expect(fixture.componentInstance.srcCP.length).toBe(1); + expect(fixture.componentInstance.srcCP[0]).toEqual({ + "value": value, + "label": label, + "hidden": false, + "selected": false + }); + }); + + + it('parseInitialData() -> link.fromNode & link.fromCP & link.toNode Exist => Verify srcCP' ,() => { + // init values / mock functions + + //Simulate Array<ServicePathMapItem to pass to the function + const mapItemData1: MapItemData = { id: 'mapItemID', name: 'mapItemName'}; + const servicePathItem1: ServicePathMapItem = { id: 'servicePathItemId', data: mapItemData1 }; + const arrServicePathItems: ServicePathMapItem[] = [servicePathItem1]; + + //Simulate link + let link = { + fromNode:'testVal', + fromCP: 'testVal', + toNode: 'testVal' + }; + fixture.componentInstance.link = link; + + //Simulate the response from convertValuesToDropDownOptions() + const value = "expected_id_fromNode_and_fromCP_and_toNode"; + const label = "expected_label_fromNode_and_fromCP_and_toNode" + let result:Array<DropdownValue> = []; + result[0] = new DropdownValue(value, label); + fixture.componentInstance.convertValuesToDropDownOptions = jest.fn(() => result); + + //Simulate the response from findOptions() + const innerMapItemData1: MapItemData = { id: 'innerMapItemData1_id', name: 'innerMapItemData1_name', options: []}; + const options: ServicePathMapItem = { id: 'innerServicePathItem_id', data: innerMapItemData1 }; + fixture.componentInstance.findOptions = jest.fn(() => options); + + + // call to the tested function + fixture.componentInstance.parseInitialData(arrServicePathItems); + + // expect that + expect(fixture.componentInstance.srcCP.length).toBe(1); + expect(fixture.componentInstance.srcCP[0]).toEqual({ + "value": value, + "label": label, + "hidden": false, + "selected": false + }); + }); + + + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.ts index e4fc1d4522..83c30b1a60 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link-row.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link-row.component.ts @@ -2,6 +2,7 @@ import {Component, Input} from '@angular/core'; import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; import {Link} from './link.model'; import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; +import * as _ from "lodash"; @Component({ selector: 'link-row', diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link.model.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link.model.ts index 80128eb42e..80128eb42e 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/link-row/link.model.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/link-row/link.model.ts diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.html index cc14b4961f..db0d912934 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.html @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <div class="service-path-creator"> <form class="w-sdc-form"> <div class="i-sdc-form-item" > diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.less index 5c9e53e229..2a3efbdd3c 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.less @@ -1,4 +1,4 @@ -@import './../../../../assets/styles/variables.less'; +@import './../../../../../../assets/styles/variables.less'; .service-path-creator { font-family: @font-opensans-regular; .separator-buttons { diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.ts index bffb1c5e7e..17c2081a75 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component.ts @@ -25,6 +25,7 @@ import {ForwardingPath} from 'app/models/forwarding-path'; import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; import {ForwardingPathLink} from "app/models/forwarding-path-link"; import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; +import {CompositionService} from "app/ng2/pages/composition/composition.service"; @Component({ selector: 'service-path-creator', @@ -43,7 +44,8 @@ export class ServicePathCreatorComponent { forwardingPath:ForwardingPath; //isExtendAllowed:boolean = false; - constructor(private serviceService: ServiceServiceNg2) { + constructor(private serviceService: ServiceServiceNg2, + private compositionService: CompositionService) { this.forwardingPath = new ForwardingPath(); this.links = [new Link(new ForwardingPathLink('', '', '', '', '', ''), true, false, true)]; this.headers = ['Source', 'Source Connection Point', 'Target', 'Target Connection Point', ' ']; @@ -57,7 +59,7 @@ export class ServicePathCreatorComponent { } ngOnInit() { - this.serviceService.getNodesAndLinksMap(this.input.service).subscribe((res:any) => { + this.serviceService.getNodesAndLinksMap(this.input.serviceId).subscribe((res:any) => { this.linksMap = res; }); this.processExistingPath(); @@ -66,7 +68,7 @@ export class ServicePathCreatorComponent { private processExistingPath() { if (this.input.pathId) { - let forwardingPath = <ForwardingPath>{...this.input.service.forwardingPaths[this.input.pathId]}; + let forwardingPath = <ForwardingPath>{...this.compositionService.forwardingPaths[this.input.pathId]}; this.forwardingPath.name = forwardingPath.name; this.forwardingPath.destinationPortNumber = forwardingPath.destinationPortNumber; this.forwardingPath.protocol = forwardingPath.protocol; diff --git a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.module.ts index 78005317a2..78005317a2 100644 --- a/catalog-ui/src/app/ng2/pages/service-path-creator/service-path-creator.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-creator/service-path-creator.module.ts diff --git a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.html index e1a4f68a9b..e1a4f68a9b 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.html diff --git a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.less index f3cb4a3c34..f618d6b6f4 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.less @@ -1,4 +1,4 @@ -@import './../../../../../assets/styles/variables.less'; +@import './../../../../../../assets/styles/variables.less'; .service-path-selector { margin: 10px 35px 10px 0; display: flex; diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.ts new file mode 100644 index 0000000000..0dba906f64 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.component.ts @@ -0,0 +1,142 @@ +import {Component, Input, KeyValueDiffer, IterableDiffers, KeyValueDiffers, DoCheck} from '@angular/core'; +import {Service} from "app/models/components/service"; +import {TranslateService} from "app/ng2/shared/translator/translate.service"; +import {ForwardingPath} from "app/models/forwarding-path"; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {CompositionService} from "app/ng2/pages/composition/composition.service"; +import {EventListenerService} from "app/services/event-listener-service"; +import {GRAPH_EVENTS} from "app/utils/constants"; + +@Component({ + selector: 'service-path-selector', + templateUrl: './service-path-selector.component.html', + styleUrls: ['service-path-selector.component.less'] +}) + +export class ServicePathSelectorComponent { + + defaultSelectedId: string; + hideAllValue: string; + hideAllId: string = '0'; + showAllValue: string; + showAllId: string = '1'; + + paths: Array<ForwardingPath> = []; + dropdownOptions: Array<DropdownValue>; + differ: KeyValueDiffer<string, ForwardingPath>; + + @Input() drawPath: Function; + @Input() deletePaths: Function; + @Input() selectedPathId: string; + + constructor(private differs: KeyValueDiffers, + private translateService: TranslateService, + private compositionService: CompositionService, + private eventListenerService: EventListenerService + ) { + + this.defaultSelectedId = this.hideAllId; + this.convertPathsToDropdownOptions(); + + this.translateService.languageChangedObservable.subscribe(lang => { + this.hideAllValue = this.translateService.translate("SERVICE_PATH_SELECTOR_HIDE_ALL_VALUE"); + this.showAllValue = this.translateService.translate("SERVICE_PATH_SELECTOR_SHOW_ALL_VALUE"); + this.convertPathsToDropdownOptions(); + }); + + } + + ngOnInit(): void { + + this.selectedPathId = this.defaultSelectedId; + this.differ = this.differs.find(this.compositionService.forwardingPaths).create(); + this.updatePaths(); + this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_SERVICE_PATH_CREATED, (createdId) => { + this.selectedPathId = createdId; + this.updatePaths(); + } ) + + } + + updatePaths(): void { + + const pathsChanged = this.differ.diff(this.compositionService.forwardingPaths); + + if (pathsChanged) { + let oldPaths = _.cloneDeep(this.paths); + this.populatePathsFromService(); + + if (!(_.isEqual(oldPaths, this.paths))) { + this.convertPathsToDropdownOptions(); + + let temp = this.selectedPathId; + this.selectedPathId = '-1'; + + setTimeout(() => { + this.selectedPathId = temp; + this.onSelectPath(); + }, 0); + } + } + + } + + populatePathsFromService(): void { + + this.paths = []; + + _.forEach(this.compositionService.forwardingPaths, path => { + this.paths.push(path); + }); + this.paths.sort((a: ForwardingPath, b: ForwardingPath) => { + return a.name.localeCompare(b.name); + }); + + } + + convertPathsToDropdownOptions(): void { + + let result = [ + new DropdownValue(this.hideAllId, this.hideAllValue), + new DropdownValue(this.showAllId, this.showAllValue) + ]; + + _.forEach(this.paths, (value: ForwardingPath) => { + result[result.length] = new DropdownValue(value.uniqueId, value.name); + }); + + this.dropdownOptions = result; + + } + + onSelectPath = (): void => { + + if (this.selectedPathId !== '-1') { + this.deletePaths(); + + switch (this.selectedPathId) { + case this.hideAllId: + break; + + case this.showAllId: + _.forEach(this.paths, path => + this.drawPath(path) + ); + break; + + default: + let path = this.paths.find(path => + path.uniqueId === this.selectedPathId + ); + if (!path) { + this.selectedPathId = this.defaultSelectedId; + this.onSelectPath(); // currently does nothing in default case, but if one day it does, we want the selection to behave accordingly. + break; + } + this.drawPath(path); + break; + } + } + + } +} diff --git a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.module.ts index c07061ce9a..6782c88b76 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-path-selector/service-path-selector.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-path-selector/service-path-selector.module.ts @@ -2,6 +2,7 @@ import { NgModule } from "@angular/core"; import {CommonModule} from "@angular/common"; import {ServicePathSelectorComponent} from "./service-path-selector.component"; import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import {CompositionService} from "app/ng2/pages/composition/composition.service"; @NgModule({ declarations: [ @@ -11,11 +12,11 @@ import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; CommonModule, UiElementsModule ], - exports: [], + exports: [ServicePathSelectorComponent], entryComponents: [ ServicePathSelectorComponent ], - providers: [] + providers: [CompositionService] }) export class ServicePathSelectorModule { }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.html b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.html index 33a0090372..39c41916a2 100644 --- a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.html @@ -1,19 +1,3 @@ -<!-- - ~ 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="service-path-list"> <div class="add-path-link" *ngIf="!isViewOnly"><a (click)="onAddServicePath()" data-tests-id="add-service-path-lnk" >+ Add Flow</a></div> <div class="generic-table table-container" > diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.less b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.less index 291119f58c..17f70926ff 100644 --- a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.less @@ -1,4 +1,4 @@ -@import './../../../../assets/styles/variables.less'; +@import './../../../../../../assets/styles/variables.less'; .add-path-link { display: flex; diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.ts index 1625ab4b66..81abe42cb3 100644 --- a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component.ts @@ -24,6 +24,7 @@ import {ForwardingPath} from "app/models/forwarding-path"; import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; import {ModalService} from "app/ng2/services/modal.service"; import {ModalComponent} from "app/ng2/components/ui/modal/modal.component"; +import {CompositionService} from "app/ng2/pages/composition/composition.service"; @Component({ selector: 'service-paths-list', @@ -31,7 +32,7 @@ import {ModalComponent} from "app/ng2/components/ui/modal/modal.component"; styleUrls:['service-paths-list.component.less'], providers: [ServiceServiceNg2, ModalService] }) -export default class ServicePathsListComponent { +export class ServicePathsListComponent { modalInstance: ComponentRef<ModalComponent>; headers: Array<string> = []; paths: Array<ForwardingPath> = []; @@ -40,12 +41,13 @@ export default class ServicePathsListComponent { onEditServicePath: Function; isViewOnly: boolean; - constructor(private serviceService:ServiceServiceNg2) { + constructor(private serviceService:ServiceServiceNg2, + private compositionService: CompositionService) { this.headers = ['Flow Name','Actions']; } ngOnInit() { - _.forEach(this.input.service.forwardingPaths, (path: ForwardingPath)=> { + _.forEach(this.compositionService.forwardingPaths, (path: ForwardingPath)=> { this.paths[this.paths.length] = path; }); this.paths.sort((a:ForwardingPath, b:ForwardingPath)=> { @@ -57,8 +59,8 @@ export default class ServicePathsListComponent { } deletePath = (id:string):void => { - this.serviceService.deleteServicePath(this.input.service, id).subscribe((res:any) => { - delete this.input.service.forwardingPaths[id]; + this.serviceService.deleteServicePath(this.input.serviceId, id).subscribe((res:any) => { + delete this.compositionService.forwardingPaths[id]; this.paths = this.paths.filter(function(path){ return path.uniqueId !== id; }); diff --git a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.module.ts b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.module.ts index c236934002..5121627a9d 100644 --- a/catalog-ui/src/app/ng2/pages/service-paths-list/service-paths-list.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/graph/service-paths-list/service-paths-list.module.ts @@ -1,6 +1,6 @@ import { NgModule } from "@angular/core"; import {CommonModule} from "@angular/common"; -import ServicePathsListComponent from "./service-paths-list.component"; +import { ServicePathsListComponent } from "./service-paths-list.component"; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts new file mode 100644 index 0000000000..bc8bd691c9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-general-utils.ts @@ -0,0 +1,268 @@ +/*- + * ============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 {ComponentInstance, Match, CompositionCiLinkBase, CompositionCiNodeUcpeCp} from "app/models"; +import {Dictionary, GraphUIObjects} from "app/utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {Injectable} from "@angular/core"; +import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; +import {ComponentServiceNg2} from "app/ng2/services/component-services/component.service"; +import {RequirementsGroup} from "app/models/requirement"; +import {CapabilitiesGroup} from "app/models/capability"; +import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service"; +import {CompositionService} from "../../composition.service"; +import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; +import {NotificationsService} from "onap-ui-angular/dist/notifications/services/notifications.service"; +import {NotificationSettings} from "onap-ui-angular/dist/notifications/utilities/notification.config"; + +export interface RequirementAndCapabilities { + capabilities: CapabilitiesGroup; + requirements: RequirementsGroup; +} + +@Injectable() +export class CompositionGraphGeneralUtils { + + public componentRequirementsAndCapabilitiesCaching = new Dictionary<string, RequirementAndCapabilities>(); + + constructor(private commonGraphUtils: CommonGraphUtils, + private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils, + private queueServiceUtils: QueueServiceUtils, + private componentService: ComponentServiceNg2, + private topologyTemplateService: TopologyTemplateService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService) { + } + + /** + * Get the offset for the link creation Menu + * @param point + * @returns {Cy.Position} + */ + public calcMenuOffset: Function = (point: Cy.Position): Cy.Position => { + point.x = point.x + 60; + point.y = point.y + 105; + return point; + }; + + /** + * return the top left position of the link menu + * @param cy + * @param targetNodePosition + * @returns {Cy.Position} + */ + public getLinkMenuPosition = (cy: Cy.Instance, targetNodePosition: Cy.Position) => { + let menuPosition: Cy.Position = this.calcMenuOffset(targetNodePosition); //get the link mid point + if ($(document.body).height() < menuPosition.y + GraphUIObjects.LINK_MENU_HEIGHT + $(document.getElementsByClassName('sdc-composition-graph-wrapper')).offset().top) { // if position menu is overflow bottom + menuPosition.y = $(document.body).height() - GraphUIObjects.TOP_HEADER_HEIGHT - GraphUIObjects.LINK_MENU_HEIGHT; + } + return menuPosition; + }; + + public zoomGraphTo = (cy: Cy.Instance, zoomLevel: number): void => { + let zy = cy.height() / 2; + let zx = cy.width() / 2; + cy.zoom({ + level: zoomLevel, + renderedPosition: {x: zx, y: zy} + }); + } + + //saves the current zoom, and then sets a temporary maximum zoom for zoomAll, and then reverts to old value + public zoomAllWithMax = (cy: Cy.Instance, maxZoom: number): void => { + + let oldMaxZoom: number = cy.maxZoom(); + + cy.maxZoom(maxZoom); + this.zoomAll(cy); + cy.maxZoom(oldMaxZoom); + + }; + + //Zooms to fit all of the nodes in the collection passed in. If no nodes are passed in, will zoom to fit all nodes on graph + public zoomAll = (cy: Cy.Instance, nodes?: Cy.CollectionNodes): void => { + + if (!nodes || !nodes.length) { + nodes = cy.nodes(); + } + + cy.resize(); + cy.animate({ + fit: {eles: nodes, padding: 20}, + center: {eles: nodes} + }, {duration: 400}); + }; + + /** + * will return true/false if two nodes overlapping + * + * @param graph node + */ + private isNodesOverlapping(node: Cy.CollectionFirstNode, draggedNode: Cy.CollectionFirstNode): boolean { + + let nodeBoundingBox: Cy.BoundingBox = node.renderedBoundingBox(); + let secondNodeBoundingBox: Cy.BoundingBox = draggedNode.renderedBoundingBox(); + + return this.isBBoxOverlapping(nodeBoundingBox, secondNodeBoundingBox); + } + + /** + * Checks whether the bounding boxes of two nodes are overlapping on any side + * @param nodeOneBBox + * @param nodeTwoBBox + * @returns {boolean} + */ + private isBBoxOverlapping(nodeOneBBox: Cy.BoundingBox, nodeTwoBBox: Cy.BoundingBox) { + return (((nodeOneBBox.x1 < nodeTwoBBox.x1 && nodeOneBBox.x2 > nodeTwoBBox.x1) || + (nodeOneBBox.x1 < nodeTwoBBox.x2 && nodeOneBBox.x2 > nodeTwoBBox.x2) || + (nodeTwoBBox.x1 < nodeOneBBox.x1 && nodeTwoBBox.x2 > nodeOneBBox.x2)) && + ((nodeOneBBox.y1 < nodeTwoBBox.y1 && nodeOneBBox.y2 > nodeTwoBBox.y1) || + (nodeOneBBox.y1 < nodeTwoBBox.y2 && nodeOneBBox.y2 > nodeTwoBBox.y2) || + (nodeTwoBBox.y1 < nodeOneBBox.y1 && nodeTwoBBox.y2 > nodeOneBBox.y2))) + } + + /** + * Checks whether a specific topologyTemplate instance can be hosted on the UCPE instance + * @param cy - Cytoscape instance + * @param fromUcpeInstance + * @param toComponentInstance + * @returns {Match} + */ + public canBeHostedOn(cy: Cy.Instance, fromUcpeInstance: ComponentInstance, toComponentInstance: ComponentInstance): Match { + + let matches: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromUcpeInstance, toComponentInstance, this.getAllCompositionCiLinks(cy)); + let hostedOnMatch: Match = _.find(matches, (match: Match) => { + return match.requirement.capability.toLowerCase() === 'tosca.capabilities.container'; + }); + + return hostedOnMatch; + }; + + /** + * Checks whether node can be dropped into UCPE + * @param cy + * @param nodeToInsert + * @param ucpeNode + * @returns {boolean} + */ + private isValidDropInsideUCPE(cy: Cy.Instance, nodeToInsert: ComponentInstance, ucpeNode: ComponentInstance): boolean { + + let hostedOnMatch: Match = this.canBeHostedOn(cy, ucpeNode, nodeToInsert); + let result: boolean = !angular.isUndefined(hostedOnMatch) || nodeToInsert.isVl(); //group validation + return result; + + }; + + /** + * For drops from palette, checks whether the node can be dropped. If node is being held over another node, check if capable of hosting + * @param cy + * @param pseudoNodeBBox + * @param paletteComponentInstance + * @returns {boolean} + */ + public isPaletteDropValid(cy: Cy.Instance, pseudoNodeBBox: Cy.BoundingBox) { + + let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode: Cy.CollectionFirstNode) => { + if (this.isBBoxOverlapping(pseudoNodeBBox, graphNode.renderedBoundingBox())) { + return true; + } + return false; + }); + + return illegalOverlappingNodes.length === 0; + } + + /** + * will return true/false if a drop of a single node is valid + * + * @param graph node + */ + public isValidDrop(cy: Cy.Instance, draggedNode: Cy.CollectionFirstNode): boolean { + + let illegalOverlappingNodes = _.filter(cy.nodes("[isSdcElement]"), (graphNode: Cy.CollectionFirstNode) => { //all sdc nodes, removing child nodes (childe node allways collaps + + if (draggedNode.data().isUcpe && (graphNode.isChild() || graphNode.data().isInsideGroup)) { //ucpe cps always inside ucpe, no overlapping + return false; + } + if (draggedNode.data().isInsideGroup && (!draggedNode.active() || graphNode.data().isUcpe)) { + return false; + } + + if (!draggedNode.data().isUcpe && !(draggedNode.data() instanceof CompositionCiNodeUcpeCp) && graphNode.data().isUcpe) { //case we are dragging a node into UCPE + let isEntirelyInUCPE: boolean = this.commonGraphUtils.isFirstBoxContainsInSecondBox(draggedNode.renderedBoundingBox(), graphNode.renderedBoundingBox()); + if (isEntirelyInUCPE) { + if (this.isValidDropInsideUCPE(cy, draggedNode.data().componentInstance, graphNode.data().componentInstance)) { //if this is valid insert into ucpe, we return false - no illegal overlapping nodes + return false; + } + } + } + return graphNode.data().id !== draggedNode.data().id && this.isNodesOverlapping(draggedNode, graphNode); + + }); + // return false; + return illegalOverlappingNodes.length === 0; + }; + + /** + * will return true/false if the move of the nodes is valid (no node overlapping and verifying if insert into UCPE is valid) + * + * @param nodesArray - the selected drags nodes + */ + public isGroupValidDrop(cy: Cy.Instance, nodesArray: Cy.CollectionNodes): boolean { + let filterDraggedNodes = nodesArray.filter('[?isDraggable]'); + let isValidDrop = _.every(filterDraggedNodes, (node: Cy.CollectionFirstNode) => { + return this.isValidDrop(cy, node); + + }); + return isValidDrop; + }; + + /** + * get all links in diagram + * @param cy + * @returns {any[]|boolean[]} + */ + public getAllCompositionCiLinks = (cy: Cy.Instance): Array<CompositionCiLinkBase> => { + return _.map(cy.edges("[isSdcElement]"), (edge: Cy.CollectionEdges) => { + return edge.data(); + }); + }; + + /** + * + * @param blockAction - true/false if this is a block action + * @param instances + * @param component + */ + public pushMultipleUpdateComponentInstancesRequestToQueue = (instances: Array<ComponentInstance>): void => { + this.queueServiceUtils.addNonBlockingUIAction(() => { + return new Promise<boolean>((resolve, reject) => { + let uniqueId = this.workspaceService.metadata.uniqueId; + let topologyType = this.workspaceService.metadata.componentType; + this.topologyTemplateService.updateMultipleComponentInstances(uniqueId, topologyType, instances).subscribe(instancesResult => { + this.compositionService.updateComponentInstances(instancesResult); + resolve(true); + }); + }); + }); + } +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts new file mode 100644 index 0000000000..6035d05b7f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-links-utils.ts @@ -0,0 +1,342 @@ +/*- + * ============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========================================================= + */ + +/** + * Created by obarda on 6/28/2016. + */ +import * as _ from "lodash"; +import {GraphUIObjects} from "app/utils"; +import { + Match, + CompositionCiNodeBase, + RelationshipModel, + ConnectRelationModel, + LinksFactory, + Component, + LinkMenu, + Point, + CompositionCiLinkBase, + Requirement, + Capability, + Relationship, + ComponentInstance +} from "app/models"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link"; +import {Injectable} from "@angular/core"; +import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; +import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service"; +import {SdcUiServices} from "onap-ui-angular"; +import {CompositionService} from "../../composition.service"; +import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; + +@Injectable() +export class CompositionGraphLinkUtils { + + constructor(private linksFactory: LinksFactory, + private generalGraphUtils: CompositionGraphGeneralUtils, + private commonGraphUtils: CommonGraphUtils, + private queueServiceUtils: QueueServiceUtils, + private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils, + private topologyTemplateService: TopologyTemplateService, + private loaderService: SdcUiServices.LoaderService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService) { + + + } + + /** + * Delete the link on server and then remove it from graph + * @param component + * @param releaseLoading - true/false release the loader when finished + * @param link - the link to delete + */ + public deleteLink = (cy: Cy.Instance, component: Component, releaseLoading: boolean, link: Cy.CollectionEdges) => { + + this.loaderService.activate(); + this.queueServiceUtils.addBlockingUIAction(() => { + this.topologyTemplateService.deleteRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.data().relation).subscribe((deletedRelation) => { + this.compositionService.deleteRelation(deletedRelation); + cy.remove(link); + this.loaderService.deactivate(); + }, (error) => {this.loaderService.deactivate()}); + }); + }; + + /** + * create the link on server and than draw it on graph + * @param link - the link to create + * @param cy + * @param component + */ + public createLink = (link: CompositionCiLinkBase, cy: Cy.Instance): void => { + + this.loaderService.activate(); + link.updateLinkDirection(); + + this.queueServiceUtils.addBlockingUIAction(() => { + this.topologyTemplateService.createRelation(this.workspaceService.metadata.uniqueId, this.workspaceService.metadata.componentType, link.relation).subscribe((relation) => { + link.setRelation(relation); + this.insertLinkToGraph(cy, link); + this.compositionService.addRelation(relation); + this.loaderService.deactivate(); + }, (error) => {this.loaderService.deactivate()}) + }); + }; + + private createSimpleLink = (match: Match, cy: Cy.Instance): void => { + let newRelation: RelationshipModel = match.matchToRelationModel(); + let linkObg: CompositionCiLinkBase = this.linksFactory.createGraphLink(cy, newRelation, newRelation.relationships[0]); + this.createLink(linkObg, cy); + }; + + public createLinkFromMenu = (cy: Cy.Instance, chosenMatch: Match): void => { + + if (chosenMatch) { + if (chosenMatch && chosenMatch instanceof Match) { + this.createSimpleLink(chosenMatch, cy); + } + } + } + + /** + * open the connect link menu if the link drawn is valid - match requirements & capabilities + * @param cy + * @param fromNode + * @param toNode + * @returns {any} + */ + public onLinkDrawn(cy: Cy.Instance, fromNode: Cy.CollectionFirstNode, toNode: Cy.CollectionFirstNode): ConnectRelationModel { + + let linkModel: Array<CompositionCiLinkBase> = this.generalGraphUtils.getAllCompositionCiLinks(cy); + + let possibleRelations: Array<Match> = this.matchCapabilitiesRequirementsUtils.getMatchedRequirementsCapabilities(fromNode.data().componentInstance, + toNode.data().componentInstance, linkModel); + + //if found possibleRelations between the nodes we create relation menu directive and open the link menu + if (possibleRelations.length) { + // let menuPosition = this.generalGraphUtils.getLinkMenuPosition(cy, toNode.renderedPoint()); + return new ConnectRelationModel(fromNode.data(), toNode.data(), possibleRelations); + } + return null; + }; + + private handlePathLink(cy: Cy.Instance, event: Cy.EventObject) { + let linkData = event.cyTarget.data(); + let selectedPathId = linkData.pathId; + let pathEdges = cy.collection(`[pathId='${selectedPathId}']`); + if (pathEdges.length > 1) { + setTimeout(() => { + pathEdges.select(); + }, 0); + } + } + + private handleVLLink(event: Cy.EventObject) { + let vl: Cy.CollectionNodes = event.cyTarget[0].target('.vl-node'); + let connectedEdges: Cy.CollectionEdges = vl.connectedEdges(`[type!="${CompositionCiServicePathLink.LINK_TYPE}"]`); + if (vl.length && connectedEdges.length > 1) { + setTimeout(() => { + vl.select(); + connectedEdges.select(); + }, 0); + } + } + + + /** + * Handles click event on links. + * If one edge selected: do nothing. + * Two or more edges: first click - select all, secondary click - select single. + * @param cy + * @param event + */ + public handleLinkClick(cy: Cy.Instance, event: Cy.EventObject) { + if (cy.$('edge:selected').length > 1 && event.cyTarget[0].selected()) { + cy.$(':selected').unselect(); + } else { + if (event.cyTarget[0].data().type === CompositionCiServicePathLink.LINK_TYPE) { + this.handlePathLink(cy, event); + } + else { + this.handleVLLink(event); + } + } + } + + + /** + * Calculates the position for the menu that modifies an existing link + * @param event + * @param elementWidth + * @param elementHeight + * @returns {Point} + */ + public calculateLinkMenuPosition(event, elementWidth, elementHeight): Point { + let point: Point = new Point(event.originalEvent.clientX, event.originalEvent.clientY); + if (event.originalEvent.view.screen.height - elementHeight < point.y) { + point.y = event.originalEvent.view.screen.height - elementHeight; + } + if (event.originalEvent.view.screen.width - elementWidth < point.x) { + point.x = event.originalEvent.view.screen.width - elementWidth; + } + return point; + }; + + + /** + * Gets the menu that is displayed when you click an existing link. + * @param link + * @param event + * @returns {LinkMenu} + */ + public getModifyLinkMenu(link: Cy.CollectionFirstEdge, event: Cy.EventObject): LinkMenu { + let point: Point = this.calculateLinkMenuPosition(event, GraphUIObjects.MENU_LINK_VL_WIDTH_OFFSET, GraphUIObjects.MENU_LINK_VL_HEIGHT_OFFSET); + let menu: LinkMenu = new LinkMenu(point, true, link); + return menu; + }; + + /** + * Returns relation source and target nodes. + * @param nodes - all nodes in graph in order to find the edge connecting the two nodes + * @param fromNodeId + * @param toNodeId + * @returns [source, target] array of source node and target node. + */ + public getRelationNodes(nodes: Cy.CollectionNodes, fromNodeId: string, toNodeId: string) { + return [ + _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === fromNodeId), + _.find(nodes, (node: Cy.CollectionFirst) => node.data().id === toNodeId) + ]; + } + + + /** + * go over the relations and draw links on the graph + * @param cy + * @param getRelationRequirementCapability - function to get requirement and capability of a relation + */ + public initGraphLinks(cy: Cy.Instance, relations: RelationshipModel[]) { + if (relations) { + _.forEach(relations, (relationshipModel: RelationshipModel) => { + _.forEach(relationshipModel.relationships, (relationship: Relationship) => { + let linkToCreate = this.linksFactory.createGraphLink(cy, relationshipModel, relationship); + this.insertLinkToGraph(cy, linkToCreate); + }); + }); + } + } + + /** + * Add link to graph - only draw the link + * @param cy + * @param link + * @param getRelationRequirementCapability + */ + public insertLinkToGraph = (cy: Cy.Instance, link: CompositionCiLinkBase) => { + const relationNodes = this.getRelationNodes(cy.nodes(), link.source, link.target); + const sourceNode: CompositionCiNodeBase = relationNodes[0] && relationNodes[0].data(); + const targetNode: CompositionCiNodeBase = relationNodes[1] && relationNodes[1].data(); + if ((sourceNode && !sourceNode.certified) || (targetNode && !targetNode.certified)) { + link.classes = 'not-certified-link'; + } + let linkElement = cy.add({ + group: 'edges', + data: link, + classes: link.classes + }); + + const getLinkRequirementCapability = () => + this.getRelationRequirementCapability(link.relation.relationships[0], sourceNode.componentInstance, targetNode.componentInstance); + this.commonGraphUtils.initLinkTooltip(linkElement, link.relation.relationships[0], getLinkRequirementCapability); + }; + + public syncComponentByRelation(relation: RelationshipModel) { + let componentInstances = this.compositionService.getComponentInstances(); + relation.relationships.forEach((rel) => { + if (rel.capability) { + const toComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.toNode); + const toComponentInstanceCapability: Capability = toComponentInstance.findCapability( + rel.capability.type, rel.capability.uniqueId, rel.capability.ownerId, rel.capability.name); + const isCapabilityFulfilled: boolean = rel.capability.isFulfilled(); + if (isCapabilityFulfilled && toComponentInstanceCapability) { + // if capability is fulfilled and in component, then remove it + console.log('Capability is fulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences); + toComponentInstance.capabilities[rel.capability.type].splice( + toComponentInstance.capabilities[rel.capability.type].findIndex((cap) => cap === toComponentInstanceCapability), 1 + ) + } else if (!isCapabilityFulfilled && !toComponentInstanceCapability) { + // if capability is unfulfilled and not in component, then add it + console.log('Capability is unfulfilled', rel.capability.getFullTitle(), rel.capability.leftOccurrences); + toComponentInstance.capabilities[rel.capability.type].push(rel.capability); + } + } + if (rel.requirement) { + const fromComponentInstance: ComponentInstance = componentInstances.find((inst) => inst.uniqueId === relation.fromNode); + const fromComponentInstanceRequirement: Requirement = fromComponentInstance.findRequirement( + rel.requirement.capability, rel.requirement.uniqueId, rel.requirement.ownerId, rel.requirement.name); + const isRequirementFulfilled: boolean = rel.requirement.isFulfilled(); + if (isRequirementFulfilled && fromComponentInstanceRequirement) { + // if requirement is fulfilled and in component, then remove it + console.log('Requirement is fulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences); + fromComponentInstance.requirements[rel.requirement.capability].splice( + fromComponentInstance.requirements[rel.requirement.capability].findIndex((req) => req === fromComponentInstanceRequirement), 1 + ) + } else if (!isRequirementFulfilled && !fromComponentInstanceRequirement) { + // if requirement is unfulfilled and not in component, then add it + console.log('Requirement is unfulfilled', rel.requirement.getFullTitle(), rel.requirement.leftOccurrences); + fromComponentInstance.requirements[rel.requirement.capability].push(rel.requirement); + } + } + }); + } + + public getRelationRequirementCapability(relationship: Relationship, sourceNode: ComponentInstance, targetNode: ComponentInstance): Promise<{ requirement: Requirement, capability: Capability }> { + // try find the requirement and capability in the source and target component instances: + let capability: Capability = targetNode.findCapability(undefined, + relationship.relation.capabilityUid, + relationship.relation.capabilityOwnerId, + relationship.relation.capability); + let requirement: Requirement = sourceNode.findRequirement(undefined, + relationship.relation.requirementUid, + relationship.relation.requirementOwnerId, + relationship.relation.requirement); + + return new Promise<{ requirement: Requirement, capability: Capability }>((resolve, reject) => { + if (capability && requirement) { + resolve({capability, requirement}); + } + else { + // if requirement and/or capability is missing, then fetch the full relation with its requirement and capability: + this.topologyTemplateService.fetchRelation(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, relationship.relation.id).subscribe((fetchedRelation) => { + this.syncComponentByRelation(fetchedRelation); + resolve({ + capability: capability || fetchedRelation.relationships[0].capability, + requirement: requirement || fetchedRelation.relationships[0].requirement + }); + }, reject); + } + }); + } +} + diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts new file mode 100644 index 0000000000..9dcc47f7cc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.spec.ts @@ -0,0 +1,158 @@ +import { TestBed } from '@angular/core/testing'; +import { SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/Rx'; +import CollectionNodes = Cy.CollectionNodes; +import { Mock } from 'ts-mockery'; +import { ComponentInstance } from '../../../../../models'; +import { ComponentMetadata } from '../../../../../models/component-metadata'; +import { Resource } from '../../../../../models/components/resource'; +import { CompositionCiNodeCp } from '../../../../../models/graph/nodes/composition-graph-nodes/composition-ci-node-cp'; +import { CompositionCiNodeVl } from '../../../../../models/graph/nodes/composition-graph-nodes/composition-ci-node-vl'; +import { EventListenerService } from '../../../../../services'; +import CollectionEdges = Cy.CollectionEdges; +import { GRAPH_EVENTS } from '../../../../../utils/constants'; +import { ServiceServiceNg2 } from '../../../../services/component-services/service.service'; +import { TopologyTemplateService } from '../../../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../../../services/responses/component-generic-response'; +import { QueueServiceUtils } from '../../../../utils/queue-service-utils'; +import { WorkspaceService } from '../../../workspace/workspace.service'; +import { CompositionService } from '../../composition.service'; +import { CommonGraphUtils } from '../common/common-graph-utils'; +import { CompositionGraphGeneralUtils } from './composition-graph-general-utils'; +import { CompositionGraphNodesUtils } from './composition-graph-nodes-utils'; + +describe('composition graph nodes utils', () => { + + const CP_TO_DELETE_ID = 'cp1'; + const VL_TO_DELETE_ID = 'vl'; + const CP2_ID = 'cp2'; + + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let service: CompositionGraphNodesUtils; + let topologyServiceMock: TopologyTemplateService; + let queueServiceMock: QueueServiceUtils; + let workspaceServiceMock: WorkspaceService; + let compositionServiceMock: CompositionService; + let eventListenerServiceMock: EventListenerService; + const cpInstanceMock: ComponentInstance = Mock.of<ComponentInstance>({ + uniqueId: CP_TO_DELETE_ID, + isVl: () => false + }); + const vlInstanceMock: ComponentInstance = Mock.of<ComponentInstance>({ + uniqueId: VL_TO_DELETE_ID, + isVl: () => true + }); + const cp2InstanceMock: ComponentInstance = Mock.of<ComponentInstance>({ + uniqueId: CP2_ID, + isVl: () => false + }); + + const cyMock = Mock.of<Cy.Instance>({ + remove: jest.fn(), + collection: jest.fn() + }); + + const serviceServiceMock = Mock.of<ServiceServiceNg2>({ + getComponentCompositionData : () => Observable.of(Mock.of<ComponentGenericResponse>()) + }); + + // Instances on the graph cp, vl, cp2 + const cp = Mock.from<CompositionCiNodeCp>({ id: CP_TO_DELETE_ID, componentInstance: cpInstanceMock }); + const vl = Mock.from<CompositionCiNodeVl>({ id: VL_TO_DELETE_ID, componentInstance: vlInstanceMock }); + const cp2 = Mock.from<CompositionCiNodeCp>({ id: CP2_ID, componentInstance: cp2InstanceMock }); + + beforeEach(() => { + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + + topologyServiceMock = Mock.of<TopologyTemplateService>({ + deleteComponentInstance : () => Observable.of(cpInstanceMock) + }); + + queueServiceMock = Mock.of<QueueServiceUtils>({ + addBlockingUIAction : ( (f) => f() ) + }); + + workspaceServiceMock = Mock.of<WorkspaceService>({ + metadata: Mock.of<ComponentMetadata>( { uniqueId: 'topologyTemplateUniqueId' } ) + }); + + compositionServiceMock = Mock.of<CompositionService>({ + deleteComponentInstance : jest.fn() + }); + + eventListenerServiceMock = Mock.of<EventListenerService>({ + notifyObservers : jest.fn() + }); + + TestBed.configureTestingModule({ + imports: [], + providers: [ + CompositionGraphNodesUtils, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: TopologyTemplateService, useValue: topologyServiceMock}, + {provide: CompositionService, useValue: compositionServiceMock}, + {provide: CompositionGraphGeneralUtils, useValue: {}}, + {provide: CommonGraphUtils, useValue: {}}, + {provide: EventListenerService, useValue: eventListenerServiceMock}, + {provide: QueueServiceUtils, useValue: queueServiceMock}, + {provide: ServiceServiceNg2, useValue: serviceServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock} + ] + }); + service = TestBed.get(CompositionGraphNodesUtils); + }); + + it('When a CP is deleted which is connected to a VL that has another leg to another CP, the VL is deleted as well', () => { + // Prepare a VL that is connected to both CP and CP2 + const vlToDelete = Mock.of<CollectionNodes>({ + data: () => vl, + connectedEdges: () => Mock.of<CollectionEdges>({ + length: 2, + connectedNodes: () => [cp, cp2] as CollectionNodes + }) + }); + + // Prepare a CP which is connected to a VL + const cpToDelete = Mock.of<CollectionNodes>({ + data: () => cp, + connectedEdges: () => Mock.of<CollectionEdges>({ + length: 1, + connectedNodes: () => [vlToDelete] as CollectionNodes + }) + }); + service.deleteNode(cyMock, Mock.of<Resource>(), cpToDelete); + expect(compositionServiceMock.deleteComponentInstance).toHaveBeenCalledWith(CP_TO_DELETE_ID); + expect(eventListenerServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, VL_TO_DELETE_ID); + expect(eventListenerServiceMock.notifyObservers).toHaveBeenLastCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, CP_TO_DELETE_ID); + expect(cyMock.remove).toHaveBeenCalled(); + }); + + it('When a CP is deleted which is solely connected to another VL the VL is not deleted', () => { + // Prepare a VL that is connected only to 1 CP + const vlToDelete = Mock.of<CollectionNodes>({ + data: () => vl, + connectedEdges: () => Mock.of<CollectionEdges>({ + length: 1, + connectedNodes: () => [cp] as CollectionNodes + }) + }); + + // Prepare a CP which is connected to a VL + const cpToDelete = Mock.of<CollectionNodes>({ + data: () => cp, + connectedEdges: () => Mock.of<CollectionEdges>({ + length: 1, + connectedNodes: () => [vlToDelete] as CollectionNodes + }) + }); + service.deleteNode(cyMock, Mock.of<Resource>(), cpToDelete); + expect(compositionServiceMock.deleteComponentInstance).toHaveBeenCalledWith(CP_TO_DELETE_ID); + expect(eventListenerServiceMock.notifyObservers).toHaveBeenLastCalledWith(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, CP_TO_DELETE_ID); + expect(eventListenerServiceMock.notifyObservers).toHaveBeenCalledTimes(1); + expect(cyMock.remove).toHaveBeenCalled(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts new file mode 100644 index 0000000000..ea876c6d1a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-nodes-utils.ts @@ -0,0 +1,202 @@ +/*- + * ============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 { Injectable } from '@angular/core'; +import { Component as TopologyTemplate } from 'app/models'; +import { + ComponentInstance, + CompositionCiNodeVl, Service +} from 'app/models'; +import { CompositionCiServicePathLink } from 'app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import { ServiceServiceNg2 } from 'app/ng2/services/component-services/service.service'; +import { TopologyTemplateService } from 'app/ng2/services/component-services/topology-template.service'; +import { ServiceGenericResponse } from 'app/ng2/services/responses/service-generic-response'; +import { QueueServiceUtils } from 'app/ng2/utils/queue-service-utils'; +import { EventListenerService } from 'app/services'; +import { GRAPH_EVENTS } from 'app/utils'; +import * as _ from 'lodash'; +import { SdcUiServices } from 'onap-ui-angular'; +import { CompositionService } from '../../composition.service'; +import { CommonGraphUtils } from '../common/common-graph-utils'; +import { CompositionGraphGeneralUtils } from './composition-graph-general-utils'; + +/** + * Created by obarda on 11/9/2016. + */ +@Injectable() +export class CompositionGraphNodesUtils { + constructor(private generalGraphUtils: CompositionGraphGeneralUtils, + private commonGraphUtils: CommonGraphUtils, + private eventListenerService: EventListenerService, + private queueServiceUtils: QueueServiceUtils, + private serviceService: ServiceServiceNg2, + private loaderService: SdcUiServices.LoaderService, + private compositionService: CompositionService, + private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService) { + } + + /** + * Returns component instances for all nodes passed in + * @param nodes - Cy nodes + * @returns {any[]} + */ + public getAllNodesData(nodes: Cy.CollectionNodes) { + return _.map(nodes, (node: Cy.CollectionFirstNode) => { + return node.data(); + }); + } + + public highlightMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string) => { + + cy.batch(() => { + cy.nodes("[name !@^= '" + nameToMatch + "']").style({'background-image-opacity': 0.4}); + cy.nodes("[name @^= '" + nameToMatch + "']").style({'background-image-opacity': 1}); + }); + + } + + // Returns all nodes whose name starts with searchTerm + public getMatchingNodesByName = (cy: Cy.Instance, nameToMatch: string): Cy.CollectionNodes => { + return cy.nodes("[name @^= '" + nameToMatch + "']"); + } + + /** + * Deletes component instances on server and then removes it from the graph as well + * @param cy + * @param component + * @param nodeToDelete + */ + public deleteNode(cy: Cy.Instance, component: TopologyTemplate, nodeToDelete: Cy.CollectionNodes): void { + + this.loaderService.activate(); + const onSuccess: (response: ComponentInstance) => void = (response: ComponentInstance) => { + // check whether the node is connected to any VLs that only have one other connection. If so, delete that VL as well + this.loaderService.deactivate(); + this.compositionService.deleteComponentInstance(response.uniqueId); + + const nodeToDeleteIsNotVl = nodeToDelete.data().componentInstance && !(nodeToDelete.data().componentInstance.isVl()); + if (nodeToDeleteIsNotVl) { + const connectedVls: Cy.CollectionFirstNode[] = this.getConnectedVlToNode(nodeToDelete); + this.handleConnectedVlsToDelete(connectedVls); + } + + // check whether there is a service path going through this node, and if so clean it from the graph. + const nodeId = nodeToDelete.data().id; + const connectedPathLinks = cy.collection(`[type="${CompositionCiServicePathLink.LINK_TYPE}"][source="${nodeId}"], [type="${CompositionCiServicePathLink.LINK_TYPE}"][target="${nodeId}"]`); + _.forEach(connectedPathLinks, (link, key) => { + cy.remove(`[pathId="${link.data().pathId}"]`); + }); + + // update service path list + this.serviceService.getComponentCompositionData(component).subscribe((serviceResponse: ServiceGenericResponse) => { + (component as Service).forwardingPaths = serviceResponse.forwardingPaths; + }); + + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE_SUCCESS, nodeId); + + // update UI + cy.remove(nodeToDelete); + }; + + const onFailed: (response: any) => void = (response: any) => { + this.loaderService.deactivate(); + }; + + this.queueServiceUtils.addBlockingUIAction( + () => { + const uniqueId = this.workspaceService.metadata.uniqueId; + const componentType = this.workspaceService.metadata.componentType; + this.topologyTemplateService.deleteComponentInstance(componentType, uniqueId, nodeToDelete.data().componentInstance.uniqueId).subscribe(onSuccess, onFailed); + } + ); + } + + /** + * Finds all VLs connected to a single node + * @param node + * @returns {Array<Cy.CollectionFirstNode>} + */ + public getConnectedVlToNode = (node: Cy.CollectionNodes): Cy.CollectionFirstNode[] => { + const connectedVls: Cy.CollectionFirstNode[] = new Array<Cy.CollectionFirstNode>(); + _.forEach(node.connectedEdges().connectedNodes(), (connectedNode: Cy.CollectionFirstNode) => { + const connectedNodeIsVl = connectedNode.data().componentInstance.isVl(); + if (connectedNodeIsVl) { + connectedVls.push(connectedNode); + } + }); + return connectedVls; + } + + /** + * Delete all VLs that have only two connected nodes (this function is called when deleting a node) + * @param connectedVls + */ + public handleConnectedVlsToDelete = (connectedVls: Cy.CollectionFirstNode[]) => { + _.forEach(connectedVls, (vlToDelete: Cy.CollectionNodes) => { + + if (vlToDelete.connectedEdges().length === 2) { // if vl connected only to 2 nodes need to delete the vl + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_DELETE_COMPONENT_INSTANCE, vlToDelete.data().componentInstance.uniqueId); + } + }); + } + + /** + * This function will update nodes position. + * @param cy + * @param component + * @param nodesMoved - the node/multiple nodes now moved by the user + */ + public onNodesPositionChanged = (cy: Cy.Instance, component: TopologyTemplate, nodesMoved: Cy.CollectionNodes): void => { + + if (nodesMoved.length === 0) { + return; + } + + const isValidMove: boolean = this.generalGraphUtils.isGroupValidDrop(cy, nodesMoved); + if (isValidMove) { + + const instancesToUpdate: ComponentInstance[] = new Array<ComponentInstance>(); + + _.each(nodesMoved, (node: Cy.CollectionFirstNode) => { // update all nodes new position + + // update position + const newPosition: Cy.Position = this.commonGraphUtils.getNodePosition(node); + node.data().componentInstance.updatePosition(newPosition.x, newPosition.y); + instancesToUpdate.push(node.data().componentInstance); + + }); + + if (instancesToUpdate.length > 0) { + this.generalGraphUtils.pushMultipleUpdateComponentInstancesRequestToQueue(instancesToUpdate); + } + } else { + // reset nodes position + nodesMoved.positions((i, node) => { + return { + x: +node.data().componentInstance.posX, + y: +node.data().componentInstance.posY + }; + }); + } + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts new file mode 100644 index 0000000000..1776c2f9b9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-palette-utils.ts @@ -0,0 +1,233 @@ +/*- + * ============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 {Injectable} from "@angular/core"; +import {CompositionGraphGeneralUtils, RequirementAndCapabilities} from "./composition-graph-general-utils"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {EventListenerService} from "../../../../../services/event-listener-service"; +import {ResourceNamePipe} from "app/ng2/pipes/resource-name.pipe"; +import {ComponentInstanceFactory} from "app/utils/component-instance-factory"; +import {GRAPH_EVENTS, GraphUIObjects} from "app/utils/constants"; +import {TopologyTemplateService} from "app/ng2/services/component-services/topology-template.service"; +import {DndDropEvent} from "ngx-drag-drop/ngx-drag-drop"; +import {SdcUiServices} from "onap-ui-angular" +import { Component as TopologyTemplate, NodesFactory, CapabilitiesGroup, RequirementsGroup, + CompositionCiNodeBase, ComponentInstance, LeftPaletteComponent, Point } from "app/models"; +import {CompositionService} from "../../composition.service"; +import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; +import { QueueServiceUtils } from "app/ng2/utils/queue-service-utils"; +import {ComponentGenericResponse} from "../../../../services/responses/component-generic-response"; +import {MatchCapabilitiesRequirementsUtils} from "./match-capability-requierment-utils"; +import {CompositionGraphNodesUtils} from "./index"; + +@Injectable() +export class CompositionGraphPaletteUtils { + + constructor(private generalGraphUtils:CompositionGraphGeneralUtils, + private nodesFactory:NodesFactory, + private commonGraphUtils:CommonGraphUtils, + private queueServiceUtils:QueueServiceUtils, + private eventListenerService:EventListenerService, + private topologyTemplateService: TopologyTemplateService, + private loaderService: SdcUiServices.LoaderService, + private compositionService: CompositionService, + private workspaceService: WorkspaceService, + private matchCapabilitiesRequirementsUtils: MatchCapabilitiesRequirementsUtils, + private nodesGraphUtils: CompositionGraphNodesUtils) { + } + + /** + * + * @param Calculate matching nodes, highlight the matching nodes and fade the non matching nodes + * @param leftPaletteComponent + * @param _cy + * @returns void + * @private + */ + + public onComponentHoverIn = (leftPaletteComponent: LeftPaletteComponent, _cy: Cy.Instance) => { + const nodesData = this.nodesGraphUtils.getAllNodesData(_cy.nodes()); + const nodesLinks = this.generalGraphUtils.getAllCompositionCiLinks(_cy); + + if (this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(leftPaletteComponent.uniqueId)) { + const reqAndCap: RequirementAndCapabilities = this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(leftPaletteComponent.uniqueId); + const filteredNodesData = this.matchCapabilitiesRequirementsUtils.findMatchingNodesToComponentInstance( + { uniqueId: leftPaletteComponent.uniqueId, requirements: reqAndCap.requirements, capabilities: reqAndCap.capabilities} as ComponentInstance, nodesData, nodesLinks); + + this.matchCapabilitiesRequirementsUtils.highlightMatchingComponents(filteredNodesData, _cy); + this.matchCapabilitiesRequirementsUtils.fadeNonMachingComponents(filteredNodesData, nodesData, _cy); + } else { + + this.topologyTemplateService.getCapabilitiesAndRequirements(leftPaletteComponent.componentType, leftPaletteComponent.uniqueId).subscribe((response: ComponentGenericResponse) => { + let reqAndCap: RequirementAndCapabilities = { + capabilities: response.capabilities, + requirements: response.requirements + } + this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.setValue(leftPaletteComponent.uniqueId, reqAndCap); + }); + } + } + + /** + * Calculate the dragged element (html element) position on canvas + * @param cy + * @param event + * @param position + * @returns {Cy.BoundingBox} + * @private + */ + private _getNodeBBox(cy:Cy.Instance, event:DragEvent, position?:Cy.Position, eventPosition?: Point) { + let bbox = <Cy.BoundingBox>{}; + if (!position) { + position = event ? this.commonGraphUtils.getCytoscapeNodePosition(cy, event) : eventPosition; + } + let cushionWidth:number = 40; + let cushionHeight:number = 40; + + bbox.x1 = position.x - cushionWidth / 2; + bbox.y1 = position.y - cushionHeight / 2; + bbox.x2 = position.x + cushionWidth / 2; + bbox.y2 = position.y + cushionHeight / 2; + return bbox; + } + + /** + * Create the component instance, update data from parent component in the left palette and notify on_insert_to_ucpe if component was dragg into ucpe + * @param cy + * @param fullComponent + * @param event + * @param component + */ + private _createComponentInstanceOnGraphFromPaletteComponent(cy:Cy.Instance, fullComponent:LeftPaletteComponent, event:DragEvent) { + + let componentInstanceToCreate:ComponentInstance = ComponentInstanceFactory.createComponentInstanceFromComponent(fullComponent); + let cytoscapePosition:Cy.Position = this.commonGraphUtils.getCytoscapeNodePosition(cy, event); + componentInstanceToCreate.posX = cytoscapePosition.x; + componentInstanceToCreate.posY = cytoscapePosition.y; + + let onFailedCreatingInstance:(error:any) => void = (error:any) => { + this.loaderService.deactivate(); + }; + + //on success - update node data + let onSuccessCreatingInstance = (createInstance:ComponentInstance):void => { + + this.loaderService.deactivate(); + this.compositionService.addComponentInstance(createInstance); + createInstance.name = ResourceNamePipe.getDisplayName(createInstance.name); + createInstance.requirements = new RequirementsGroup(createInstance.requirements); + createInstance.capabilities = new CapabilitiesGroup(createInstance.capabilities); + createInstance.componentVersion = fullComponent.version; + createInstance.icon = fullComponent.icon; + createInstance.setInstanceRC(); + + let newNode:CompositionCiNodeBase = this.nodesFactory.createNode(createInstance); + this.commonGraphUtils.addComponentInstanceNodeToGraph(cy, newNode); + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_CREATE_COMPONENT_INSTANCE); + }; + + this.queueServiceUtils.addBlockingUIAction(() => { + let uniqueId = this.workspaceService.metadata.uniqueId; + let componentType = this.workspaceService.metadata.componentType; + this.topologyTemplateService.createComponentInstance(componentType, uniqueId, componentInstanceToCreate).subscribe(onSuccessCreatingInstance, onFailedCreatingInstance); + + }); + } + // + // /** + // * Thid function applay red/green background when component dragged from palette + // * @param cy + // * @param event + // * @param dragElement + // * @param dragComponent + // */ + // public onComponentDrag(cy:Cy.Instance, event) { + // let draggedElement = document.getElementById("draggable_element"); + // // event.dataTransfer.setDragImage(draggableElement, 0, 0); + // if (event.clientX < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || event.clientY < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop + // draggedElement.className = 'invalid-drag'; + // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0); + // return; + // } + // + // let offsetPosition = { + // x: event.clientX - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET, + // y: event.clientY - GraphUIObjects.DIAGRAM_HEADER_OFFSET + // }; + // let bbox = this._getNodeBBox(cy, event, offsetPosition); + // + // if (this.generalGraphUtils.isPaletteDropValid(cy, bbox)) { + // draggedElement.className = 'valid-drag'; + // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0); + // // event.dataTransfer.setDragImage(draggedElement, 0, 0); + // // event.dataTransfer.setDragImage(draggedElement, 0, 0); + // + // } else { + // draggedElement.className = 'invalid-drag'; + // event.dataTransfer.setDragImage(draggedElement.cloneNode(true), 0, 0); + // } + // } + + public isDragValid(cy:Cy.Instance, position: Point):boolean { + if (position.x < GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET || position.y < GraphUIObjects.DIAGRAM_HEADER_OFFSET) { //hovering over palette. Dont bother computing validity of drop + return false; + } + + let offsetPosition = { + x: position.x - GraphUIObjects.DIAGRAM_PALETTE_WIDTH_OFFSET, + y: position.y - GraphUIObjects.DIAGRAM_HEADER_OFFSET + }; + let bbox = this._getNodeBBox(cy, null, offsetPosition, position); + + if (this.generalGraphUtils.isPaletteDropValid(cy, bbox)) { + return true; + } else { + return false; + } + } + /** + * This function is called when after dropping node on canvas + * Check if the capability & requirements fulfilled and if not get from server + * @param cy + * @param dragEvent + * @param component + */ + public addNodeFromPalette(cy:Cy.Instance, dragEvent:DndDropEvent) { + this.loaderService.activate(); + + let draggedComponent:LeftPaletteComponent = dragEvent.data; + + if (this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.containsKey(draggedComponent.uniqueId)) { + let fullComponent = this.generalGraphUtils.componentRequirementsAndCapabilitiesCaching.getValue(draggedComponent.uniqueId); + draggedComponent.capabilities = fullComponent.capabilities; + draggedComponent.requirements = fullComponent.requirements; + this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, dragEvent.event); + + } else { + + this.topologyTemplateService.getFullComponent(draggedComponent.componentType, draggedComponent.uniqueId).subscribe((topologyTemplate:TopologyTemplate) => { + draggedComponent.capabilities = topologyTemplate.capabilities; + draggedComponent.requirements = topologyTemplate.requirements; + this._createComponentInstanceOnGraphFromPaletteComponent(cy, draggedComponent, dragEvent.event); + }); + } + } +} + diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts new file mode 100644 index 0000000000..bc124fe9d1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-service-path-utils.ts @@ -0,0 +1,148 @@ +/*- + * ============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 {CompositionGraphGeneralUtils} from "./composition-graph-general-utils"; +import {ServiceServiceNg2} from 'app/ng2/services/component-services/service.service'; +import {Service} from "app/models/components/service"; +import {ForwardingPath} from "app/models/forwarding-path"; +import {ForwardingPathLink} from "app/models/forwarding-path-link"; +import {ComponentRef, Injectable} from "@angular/core"; +import {CompositionCiServicePathLink} from "app/models/graph/graph-links/composition-graph-links/composition-ci-service-path-link"; +import {SdcUiServices} from "onap-ui-angular"; +import {QueueServiceUtils} from "app/ng2/utils/queue-service-utils"; +import {ServicePathsListComponent} from "app/ng2/pages/composition/graph/service-paths-list/service-paths-list.component"; +import {ButtonModel, ModalModel} from "app/models"; +import {ServicePathCreatorComponent} from "app/ng2/pages/composition/graph/service-path-creator/service-path-creator.component"; +import {ModalService} from "app/ng2/services/modal.service"; +import {ModalComponent} from "app/ng2/components/ui/modal/modal.component"; +import {Select, Store} from "@ngxs/store"; +import {WorkspaceState} from "app/ng2/store/states/workspace.state"; +import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; +import {CompositionService} from "../../composition.service"; +import {CommonGraphUtils} from "../common/common-graph-utils"; +import {GRAPH_EVENTS} from "app/utils/constants"; +import {EventListenerService} from "app/services/event-listener-service"; + +@Injectable() +export class ServicePathGraphUtils { + + constructor( + private generalGraphUtils: CompositionGraphGeneralUtils, + private serviceService: ServiceServiceNg2, + private commonGraphUtils: CommonGraphUtils, + private loaderService: SdcUiServices.LoaderService, + private queueServiceUtils: QueueServiceUtils, + private modalService: ModalService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService, + private store:Store, + private eventListenerService: EventListenerService + ) { + } + + private isViewOnly = (): boolean => { + return this.store.selectSnapshot(state => state.workspace.isViewOnly); + } + private modalInstance: ComponentRef<ModalComponent>; + + public deletePathsFromGraph(cy: Cy.Instance) { + cy.remove(`[type="${CompositionCiServicePathLink.LINK_TYPE}"]`); + } + + public drawPath(cy: Cy.Instance, forwardingPath: ForwardingPath) { + let pathElements = forwardingPath.pathElements.listToscaDataDefinition; + + _.forEach(pathElements, (link: ForwardingPathLink) => { + let data: CompositionCiServicePathLink = new CompositionCiServicePathLink(link); + data.source = _.find( + this.compositionService.componentInstances, + instance => instance.name === data.forwardingPathLink.fromNode + ).uniqueId; + data.target = _.find( + this.compositionService.componentInstances, + instance => instance.name === data.forwardingPathLink.toNode + ).uniqueId; + data.pathId = forwardingPath.uniqueId; + data.pathName = forwardingPath.name; + this.commonGraphUtils.insertServicePathLinkToGraph(cy, data); + }); + } + + public createOrUpdateServicePath = (path: any): void => { + this.loaderService.activate(); + + let onSuccess: (response: ForwardingPath) => void = (response: ForwardingPath) => { + this.loaderService.deactivate(); + this.compositionService.forwardingPaths[response.uniqueId] = response; + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_SERVICE_PATH_CREATED, response.uniqueId) + }; + + this.queueServiceUtils.addBlockingUIAction( + () => this.serviceService.createOrUpdateServicePath(this.workspaceService.metadata.uniqueId, path).subscribe(onSuccess + , (error) => {this.loaderService.deactivate()}) + ); + }; + + public onCreateServicePath = (): void => { + // this.showServicePathMenu = false; + let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.modalService.closeCurrentModal); + let saveButton: ButtonModel = new ButtonModel('Create', 'blue', this.createPath, this.getDisabled); + let modalModel: ModalModel = new ModalModel('l', 'Create Service Flow', '', [saveButton, cancelButton], 'standard', true); + this.modalInstance = this.modalService.createCustomModal(modalModel); + this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, {serviceId: this.workspaceService.metadata.uniqueId}); + this.modalInstance.instance.open(); + }; + + public onListServicePath = (): void => { + // this.showServicePathMenu = false; + let cancelButton: ButtonModel = new ButtonModel('Close', 'outline white', this.modalService.closeCurrentModal); + let modalModel: ModalModel = new ModalModel('md', 'Service Flows List', '', [cancelButton], 'standard', true); + this.modalInstance = this.modalService.createCustomModal(modalModel); + this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathsListComponent, { + serviceId: this.workspaceService.metadata.uniqueId, + onCreateServicePath: this.onCreateServicePath, + onEditServicePath: this.onEditServicePath, + isViewOnly: this.isViewOnly() + }); + this.modalInstance.instance.open(); + }; + + public onEditServicePath = (id: string): void => { + let cancelButton: ButtonModel = new ButtonModel('Cancel', 'outline white', this.modalService.closeCurrentModal); + let saveButton: ButtonModel = new ButtonModel('Save', 'blue', this.createPath, this.getDisabled); + let modalModel: ModalModel = new ModalModel('l', 'Edit Path', '', [saveButton, cancelButton], 'standard', true); + this.modalInstance = this.modalService.createCustomModal(modalModel); + this.modalService.addDynamicContentToModal(this.modalInstance, ServicePathCreatorComponent, { + serviceId: this.workspaceService.metadata.uniqueId, + pathId: id + }); + this.modalInstance.instance.open(); + }; + + public getDisabled = (): boolean => { + return this.isViewOnly() || !this.modalInstance.instance.dynamicContent.instance.checkFormValidForSubmit(); + }; + + public createPath = (): void => { + this.createOrUpdateServicePath(this.modalInstance.instance.dynamicContent.instance.createServicePathData()); + this.modalService.closeCurrentModal(); + }; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts new file mode 100644 index 0000000000..9e97ec0f00 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/composition-graph-zone-utils.ts @@ -0,0 +1,204 @@ +import { + Point, + PolicyInstance, + Zone, + LeftPaletteMetadataTypes, + ZoneInstance, + ZoneInstanceType, + ZoneInstanceAssignmentType +} from "app/models"; +import {CanvasHandleTypes} from "app/utils"; +import {Observable} from "rxjs"; +import {GroupInstance} from "app/models/graph/zones/group-instance"; +import {Injectable} from "@angular/core"; +import {DynamicComponentService} from "app/ng2/services/dynamic-component.service"; +import {PoliciesService} from "app/ng2/services/policies.service"; +import {GroupsService} from "app/ng2/services/groups.service"; +import {Store} from "@ngxs/store"; +import {CompositionService} from "../../composition.service"; +import {WorkspaceService} from "app/ng2/pages/workspace/workspace.service"; +import { PaletteAnimationComponent } from "app/ng2/pages/composition/palette/palette-animation/palette-animation.component"; + +@Injectable() +export class CompositionGraphZoneUtils { + + constructor(private dynamicComponentService: DynamicComponentService, + private policiesService: PoliciesService, + private groupsService: GroupsService, + private workspaceService: WorkspaceService, + private compositionService: CompositionService) { + } + + + public createCompositionZones = (): Array<Zone> => { + let zones: Array<Zone> = []; + + zones[ZoneInstanceType.POLICY] = new Zone('Policies', 'P', ZoneInstanceType.POLICY); + zones[ZoneInstanceType.GROUP] = new Zone('Groups', 'G', ZoneInstanceType.GROUP); + + return zones; + } + + public showZone = (zone: Zone): void => { + zone.visible = true; + zone.minimized = false; + } + + public getZoneTypeForPaletteComponent = (componentCategory: LeftPaletteMetadataTypes) => { + if (componentCategory == LeftPaletteMetadataTypes.Group) { + return ZoneInstanceType.GROUP; + } else if (componentCategory == LeftPaletteMetadataTypes.Policy) { + return ZoneInstanceType.POLICY; + } + }; + + public initZoneInstances(zones: Array<Zone>) { + + if (this.compositionService.groupInstances && this.compositionService.groupInstances.length) { + this.showZone(zones[ZoneInstanceType.GROUP]); + zones[ZoneInstanceType.GROUP].instances = []; + _.forEach(this.compositionService.groupInstances, (group: GroupInstance) => { + let newInstance = new ZoneInstance(group, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId); + this.addInstanceToZone(zones[ZoneInstanceType.GROUP], newInstance); + }); + } + + if (this.compositionService.policies && this.compositionService.policies.length) { + this.showZone(zones[ZoneInstanceType.POLICY]); + zones[ZoneInstanceType.POLICY].instances = []; + _.forEach(this.compositionService.policies, (policy: PolicyInstance) => { + let newInstance = new ZoneInstance(policy, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId); + this.addInstanceToZone(zones[ZoneInstanceType.POLICY], newInstance); + + }); + } + } + + public findAndUpdateZoneInstanceData(zones: Array<Zone>, instanceData: PolicyInstance | GroupInstance) { + _.forEach(zones, (zone: Zone) => { + _.forEach(zone.instances, (zoneInstance: ZoneInstance) => { + if (zoneInstance.instanceData.uniqueId === instanceData.uniqueId) { + zoneInstance.updateInstanceData(instanceData); + } + }); + }); + } + + public updateTargetsOrMembersOnCanvasDelete = (canvasNodeID: string, zones: Array<Zone>, type: ZoneInstanceAssignmentType): void => { + _.forEach(zones, (zone) => { + _.forEach(zone.instances, (zoneInstance: ZoneInstance) => { + if (zoneInstance.isAlreadyAssigned(canvasNodeID)) { + zoneInstance.addOrRemoveAssignment(canvasNodeID, type); + //remove it from our list of BE targets and members as well (so that it will not be sent in future calls to BE). + zoneInstance.instanceData.setSavedAssignments(zoneInstance.assignments); + } + }); + }); + }; + + public createZoneInstanceFromLeftPalette = (zoneType: ZoneInstanceType, paletteComponentType: string): Observable<ZoneInstance> => { + + if (zoneType === ZoneInstanceType.POLICY) { + return this.policiesService.createPolicyInstance(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, paletteComponentType).map(response => { + let newInstance = new PolicyInstance(response); + this.compositionService.addPolicyInstance(newInstance); + return new ZoneInstance(newInstance, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId); + }); + } else if (zoneType === ZoneInstanceType.GROUP) { + return this.groupsService.createGroupInstance(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId, paletteComponentType).map(response => { + let newInstance = new GroupInstance(response); + this.compositionService.addGroupInstance(newInstance); + return new ZoneInstance(newInstance, this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId); + }); + } + } + + public addInstanceToZone(zone: Zone, instance: ZoneInstance, hide?: boolean) { + if (hide) { + instance.hidden = true; + } + zone.instances.push(instance); + + }; + + private findZoneCoordinates(zoneType): Point { + let point: Point = new Point(0, 0); + let zone = angular.element(document.querySelector('.' + zoneType + '-zone')); + let wrapperZone = zone.offsetParent(); + point.x = zone.prop('offsetLeft') + wrapperZone.prop('offsetLeft'); + point.y = zone.prop('offsetTop') + wrapperZone.prop('offsetTop'); + return point; + } + + public createPaletteToZoneAnimation = (startPoint: Point, zoneType: ZoneInstanceType, newInstance: ZoneInstance) => { + let zoneTypeName = ZoneInstanceType[zoneType].toLowerCase(); + let paletteToZoneAnimation = this.dynamicComponentService.createDynamicComponent(PaletteAnimationComponent); + paletteToZoneAnimation.instance.from = startPoint; + paletteToZoneAnimation.instance.type = zoneType; + paletteToZoneAnimation.instance.to = this.findZoneCoordinates(zoneTypeName); + paletteToZoneAnimation.instance.zoneInstance = newInstance; + paletteToZoneAnimation.instance.iconName = zoneTypeName; + paletteToZoneAnimation.instance.runAnimation(); + } + + public startCyTagMode = (cy: Cy.Instance) => { + cy.autolock(true); + cy.nodes().unselectify(); + cy.emit('tagstart'); //dont need to show handles because they're already visible bcz of hover event + + }; + + public endCyTagMode = (cy: Cy.Instance) => { + cy.emit('tagend'); + cy.nodes().selectify(); + cy.autolock(false); + }; + + public handleTagClick = (cy: Cy.Instance, zoneInstance: ZoneInstance, nodeId: string) => { + zoneInstance.addOrRemoveAssignment(nodeId, ZoneInstanceAssignmentType.COMPONENT_INSTANCES); + this.showZoneTagIndicationForNode(nodeId, zoneInstance, cy); + }; + + public showGroupZoneIndications = (groupInstances: Array<ZoneInstance>, policyInstance: ZoneInstance) => { + groupInstances.forEach((groupInstance: ZoneInstance) => { + let handle: string = this.getCorrectHandleForNode(groupInstance.instanceData.uniqueId, policyInstance); + groupInstance.showHandle(handle); + }) + }; + + public hideGroupZoneIndications = (instances: Array<ZoneInstance>) => { + instances.forEach((instance) => { + instance.hideHandle(); + }) + } + + public showZoneTagIndications = (cy: Cy.Instance, zoneInstance: ZoneInstance) => { + + cy.nodes().forEach(node => { + let handleType: string = this.getCorrectHandleForNode(node.id(), zoneInstance); + cy.emit('showhandle', [node, handleType]); + }); + }; + + public showZoneTagIndicationForNode = (nodeId: string, zoneInstance: ZoneInstance, cy: Cy.Instance) => { + let node = cy.getElementById(nodeId); + let handleType: string = this.getCorrectHandleForNode(nodeId, zoneInstance); + cy.emit('showhandle', [node, handleType]); + } + + public hideZoneTagIndications = (cy: Cy.Instance) => { + cy.emit('hidehandles'); + }; + + public getCorrectHandleForNode = (nodeId: string, zoneInstance: ZoneInstance): string => { + if (zoneInstance.isAlreadyAssigned(nodeId)) { + if (zoneInstance.type == ZoneInstanceType.POLICY) { + return CanvasHandleTypes.TAGGED_POLICY; + } else { + return CanvasHandleTypes.TAGGED_GROUP; + } + } else { + return CanvasHandleTypes.TAG_AVAILABLE; + } + }; +} diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts new file mode 100644 index 0000000000..e7f11af248 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/index.ts @@ -0,0 +1,29 @@ +/** + * Created by ob0695 on 6/3/2018. + */ +// export * from './composition-graph-general-utils'; +// export * from './composition-graph-links-utils'; +// export * from './composition-graph-nodes-utils'; +// export * from './composition-graph-palette-utils'; +// export * from './composition-graph-service-path-utils'; +// export * from './composition-graph-zone-utils'; + + +import {CompositionGraphGeneralUtils} from './composition-graph-general-utils'; +import {CompositionGraphNodesUtils} from './composition-graph-nodes-utils'; +import {MatchCapabilitiesRequirementsUtils} from './match-capability-requierment-utils' +import {CompositionGraphPaletteUtils} from './composition-graph-palette-utils'; +import {CompositionGraphZoneUtils} from './composition-graph-zone-utils'; +import {ServicePathGraphUtils} from './composition-graph-service-path-utils'; +import {CompositionGraphLinkUtils} from "./composition-graph-links-utils"; + + +export { + CompositionGraphGeneralUtils, + CompositionGraphLinkUtils, + CompositionGraphNodesUtils, + MatchCapabilitiesRequirementsUtils, + CompositionGraphPaletteUtils, + CompositionGraphZoneUtils, + ServicePathGraphUtils +};
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts new file mode 100644 index 0000000000..dbfc3e7219 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.spec.ts @@ -0,0 +1,342 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { Mock } from 'ts-mockery'; +import { + CapabilitiesGroup, + Capability, ComponentInstance, CompositionCiLinkBase, CompositionCiNodeBase, CompositionCiNodeCp, + CompositionCiNodeVf, CompositionCiNodeVl, + Requirement, RequirementsGroup +} from '../../../../../models'; +import { MatchCapabilitiesRequirementsUtils } from './match-capability-requierment-utils'; + +describe('match capability requirements utils service ', () => { + + const bindableReq = Mock.of<Requirement>({ + capability : 'tosca.capabilities.network.Bindable', + name: 'virtualBinding', + relationship: 'tosca.relationships.network.BindsTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualBinding', + ownerId : 'extcp0', + ownerName : 's' + }); + + const virtualLinkReq = Mock.of<Requirement>({ + capability: 'tosca.capabilities.network.Linkable', + name: 'virtualLink', + relationship: 'tosca.relationships.network.LinksTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.virtualLink', + ownerId : '', + ownerName : 's' + }); + + const storeAttachmentReq = Mock.of<Requirement>({ + capability: 'tosca.capabilities.Attachment', + name: 'local_storage', + relationship: 'tosca.relationships.AttachesTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage', + node: 'tosca.nodes.BlockStorage', + ownerId : '', + ownerName : 's' + }); + + const vlAttachmentReq = Mock.of<Requirement>({ + capability: 'tosca.capabilities.Attachment', + name: 'local_storage', + relationship: 'tosca.relationships.AttachesTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.local_storage', + node: 'tosca.nodes.BlockStorage', + ownerId : '', + ownerName : 's' + }); + + const extVirtualLinkReq = Mock.of<Requirement>({ + capability: 'tosca.capabilities.network.Linkable', + name: 'external_virtualLink', + relationship: 'tosca.relationships.network.LinksTo', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.external_virtualLink' + }); + + const dependencyReq = Mock.of<Requirement>({ + capability: 'tosca.capabilities.Node', + name: 'dependency', + relationship: 'tosca.relationships.DependsOn', + uniqueId: 'eef99154-8039-4227-ba68-62a32e6b0d98.dependency' + }); + + const featureCap = Mock.of<Capability>({ + type: 'tosca.capabilities.Node', + name: 'feature', + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.feature', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const internalConnPointCap = Mock.of<Capability>({ + type: 'tosca.capabilities.Node', + name: 'internal_connectionPoint', + capabilitySources : ['org.openecomp.resource.cp.extCP'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.internal_connectionPoint', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const blockStoreAttachmentCap = Mock.of<Capability>({ + type: 'tosca.capabilities.Attachment', + name: 'attachment', + capabilitySources: ['tosca.nodes.BlockStorage'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.attachment', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const bindingCap = Mock.of<Capability>({ + type: 'tosca.capabilities.network.Bindable', + name: 'binding', + capabilitySources: ['tosca.nodes.Compute'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.binding', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1', + }); + + const linkableCap = Mock.of<Capability>({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['org.openecomp.resource.vl.extVL'], + uniqueId: 'capability.ddf1301e-866b-4fa3-bc4f-edbd81e532cd.virtual_linkable', + maxOccurrences: 'UNBOUNDED', + minOccurrences: '1' + }); + + const nodeCompute = Mock.of<CompositionCiNodeVf>({ + name: 'Compute 0', + componentInstance: Mock.of<ComponentInstance>({ + componentName: 'Compute', + uniqueId : 'compute0', + requirements: Mock.of<RequirementsGroup>({ + 'tosca.capabilities.Node' : [ dependencyReq ], + 'tosca.capabilities.Attachment' : [ storeAttachmentReq ] + }), + capabilities: Mock.of<CapabilitiesGroup>({ + 'tosca.capabilities.network.Bindable' : [ bindingCap ], + 'tosca.capabilities.Node' : [ featureCap ] + }) + }) + }); + + const nodeBlockStorage = Mock.of<CompositionCiNodeVf>({ + name: 'BlockStorage 0', + componentInstance: Mock.of<ComponentInstance>({ + componentName: 'BlockStorage', + uniqueId : 'blockstorage0', + requirements: Mock.of<RequirementsGroup>({ + 'tosca.capabilities.Node' : [ dependencyReq ] + }), + capabilities: Mock.of<CapabilitiesGroup>({ + 'tosca.capabilities.Attachment' : [ blockStoreAttachmentCap ], + 'tosca.capabilities.Node' : [ featureCap ] + }) + }) + }); + + const nodeVl = Mock.of<CompositionCiNodeVl>({ + name: 'ExtVL 0', + componentInstance: Mock.of<ComponentInstance>({ + componentName: 'BlockStorage', + uniqueId : 'extvl0', + requirements: Mock.of<RequirementsGroup>({ + 'tosca.capabilities.Node' : [ dependencyReq ] + }), + capabilities: Mock.of<CapabilitiesGroup>({ + 'tosca.capabilities.network.Linkable' : [ linkableCap ], + 'tosca.capabilities.Node' : [ featureCap ] + }) + }) + }); + + const nodeCp = Mock.of<CompositionCiNodeCp>({ + name: 'ExtCP 0', + componentInstance: Mock.of<ComponentInstance>({ + componentName: 'ExtCP', + uniqueId : 'extcp0', + requirements: Mock.of<RequirementsGroup>({ + 'tosca.capabilities.network.Linkable' : [ virtualLinkReq ], + 'tosca.capabilities.network.Bindable' : [ bindableReq ] + }), + capabilities: Mock.of<CapabilitiesGroup>({ + 'tosca.capabilities.Node' : [ featureCap ] + }) + }) + }); + + let service: MatchCapabilitiesRequirementsUtils; + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [MatchCapabilitiesRequirementsUtils] + }); + + service = TestBed.get(MatchCapabilitiesRequirementsUtils); + }); + + it('match capability requirements utils should be defined', () => { + console.log(JSON.stringify(service)); + expect(service).toBeDefined(); + }); + + describe('isMatch function ', () => { + + it('capability type not equal to requirement capability, match is false', () => { + const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable11'}); + const capability = Mock.of<Capability>({type: 'tosca.capabilities.network.Linkable'}); + expect(service.isMatch(requirement, capability)).toBeFalsy(); + }); + + it('capability type equal to requirement capability and requirement node not exist, match is true', () => { + const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable'}); + const capability = Mock.of<Capability>({type: 'tosca.capabilities.network.Linkable'}); + expect(service.isMatch(requirement, capability)).toBeTruthy(); + }); + + it('is match - capability type equal to requirement capability and requirement node exist and includes in capability sources, match is true', () => { + const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable', node: 'node1'}); + const capability = Mock.of<Capability>({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['node1', 'node2', 'node3'] + }); + expect(service.isMatch(requirement, capability)).toBeTruthy(); + }); + + it('no match - capability type equal to requirement capability and requirement node but not includes in capability sources, match is false', () => { + const requirement = Mock.of<Requirement>({capability: 'tosca.capabilities.network.Linkable', node: 'node4'}); + const capability = Mock.of<Capability>({ + type: 'tosca.capabilities.network.Linkable', + capabilitySources: ['node1', 'node2', 'node3'] + }); + expect(service.isMatch(requirement, capability)).toBeFalsy(); + }); + }); + + describe('hasUnfulfilledRequirementContainingMatch function ', () => { + + it('node have no componentInstance, return false', () => { + const node = Mock.of<CompositionCiNodeVf>({componentInstance: undefined}); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data but no unfulfilled requirements, return false', () => { + const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data and unfulfilled requirements but no match found, return false', () => { + const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]); + jest.spyOn(service, 'containsMatch').mockReturnValue(false); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeFalsy(); + }); + + it('node have componentInstance data with unfulfilled requirements and match found, return true', () => { + const node = Mock.of<CompositionCiNodeVf>({componentInstance: Mock.of<ComponentInstance>()}); + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]); + jest.spyOn(service, 'containsMatch').mockReturnValue(true); + expect(service.hasUnfulfilledRequirementContainingMatch(node, [], {}, [])).toBeTruthy(); + }); + }); + + describe('getMatches function ', () => { + let fromId: string; + let toId: string; + + beforeEach(() => { + fromId = 'from_id'; + toId = 'to_id'; + }); + + it('node have no unfulfilled requirements, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([]); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have unfulfilled requirements but no capabilities, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have unfulfilled requirements and capabilities but no match found, return empty match array', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]); + jest.spyOn(service, 'isMatch').mockReturnValue(false); + expect(service.getMatches({}, {}, [], fromId, toId, true)).toHaveLength(0); + }); + + it('node have 2 unfulfilled requirements and 2 capabilities and match found, return 4 matches', () => { + jest.spyOn(service, 'getUnfulfilledRequirements').mockReturnValue([Mock.of<Requirement>(), Mock.of<Requirement>()]); + const capabilities = {aaa: Mock.of<Capability>(), bbb: Mock.of<Capability>()}; + jest.spyOn(service, 'isMatch').mockReturnValue(true); + expect(service.getMatches({}, capabilities, [], fromId, toId, true)).toHaveLength(4); + }); + }); + + describe('Find matching nodes ===>', () => { + + it('should find matching nodes with component instance', () => { + const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; + let matchingNodes: any; + + // Compute can connect to Block Store + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeBlockStorage); + + // Block Storage can connect to Compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeBlockStorage.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeCompute); + + // Vl has no matches + matchingNodes = service.findMatchingNodesToComponentInstance(nodeVl.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(0); + + // CP should be able to connect to VL and Compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(2); + expect(matchingNodes).toContain(nodeCompute); + expect(matchingNodes).toContain(nodeVl); + }); + + it('try with empty list of nodes', () => { + const nodes = [ ]; + let matchingNodes: any; + + // Compute can connect to Block Store + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCompute.componentInstance, nodes, []); + expect(matchingNodes).toHaveLength(0); + }); + + it('should detect fulfilled connection with compute node', () => { + const nodes = [ nodeBlockStorage, nodeCompute, nodeVl ]; + let matchingNodes: any; + const link = { + relation: { + fromNode: 'extcp0', + toNode: 'compute0', + relationships: [{ + relation: { + requirementOwnerId: 'extcp0', + requirement: 'virtualBinding', + relationship: { + type: 'tosca.relationships.network.BindsTo' + } + + } + }] + } + }; + + const links = [link]; + // CP should be able to connect to VL only since it already has a link with compute + matchingNodes = service.findMatchingNodesToComponentInstance(nodeCp.componentInstance, nodes, links as CompositionCiLinkBase[]); + expect(matchingNodes).toHaveLength(1); + expect(matchingNodes).toContain(nodeVl); + }); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts new file mode 100644 index 0000000000..c3a1286a97 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/graph/utils/match-capability-requierment-utils.ts @@ -0,0 +1,196 @@ +/*- + * ============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 { Injectable } from '@angular/core'; +import { + CapabilitiesGroup, Capability, ComponentInstance, CompositionCiLinkBase, + CompositionCiNodeBase, Match, Requirement, RequirementsGroup +} from 'app/models'; +import * as _ from 'lodash'; + +/** + * Created by obarda on 1/1/2017. + */ +@Injectable() +export class MatchCapabilitiesRequirementsUtils { + + /** + * Shows + icon in corner of each node passed in + * @param filteredNodesData + * @param cy + */ + public highlightMatchingComponents(filteredNodesData, cy: Cy.Instance) { + _.each(filteredNodesData, (data: any) => { + const node = cy.getElementById(data.id); + cy.emit('showhandle', [node]); + }); + } + + /** + * Adds opacity to each node that cannot be linked to hovered node + * @param filteredNodesData + * @param nodesData + * @param cy + * @param hoveredNodeData + */ + public fadeNonMachingComponents(filteredNodesData, nodesData, cy: Cy.Instance, hoveredNodeData?) { + const fadeNodes = _.xorWith(nodesData, filteredNodesData, (node1, node2) => { + return node1.id === node2.id; + }); + if (hoveredNodeData) { + _.remove(fadeNodes, hoveredNodeData); + } + cy.batch(() => { + _.each(fadeNodes, (node) => { + cy.getElementById(node.id).style({'background-image-opacity': 0.4}); + }); + }); + } + + /** + * Resets all nodes to regular opacity + * @param cy + */ + public resetFadedNodes(cy: Cy.Instance) { + cy.batch(() => { + cy.nodes().style({'background-image-opacity': 1}); + }); + } + + public getMatchedRequirementsCapabilities(fromComponentInstance: ComponentInstance, + toComponentInstance: ComponentInstance, + links: CompositionCiLinkBase[]): Match[] { + const fromToMatches: Match[] = this.getMatches(fromComponentInstance.requirements, + toComponentInstance.capabilities, + links, + fromComponentInstance.uniqueId, + toComponentInstance.uniqueId, true); + const toFromMatches: Match[] = this.getMatches(toComponentInstance.requirements, + fromComponentInstance.capabilities, + links, + toComponentInstance.uniqueId, + fromComponentInstance.uniqueId, false); + + return fromToMatches.concat(toFromMatches); + } + + /***** REFACTORED FUNCTIONS START HERE *****/ + + public getMatches(requirements: RequirementsGroup, capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[], + fromId: string, toId: string, isFromTo: boolean): Match[] { + const matches: Match[] = []; + const unfulfilledReqs = this.getUnfulfilledRequirements(fromId, requirements, links); + _.forEach(unfulfilledReqs, (req) => { + _.forEach(_.flatten(_.values(capabilities)), (capability: Capability) => { + if (this.isMatch(req, capability)) { + if (isFromTo) { + matches.push(new Match(req, capability, isFromTo, fromId, toId)); + } else { + matches.push(new Match(req, capability, isFromTo, toId, fromId)); + } + } + }); + }); + return matches; + } + + public getUnfulfilledRequirements = (fromNodeId: string, requirements: RequirementsGroup, links: CompositionCiLinkBase[]): Requirement[] => { + const requirementArray: Requirement[] = []; + _.forEach(_.flatten(_.values(requirements)), (requirement: Requirement) => { + const reqFulfilled = this.isRequirementFulfilled(fromNodeId, requirement, links); + if (requirement.name !== 'dependency' && requirement.parentName !== 'dependency' && !reqFulfilled) { + requirementArray.push(requirement); + } + }); + return requirementArray; + } + + /** + * Returns true if there is a match between the capabilities and requirements that are passed in + * @param requirements + * @param capabilities + * @returns {boolean} + */ + public containsMatch = (requirements: Requirement[], capabilities: CapabilitiesGroup): boolean => { + return _.some(requirements, (req: Requirement) => { + return _.some(_.flatten(_.values(capabilities)), (capability: Capability) => { + return this.isMatch(req, capability); + }); + }); + } + + public hasUnfulfilledRequirementContainingMatch = (node: CompositionCiNodeBase, componentRequirements: Requirement[], capabilities: CapabilitiesGroup, links: CompositionCiLinkBase[]) => { + if (node && node.componentInstance) { + // Check if node has unfulfilled requirement that can be filled by component (#2) + const nodeRequirements: Requirement[] = this.getUnfulfilledRequirements(node.componentInstance.uniqueId, node.componentInstance.requirements, links); + if (!nodeRequirements.length) { + return false; + } + if (this.containsMatch(nodeRequirements, capabilities)) { + return true; + } + } + } + + /** + * Returns array of nodes that can connect to the component. + * In order to connect, one of the following conditions must be met: + * 1. component has an unfulfilled requirement that matches a node's capabilities + * 2. node has an unfulfilled requirement that matches the component's capabilities + * 3. vl is passed in which has the capability to fulfill requirement from component and requirement on node. + */ + public findMatchingNodesToComponentInstance(componentInstance: ComponentInstance, nodeDataArray: CompositionCiNodeBase[], links: CompositionCiLinkBase[]): any[] { + return _.filter(nodeDataArray, (node: CompositionCiNodeBase) => { + const matchedRequirementsCapabilities = this.getMatchedRequirementsCapabilities(node.componentInstance, componentInstance, links); + return matchedRequirementsCapabilities && matchedRequirementsCapabilities.length > 0; + }); + } + + public isMatch(requirement: Requirement, capability: Capability): boolean { + if (capability.type === requirement.capability) { + if (requirement.node) { + if (_.includes(capability.capabilitySources, requirement.node)) { + return true; + } + } else { + return true; + } + } + return false; + } + + private isRequirementFulfilled(fromNodeId: string, requirement: any, links: CompositionCiLinkBase[]): boolean { + return _.some(links, { + relation: { + fromNode: fromNodeId, + relationships: [{ + relation: { + requirementOwnerId: requirement.ownerId, + requirement: requirement.name, + relationship: { + type: requirement.relationship + } + + } + }] + } + }); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/__snapshots__/palette.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/palette/__snapshots__/palette.component.spec.ts.snap new file mode 100644 index 0000000000..74517e1eb0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/__snapshots__/palette.component.spec.ts.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`palette component should match current snapshot of palette component 1`] = ` +<composition-palette + buildPaletteByCategories={[Function Function]} + compositionPaletteService={[Function Object]} + eventListenerService={[Function Object]} + numberOfElements="0" + onDragStart={[Function Function]} + onDraggableMoved={[Function Function]} + onDrop={[Function Function]} + onMouseOut={[Function Function]} + onMouseOver={[Function Function]} + onSearchChanged={[Function Function]} + position={[Function Point]} +> + <div + class="composition-palette-component" + > + <div + class="palette-elements-count" + > + Elements + <span + class="palette-elements-count-value" + > + + </span> + </div> + <sdc-filter-bar + placeholder="Search..." + testid="searchAsset" + /> + <div + class="palette-elements-list" + > + <sdc-loader + name="palette-loader" + testid="palette-loader" + /> + + + </div> + </div><div + dnddropzone="" + id="draggable_element" + > + + </div> +</composition-palette> +`; diff --git a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.html b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.html index efd619687c..efd619687c 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.html diff --git a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.less b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.less index 54f04189c0..54f04189c0 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.less diff --git a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.ts index 7e45b9e55b..a445c87f42 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.component.ts @@ -24,7 +24,7 @@ import { setTimeout } from 'core-js/library/web/timers'; import { EventListenerService } from 'app/services'; import { GRAPH_EVENTS } from 'app/utils'; import { Point } from 'app/models'; -import { ZoneInstanceType, ZoneInstance } from '../../../../models/graph/zones/zone-instance'; +import { ZoneInstanceType, ZoneInstance } from 'app/models/graph/zones/zone-instance'; diff --git a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.module.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.module.ts index 8674571138..8674571138 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-animation/palette-animation.module.ts +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-animation/palette-animation.module.ts diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/__snapshots__/palette-element.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/__snapshots__/palette-element.component.spec.ts.snap new file mode 100644 index 0000000000..40df575519 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/__snapshots__/palette-element.component.spec.ts.snap @@ -0,0 +1,29 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`palette element component should match current snapshot of palette element component 1`] = ` +<palette-element> + <div + class="palette-element" + > + <sdc-element-icon + class="palette-element-icon" + /> + <div + class="palette-element-text" + > + <div + class="palette-element-name" + sdc-tooltip="" + > + + </div> + <span> + V. + </span> + <span> + + </span> + </div> + </div> +</palette-element> +`; diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.html b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.html new file mode 100644 index 0000000000..3a6be5d082 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.html @@ -0,0 +1,11 @@ +<div class="palette-element" > + <sdc-element-icon class="palette-element-icon" [iconName]="paletteElement.icon" + [elementType]="paletteElement.componentSubType"[uncertified]="this.paletteElement.certifiedIconClass"></sdc-element-icon> + <div class="palette-element-text"> + <div class="palette-element-name" sdc-tooltip + tooltip-text='{{paletteElement.name | resourceName}}'>{{paletteElement.name | resourceName}} + </div> + <span> V.{{paletteElement.version}}</span> + <span>{{paletteElement.componentSubType}}</span> + </div> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.less b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.less new file mode 100644 index 0000000000..e9c3253fbd --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.less @@ -0,0 +1,32 @@ +@import "./../../../../../../assets/styles/override"; +.palette-element { + cursor: pointer; + display: flex; + flex-direction: row; + max-height: 65px; + border-bottom: 1px solid @sdcui_color_silver; + padding: 10px; + align-items: center; + .palette-element-icon { + min-width: 45px; + text-align: center; + } + + .palette-element-text { + display: flex; + flex-direction: column; + font-size: 13px; + line-height: 15px; + padding-left: 10px; + font-family: OpenSans-Regular, sans-serif; + overflow: hidden; + + .palette-element-name { + color: @sdcui_color_dark-gray; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; + } + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.spec.ts new file mode 100644 index 0000000000..64ed45ba9c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-element/palette-element.component.spec.ts @@ -0,0 +1,30 @@ +import {async, ComponentFixture} from "@angular/core/testing"; +import {ConfigureFn, configureTests} from "../../../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {PaletteElementComponent} from "./palette-element.component"; +import {ResourceNamePipe} from "../../../../pipes/resource-name.pipe"; + +describe('palette element component', () => { + + let fixture: ComponentFixture<PaletteElementComponent>; + + beforeEach( + async(() => { + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [PaletteElementComponent, ResourceNamePipe], + imports: [], + schemas: [NO_ERRORS_SCHEMA] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(PaletteElementComponent); + }); + }) + ); + + it('should match current snapshot of palette element component', () => { + expect(fixture).toMatchSnapshot(); + }); +});
\ No newline at end of file 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/palette/palette-element/palette-element.component.ts index 26602224da..9e9e5a29da 100644 --- 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/palette/palette-element/palette-element.component.ts @@ -1,3 +1,6 @@ +/** + * Created by ob0695 on 6/28/2018. + */ /*- * ============LICENSE_START======================================================= * SDC @@ -18,22 +21,15 @@ * ============LICENSE_END========================================================= */ -import * as _ from "lodash"; -import { Component, Inject, Input, Output, EventEmitter } from "@angular/core"; -import { GroupInstance } from 'app/models/graph/zones/group-instance'; +import {Component, Input} from "@angular/core"; +import {LeftPaletteComponent} from "app/models/components/displayComponent"; @Component({ - selector: 'group-information-tab', - templateUrl: './group-information-tab.component.html', - styleUrls: ['./../base/base-tab.component.less'] + selector: 'palette-element', + templateUrl: './palette-element.component.html', + styleUrls: ['./palette-element.component.less'] }) -export class GroupInformationTabComponent { - - @Input() group: GroupInstance; - @Input() isViewOnly: boolean; - - constructor() { - - } +export class PaletteElementComponent { + @Input() paletteElement: LeftPaletteComponent; } diff --git a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.html b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.html index 11261e95b4..86847eb28a 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.html @@ -23,3 +23,8 @@ <div class="popup-panel-title">{{panelTitle}}</div> </div> </div> +<!--<popup-menu-list [menuItemsData]="getMenuItems()">--> + + + +<!--</popup-menu-list>-->
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.less b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.less index 24f0485e76..24f0485e76 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.less diff --git a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.ts index a10ca7dc81..5d98fc7f78 100644 --- a/catalog-ui/src/app/ng2/components/ui/palette-popup-panel/palette-popup-panel.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette-popup-panel/palette-popup-panel.component.ts @@ -1,8 +1,8 @@ import {Component, OnInit} from '@angular/core'; -import {GRAPH_EVENTS} from "app/utils"; +import {GRAPH_EVENTS, SdcElementType} from "app/utils"; import {LeftPaletteComponent, Point} from "app/models"; import {EventListenerService} from "app/services"; -import {LeftPaletteMetadataTypes} from "../../../../models/components/displayComponent"; +import {LeftPaletteMetadataTypes} from "app/models/components/displayComponent"; @Component({ selector: 'app-palette-popup-panel', @@ -29,6 +29,18 @@ export class PalettePopupPanelComponent implements OnInit { this.isShowPanel = true; } + public getMenuItems = () => { + return [{ + text: 'Delete', + iconName: 'trash-o', + iconType: 'common', + iconMode: 'secondary', + iconSize: 'small', + type: '', + action: () => this.addZoneInstance() + }]; + } + public onMouseLeave() { this.isShowPanel = false; } @@ -43,9 +55,7 @@ export class PalettePopupPanelComponent implements OnInit { private registerObserverCallbacks() { this.eventListenerService.registerObserverCallback(GRAPH_EVENTS.ON_PALETTE_COMPONENT_SHOW_POPUP_PANEL, - (component: Component, displayComponent: LeftPaletteComponent, sectionElem: HTMLElement) => { - - this.component = component; + (displayComponent: LeftPaletteComponent, sectionElem: HTMLElement) => { this.showPopupPanel(displayComponent, sectionElem); }); @@ -60,12 +70,12 @@ export class PalettePopupPanelComponent implements OnInit { }; private setPopupPanelTitle(component: LeftPaletteComponent): void { - if (component.categoryType === LeftPaletteMetadataTypes.Group) { + if (component.componentSubType === SdcElementType.GROUP) { this.panelTitle = "Add Group"; return; } - if (component.categoryType === LeftPaletteMetadataTypes.Policy) { + if (component.componentSubType === SdcElementType.POLICY) { this.panelTitle = "Add Policy"; return; } diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.html b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.html new file mode 100644 index 0000000000..7963dd18b7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.html @@ -0,0 +1,41 @@ +<div class="composition-palette-component"> + <div class="palette-elements-count">Elements + <span class="palette-elements-count-value">{{numberOfElements}}</span> + </div> + + <sdc-filter-bar placeholder="Search..." (valueChange)="onSearchChanged($event)" testId="searchAsset"></sdc-filter-bar> + + <div class="palette-elements-list"> + <sdc-loader [global]="false" name="palette-loader" testId="palette-loader" [active]="this.isPaletteLoading" [class.inactive]="!this.isPaletteLoading"></sdc-loader> + <div *ngIf="numberOfElements === 0 && searchText" class="no-elements-found">No Elements Found</div> + <sdc-accordion *ngFor="let mapByCategory of paletteElements | keyValue; let first = first" [attr.data-tests-id]="'leftPalette.category.'+mapByCategory.key" [title]="mapByCategory.key" [css-class]="'palette-category'"> + <div *ngFor="let mapBySubCategory of mapByCategory.value | keyValue"> + <div class="palette-subcategory">{{mapBySubCategory.key}}</div> + <ng-container *ngIf="!(isViewOnly$ | async)"> + <div *ngFor="let paletteElement of mapBySubCategory.value" + [dndDraggable]="paletteElement" + [dndDisableIf]="paletteElement.componentSubType == 'GROUP' && paletteElement.componentSubType == 'POLICY'" + (dndStart)="onDragStart($event, paletteElement)" + (drag)="onDraggableMoved($event)" + [dndEffectAllowed]="'copyMove'" + (mouseenter)="onMouseOver($event, paletteElement)" + (mouseleave)="onMouseOut(paletteElement)" + [attr.data-tests-id]="paletteElement.name"> + <palette-element [paletteElement]="paletteElement"></palette-element> + </div> + </ng-container> + <ng-container *ngIf="(isViewOnly$ | async)"> + <div *ngFor="let paletteElement of mapBySubCategory.value" + [attr.data-tests-id]="paletteElement.name"> + <palette-element [paletteElement]="paletteElement"></palette-element> + </div> + </ng-container> + </div> + </sdc-accordion> + </div> +</div> + +<div id="draggable_element" dndDropzone (dndDrop)="onDrop($event)" [dndAllowExternal]="true"> + <sdc-element-icon *ngIf="paletteDraggedElement" [iconName]="paletteDraggedElement.icon" + [elementType]="paletteDraggedElement.componentSubType" [uncertified]="paletteDraggedElement.certifiedIconClass"></sdc-element-icon> +</div> diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.less b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.less new file mode 100644 index 0000000000..37461ba1c5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.less @@ -0,0 +1,84 @@ +@import "./../../../../../assets/styles/override"; + +:host(composition-palette) { + display:flex; + flex: 0 0 244px; +} + +sdc-loader.inactive { + display:none; +} + +:host ::ng-deep .sdc-filter-bar .sdc-input { + margin-bottom:0px; +} +:host ::ng-deep .sdc-loader-wrapper { + position:static; +} + +.composition-palette-component { + background-color: @sdcui_color_white; + overflow-y: auto; + overflow-x: hidden; + display: flex; + flex-direction: column; + position:relative; + width: 244px; + box-shadow: 7px -3px 6px -8px @sdcui_color_gray; + + .palette-elements-count { + background-color: @sdcui_color_gray; + line-height: 40px; + padding: 0 17px; + color: @sdcui_color_white; + .palette-elements-count-value { + float: right; + } + } + + .palette-elements-list { + + .no-elements-found { + padding-left: 40px; + } + /deep/ .palette-category { + display: flex; + margin: 0px; + .sdc-accordion-header { + background-color: @sdcui_color_silver; + margin: 0px; + line-height: 40px; + padding: 0px 10px; + } + .sdc-accordion-body { + padding: 0px; + } + } + .palette-subcategory { + padding: 0 10px; + background-color: @sdcui_color_lighter-silver; + line-height: 35px; + } + } +} + +#draggable_element { + display: inline-block; + border-radius: 50%; + background: transparent; + position: absolute; + top: -9999px; + left: 0; + z-index: 100; +} + +.invalid-drag { + border: 7px solid @red-shadow; +} + +.valid-drag { + border: 7px solid @green-shadow; +} + +@green-shadow: rgba(29, 154, 149, 0.3); +@red-shadow: rgba(218, 31, 61, 0.3); diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.spec.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.spec.ts new file mode 100644 index 0000000000..efa9cd3370 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.spec.ts @@ -0,0 +1,102 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {CompositionPaletteService} from "./services/palette.service"; +import {EventListenerService} from "../../../../services/event-listener-service"; +import {PaletteElementComponent} from "./palette-element/palette-element.component"; +import {PaletteComponent} from "./palette.component"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {GRAPH_EVENTS} from "../../../../utils/constants"; +import {KeyValuePipe} from "../../../pipes/key-value.pipe"; +import {ResourceNamePipe} from "../../../pipes/resource-name.pipe"; +import {LeftPaletteComponent} from "../../../../models/components/displayComponent"; +import {Observable} from "rxjs/Observable"; +import {leftPaletteElements} from "../../../../../jest/mocks/left-paeltte-elements.mock"; +import {NgxsModule, Select} from '@ngxs/store'; +import { WorkspaceState } from 'app/ng2/store/states/workspace.state'; + + +describe('palette component', () => { + + const mockedEvent = <MouseEvent>{ target: {} } + let fixture: ComponentFixture<PaletteComponent>; + let eventServiceMock: Partial<EventListenerService>; + let compositionPaletteMockService: Partial<CompositionPaletteService>; + + beforeEach( + async(() => { + eventServiceMock = { + notifyObservers: jest.fn() + } + compositionPaletteMockService = { + subscribeToLeftPaletteElements: jest.fn().mockImplementation(()=> Observable.of(leftPaletteElements)), + getLeftPaletteElements: jest.fn().mockImplementation(()=> leftPaletteElements) + } + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [PaletteComponent, PaletteElementComponent, KeyValuePipe, ResourceNamePipe], + imports: [NgxsModule.forRoot([WorkspaceState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: CompositionPaletteService, useValue: compositionPaletteMockService}, + {provide: EventListenerService, useValue: eventServiceMock} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(PaletteComponent); + }); + }) + ); + + it('should match current snapshot of palette component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should call on palette component hover in event', () => { + let paletteObject = <LeftPaletteComponent>{categoryType: 'COMPONENT'}; + fixture.componentInstance.onMouseOver(mockedEvent, paletteObject); + expect(eventServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, paletteObject); + }); + + it('should call on palette component hover out event', () => { + let paletteObject = <LeftPaletteComponent>{categoryType: 'COMPONENT'}; + fixture.componentInstance.onMouseOut(paletteObject); + expect(eventServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT); + }); + + it('should call show popup panel event', () => { + let paletteObject = <LeftPaletteComponent>{categoryType: 'GROUP'}; + fixture.componentInstance.onMouseOver(mockedEvent, paletteObject); + expect(eventServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_PALETTE_COMPONENT_SHOW_POPUP_PANEL, paletteObject, mockedEvent.target); + }); + + it('should call hide popup panel event', () => { + let paletteObject = <LeftPaletteComponent>{categoryType: 'GROUP'}; + fixture.componentInstance.onMouseOut(paletteObject); + expect(eventServiceMock.notifyObservers).toHaveBeenCalledWith(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HIDE_POPUP_PANEL); + }); + + it('should build Palette By Categories without searchText', () => { + fixture.componentInstance.buildPaletteByCategories(); + expect(fixture.componentInstance.paletteElements["Generic"]["Network"].length).toBe(5); + expect(fixture.componentInstance.paletteElements["Generic"]["Network"][0].searchFilterTerms).toBe("extvirtualmachineinterfacecp external port for virtual machine interface extvirtualmachineinterfacecp 3.0"); + expect(fixture.componentInstance.paletteElements["Generic"]["Network"][1].searchFilterTerms).toBe("newservice2 asdfasdfa newservice2 0.3"); + + expect(fixture.componentInstance.paletteElements["Generic"]["Configuration"].length).toBe(1); + expect(fixture.componentInstance.paletteElements["Generic"]["Configuration"][0].systemName).toBe("Extvirtualmachineinterfacecp"); + }); + + it('should build Palette By Categories with searchText', () => { + fixture.componentInstance.buildPaletteByCategories("testVal"); + expect(fixture.componentInstance.paletteElements["Generic"]["Network"].length).toBe(1); + expect(fixture.componentInstance.paletteElements["Generic"]["Network"][0].searchFilterTerms).toBe("testVal and other values"); + }); + + it('should change numbers of elements', () => { + fixture.componentInstance.buildPaletteByCategories(); + expect(fixture.componentInstance.numberOfElements).toEqual(6); + fixture.componentInstance.buildPaletteByCategories("testVal"); + expect(fixture.componentInstance.numberOfElements).toEqual(1); + }); +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.ts new file mode 100644 index 0000000000..02d270b39a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette.component.ts @@ -0,0 +1,172 @@ +/** + * Created by ob0695 on 6/28/2018. + */ +/*- + * ============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, HostListener } from '@angular/core'; +import { Select } from '@ngxs/store'; +import { LeftPaletteComponent, LeftPaletteMetadataTypes } from 'app/models/components/displayComponent'; +import { Point } from 'app/models/graph/point'; +import { WorkspaceState } from 'app/ng2/store/states/workspace.state'; +import Dictionary = _.Dictionary; +import { EventListenerService } from 'app/services/event-listener-service'; +import { GRAPH_EVENTS } from 'app/utils/constants'; +import { DndDropEvent } from 'ngx-drag-drop/ngx-drag-drop'; +import { CompositionPaletteService } from './services/palette.service'; +import {PolicyMetadata} from "../../../../models/policy-metadata"; +import {GenericBrowserDomAdapter} from "@angular/platform-browser/src/browser/generic_browser_adapter"; + +@Component({ + selector: 'composition-palette', + templateUrl: './palette.component.html', + styleUrls: ['./palette.component.less'] +}) +export class PaletteComponent { + + constructor(private compositionPaletteService: CompositionPaletteService, private eventListenerService: EventListenerService) {} + + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + private paletteElements: Dictionary<Dictionary<LeftPaletteComponent[]>>; + public numberOfElements: number = 0; + public isPaletteLoading: boolean; + private paletteDraggedElement: LeftPaletteComponent; + public position: Point = new Point(); + + ngOnInit() { + this.isPaletteLoading = true; + + this.compositionPaletteService.subscribeToLeftPaletteElements((leftPaletteElementsResponse) => { + this.paletteElements = leftPaletteElementsResponse; + this.numberOfElements = this.countLeftPalleteElements(this.paletteElements); + this.isPaletteLoading = false; + }, () => { + this.isPaletteLoading = false; + }); + + } + + public buildPaletteByCategories = (searchText?: string) => { // create nested by category & subcategory, filtered by search parans + // Flat the object and run on its leaves + if (searchText) { + searchText = searchText.toLowerCase(); + const paletteElementsAfterSearch = {}; + this.paletteElements = this.compositionPaletteService.getLeftPaletteElements(); + for (const category in this.paletteElements) { + for (const subCategory in this.paletteElements[category]) { + const subCategoryToCheck = this.paletteElements[category][subCategory]; + const res = subCategoryToCheck.filter((item) => item.searchFilterTerms.toLowerCase().indexOf(searchText) >= 0) + if (res.length > 0) { + paletteElementsAfterSearch[category] = {}; + paletteElementsAfterSearch[category][subCategory] = res; + } + } + } + this.paletteElements = paletteElementsAfterSearch; + } else { + this.paletteElements = this.compositionPaletteService.getLeftPaletteElements(); + } + this.numberOfElements = this.countLeftPalleteElements(this.paletteElements); + } + + public onSearchChanged = (searchText: string) => { + + if (this.compositionPaletteService.getLeftPaletteElements()) { + this.buildPaletteByCategories(searchText); + } + } + + private countLeftPalleteElements(leftPalleteElements: Dictionary<Dictionary<LeftPaletteComponent[]>>) { + // Use _ & flat map + let counter = 0; + for (const category in leftPalleteElements) { + for (const subCategory in leftPalleteElements[category]) { + counter += leftPalleteElements[category][subCategory].length; + } + } + return counter; + } + + private isGroupOrPolicy(component: LeftPaletteComponent): boolean { + if (component && + (component.categoryType === LeftPaletteMetadataTypes.Group || + component.categoryType === LeftPaletteMetadataTypes.Policy)) { + return true; + } + return false; + } + @HostListener('document:dragover', ['$event']) + public onDrag(event) { + this.position.x = event.clientX; + this.position.y = event.clientY; + } + + //---------------------------------------Palette Events-----------------------------------------// + + public onDraggableMoved = (event:DragEvent) => { + let draggedElement = document.getElementById("draggable_element"); + draggedElement.style.top = (this.position.y - 80) + "px"; + draggedElement.style.left = (this.position.x - 30) + "px"; + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DRAG_ACTION, this.position); + } + + public onDragStart = (event, draggedElement:LeftPaletteComponent) => { // Applying the dragged svg component to the draggable element + + this.paletteDraggedElement = draggedElement; + event.dataTransfer.dropEffect = "copy"; + let hiddenImg = document.createElement("span"); + event.dataTransfer.setDragImage(hiddenImg, 0, 0); + } + + + public onDrop = (event:DndDropEvent) => { + let draggedElement = document.getElementById("draggable_element"); + draggedElement.style.top = "-9999px"; + if(draggedElement.classList.contains('valid-drag')) { + if(!event.data){ + event.data = this.paletteDraggedElement; + } + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_DROP, event); + } else { + console.log("INVALID drop"); + } + this.paletteDraggedElement = undefined; + + } + + public onMouseOver = (sectionElem:MouseEvent, displayComponent:LeftPaletteComponent) => { + console.debug("On palette element MOUSE HOVER: ", displayComponent); + if (this.isGroupOrPolicy(displayComponent)) { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_SHOW_POPUP_PANEL, displayComponent, sectionElem.target); + } else { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_IN, displayComponent); + } + }; + + public onMouseOut = (displayComponent:LeftPaletteComponent) => { + console.debug("On palette element MOUSE OUT: ", displayComponent); + if (this.isGroupOrPolicy(displayComponent)) { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HIDE_POPUP_PANEL); + } else { + this.eventListenerService.notifyObservers(GRAPH_EVENTS.ON_PALETTE_COMPONENT_HOVER_OUT); + } + }; + +} diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/palette.module.ts b/catalog-ui/src/app/ng2/pages/composition/palette/palette.module.ts new file mode 100644 index 0000000000..aeb4c4c60b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/palette.module.ts @@ -0,0 +1,25 @@ +import { NgModule } from "@angular/core"; +import { CompositionPaletteService } from "./services/palette.service"; +import { PaletteComponent } from "./palette.component"; +import { SdcUiComponentsModule } from "onap-ui-angular"; +import { GlobalPipesModule } from "app/ng2/pipes/global-pipes.module"; +import { CommonModule } from "@angular/common"; +import { DndModule } from "ngx-drag-drop"; +import {PaletteElementComponent} from "./palette-element/palette-element.component"; +import {EventListenerService} from "app/services/event-listener-service"; +import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; + +@NgModule({ + declarations: [PaletteComponent, PaletteElementComponent], + imports: [CommonModule, SdcUiComponentsModule, GlobalPipesModule, UiElementsModule, DndModule], + exports: [PaletteComponent], + entryComponents: [PaletteComponent], + providers: [CompositionPaletteService, EventListenerService] +}) +export class PaletteModule { + + constructor() { + + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.spec.ts b/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.spec.ts new file mode 100644 index 0000000000..3a660c1de7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.spec.ts @@ -0,0 +1,41 @@ +import {TestBed} from "@angular/core/testing"; +import {CompositionPaletteService} from "./palette.service"; +import {ISdcConfig, SdcConfigToken} from "../../../../config/sdc-config.config"; +import {WorkspaceService} from "../../../../pages/workspace/workspace.service"; +import { HttpClient } from "@angular/common/http"; +describe('palette component', () => { + + let service: CompositionPaletteService; + + let httpServiceMock: Partial<HttpClient> = { + get: jest.fn() + } + + let sdcConfigToken: Partial<ISdcConfig> = { + "api": { + "root": '' + } + } + + beforeEach(() => { + TestBed.configureTestingModule({ + imports: [], + providers: [CompositionPaletteService, + {provide: HttpClient, useValue: httpServiceMock}, + {provide: SdcConfigToken, useValue: sdcConfigToken}, + {provide: WorkspaceService, useValue{}} + ] + }); + + service = TestBed.get(CompositionPaletteService); + }); + + it('should create an instance', () => { + expect(service).toBeDefined(); + }); + + // it('should create an instance2', async () => { + // expect(await service.subscribeToLeftPaletteElements("resources")).toEqual([]); + // }); +}); + diff --git a/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.ts b/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.ts new file mode 100644 index 0000000000..7587c5206f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/composition/palette/services/palette.service.ts @@ -0,0 +1,98 @@ +import { HttpClient, HttpParams } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { LeftPaletteComponent, LeftPaletteMetadataTypes } from 'app/models/components/displayComponent'; +import { GroupMetadata } from 'app/models/group-metadata'; +import { PolicyMetadata } from 'app/models/policy-metadata'; +import { SdcConfigToken } from 'app/ng2/config/sdc-config.config'; +import { ISdcConfig } from 'app/ng2/config/sdc-config.config.factory'; +import { WorkspaceService } from 'app/ng2/pages/workspace/workspace.service'; +import 'rxjs/add/observable/forkJoin'; +import { Observable } from 'rxjs/Rx'; +import Dictionary = _.Dictionary; + + + +@Injectable() +export class CompositionPaletteService { + + protected baseUrl = ''; + + private leftPaletteComponents: Dictionary<Dictionary<LeftPaletteComponent[]>>; + private facadeUrl: string; + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig, private workspaceService: WorkspaceService) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + this.facadeUrl = sdcConfig.api.uicache_root + sdcConfig.api.GET_uicache_left_palette; + + } + + public subscribeToLeftPaletteElements(next, error) { + + let params = new HttpParams(); + params = params.append('internalComponentType', this.workspaceService.getMetadataType()); + + const loadInstances = this.http.get(this.facadeUrl, {params}); + const loadGroups = this.http.get(this.baseUrl + 'groupTypes', {params}); + const loadPolicies = this.http.get(this.baseUrl + 'policyTypes', {params}); + + Observable.forkJoin( + loadInstances, loadGroups, loadPolicies + ).subscribe( ([resInstances, resGrouops, resPolicies]) => { + const combinedDictionary = this.combineResoponses(resInstances, resGrouops, resPolicies); + this.leftPaletteComponents = combinedDictionary; + next(this.leftPaletteComponents); + }); + } + + public getLeftPaletteElements = (): Dictionary<Dictionary<LeftPaletteComponent[]>> => { + return this.leftPaletteComponents; + } + + + public convertPoliciesOrGroups = (paletteListResult, type: string ) => { + const components: LeftPaletteComponent[] = []; + + if (type === 'Policies') { + _.forEach(paletteListResult, (policyMetadata: PolicyMetadata) => { + components.push(new LeftPaletteComponent(LeftPaletteMetadataTypes.Policy, policyMetadata)); + }); + return { + Policies: components + }; + } + + if (type === 'Groups') { + _.forEach(paletteListResult, (groupMetadata: GroupMetadata) => { + const item = new LeftPaletteComponent(LeftPaletteMetadataTypes.Group, groupMetadata); + components.push(item); + }); + return { + Groups: components + }; + } + + return {}; + } + + private combineResoponses(resInstances: object, resGrouops: object, resPolicies: object) { + const retValObject = {}; + // Generic will be the 1st category in the left Pallete + if (resInstances['Generic']) { + retValObject['Generic'] = resInstances['Generic']; + } + // Add all other categories + for (const category in resInstances) { + if (category === 'Generic') { + continue; + } + retValObject[category] = resInstances[category]; + } + + // Add Groups + retValObject["Groups"] = this.convertPoliciesOrGroups(resGrouops, 'Groups'); + + // Add policies + retValObject["Policies"] = this.convertPoliciesOrGroups(resPolicies, 'Policies'); + + return retValObject; + } +} 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/components/ui/forms/value-edit/value-edit.component.html b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.html index 75ee2d520f..75ee2d520f 100644 --- a/catalog-ui/src/app/ng2/components/ui/forms/value-edit/value-edit.component.html +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.html diff --git a/catalog-ui/src/app/ng2/components/ui/forms/value-edit/value-edit.component.less b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.less index b958ca17b7..b958ca17b7 100644 --- a/catalog-ui/src/app/ng2/components/ui/forms/value-edit/value-edit.component.less +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.less diff --git a/catalog-ui/src/app/ng2/components/ui/forms/value-edit/value-edit.component.ts b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.ts index 08bc0586c7..9c4aab206e 100644 --- a/catalog-ui/src/app/ng2/components/ui/forms/value-edit/value-edit.component.ts +++ b/catalog-ui/src/app/ng2/pages/composition/panel/panel-header/edit-name-modal/edit-name-modal.component.ts @@ -1,11 +1,11 @@ import { Component, Input } from "@angular/core"; @Component({ - selector: 'value-edit', - templateUrl: './value-edit.component.html', - styleUrls: ['./value-edit.component.less'] + selector: 'edit-name-modal', + templateUrl: './edit-name-modal.component.html', + styleUrls: ['./edit-name-modal.component.less'] }) -export class ValueEditComponent { +export class EditNameModalComponent { @Input() name:String; @Input() validityChangedCallback: Function; 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-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.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-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 { - -} diff --git a/catalog-ui/src/app/ng2/pages/home/__snapshots__/home.component.spec.ts.snap b/catalog-ui/src/app/ng2/pages/home/__snapshots__/home.component.spec.ts.snap new file mode 100644 index 0000000000..ae5445e546 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/__snapshots__/home.component.spec.ts.snap @@ -0,0 +1,28 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`home component should match current snapshot 1`] = ` +<home-page + $state={[Function Object]} + authService={[Function Object]} + cacheService={[Function Object]} + componentShouldReload={[Function Function]} + homeService={[Function Object]} + importVSPService={[Function Object]} + initFolders={[Function Function]} + isDefaultFilter={[Function Function]} + loaderService={[Function Object]} + modalService={[Function Object]} + modalsHandler={[Function Object]} + sdcConfig={[Function Object]} + sdcMenu={[Function Object]} + translateService={[Function Object]} + updateFilter={[Function Function]} +> + <div + class="sdc-catalog-container" + > + + <top-nav /> + </div> +</home-page> +`; diff --git a/catalog-ui/src/app/ng2/pages/home/folders.ts b/catalog-ui/src/app/ng2/pages/home/folders.ts new file mode 100644 index 0000000000..036ae329b7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/folders.ts @@ -0,0 +1,93 @@ + +export interface IItemMenu { + +} + +export interface IMenuItemProperties { + text:string; + group:string; + state:string; + dist:string; + groupname:string; + states:Array<any>; +} + +export class FoldersMenu { + private _folders:Array<FoldersItemsMenu> = []; + + constructor(folders:Array<IMenuItemProperties>) { + let self = this; + folders.forEach(function (folder:IMenuItemProperties) { + if (folder.groupname) { + self._folders.push(new FoldersItemsMenuGroup(folder)); + } else { + self._folders.push(new FoldersItemsMenu(folder)); + } + }); + self._folders[0].setSelected(true); + } + + public getFolders():Array<FoldersItemsMenu> { + return this._folders; + } + + public getCurrentFolder():FoldersItemsMenu { + let menuItem:FoldersItemsMenu = undefined; + this.getFolders().forEach(function (tmpFolder:FoldersItemsMenu) { + if (tmpFolder.isSelected()) { + menuItem = tmpFolder; + } + }); + return menuItem; + } + + public setSelected(folder:FoldersItemsMenu):void { + this.getFolders().forEach(function (tmpFolder:FoldersItemsMenu) { + tmpFolder.setSelected(false); + }); + folder.setSelected(true); + } +} + +export class FoldersItemsMenu implements IItemMenu { + public text:string; + public group:string; + public state:string; + public dist:string; + public states:Array<any>; + + private selected:boolean = false; + + constructor(menuProperties:IMenuItemProperties) { + this.text = menuProperties.text; + this.group = menuProperties.group; + this.state = menuProperties.state; + this.states = menuProperties.states; + this.dist = menuProperties.dist; + } + + public isSelected():boolean { + return this.selected; + } + + public setSelected(value:boolean):void { + this.selected = value; + } + + public isGroup():boolean { + return false; + } +} + +export class FoldersItemsMenuGroup extends FoldersItemsMenu { + public groupname:string; + + constructor(menuProperties:IMenuItemProperties) { + super(menuProperties); + this.groupname = menuProperties.groupname; + } + + public isGroup():boolean { + return true; + } +} diff --git a/catalog-ui/src/app/ng2/pages/home/home.component.html b/catalog-ui/src/app/ng2/pages/home/home.component.html new file mode 100644 index 0000000000..1c8c2b4373 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/home.component.html @@ -0,0 +1,88 @@ +<div class="sdc-catalog-container"> + <div class="w-sdc-main-container" *ngIf="user"> + + <div id="dashboard-main-scroll" infiniteScroll class="w-sdc-main-right-container" (infiniteScroll)="raiseNumberOfElementToDisplay()" [infiniteScrollDistance]="100"> + + <div class='w-sdc-row-flex-items'> + + <!-- ADD Component --> + <div *ngIf="user.role === 'DESIGNER'" class="w-sdc-dashboard-card-new" + (mouseleave)="setDisplayActions(false)" + (mouseover)="setDisplayActions(true)"> + <div class="w-sdc-dashboard-card-new-content" data-tests-id="AddButtonsArea"> + <div class="w-sdc-dashboard-card-new-content-plus" [hidden]="displayActions"></div> + <div class="sdc-dashboard-create-element-container" [hidden]="!displayActions"> + <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" testId="createResourceButton" size="medium" type="secondary" text="Add VF" (click)="openCreateModal('RESOURCE')"></sdc-button> + <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" testId="createCRButton" size="medium" type="secondary" text="Add CR" (click)="createCR()"></sdc-button> + <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" testId="createPNFButton" size="medium" type="secondary" text="Add PNF" (click)="createPNF()"></sdc-button> + <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" testId="createServiceButton" size="medium" type="secondary" text="Add Service" (click)="openCreateModal('SERVICE')"></sdc-button> + </div> + </div> + </div> + + <!-- Import Component --> + <div *ngIf="user.role === 'DESIGNER'" class="w-sdc-dashboard-card-new" + (mouseleave)="setDisplayActions(false)" + (mouseover)="setDisplayActions(true)"> + <div class="w-sdc-dashboard-card-new-content" data-tests-id="importButtonsArea" > + <div class="w-sdc-dashboard-card-import-content-plus" [hidden]="displayActions"></div> + <div class="sdc-dashboard-import-element-container" [hidden]="!displayActions"> + <sdc-button-file-opener + *ngIf="roles[user.role].dashboard.showCreateNew" + size="medium" + type="secondary" + text="Import VFC" + testId="importVFCbutton" + [extensions]="sdcConfig.toscaFileExtension" + (fileUpload)="onImportVfc($event)" + [convertToBase64]="true" + ></sdc-button-file-opener> + <sdc-button *ngIf="roles[user.role].dashboard.showCreateNew" data-tests-id="importButtonsVSP" size="medium" type="secondary" text="Import VSP" (click)="notificationIconCallback()"></sdc-button> + <sdc-button-file-opener + *ngIf="roles[user.role].dashboard.showCreateNew" + size="medium" + type="secondary" + text="Import DCAE" + testId="importDCAE" + [extensions]="sdcConfig.csarFileExtension" + (fileUpload)="onImportVf($event)" + [convertToBase64]="true" + ></sdc-button-file-opener> + </div> + </div> + </div> + + <!-- Tile new --> + <ui-tile *ngFor="let item of homeFilteredSlicedItems" + [component]="item" (onTileClick)="goToComponent(item)"></ui-tile> + <!-- Tile new --> + + </div> + + </div> + + <div class="w-sdc-left-sidebar"> + <div class="i-sdc-left-sidebar-item " + *ngFor="let folder of folders.getFolders()" + [ngClass]="{'category-title': folder.isGroup(), 'selectedLink': folder.isSelected()}"> + + <span *ngIf="folder.isGroup()" class="title-text">{{folder.text}}</span> + <sdc-checkbox *ngIf="!folder.isGroup() && !folder.dist" + [label]="folder.text" + [attr.data-tests-id]="'filter-' + folder.state" + [checked]="homeFilter.selectedStatuses.indexOf(folder.state) !== -1" + (checkedChange)="changeCheckboxesFilter(homeFilter.selectedStatuses, folder.state, $event)"></sdc-checkbox> + + <sdc-checkbox *ngIf="!folder.isGroup() && folder.dist" + [label]="folder.text" + [checked]="homeFilter.distributed.indexOf(folder.dist) !== -1" + (checkedChange)="changeCheckboxesFilter(homeFilter.distributed, folder.dist, $event)"></sdc-checkbox> + <span class="i-sdc-left-sidebar-item-state-count" [attr.data-tests-id]="'count-' + folder.state">{{entitiesCount(folder)}}</span> + </div> + </div> + + </div> + + <top-nav [topLvlSelectedIndex]="0" [version]="version" [searchTerm]="homeFilter.search.filterTerm" (searchTermChange)="changeFilterTerm($event)" [notificationIconCallback]="notificationIconCallback"></top-nav> + +</div> diff --git a/catalog-ui/src/app/ng2/pages/home/home.component.less b/catalog-ui/src/app/ng2/pages/home/home.component.less new file mode 100644 index 0000000000..c5b73748ba --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/home.component.less @@ -0,0 +1,126 @@ +@import '../../../../assets/styles/mixins_old'; +@import '../../../../assets/styles/sprite'; +.w-sdc-left-sidebar-nav { + margin-top: 46px; +} + +.w-sdc-main-right-container { + height: 100%; + overflow-y: scroll; +} + +.w-sdc-main-right-container-element { + float: left; + height: 217px; + width: 217px; + margin: 10px; + position: relative; +} + +.w-sdc-main-right-container-element-details-container { + position: absolute; + top: 165px; + left: 50px; +} + +.w-sdc-main-right-container-element-name { + font-weight: bold; +} + +.i-sdc-left-sidebar-item{ + display: flex; + &.category-title .title-text, sdc-checkbox{ + flex-grow: 1; + } + &:not(.category-title).i-sdc-left-sidebar-item-state-count { + line-height: 14px; + } +} + + +//////////////////////////////Cards//////////////////// +.w-sdc-dashboard-card-new { + border: 2px dashed @color_m; + .border-radius(2px); + cursor: pointer; + display: inline-block; + height: 198px; + margin: 11px; + position: relative; + vertical-align: middle; + width: 202px; +} + +.w-sdc-dashboard-card-new-content { + display: flex; + justify-content: center; + align-items: center; + flex-direction: column; + height: 100%; +} + +.w-sdc-dashboard-card-new-content-plus { + .sprite-new; + .add-icon; + position: relative; + margin-bottom: 20px; + + &:after { + .n_14_m; + content: 'ADD'; + position: absolute; + top: 25px; + left: -3px; + vertical-align: -50%; + } +} + +.w-sdc-dashboard-card-import-content-plus { + .sprite-new; + .import-icon; + position: relative; + margin-bottom: 20px; + + &:after { + .n_14_m; + content: 'IMPORT'; + position: absolute; + top: 25px; + left: -16px; + vertical-align: -50%; + } +} + +.sdc-dashboard-create-element-container, +.sdc-dashboard-import-element-container { + + width: 140px; + + sdc-button, + sdc-button-file-opener { + padding-bottom: 5px; + &:last-child{ + padding-bottom: 0; + } + } + + .import-file{ + position: relative; + file-opener{ + position: absolute; + top: 0; + /deep/ input[type="file"] { + .hand; + filter: alpha(opacity=0); + opacity: 0; + position: absolute; + top: 0; + left: 0; + width: 140px; + height: 36px; + } + } + } +} + + diff --git a/catalog-ui/src/app/ng2/pages/home/home.component.spec.ts b/catalog-ui/src/app/ng2/pages/home/home.component.spec.ts new file mode 100644 index 0000000000..df854024fa --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/home.component.spec.ts @@ -0,0 +1,270 @@ + +import { SdcConfigToken, ISdcConfig } from "../../config/sdc-config.config"; +import { SdcMenuToken, IAppMenu } from "../../config/sdc-menu.config"; + + +import { async, ComponentFixture, TestBed } from "@angular/core/testing"; +import { HomeComponent } from "./home.component"; +import {ConfigureFn, configureTests} from "../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import { TranslateService } from "../../shared/translator/translate.service"; +import { HomeService, CacheService, AuthenticationService, ImportVSPService } from '../../../../app/services-ng2'; +import { ModalsHandler } from "../../../../app/utils"; +import { SdcUiServices } from "onap-ui-angular"; +import {ComponentType, ResourceType} from "../../../utils/constants"; +import { FoldersMenu, FoldersItemsMenu, FoldersItemsMenuGroup } from './folders'; +import { HomeFilter } from "../../../../app/models/home-filter"; +import {Component} from "../../../models/components/component"; + + + + +describe('home component', () => { + + // const mockedEvent = <MouseEvent>{ target: {} } + let fixture: ComponentFixture<HomeComponent>; + // let eventServiceMock: Partial<EventListenerService>; + + let importVspService: Partial<ImportVSPService>; + let mockStateService; + let modalServiceMock :Partial<SdcUiServices.ModalService>; + let translateServiceMock : Partial<TranslateService>; + let foldersItemsMenuMock; + let homeFilterMock :Partial<HomeFilter>; + let foldersMock; + let loaderServiceMock; + + + beforeEach( + async(() => { + modalServiceMock = { + openWarningModal: jest.fn() + } + + mockStateService = { + // go: jest.fn().mockReturnValue( new Promise.resolve((resolve, reject )=> resolve())) + go: jest.fn() + } + + translateServiceMock = { + translate: jest.fn() + } + + homeFilterMock = { + search: jest.fn, + toUrlParam: jest.fn() + } + + foldersMock = { + setSelected: jest.fn() + } + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [HomeComponent], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: SdcConfigToken, useValue: {"csarFileExtension":"csar", "toscaFileExtension":"yaml,yml"}}, + {provide: SdcMenuToken, useValue: {}}, + {provide: "$state", useValue: mockStateService}, + {provide: HomeService, useValue: {}}, + {provide: AuthenticationService, useValue: {}}, + {provide: CacheService, useValue: {}}, + {provide: TranslateService, useValue: translateServiceMock}, + {provide: ModalsHandler, useValue: {}}, + {provide: SdcUiServices.ModalService, useValue: modalServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock}, + {provide: ImportVSPService, useValue: {}} + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(HomeComponent); + }); + }) + ); + + + it('should match current snapshot', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should call on home component openCreateModal with null imported file', () => { + const component = TestBed.createComponent(HomeComponent); + let componentType:string = 'test'; + let importedFile:any = null; + component.componentInstance.openCreateModal(componentType, importedFile); + expect(mockStateService.go).toBeCalledWith('workspace.general', {type: componentType.toLowerCase()}); + }); + + + it('should call on home component openCreateModal with imported file', () => { + const component = TestBed.createComponent(HomeComponent); + component.componentInstance.initEntities = jest.fn(); + let componentType:string = 'test'; + let importedFile:any = 'importedFile'; + component.componentInstance.openCreateModal(componentType, importedFile); + expect(component.componentInstance.initEntities).toBeCalledWith(true); + }); + + + it ('should call on home component onImportVf without file without extension', () => { + const component = TestBed.createComponent(HomeComponent); + let file:any = {filename : 'test'}; + let expectedTitle:string = translateServiceMock.translate("NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS_TITLE"); + let expectedMessage:string = translateServiceMock.translate("NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS", {"csarFileExtension":"csar"}); + component.componentInstance.onImportVf(file); + expect(modalServiceMock.openWarningModal).toBeCalledWith(expectedTitle, expectedMessage , 'error-invalid-csar-ext'); + }); + + + it ('should call on home component onImportVf with file without extension' , () => { + const component = TestBed.createComponent(HomeComponent); + let file:any = {filename : 'test.csar'}; + component.componentInstance.onImportVf(file); + expect(mockStateService.go).toBeCalledWith('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VF + }); + }); + + + it ('should call on home component onImportVfc without file without extension', () => { + const component = TestBed.createComponent(HomeComponent); + let file:any = {filename : 'test'}; + let expectedTitle:string = translateServiceMock.translate("NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS_TITLE"); + let expectedMessage:string = translateServiceMock.translate("NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS", {"toscaFileExtension":"yaml,yml"}); + component.componentInstance.onImportVfc(file); + expect(modalServiceMock.openWarningModal).toBeCalledWith(expectedTitle, expectedMessage , 'error-invalid-tosca-ext'); + }); + + it ('should call on home component onImportVfc with file without extension' , () => { + const component = TestBed.createComponent(HomeComponent); + let file:any = {filename : 'test.yml'}; + component.componentInstance.onImportVfc(file); + expect(mockStateService.go).toBeCalledWith('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VFC + }); + }); + + it ('should call on home component createPNF' , () => { + const component = TestBed.createComponent(HomeComponent); + component.componentInstance.createPNF(); + expect(mockStateService.go).toBeCalledWith('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + resourceType: ResourceType.PNF + }); + }); + + it ('should call on home component createCR' , () => { + const component = TestBed.createComponent(HomeComponent); + component.componentInstance.createCR(); + expect(mockStateService.go).toBeCalledWith('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + resourceType: ResourceType.CR + }); + }); + + + it ('should call on home component updateFilter' , () => { + const component = TestBed.createComponent(HomeComponent); + component.componentInstance.homeFilter = homeFilterMock; + component.componentInstance.filterHomeItems = jest.fn(); + component.componentInstance.updateFilter(); + + expect(mockStateService.go).toBeCalledWith('.', homeFilterMock.toUrlParam(), {location: 'replace', notify: false}); + // expect(spy).toHaveBeenCalledTimes(1); + + // let spy = spyOn(homeFilterMock, 'toUrlParam').and.returnValue({ + // 'filter.term': '', + // 'filter.distributed': '', + // 'filter.status':'' + // }); + }); + + // it ('should call on home component setSelectedFolder' , () => { + // const component = TestBed.createComponent(HomeComponent); + // let folderItem:Partial<FoldersItemsMenu> = { text:'someThing'}; + // let folderItem1:number; + // component.componentInstance.folders = foldersMock; + // expect(foldersMock.setSelected).toBeCalledWith(folderItem); + // }); + + // it ('should call on home component goToComponent' , () => { + // const component = TestBed.createComponent(HomeComponent); + // let componentParam:Partial<Component> = { uuid:'someThing', uniqueId:'uniqueID', componentType:'componentType'}; + // component.componentInstance.goToComponent(componentParam); + // expect(loaderServiceMock.activate).toHaveBeenCalled(); + // // expect(mockStateService.go).toBeCalledWith('workspace.general', {id: componentParam.uniqueId, type: componentParam.componentType.toLowerCase()}).then(function(){ + // // loaderServiceMock.deactivate(); + // // }); + // expect(mockStateService.go).toBeCalled(); + // }); + + // it ('should call on home component raiseNumberOfElementToDisplay so numberOfItemToDisplay will be 0' , () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.raiseNumberOfElementToDisplay(); + // expect(component.componentInstance.numberOfItemToDisplay).toEqual(0); + // }); + // + // it ('should call on home component raiseNumberOfElementToDisplay with min(2,70) so numberOfItemToDisplay will be 2' , () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.homeItems = ['item1', 'item2']; + // component.componentInstance.numberOfItemToDisplay = 70; + // component.componentInstance.raiseNumberOfElementToDisplay(true); + // expect(component.componentInstance.numberOfItemToDisplay).toEqual(2); + // }); + // + // it ('should call on home component raiseNumberOfElementToDisplay with min(3,35) so numberOfItemToDisplay will be 2 after fullPagesAmount is calculated' , () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.homeItems = ['item1', 'item2', 'item3']; + // component.componentInstance.numberOfItemToDisplay = 70; + // component.componentInstance.numberOfItemToDisplay = 0; + // component.componentInstance.raiseNumberOfElementToDisplay(false); + // expect(component.componentInstance.numberOfItemToDisplay).toEqual(3); + // }); + // + // + // it ('should call on home component changeFilterTerm' , () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.changeFilterTerm("testStr"); + // // expect ( "testStr" ).toEqual(homeFilterMock.search.) + // }); + + + + + + + // it ('should call on home component entitiesCount' , () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.entitiesCount("aaa"); + // expect(mockStateService.go).toBeCalledWith('workspace.general', { + // type: ComponentType.RESOURCE.toLowerCase(), + // resourceType: ResourceType.CR + // }); + // }); + + + // it('should call on home component notificationIconCallback', () => { + // const component = TestBed.createComponent(HomeComponent); + // component.componentInstance.initEntities = jest.fn(); + // component.componentInstance.notificationIconCallback(); + // expect(mockStateService.go).toBeCalledWith('workspace.general', {}); + // }); + + + + + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/home/home.component.ts b/catalog-ui/src/app/ng2/pages/home/home.component.ts new file mode 100644 index 0000000000..1b69eba929 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/home.component.ts @@ -0,0 +1,358 @@ +/*- + * ============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========================================================= + */ +'use strict'; +import { Component as NgComponent, Inject, OnInit } from '@angular/core'; +import { Component, IConfigRoles, IUserProperties, Resource } from 'app/models'; +import { HomeFilter } from 'app/models/home-filter'; +import { AuthenticationService, CacheService, HomeService } from 'app/services-ng2'; +import { ModalsHandler } from 'app/utils'; +import { SdcUiServices } from 'onap-ui-angular'; +import { CHANGE_COMPONENT_CSAR_VERSION_FLAG, ComponentType, ResourceType } from '../../../utils/constants'; +import { ImportVSPService } from '../../components/modals/onboarding-modal/import-vsp.service'; +import { ISdcConfig, SdcConfigToken } from '../../config/sdc-config.config'; +import { IAppMenu, SdcMenuToken } from '../../config/sdc-menu.config'; +import { EntityFilterPipe } from '../../pipes/entity-filter.pipe'; +import { TranslateService } from '../../shared/translator/translate.service'; +import { FoldersItemsMenu, FoldersItemsMenuGroup, FoldersMenu } from './folders'; + +@NgComponent({ + selector: 'home-page', + templateUrl: './home.component.html', + styleUrls: ['./home.component.less'] +}) +export class HomeComponent implements OnInit { + public numberOfItemToDisplay: number; + public homeItems: Component[]; + public homeFilteredItems: Component[]; + public homeFilteredSlicedItems: Component[]; + public folders: FoldersMenu; + public roles: IConfigRoles; + public user: IUserProperties; + public showTutorial: boolean; + public isFirstTime: boolean; + public version: string; + public homeFilter: HomeFilter; + public vfcmtType: string; + public displayActions: boolean; + + constructor( + @Inject(SdcConfigToken) private sdcConfig: ISdcConfig, + @Inject(SdcMenuToken) public sdcMenu: IAppMenu, + @Inject('$state') private $state: ng.ui.IStateService, + private homeService: HomeService, + private authService: AuthenticationService, + private cacheService: CacheService, + private translateService: TranslateService, + private modalsHandler: ModalsHandler, + private modalService: SdcUiServices.ModalService, + private loaderService: SdcUiServices.LoaderService, + private importVSPService: ImportVSPService + ) {} + + ngOnInit(): void { + this.initHomeComponentVars(); + this.initFolders(); + this.initEntities(); + + if (this.$state.params) { + if (this.$state.params.folder) { + const folderName = this.$state.params.folder.replaceAll('_', ' '); + + const selectedFolder = this.folders.getFolders().find((tmpFolder: FoldersItemsMenu) => tmpFolder.text === folderName); + if (selectedFolder) { + this.setSelectedFolder(selectedFolder); + } + // Show the tutorial if needed when the dashboard page is opened.<script src="bower_components/angular-filter/dist/angular-filter.min.js"></script> + // This is called from the welcome page. + } else if (this.$state.params.show === 'tutorial') { + this.showTutorial = true; + this.isFirstTime = true; + } + } + } + + // Open onboarding modal + public notificationIconCallback(): void { + this.importVSPService.openOnboardingModal().subscribe((result) => { + if (!result.previousComponent || result.previousComponent.csarVersion !== result.componentCsar.csarVersion) { + this.cacheService.set(CHANGE_COMPONENT_CSAR_VERSION_FLAG, result.componentCsar.csarVersion); + } + this.$state.go('workspace.general', { + id: result.previousComponent && result.previousComponent.uniqueId, + componentCsar: result.componentCsar, + type: result.type + }); + }); + } + + public onImportVf(file: any): void { + if (file && file.filename) { + // Check that the file has valid extension. + const fileExtension: string = file.filename.split('.').pop(); + if (this.sdcConfig.csarFileExtension.indexOf(fileExtension.toLowerCase()) !== -1) { + this.$state.go('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VF + }); + } else { + const title: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS_TITLE'); + const message: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_CSAR_EXTENSIONS', {extensions: this.sdcConfig.csarFileExtension}); + this.modalService.openWarningModal(title, message, 'error-invalid-csar-ext'); + } + } + } + + public onImportVfc(file: any): void { + if (file && file.filename) { + // Check that the file has valid extension. + const fileExtension: string = file.filename.split('.').pop(); + if (this.sdcConfig.toscaFileExtension.indexOf(fileExtension.toLowerCase()) !== -1) { + this.$state.go('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + importedFile: file, + resourceType: ResourceType.VFC + }); + } else { + const title: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS_TITLE'); + const message: string = this.translateService.translate('NEW_SERVICE_RESOURCE_ERROR_VALID_TOSCA_EXTENSIONS', {extensions: this.sdcConfig.toscaFileExtension}); + this.modalService.openWarningModal(title, message, 'error-invalid-tosca-ext'); + } + } + } + + public openCreateModal(componentType: string, importedFile: any): void { + if (importedFile) { + this.initEntities(true); // Return from import + } else { + this.$state.go('workspace.general', {type: componentType.toLowerCase()}); + } + } + + public createPNF(): void { + this.$state.go('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + resourceType: ResourceType.PNF + }); + } + + public createCR(): void { + this.$state.go('workspace.general', { + type: ComponentType.RESOURCE.toLowerCase(), + resourceType: ResourceType.CR + }); + } + + public entitiesCount(folderItem: FoldersItemsMenu): any { + let total: number = 0; + if (folderItem.isGroup()) { + this.folders.getFolders().forEach((tmpFolder: FoldersItemsMenu) => { + if (tmpFolder.group && tmpFolder.group === (folderItem as FoldersItemsMenuGroup).groupname) { + total = total + this._getTotalCounts(tmpFolder); + } + }); + } else { + total = total + this._getTotalCounts(folderItem); + } + return total; + } + + public updateFilter = () => { + this.$state.go('.', this.homeFilter.toUrlParam(), {location: 'replace', notify: false}); + this.filterHomeItems(); + } + + public getCurrentFolderDistributed(): any[] { + const states = []; + if (this.folders) { + const folderItem: FoldersItemsMenu = this.folders.getCurrentFolder(); + if (folderItem.isGroup()) { + this.folders.getFolders().forEach((tmpFolder: FoldersItemsMenu) => { + if (tmpFolder.group && tmpFolder.group === (folderItem as FoldersItemsMenuGroup).groupname) { + this._setStates(tmpFolder, states); + } + }); + } else { + this._setStates(folderItem, states); + } + } + return states; + } + + public setSelectedFolder(folderItem: FoldersItemsMenu): void { + this.folders.setSelected(folderItem); + } + + public goToComponent(component: Component): void { + const loaderService = this.loaderService; + loaderService.activate(); + this.$state.go('workspace.general', {id: component.uniqueId, type: component.componentType.toLowerCase()}).then(() => { + loaderService.deactivate(); + }); + } + + public raiseNumberOfElementToDisplay(recalculate: boolean = false) { + const scrollPageAmount = 35; + if (!this.homeItems) { + this.numberOfItemToDisplay = 0; + } else if (this.homeItems.length > this.numberOfItemToDisplay || recalculate) { + let fullPagesAmount = Math.ceil(this.numberOfItemToDisplay / scrollPageAmount) * scrollPageAmount; + if (!recalculate || fullPagesAmount === 0) { // TODO trigger infiniteScroll to check bottom and fire onBottomHit by itself (sdc-ui) + fullPagesAmount += scrollPageAmount; + } + this.numberOfItemToDisplay = Math.min(this.homeItems.length, fullPagesAmount); + this.homeFilteredSlicedItems = this.homeFilteredItems.slice(0, this.numberOfItemToDisplay); + } + } + + public changeCheckboxesFilter(checkboxesFilterArray: string[], checkboxValue: string, checked?: boolean) { + const checkboxIdx = checkboxesFilterArray.indexOf(checkboxValue); + + checked = (checked !== undefined) ? checked : checkboxIdx === -1; + if (checked && checkboxIdx === -1) { + checkboxesFilterArray.push(checkboxValue); + } else if (!checked && checkboxIdx !== -1) { + checkboxesFilterArray.splice(checkboxIdx, 1); + } + this.updateFilter(); + } + + public changeFilterTerm(filterTerm: string): void { + this.homeFilter.search = { filterTerm }; + this.updateFilter(); + } + + public setDisplayActions(display?: boolean) { + this.displayActions = display !== undefined ? display : !this.displayActions; + } + + private _getTotalCounts(tmpFolder): number { + let total: number = 0; + if (tmpFolder.dist !== undefined) { + const distributions = tmpFolder.dist.split(','); + distributions.forEach((item: any) => { + total = total + this.getEntitiesByStateDist(tmpFolder.state, item).length; + }); + } else { + total = total + this.getEntitiesByStateDist(tmpFolder.state, tmpFolder.dist).length; + } + return total; + } + + private _setStates(tmpFolder, states) { + if (tmpFolder.states !== undefined) { + tmpFolder.states.forEach((item: any) => { + states.push({state: item.state, dist: item.dist}); + }); + } else { + states.push({state: tmpFolder.state, dist: tmpFolder.dist}); + } + } + + private initEntities(reload?: boolean) { + if (reload || this.componentShouldReload()) { + this.loaderService.activate(); + this.homeService.getAllComponents(true).subscribe( + (components: Component[]) => { + this.cacheService.set('breadcrumbsComponentsState', this.$state.current.name); // dashboard + this.cacheService.set('breadcrumbsComponents', components); + this.homeItems = components; + this.loaderService.deactivate(); + this.filterHomeItems(); + }, (error) => { this.loaderService.deactivate(); }); + } else { + this.homeItems = this.cacheService.get('breadcrumbsComponents'); + this.filterHomeItems(); + } + } + + private isDefaultFilter = (): boolean => { + const defaultFilter = new HomeFilter(); + return angular.equals(defaultFilter, this.homeFilter); + } + + private componentShouldReload = (): boolean => { + const breadcrumbsValid: boolean = (this.$state.current.name === this.cacheService.get('breadcrumbsComponentsState') && this.cacheService.contains('breadcrumbsComponents')); + return !breadcrumbsValid || this.isDefaultFilter(); + } + + private getEntitiesByStateDist(state: string, dist: string): Component[] { + let gObj: Component[]; + if (this.homeItems && (state || dist)) { + gObj = this.homeItems.filter((obj: Component) => { + if (dist !== undefined && obj.distributionStatus === dist && obj.lifecycleState === state) { + return true; + } else if (dist === undefined && (obj.lifecycleState === state || obj.distributionStatus === state)) { + return true; + } + return false; + }); + } else { + gObj = []; + } + return gObj; + } + + private filterHomeItems() { + this.homeFilteredItems = this.makeFilteredItems(this.homeItems, this.homeFilter); + this.raiseNumberOfElementToDisplay(true); + this.homeFilteredSlicedItems = this.homeFilteredItems.slice(0, this.numberOfItemToDisplay); + } + + private makeFilteredItems(homeItems: Component[], filter: HomeFilter) { + let filteredComponents: Component[] = homeItems; + + // filter: exclude all resources of type 'vfcmtType': + filteredComponents = filteredComponents.filter((c) => + !c.isResource() || (c as Resource).resourceType.indexOf(this.vfcmtType) === -1); + + // common entity filter + // -------------------------------------------------------------------------- + filteredComponents = EntityFilterPipe.transform(filteredComponents, filter); + + return filteredComponents; + } + + private initFolders = (): void => { + // Note: Do not use SdcUi.ChecklistComponent for folders checkboxes, since from the data structure + // it is not determined that all checkboxes under the same group are managed by the same selectedValues array. + if (this.user) { + this.folders = new FoldersMenu(this.roles[this.user.role].folder); + } + } + + private initHomeComponentVars(): void { + this.version = this.cacheService.get('version'); + this.numberOfItemToDisplay = 0; + this.displayActions = false; + this.user = this.authService.getLoggedinUser(); + this.roles = this.sdcMenu.roles; + this.showTutorial = false; + this.isFirstTime = false; + this.vfcmtType = ResourceType.VFCMT; + + // Checkboxes filter init + this.homeFilter = new HomeFilter(this.$state.params); + + // bind callbacks that are transferred as inputs + this.notificationIconCallback = this.notificationIconCallback.bind(this); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/home/home.module.ts b/catalog-ui/src/app/ng2/pages/home/home.module.ts new file mode 100644 index 0000000000..3e7c0cd312 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/home/home.module.ts @@ -0,0 +1,31 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { HomeComponent } from "./home.component"; +import { LayoutModule } from "../../components/layout/layout.module"; +import { UiElementsModule } from "../../components/ui/ui-elements.module"; +import { GlobalPipesModule } from "../../pipes/global-pipes.module"; +import { TranslateModule } from "../../shared/translator/translate.module"; +import { SdcUiComponentsModule } from "onap-ui-angular"; + +@NgModule({ + declarations: [ + HomeComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + LayoutModule, + UiElementsModule, + GlobalPipesModule, + TranslateModule + ], + exports: [ + HomeComponent + ], + entryComponents: [ + HomeComponent + ], + providers: [] +}) +export class HomeModule { +} diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.module.ts b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.module.ts index 6292d85422..941b10f943 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.module.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.module.ts @@ -1,9 +1,9 @@ import {NgModule} from "@angular/core"; import {CommonModule} from "@angular/common"; import {InterfaceOperationComponent} from "./interface-operation.page.component"; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; import {TranslateModule} from "app/ng2/shared/translator/translate.module"; +import { SdcUiComponentsModule } from 'onap-ui-angular'; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.html b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.html index e32a0b60f5..cd06e18267 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.html +++ b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.html @@ -1,5 +1,5 @@ <!-- - ~ Copyright © 2016-2018 European Support Limited + ~ Copyright � 2016-2018 European Support Limited ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts index c2a9582ed4..9d41c375f5 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/interface-operation.page.component.ts @@ -1,14 +1,14 @@ import * as _ from "lodash"; -import {Component, Input, Output, ComponentRef, Inject} from '@angular/core'; -import {Component as IComponent} from 'app/models/components/component'; +import { Component, Input, Output, ComponentRef, Inject } from '@angular/core'; +import {Component as IComponent } from 'app/models/components/component'; -import {SdcConfigToken, ISdcConfig} from "app/ng2/config/sdc-config.config"; -import {TranslateService} from "app/ng2/shared/translator/translate.service"; +import { SdcConfigToken, ISdcConfig } from "app/ng2/config/sdc-config.config"; +import {TranslateService } from "app/ng2/shared/translator/translate.service"; -import {Observable} from "rxjs/Observable"; +import {Observable } from "rxjs/Observable"; -import {ModalComponent} from 'app/ng2/components/ui/modal/modal.component'; -import {ModalService} from 'app/ng2/services/modal.service'; +import {ModalComponent } from 'app/ng2/components/ui/modal/modal.component'; +import {ModalService } from 'app/ng2/services/modal.service'; import { InputBEModel, OperationModel, @@ -18,15 +18,19 @@ import { Capability } from 'app/models'; -import {IModalConfig, IModalButtonComponent} from "sdc-ui/lib/angular/modals/models/modal-config"; -import {SdcUiComponents} from "sdc-ui/lib/angular"; -import {ModalButtonComponent} from "sdc-ui/lib/angular/components"; +// import {SdcUiComponents } from 'sdc-ui/lib/angular'; +// import {ModalButtonComponent } from 'sdc-ui/lib/angular/components'; +// import { IModalButtonComponent, IModalConfig } from 'sdc-ui/lib/angular/modals/models/modal-config'; -import {ComponentServiceNg2} from 'app/ng2/services/component-services/component.service'; -import {WorkflowServiceNg2} from 'app/ng2/services/workflow.service'; -import {PluginsService} from "app/ng2/services/plugins.service"; +import {ComponentServiceNg2 } from 'app/ng2/services/component-services/component.service'; +import {PluginsService } from 'app/ng2/services/plugins.service'; +import {WorkflowServiceNg2 } from 'app/ng2/services/workflow.service'; -import {OperationCreatorComponent, OperationCreatorInput} from 'app/ng2/pages/interface-operation/operation-creator/operation-creator.component'; +import { OperationCreatorComponent, OperationCreatorInput } from 'app/ng2/pages/interface-operation/operation-creator/operation-creator.component'; +import { IModalButtonComponent } from 'onap-ui-angular'; +import { ModalButtonComponent } from 'onap-ui-angular'; +import { IModalConfig } from 'onap-ui-angular'; +import { SdcUiServices } from 'onap-ui-angular'; export class UIOperationModel extends OperationModel { isCollapsed: boolean = true; @@ -61,6 +65,7 @@ export class UIOperationModel extends OperationModel { } } +// tslint:disable-next-line:max-classes-per-file class ModalTranslation { CREATE_TITLE: string; EDIT_TITLE: string; @@ -74,7 +79,7 @@ class ModalTranslation { constructor(private TranslateService: TranslateService) { this.TranslateService.languageChangedObservable.subscribe(lang => { this.CREATE_TITLE = this.TranslateService.translate("INTERFACE_CREATE_TITLE"); - this.EDIT_TITLE = this.TranslateService.translate("INTERFACE_EDIT_TITLE"); + this.EDIT_TITLE = this.TranslateService.translate('INTERFACE_EDIT_TITLE'); this.DELETE_TITLE = this.TranslateService.translate("INTERFACE_DELETE_TITLE"); this.CANCEL_BUTTON = this.TranslateService.translate("INTERFACE_CANCEL_BUTTON"); this.SAVE_BUTTON = this.TranslateService.translate("INTERFACE_SAVE_BUTTON"); @@ -85,6 +90,7 @@ class ModalTranslation { } } +// tslint:disable-next-line:max-classes-per-file export class UIInterfaceModel extends InterfaceModel { isCollapsed: boolean = false; @@ -92,7 +98,7 @@ export class UIInterfaceModel extends InterfaceModel { super(interf); this.operations = _.map( this.operations, - operation => new UIOperationModel(operation) + (operation) => new UIOperationModel(operation) ); } @@ -101,6 +107,7 @@ export class UIInterfaceModel extends InterfaceModel { } } +// tslint:disable-next-line:max-classes-per-file @Component({ selector: 'interface-operation', templateUrl: './interface-operation.page.component.html', @@ -110,16 +117,16 @@ export class UIInterfaceModel extends InterfaceModel { export class InterfaceOperationComponent { - interfaces: Array<UIInterfaceModel>; + interfaces: UIInterfaceModel[]; modalInstance: ComponentRef<ModalComponent>; openOperation: OperationModel; enableWorkflowAssociation: boolean; - inputs: Array<InputBEModel>; + inputs: InputBEModel[]; isLoading: boolean; - interfaceTypes:{ [interfaceType: string]: Array<string> }; + interfaceTypes: { [interfaceType: string]: string[] }; modalTranslation: ModalTranslation; workflowIsOnline: boolean; - workflows: Array<any>; + workflows: any[]; capabilities: CapabilitiesGroup; @Input() component: IComponent; @@ -135,7 +142,8 @@ export class InterfaceOperationComponent { private ComponentServiceNg2: ComponentServiceNg2, private WorkflowServiceNg2: WorkflowServiceNg2, private ModalServiceNg2: ModalService, - private ModalServiceSdcUI: SdcUiComponents.ModalService + private ModalServiceSdcUI: SdcUiServices.ModalService + ) { this.enableWorkflowAssociation = sdcConfig.enableWorkflowAssociation; this.modalTranslation = new ModalTranslation(TranslateService); @@ -146,11 +154,11 @@ export class InterfaceOperationComponent { this.workflowIsOnline = !_.isUndefined(this.PluginsService.getPluginByStateUrl('workflowDesigner')); Observable.forkJoin( - this.ComponentServiceNg2.getInterfaces(this.component), + this.ComponentServiceNg2.getInterfaceOperations(this.component), this.ComponentServiceNg2.getComponentInputs(this.component), this.ComponentServiceNg2.getInterfaceTypes(this.component), this.ComponentServiceNg2.getCapabilitiesAndRequirements(this.component.componentType, this.component.uniqueId) - ).subscribe((response: Array<any>) => { + ).subscribe((response: any[]) => { const callback = (workflows) => { this.isLoading = false; this.initInterfaces(response[0].interfaces); @@ -174,36 +182,36 @@ export class InterfaceOperationComponent { }); } - initInterfaces(interfaces: Array<InterfaceModel>): void { - this.interfaces = _.map(interfaces, interf => new UIInterfaceModel(interf)); + initInterfaces(interfaces: InterfaceModel[]): void { + this.interfaces = _.map(interfaces, (interf) => new UIInterfaceModel(interf)); } sortInterfaces(): void { - this.interfaces = _.filter(this.interfaces, interf => interf.operations && interf.operations.length > 0); // remove empty interfaces + this.interfaces = _.filter(this.interfaces, (interf) => interf.operations && interf.operations.length > 0); // remove empty interfaces this.interfaces.sort((a, b) => a.type.localeCompare(b.type)); // sort interfaces alphabetically - _.forEach(this.interfaces, interf => { + _.forEach(this.interfaces, (interf) => { interf.operations.sort((a, b) => a.name.localeCompare(b.name)); // sort operations alphabetically }); } collapseAll(value: boolean = true): void { - _.forEach(this.interfaces, interf => { + _.forEach(this.interfaces, (interf) => { interf.isCollapsed = value; }); } isAllCollapsed(): boolean { - return _.every(this.interfaces, interf => interf.isCollapsed); + return _.every(this.interfaces, (interf) => interf.isCollapsed); } isAllExpanded(): boolean { - return _.every(this.interfaces, interf => !interf.isCollapsed); + return _.every(this.interfaces, (interf) => !interf.isCollapsed); } isListEmpty(): boolean { return _.filter( this.interfaces, - interf => interf.operations && interf.operations.length > 0 + (interf) => interf.operations && interf.operations.length > 0 ).length === 0; } @@ -291,11 +299,6 @@ export class InterfaceOperationComponent { } - private enableOrDisableSaveButton = (shouldEnable: boolean): void => { - let saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton'); - saveButton.disabled = !shouldEnable; - } - onRemoveOperation = (event: Event, operation: OperationModel): void => { event.stopPropagation(); @@ -303,11 +306,11 @@ export class InterfaceOperationComponent { this.ComponentServiceNg2 .deleteInterfaceOperation(this.component, operation) .subscribe(() => { - const curInterf = _.find(this.interfaces, interf => interf.type === operation.interfaceType); - const index = _.findIndex(curInterf.operations, el => el.uniqueId === operation.uniqueId); + const curInterf = _.find(this.interfaces, (interf) => interf.type === operation.interfaceType); + const index = _.findIndex(curInterf.operations, (el) => el.uniqueId === operation.uniqueId); curInterf.operations.splice(index, 1); if (!curInterf.operations.length) { - const interfIndex = _.findIndex(this.interfaces, interf => interf.type === operation.interfaceType); + const interfIndex = _.findIndex(this.interfaces, (interf) => interf.type === operation.interfaceType); this.interfaces.splice(interfIndex, 1); } }); @@ -322,13 +325,18 @@ export class InterfaceOperationComponent { ); } + private enableOrDisableSaveButton = (shouldEnable: boolean): void => { + const saveButton: ModalButtonComponent = this.ModalServiceSdcUI.getCurrentInstance().getButtonById('saveButton'); + saveButton.disabled = !shouldEnable; + } + private createOperation = (operation: OperationModel): void => { this.ComponentServiceNg2.createInterfaceOperation(this.component, operation).subscribe((response: OperationModel) => { this.openOperation = null; let curInterf = _.find( this.interfaces, - interf => interf.type === operation.interfaceType + (interf) => interf.type === operation.interfaceType ); if (!curInterf) { @@ -358,18 +366,19 @@ export class InterfaceOperationComponent { this.ComponentServiceNg2.updateInterfaceOperation(this.component, operation).subscribe((newOperation: OperationModel) => { this.openOperation = null; - let oldOpIndex, oldInterf; - _.forEach(this.interfaces, interf => { - _.forEach(interf.operations, op => { + let oldOpIndex; + let oldInterf; + _.forEach(this.interfaces, (interf) => { + _.forEach(interf.operations, (op) => { if (op.uniqueId === newOperation.uniqueId) { oldInterf = interf; - oldOpIndex = _.findIndex(interf.operations, el => el.uniqueId === op.uniqueId); + oldOpIndex = _.findIndex(interf.operations, (el) => el.uniqueId === op.uniqueId); } }) }); oldInterf.operations.splice(oldOpIndex, 1); - const newInterf = _.find(this.interfaces, interf => interf.type === operation.interfaceType); + const newInterf = _.find(this.interfaces, (interf) => interf.type === operation.interfaceType); const newOpModel = new UIOperationModel(newOperation); newInterf.operations.push(newOpModel); this.sortInterfaces(); diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.html b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.html index ec056ad6f2..df2a505fe8 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.html +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.html @@ -180,6 +180,9 @@ <span class="bold-message">{{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_1' | translate }}</span> <span>{{ 'EMPTY_PARAM_TABLE_NO_SELECTED_WORKFLOW_2' | translate }}</span> </div> + <div *ngIf="!workflows.length"> + Only <span class="bold-message">certified</span> workflow versions can be assigned to an operation + </div> </div> </div> diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.less b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.less index f2bd0f82af..2721d300c4 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.less +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.less @@ -11,7 +11,7 @@ font-size: 12px; } - .w-sdc-form .form-item { + .w-sdc-form .i-sdc-form-item { margin-bottom: 15px; } diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.ts b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.ts index e12905654b..12fba24e86 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.component.ts @@ -15,9 +15,9 @@ import { Capability } from 'app/models'; -import {IDropDownOption} from "sdc-ui/lib/angular/form-elements/dropdown/dropdown-models"; import {Tabs, Tab} from "app/ng2/components/ui/tabs/tabs.component"; import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import { IDropDownOption } from 'onap-ui-angular'; export class DropDownOption implements IDropDownOption { value: string; diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.module.ts b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.module.ts index 0b6f8336c3..b91f3aa4e3 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.module.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/operation-creator.module.ts @@ -3,10 +3,10 @@ import {CommonModule} from "@angular/common"; import {FormsModule} from "@angular/forms"; import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; import {TranslateModule} from "app/ng2/shared/translator/translate.module"; +import { SdcUiComponentsModule } from 'onap-ui-angular'; +import { UiElementsModule } from '../../../components/ui/ui-elements.module'; import {OperationCreatorComponent} from "./operation-creator.component"; import {ParamRowComponent} from './param-row/param-row.component'; diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.html b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.html index 4a4782eaee..b8173eaf15 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.html +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.html @@ -1,5 +1,5 @@ <!-- - ~ Copyright © 2016-2018 European Support Limited + ~ Copyright © 2016-2018 European Support Limited ~ ~ Licensed under the Apache License, Version 2.0 (the "License"); ~ you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - + <div class="cell field-name"> <ui-element-input *ngIf="!isAssociateWorkflow" diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.less b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.less index f6cda17777..5447fe532b 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.less +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.less @@ -32,6 +32,7 @@ input { height: 30px; + border: none; padding-left: 10px; } diff --git a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.ts b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.ts index d32edc78af..de6e703404 100644 --- a/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.ts +++ b/catalog-ui/src/app/ng2/pages/interface-operation/operation-creator/param-row/param-row.component.ts @@ -1,4 +1,5 @@ import {Component, Input} from '@angular/core'; +import {PROPERTY_DATA} from "app/utils"; import {DataTypeService} from "app/ng2/services/data-type.service"; import {OperationModel, OperationParameter, InputBEModel, DataTypeModel, Capability} from 'app/models'; import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; @@ -36,7 +37,7 @@ export class ParamRowComponent { filteredInputProps: Array<DropdownValue> = []; filteredCapabilitiesProps: Array<{capabilityName: string, properties: Array<DropdownValueType>}> = []; - constructor(private dataTypeService: DataTypeService) {} + constructor(private dataTypeService:DataTypeService) {} ngOnInit() { if (this.isInputParam) { diff --git a/catalog-ui/src/app/ng2/pages/page404/page404.component.html b/catalog-ui/src/app/ng2/pages/page404/page404.component.html index 278ab4d551..a9335a59c4 100644 --- a/catalog-ui/src/app/ng2/pages/page404/page404.component.html +++ b/catalog-ui/src/app/ng2/pages/page404/page404.component.html @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <div class="page404"> Page404 </div> diff --git a/catalog-ui/src/app/ng2/pages/plugin-not-connected/plugin-not-connected.component.html b/catalog-ui/src/app/ng2/pages/plugin-not-connected/plugin-not-connected.component.html index 98e896f4dc..0f8aeb3a13 100644 --- a/catalog-ui/src/app/ng2/pages/plugin-not-connected/plugin-not-connected.component.html +++ b/catalog-ui/src/app/ng2/pages/plugin-not-connected/plugin-not-connected.component.html @@ -13,8 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - - <div class="plugin-not-connected"> <div class="plugin-error-message"> <div class="icon-wrapper"> diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.html b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.html new file mode 100644 index 0000000000..85e83c4310 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.html @@ -0,0 +1,4 @@ +<div class="workspace-plugins"> + <plugin-frame (onLoadingDone)="onLoadingDone(plugin)" [plugin]="plugin" [queryParams]="queryParams"></plugin-frame> + <loader [display]="isLoading && plugin.isOnline" ></loader> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.less b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.less new file mode 100644 index 0000000000..c913af1931 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.less @@ -0,0 +1,2 @@ +.workspace-plugins { +} diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.ts b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.ts new file mode 100644 index 0000000000..21aa8584d5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-context-view/plugin-context-view.page.component.ts @@ -0,0 +1,58 @@ +import {Component, Inject} from "@angular/core"; +import {Component as ComponentData, IUserProperties, Plugin} from "app/models"; +import {CacheService, PluginsService} from "app/services-ng2"; + + +@Component({ + selector: 'plugin-context-view', + templateUrl: './plugin-context-view.page.component.html', + styleUrls: ['./plugin-context-view.page.component.less'] +}) + +export class PluginContextViewPageComponent { + plugin: Plugin; + user: IUserProperties; + queryParams: Object; + isLoading: boolean; + show: boolean; + component: ComponentData; + + constructor(@Inject("$stateParams") private _stateParams, + private cacheService: CacheService, + private pluginsService: PluginsService) { + + this.show = false; + this.component = this._stateParams.component; + this.plugin = this.pluginsService.getPluginByStateUrl(_stateParams.path); + this.user = this.cacheService.get('user'); + } + + ngOnInit() { + this.isLoading = true; + + this.queryParams = { + userId: this.user.userId, + userRole: this.user.role, + displayType: "context", + contextType: this.component.getComponentSubType(), + uuid: this.component.uuid, + lifecycleState: this.component.lifecycleState, + isOwner: this.component.lastUpdaterUserId === this.user.userId, + version: this.component.version, + parentUrl: window.location.origin, + eventsClientId: this.plugin.pluginId + }; + + if (this._stateParams.queryParams) { + _.assign(this.queryParams, this._stateParams.queryParams); + } + } + + onLoadingDone(plugin: Plugin) { + if (plugin.pluginId == this.plugin.pluginId) { + this.isLoading = false; + } + } + + +} 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/plugins/plugin-tab-view/plugin-tab-view.page.component.html index 482de5eacf..5ce95d11f8 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/groups/group-tabs.component.html +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.html @@ -12,16 +12,9 @@ ~ 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> +--> +<div class="sdc-catalog-container plugins-tab-container"> + <top-nav [version]="version" [hideSearch]="true"></top-nav> + <plugin-frame (onLoadingDone)="onLoadingDone(plugin)" [plugin]="plugin" [queryParams]="queryParams"></plugin-frame> + <loader [display]="isLoading"></loader> +</div> diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.less b/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.less new file mode 100644 index 0000000000..3cb5d1b421 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.less @@ -0,0 +1,2 @@ +.plugins-tab-container { +} diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.ts b/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.ts new file mode 100644 index 0000000000..7ba8474569 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugin-tab-view/plugin-tab-view.page.component.ts @@ -0,0 +1,45 @@ +import {Component, Inject} from "@angular/core"; +import {IUserProperties, Plugin} from "app/models"; +import {CacheService, PluginsService} from "app/services-ng2"; + +@Component({ + selector: 'plugin-tab-view', + templateUrl: './plugin-tab-view.page.component.html', + styleUrls: ['./plugin-tab-view.page.component.less'] +}) + +export class PluginTabViewPageComponent { + plugin: Plugin; + user: IUserProperties; + version: string; + queryParams: Object; + isLoading: boolean; + + constructor(@Inject("$stateParams") private _stateParams, + private cacheService: CacheService, + private pluginsService: PluginsService) { + + this.plugin = this.pluginsService.getPluginByStateUrl(_stateParams.path); + this.version = this.cacheService.get('version'); + this.user = this.cacheService.get('user'); + } + + ngOnInit() { + this.isLoading = true; + + this.queryParams = { + userId: this.user.userId, + userRole: this.user.role, + displayType: "tab", + parentUrl: window.location.origin, + eventsClientId: this.plugin.pluginId + }; + + } + + onLoadingDone(plugin: Plugin) { + if (plugin.pluginId == this.plugin.pluginId) { + this.isLoading = false; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/plugins/plugins-module.ts b/catalog-ui/src/app/ng2/pages/plugins/plugins-module.ts new file mode 100644 index 0000000000..763e329789 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/plugins/plugins-module.ts @@ -0,0 +1,34 @@ +import {NgModule} from "@angular/core"; +import {PluginContextViewPageComponent} from "./plugin-context-view/plugin-context-view.page.component"; +import {PluginFrameModule} from "../../components/ui/plugin/plugin-frame.module"; +import {CommonModule} from "@angular/common"; +import {UiElementsModule} from "../../components/ui/ui-elements.module"; +import {PluginTabViewPageComponent} from "./plugin-tab-view/plugin-tab-view.page.component"; +import {LayoutModule} from "../../components/layout/layout.module"; +import {HttpModule} from "@angular/http"; + +@NgModule({ + declarations: [ + PluginContextViewPageComponent, + PluginTabViewPageComponent + ], + imports: [ + CommonModule, + PluginFrameModule, + UiElementsModule, + LayoutModule, + HttpModule + ], + exports: [ + PluginContextViewPageComponent, + PluginTabViewPageComponent + ], + entryComponents: [ + PluginContextViewPageComponent, + PluginTabViewPageComponent + ] +}) +export class PluginsModule { + +} + diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.component.ts index 20e04f84b6..fe3106649b 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.component.ts @@ -18,97 +18,95 @@ * ============LICENSE_END========================================================= */ -import * as _ from "lodash"; -import {Component} from '@angular/core'; -import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; -import { DataTypeService } from "app/ng2/services/data-type.service"; -import {PropertyBEModel, DataTypesMap} from "app/models"; -import {PROPERTY_DATA} from "app/utils"; -import {PROPERTY_TYPES} from "../../../../utils"; -import { ModalService } from "app/ng2/services/modal.service"; -import { InstancePropertiesAPIMap } from "app/models/properties-inputs/property-fe-map"; -import { ModalModel } from "app/models/modal"; -import { DataTypeModel } from "app/models/data-types"; - - +import { Component } from '@angular/core'; +import { DataTypesMap, PropertyBEModel } from 'app/models'; +import { DataTypeModel } from 'app/models/data-types'; +import { ModalModel } from 'app/models/modal'; +import { InstancePropertiesAPIMap } from 'app/models/properties-inputs/property-fe-map'; +import { DropdownValue } from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component'; +import { DataTypeService } from 'app/ng2/services/data-type.service'; +import { ModalService } from 'app/ng2/services/modal.service'; +import { PROPERTY_DATA } from 'app/utils'; +import * as _ from 'lodash'; +import { PROPERTY_TYPES } from '../../../../utils'; @Component({ selector: 'declare-list', templateUrl: './declare-list.component.html', - styleUrls:['./declare-list.component.less'], + styleUrls: ['./declare-list.component.less'], }) export class DeclareListComponent { - typesProperties: Array<DropdownValue>; - typesSchemaProperties: Array<DropdownValue>; + typesProperties: DropdownValue[]; + typesSchemaProperties: DropdownValue[]; propertyModel: PropertyBEModel; - //propertyNameValidationPattern:RegExp = /^[a-zA-Z0-9_:-]{1,50}$/; - //commentValidationPattern:RegExp = /^[\u0000-\u00BF]*$/; - //types:Array<string>; - dataTypes:DataTypesMap; - isLoading:boolean; - inputsToCreate:InstancePropertiesAPIMap; - propertiesListString:string; + // propertyNameValidationPattern:RegExp = /^[a-zA-Z0-9_:-]{1,50}$/; + // commentValidationPattern:RegExp = /^[\u0000-\u00BF]*$/; + // types:Array<string>; + dataTypes: DataTypesMap; + isLoading: boolean; + inputsToCreate: InstancePropertiesAPIMap; + propertiesListString: string; privateDataType: DataTypeModel; - constructor(protected dataTypeService:DataTypeService, private modalService:ModalService) {} + constructor(protected dataTypeService: DataTypeService, private modalService: ModalService) {} ngOnInit() { console.log('DeclareListComponent.ngOnInit() - enter'); this.propertyModel = new PropertyBEModel(); this.propertyModel.type = ''; this.propertyModel.schema.property.type = ''; - const types: Array<string> = PROPERTY_DATA.TYPES; //All types - simple type + map + list - this.dataTypes = this.dataTypeService.getAllDataTypes(); //Get all data types in service - const nonPrimitiveTypes :Array<string> = _.filter(Object.keys(this.dataTypes), (type:string)=> { - return types.indexOf(type) == -1; + const types: string[] = PROPERTY_DATA.TYPES; // All types - simple type + map + list + this.dataTypes = this.dataTypeService.getAllDataTypes(); // Get all data types in service + const nonPrimitiveTypes: string[] = _.filter(Object.keys(this.dataTypes), (type: string) => { + return types.indexOf(type) === -1; }); this.typesProperties = _.map(PROPERTY_DATA.TYPES, (type: string) => new DropdownValue(type, type) ); - let typesSimpleProperties = _.map(PROPERTY_DATA.SIMPLE_TYPES, + const typesSimpleProperties = _.map(PROPERTY_DATA.SIMPLE_TYPES, (type: string) => new DropdownValue(type, type) ); - let nonPrimitiveTypesValues = _.map(nonPrimitiveTypes, + const nonPrimitiveTypesValues = _.map(nonPrimitiveTypes, (type: string) => new DropdownValue(type, - type.replace("org.openecomp.datatypes.heat.","")) + type.replace('org.openecomp.datatypes.heat.',"")) ); - this.typesProperties = _.concat(this.typesProperties,nonPrimitiveTypesValues); - this.typesSchemaProperties = _.concat(typesSimpleProperties,nonPrimitiveTypesValues); - this.typesProperties.unshift(new DropdownValue('','Select Type...')); - this.typesSchemaProperties.unshift(new DropdownValue('','Select Schema Type...')); + this.typesProperties = _.concat(this.typesProperties, nonPrimitiveTypesValues); + this.typesSchemaProperties = _.concat(typesSimpleProperties, nonPrimitiveTypesValues); + this.typesProperties.unshift(new DropdownValue('', 'Select Type...')); + this.typesSchemaProperties.unshift(new DropdownValue('', 'Select Schema Type...')); this.inputsToCreate = this.modalService.currentModal.instance.dynamicContent.instance.input.properties; this.propertiesListString = this.modalService.currentModal.instance.dynamicContent.instance.input.propertyNameList.join(", "); this.privateDataType = new DataTypeModel(null); - this.privateDataType.name = "datatype"; + this.privateDataType.name = 'datatype'; console.log('DeclareListComponent.ngOnInit() - leave'); } - checkFormValidForSubmit(){ - const showSchema:boolean = this.showSchema(); - let isSchemaValid: boolean = (showSchema && !this.propertyModel.schema.property.type)? false : true; - if (!showSchema){ + checkFormValidForSubmit() { + const showSchema: boolean = this.showSchema(); + const isSchemaValid: boolean = (showSchema && !this.propertyModel.schema.property.type) ? false : true; + if (!showSchema) { this.propertyModel.schema.property.type = ''; } return this.propertyModel.name && this.propertyModel.type && isSchemaValid; } - showSchema():boolean { + showSchema(): boolean { return [PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP].indexOf(this.propertyModel.type) > -1; - }; + } - onSchemaTypeChange():void { - if (this.propertyModel.type == PROPERTY_TYPES.MAP) { + onSchemaTypeChange(): void { + if (this.propertyModel.type === PROPERTY_TYPES.MAP) { this.propertyModel.value = JSON.stringify({'': null}); - } else if (this.propertyModel.type == PROPERTY_TYPES.LIST) { + } else if (this.propertyModel.type === PROPERTY_TYPES.LIST) { this.propertyModel.value = JSON.stringify([]); } - }; + } } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.module.ts index 54af76a9f5..97667f9261 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.module.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/declare-list/declare-list.module.ts @@ -18,13 +18,13 @@ * ============LICENSE_END========================================================= */ -import {NgModule} from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {DeclareListComponent} from "./declare-list.component"; -import {FormsModule} from "@angular/forms"; -import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {TranslateModule} from "../../../shared/translator/translate.module"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from '../../../shared/translator/translate.module'; +import { DeclareListComponent } from './declare-list.component'; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts index c46d617b86..f5500d42ae 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.module.ts @@ -19,7 +19,6 @@ */ import { NgModule } from "@angular/core"; import {HierarchyNavigationComponent} from "../../components/logic/hierarchy-navigtion/hierarchy-navigation.component"; -import {HttpModule} from "@angular/http"; import {FormsModule} from "@angular/forms"; import {PropertyTableModule} from "../../components/logic/properties-table/property-table.module"; import {UiElementsModule} from "../../components/ui/ui-elements.module"; @@ -46,12 +45,11 @@ import {ComponentModeService} from "../../services/component-services/component- imports: [ BrowserModule, FormsModule, - HttpModule, GlobalPipesModule, PropertyTableModule, PoliciesTableModule, UiElementsModule], - + entryComponents: [PropertiesAssignmentComponent], exports: [ PropertiesAssignmentComponent diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html index 580c36284b..8d4215aaec 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.html @@ -18,7 +18,7 @@ <div class="main-content"> <div class="left-column"> <div class="main-tabs-section"> - <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true"> + <tabs #propertyInputTabs tabStyle="round-tabs" (tabChanged)="tabChanged($event)" [hideIndicationOnTabChange]="true" > <tab tabTitle="Properties"> <properties-table class="properties-table" [fePropertiesMap]="instanceFePropertiesMap" @@ -42,12 +42,13 @@ </tab> <tab tabTitle="Inputs"> <inputs-table class="properties-table" - [readonly]="isReadonly" - [inputs]="inputs | searchFilter:'name':searchQuery" - [instanceNamesMap]="componentInstanceNamesMap" - [isLoading]="loadingInputs" - (deleteInput)="deleteInput($event)" - (inputChanged)="dataChanged($event)"> + [fePropertiesMap]="instanceFePropertiesMap" + [readonly]="isReadonly" + [inputs]="inputs | searchFilter:'name':searchQuery" + [instanceNamesMap]="componentInstanceNamesMap" + [isLoading]="loadingInputs" + (deleteInput)="deleteInput($event)" + (inputChanged)="dataChanged($event)"> </inputs-table> </tab> <tab tabTitle="Policies"> diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less index 855bdc5bcb..a1309aba61 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.less @@ -133,13 +133,12 @@ flex-direction:column; margin: 0px 0 0 1em; overflow-x:auto; - .add-btn{ + .add-btn{ align-self: flex-end; margin-top: 10px; margin-bottom: 19px; } - /deep/ .tabs { border-bottom: solid 1px #d0d0d0; } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts index 061439800f..4b84f0e66f 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/properties-assignment.page.component.ts @@ -19,58 +19,34 @@ */ import * as _ from "lodash"; -import {Component, ViewChild, Inject, TemplateRef} from "@angular/core"; +import { Component, ViewChild, Inject, TemplateRef } from "@angular/core"; import { PropertiesService } from "../../services/properties.service"; -import { - PropertyFEModel, - InstanceFePropertiesMap, - InstanceBePropertiesMap, - InstancePropertiesAPIMap, - Component as ComponentData, - FilterPropertiesAssignmentData, - ModalModel, - ButtonModel, - Capability, - ToscaPresentationData -} from "app/models"; +import { PropertyFEModel, InstanceFePropertiesMap, InstanceBePropertiesMap, InstancePropertiesAPIMap, Component as ComponentData, FilterPropertiesAssignmentData, ModalModel, ButtonModel } from "app/models"; import { ResourceType } from "app/utils"; -import {ComponentServiceNg2} from "../../services/component-services/component.service"; -import {ComponentInstanceServiceNg2} from "../../services/component-instance-services/component-instance.service" -import { - InputBEModel, - InputFEModel, - ComponentInstance, - GroupInstance, - PolicyInstance, - PropertyBEModel, - DerivedFEProperty, - SimpleFlatProperty, - CapabilitiesGroup -} from "app/models"; +import { ComponentServiceNg2 } from "../../services/component-services/component.service"; +import { TopologyTemplateService } from "../../services/component-services/topology-template.service"; +import { ComponentInstanceServiceNg2 } from "../../services/component-instance-services/component-instance.service" +import { InputBEModel, InputFEModel, ComponentInstance, GroupInstance, PolicyInstance, PropertyBEModel, DerivedFEProperty, SimpleFlatProperty } from "app/models"; import { KeysPipe } from 'app/ng2/pipes/keys.pipe'; -import {WorkspaceMode, EVENTS} from "../../../utils/constants"; -import {EventListenerService} from "app/services/event-listener-service" -import {HierarchyDisplayOptions} from "../../components/logic/hierarchy-navigtion/hierarchy-display-options"; -import {FilterPropertiesAssignmentComponent} from "../../components/logic/filter-properties-assignment/filter-properties-assignment.component"; -import {PropertyRowSelectedEvent} from "../../components/logic/properties-table/properties-table.component"; -import {HierarchyNavService} from "./services/hierarchy-nav.service"; -import {PropertiesUtils} from "./services/properties.utils"; -import {ComponentModeService} from "../../services/component-services/component-mode.service"; -import {ModalService} from "../../services/modal.service"; -import {Tabs, Tab} from "../../components/ui/tabs/tabs.component"; -import {InputsUtils} from "./services/inputs.utils"; -import {PropertyCreatorComponent} from "./property-creator/property-creator.component"; -import {DeclareListComponent} from "./declare-list/declare-list.component"; +import { WorkspaceMode, EVENTS, PROPERTY_TYPES } from "../../../utils/constants"; +import { EventListenerService } from "app/services/event-listener-service" +import { HierarchyDisplayOptions } from "../../components/logic/hierarchy-navigtion/hierarchy-display-options"; +import { FilterPropertiesAssignmentComponent } from "../../components/logic/filter-properties-assignment/filter-properties-assignment.component"; +import { PropertyRowSelectedEvent } from "../../components/logic/properties-table/properties-table.component"; +import { HierarchyNavService } from "./services/hierarchy-nav.service"; +import { PropertiesUtils } from "./services/properties.utils"; +import { ComponentModeService } from "../../services/component-services/component-mode.service"; +import { Tabs, Tab } from "../../components/ui/tabs/tabs.component"; +import { InputsUtils } from "./services/inputs.utils"; import { InstanceFeDetails } from "../../../models/instance-fe-details"; -import { SdcUiComponents } from "sdc-ui/lib/angular"; -//import { ModalService as ModalServiceSdcUI} from "sdc-ui/lib/angular/modals/modal.service"; -import { IModalButtonComponent } from "sdc-ui/lib/angular/modals/models/modal-config"; +import { SdcUiServices, SdcUiCommon } from "onap-ui-angular"; import { UnsavedChangesComponent } from "app/ng2/components/ui/forms/unsaved-changes/unsaved-changes.component"; -import {Observable} from "rxjs"; -import { DataTypeService } from "app/ng2/services/data-type.service"; -import { DataTypeModel } from "app/models"; -import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils"; -import { PropertyDeclareAPIModel} from "app/models"; +import {PropertyCreatorComponent} from "./property-creator/property-creator.component"; +import {ModalService} from "../../services/modal.service"; +import { DeclareListComponent } from "./declare-list/declare-list.component"; +import { CapabilitiesGroup, Capability } from "../../../models/capability"; +import { ToscaPresentationData } from "../../../models/tosca-presentation"; +import { Observable } from "rxjs"; const SERVICE_SELF_TITLE = "SELF"; @Component({ @@ -119,7 +95,7 @@ export class PropertiesAssignmentComponent { stateChangeStartUnregister:Function; serviceBePropertiesMap: InstanceBePropertiesMap; serviceBeCapabilitiesPropertiesMap: InstanceBePropertiesMap; - selectedInstance_FlattenCapabilitiesList: Array<Capability>; + selectedInstance_FlattenCapabilitiesList: Capability[]; @ViewChild('hierarchyNavTabs') hierarchyNavTabs: Tabs; @ViewChild('propertyInputTabs') propertyInputTabs: Tabs; @@ -136,12 +112,13 @@ export class PropertiesAssignmentComponent { @Inject("$state") private $state:ng.ui.IStateService, @Inject("Notification") private Notification:any, private componentModeService:ComponentModeService, - private ModalService:ModalService, private EventListenerService:EventListenerService, - private ModalServiceSdcUI: SdcUiComponents.ModalService) { + private ModalServiceSdcUI: SdcUiServices.ModalService, + private ModalService: ModalService, + private keysPipe:KeysPipe, + private topologyTemplateService: TopologyTemplateService) { this.instanceFePropertiesMap = new InstanceFePropertiesMap(); - /* This is the way you can access the component data, please do not use any data except metadata, all other data should be received from the new api calls on the first time than if the data is already exist, no need to call the api again - Ask orit if you have any questions*/ this.component = _stateParams.component; @@ -159,8 +136,8 @@ export class PropertiesAssignmentComponent { this.loadingPolicies = true; this.loadingInstances = true; this.loadingProperties = true; - this.componentServiceNg2 - .getComponentInputsWithProperties(this.component) + this.topologyTemplateService + .getComponentInputsWithProperties(this.component.componentType, this.component.uniqueId) .subscribe(response => { _.forEach(response.inputs, (input: InputBEModel) => { const newInput: InputFEModel = new InputFEModel(input); @@ -169,7 +146,7 @@ export class PropertiesAssignmentComponent { }); this.loadingInputs = false; - }); + }, error => {}); //ignore error this.componentServiceNg2 .getComponentResourcePropertiesData(this.component) .subscribe(response => { @@ -177,6 +154,7 @@ export class PropertiesAssignmentComponent { this.instances = []; this.instances.push(...response.componentInstances); this.instances.push(...response.groupInstances); + this.instances.push(...response.policies); _.forEach(response.policies, (policy: any) => { const newPolicy: InputFEModel = new InputFEModel(policy); @@ -199,7 +177,7 @@ export class PropertiesAssignmentComponent { this.loadingProperties = false; } this.selectFirstInstanceByDefault(); - }); + }, error => { this.loadingInstances = false; }); //ignore error this.stateChangeStartUnregister = this.$scope.$on('$stateChangeStart', (event, toState, toParams) => { // stop if has changed properties @@ -238,14 +216,14 @@ export class PropertiesAssignmentComponent { getServiceProperties(){ this.loadingProperties = false; - this.componentServiceNg2 - .getServiceProperties(this.component) - .subscribe(response => { + this.topologyTemplateService + .getServiceProperties(this.component.uniqueId) + .subscribe((response) => { this.serviceBePropertiesMap = new InstanceBePropertiesMap(); this.serviceBePropertiesMap[this.component.uniqueId] = response; this.processInstancePropertiesResponse(this.serviceBePropertiesMap, false); this.loadingProperties = false; - }, error => { + }, (error) => { this.loadingProperties = false; }); } @@ -267,14 +245,12 @@ export class PropertiesAssignmentComponent { this.loadingProperties = true; if (instance instanceof ComponentInstance) { let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); - this.selectedInstance_FlattenCapabilitiesList = instance.capabilities ? CapabilitiesGroup.getFlattenedCapabilities(instance.capabilities) : []; if (this.isInput(instance.originType)) { this.componentInstanceServiceNg2 .getComponentInstanceInputs(this.component, instance) .subscribe(response => { instanceBePropertiesMap[instance.uniqueId] = response; this.processInstancePropertiesResponse(instanceBePropertiesMap, true); - this.processInstanceCapabilitiesPropertiesResponse(false); this.loadingProperties = false; }, error => { }); //ignore error @@ -286,7 +262,6 @@ export class PropertiesAssignmentComponent { .subscribe(response => { instanceBePropertiesMap[instance.uniqueId] = response; this.processInstancePropertiesResponse(instanceBePropertiesMap, false); - this.processInstanceCapabilitiesPropertiesResponse(false); this.loadingProperties = false; }, error => { }); //ignore error @@ -305,7 +280,7 @@ export class PropertiesAssignmentComponent { } else if (instance instanceof PolicyInstance) { let instanceBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); this.componentInstanceServiceNg2 - .getComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId) + .getComponentPolicyInstanceProperties(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId) .subscribe((response) => { instanceBePropertiesMap[instance.uniqueId] = response; this.processInstancePropertiesResponse(instanceBePropertiesMap, false); @@ -480,7 +455,7 @@ export class PropertiesAssignmentComponent { let selectedGroupInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap(); let selectedPolicyInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap(); let selectedComponentInstancesInputs: InstanceBePropertiesMap = new InstanceBePropertiesMap(); - let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []); + let instancesIds = this.keysPipe.transform(this.instanceFePropertiesMap, []); angular.forEach(instancesIds, (instanceId: string): void => { let selectedInstanceData: any = this.instances.find(instance => instance.uniqueId == instanceId); @@ -500,7 +475,7 @@ export class PropertiesAssignmentComponent { let inputsToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(selectedComponentInstancesInputs, selectedComponentInstancesProperties, selectedGroupInstancesProperties, selectedPolicyInstancesProperties); - //move changed capabilities properties from componentInstanceInputsMap obj to componentInstanceProperties + //move changed capabilities properties from componentInstanceInputsMap obj to componentInstanceProperties inputsToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] = (inputsToCreate.componentInstanceProperties[this.selectedInstanceData.uniqueId] || []).concat( _.filter( @@ -526,15 +501,14 @@ export class PropertiesAssignmentComponent { } } ); - - this.componentServiceNg2 + this.topologyTemplateService .createInput(this.component, inputsToCreate, this.isSelf()) - .subscribe(response => { + .subscribe((response) => { this.setInputTabIndication(response.length); this.checkedPropertiesCount = 0; this.checkedChildPropertiesCount = 0; _.forEach(response, (input: InputBEModel) => { - let newInput: InputFEModel = new InputFEModel(input); + const newInput: InputFEModel = new InputFEModel(input); this.inputsUtils.resetInputDefaultValue(newInput, input.defaultValue); this.inputs.push(newInput); this.updatePropertyValueAfterDeclare(newInput); @@ -628,8 +602,8 @@ export class PropertiesAssignmentComponent { }; console.log("save button clicked. input=", input); - this.componentServiceNg2 - .createListInput(this.component, input, this.isSelf()) + this.topologyTemplateService + .createListInput(this.component.uniqueId, input, this.isSelf()) .subscribe(response => { this.setInputTabIndication(response.length); this.checkedPropertiesCount = 0; @@ -662,8 +636,8 @@ export class PropertiesAssignmentComponent { console.log('declareListProperties() - leave'); }; - /*** DECLARE PROPERTIES/POLICIES ***/ - declarePropertiesToPolicies = (): void => { + /*** DECLARE PROPERTIES/POLICIES ***/ + declarePropertiesToPolicies = (): void => { let selectedComponentInstancesProperties: InstanceBePropertiesMap = new InstanceBePropertiesMap(); let instancesIds = new KeysPipe().transform(this.instanceFePropertiesMap, []); @@ -679,7 +653,7 @@ export class PropertiesAssignmentComponent { let policiesToCreate: InstancePropertiesAPIMap = new InstancePropertiesAPIMap(null, selectedComponentInstancesProperties, null, null); this.loadingPolicies = true; - this.componentServiceNg2 + this.topologyTemplateService .createPolicy(this.component, policiesToCreate, this.isSelf()) .subscribe(response => { this.setPolicyTabIndication(response.length); @@ -688,7 +662,7 @@ export class PropertiesAssignmentComponent { this.loadingPolicies = false; }); //ignore error - }; + } displayPoliciesAsDeclared = (policies) => { _.forEach(policies, (policy: any) => { @@ -699,8 +673,7 @@ export class PropertiesAssignmentComponent { this.updatePropertyValueAfterDeclare(newPolicy); this.policies.push(policy); }); - }; - + } saveChangedData = ():Promise<(PropertyBEModel|InputBEModel)[]> => { return new Promise((resolve, reject) => { @@ -736,7 +709,8 @@ export class PropertiesAssignmentComponent { if (changedInputsProperties.length && changedCapabilitiesProperties.length) { request = Observable.forkJoin( this.componentInstanceServiceNg2.updateInstanceInputs(this.component, this.selectedInstanceData.uniqueId, changedInputsProperties), - this.componentInstanceServiceNg2.updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedCapabilitiesProperties) + this.componentInstanceServiceNg2.updateInstanceProperties(this.component.componentType, this.component.uniqueId, + this.selectedInstanceData.uniqueId, changedCapabilitiesProperties) ); } else if (changedInputsProperties.length) { @@ -745,7 +719,7 @@ export class PropertiesAssignmentComponent { } else if (changedCapabilitiesProperties.length) { request = this.componentInstanceServiceNg2 - .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedCapabilitiesProperties); + .updateInstanceProperties(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedCapabilitiesProperties); } handleSuccess = (response) => { // reset each changed property with new value and remove it from changed properties list @@ -757,19 +731,18 @@ export class PropertiesAssignmentComponent { }; } else { if (this.isSelf()) { - request = this.componentServiceNg2.updateServiceProperties(this.component, _.map(changedProperties, cp => { + request = this.topologyTemplateService.updateServiceProperties(this.component.uniqueId, _.map(changedProperties, cp => { delete cp.constraints; return cp; })); } else { request = this.componentInstanceServiceNg2 - .updateInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties); + .updateInstanceProperties(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedProperties); } handleSuccess = (response) => { // reset each changed property with new value and remove it from changed properties list response.forEach((resProp) => { - const changedProp = <PropertyFEModel>_.find(this.changedData, changedDataObject => changedDataObject.uniqueId === resProp.uniqueId); - this.changedData = _.filter(this.changedData, changedDataObject => changedDataObject.uniqueId !== resProp.uniqueId); + const changedProp = <PropertyFEModel>this.changedData.shift(); this.propertiesUtils.resetPropertyValue(changedProp, resProp.value); }); resolve(response); @@ -778,7 +751,7 @@ export class PropertiesAssignmentComponent { } } else if (this.selectedInstanceData instanceof GroupInstance) { request = this.componentInstanceServiceNg2 - .updateComponentGroupInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties); + .updateComponentGroupInstanceProperties(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedProperties); handleSuccess = (response) => { // reset each changed property with new value and remove it from changed properties list response.forEach((resProp) => { @@ -790,7 +763,7 @@ export class PropertiesAssignmentComponent { }; } else if (this.selectedInstanceData instanceof PolicyInstance) { request = this.componentInstanceServiceNg2 - .updateComponentPolicyInstanceProperties(this.component, this.selectedInstanceData.uniqueId, changedProperties); + .updateComponentPolicyInstanceProperties(this.component.componentType, this.component.uniqueId, this.selectedInstanceData.uniqueId, changedProperties); handleSuccess = (response) => { // reset each changed property with new value and remove it from changed properties list response.forEach((resProp) => { @@ -802,6 +775,7 @@ export class PropertiesAssignmentComponent { }; } } else if (this.isInputsTabSelected) { + const changedInputs: InputBEModel[] = this.changedData.map((changedInput) => { changedInput = <InputFEModel>changedInput; const inputBE = new InputBEModel(changedInput); @@ -925,27 +899,27 @@ export class PropertiesAssignmentComponent { { title: modalTitle, size: 'sm', - type: 'custom', - testId: "id", - + type: SdcUiCommon.ModalType.custom, + testId: "navigate-modal", + buttons: [ - {id: 'cancelButton', text: 'Cancel', type: 'secondary', size: 'xsm', closeModal: true, callback: () => reject()}, - {id: 'discardButton', text: 'Discard', type: 'secondary', size: 'xsm', closeModal: true, callback: () => { this.reverseChangedData(); resolve()}}, - {id: 'saveButton', text: 'Save', type: 'primary', size: 'xsm', closeModal: true, disabled: !this.isValidChangedData, callback: () => this.doSaveChangedData(resolve, reject)} - ] as IModalButtonComponent[] - }, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData}); + {id: 'cancelButton', text: 'Cancel', type: SdcUiCommon.ButtonType.secondary, size: 'xsm', closeModal: true, callback: () => reject()}, + {id: 'discardButton', text: 'Discard', type: SdcUiCommon.ButtonType.secondary, size: 'xsm', closeModal: true, callback: () => { this.reverseChangedData(); resolve()}}, + {id: 'saveButton', text: 'Save', type: SdcUiCommon.ButtonType.primary, size: 'xsm', closeModal: true, disabled: !this.isValidChangedData, callback: () => this.doSaveChangedData(resolve, reject)} + ] as SdcUiCommon.IModalButtonComponent[] + } as SdcUiCommon.IModalConfig, UnsavedChangesComponent, {isValidChangedData: this.isValidChangedData}); }); } updatePropertyValueAfterDeclare = (input: InputFEModel) => { if (this.instanceFePropertiesMap[input.instanceUniqueId]) { - let instanceName = input.instanceUniqueId.slice(input.instanceUniqueId.lastIndexOf('.') + 1); - let propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => { - return feProperty.uniqueId === input.propertyId && + const instanceName = input.instanceUniqueId.slice(input.instanceUniqueId.lastIndexOf('.') + 1); + const propertyForUpdatindVal = _.find(this.instanceFePropertiesMap[input.instanceUniqueId], (feProperty: PropertyFEModel) => { + return feProperty.name == input.relatedPropertyName && (feProperty.name == input.relatedPropertyName || input.name === instanceName.concat('_').concat(feProperty.name.replace(/[.]/g, '_'))); }); - let inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined; + const inputPath = (input.inputPath && input.inputPath != propertyForUpdatindVal.name) ? input.inputPath : undefined; propertyForUpdatindVal.setAsDeclared(inputPath); //set prop as declared before assigning value this.propertiesService.disableRelatedProperties(propertyForUpdatindVal, inputPath); this.propertiesUtils.resetPropertyValue(propertyForUpdatindVal, input.relatedPropertyValue, inputPath); @@ -968,7 +942,7 @@ export class PropertiesAssignmentComponent { setPolicyTabIndication = (numPolicies: number): void => { this.propertyInputTabs.setTabIndication('Policies', numPolicies); - }; + } resetUnsavedChangesForInput = (input:InputFEModel) => { this.inputsUtils.resetInputDefaultValue(input, input.defaultValue); @@ -1009,9 +983,9 @@ export class PropertiesAssignmentComponent { deletePolicy = (policy: PolicyInstance) => { this.loadingPolicies = true; - this.componentServiceNg2 + this.topologyTemplateService .deletePolicy(this.component, policy) - .subscribe(response => { + .subscribe((response) => { this.policies = this.policies.filter(policy => policy.uniqueId !== response.uniqueId); //Reload the whole instance for now - TODO: CHANGE THIS after the BE starts returning properties within the response, use commented code below instead! this.changeSelectedInstance(this.selectedInstanceData); @@ -1020,25 +994,25 @@ export class PropertiesAssignmentComponent { }; deleteProperty = (property: PropertyFEModel) => { - let propertyToDelete = new PropertyFEModel(property); + const propertyToDelete = new PropertyFEModel(property); this.loadingProperties = true; - let feMap = this.instanceFePropertiesMap; - this.componentServiceNg2 - .deleteServiceProperty(this.component, propertyToDelete) - .subscribe(response => { + const feMap = this.instanceFePropertiesMap; + this.topologyTemplateService + .deleteServiceProperty(this.component.uniqueId, propertyToDelete) + .subscribe((response) => { const props = feMap[this.component.uniqueId]; props.splice(props.findIndex(p => p.uniqueId === response),1); this.loadingProperties = false; - }, error => { + }, (error) => { this.loadingProperties = false; console.error(error); }); - }; + } /*** addProperty ***/ addProperty = () => { let modalTitle = 'Add Property'; - const modal = this.ModalService.createCustomModal(new ModalModel( + let modal = this.ModalService.createCustomModal(new ModalModel( 'sm', modalTitle, null, @@ -1046,10 +1020,10 @@ export class PropertiesAssignmentComponent { new ButtonModel('Save', 'blue', () => { modal.instance.dynamicContent.instance.isLoading = true; const newProperty: PropertyBEModel = modal.instance.dynamicContent.instance.propertyModel; - this.componentServiceNg2.createServiceProperty(this.component, newProperty) - .subscribe(response => { + this.topologyTemplateService.createServiceProperty(this.component.uniqueId, newProperty) + .subscribe((response) => { modal.instance.dynamicContent.instance.isLoading = false; - let newProp: PropertyFEModel = this.propertiesUtils.convertAddPropertyBAToPropertyFE(response); + const newProp: PropertyFEModel = this.propertiesUtils.convertAddPropertyBAToPropertyFE(response); this.instanceFePropertiesMap[this.component.uniqueId].push(newProp); modal.instance.close(); }, (error) => { @@ -1059,7 +1033,6 @@ export class PropertiesAssignmentComponent { title: 'Failure' }); }); - }, () => !modal.instance.dynamicContent.instance.checkFormValidForSubmit()), new ButtonModel('Cancel', 'outline grey', () => { modal.instance.close(); @@ -1069,24 +1042,23 @@ export class PropertiesAssignmentComponent { )); this.ModalService.addDynamicContentToModal(modal, PropertyCreatorComponent, {}); modal.instance.open(); - }; + } /*** SEARCH RELATED FUNCTIONS ***/ searchPropertiesInstances = (filterData:FilterPropertiesAssignmentData) => { let instanceBePropertiesMap:InstanceBePropertiesMap; this.componentServiceNg2 .filterComponentInstanceProperties(this.component, filterData) - .subscribe(response => { - + .subscribe((response) => { this.processInstancePropertiesResponse(response, false); this.hierarchyPropertiesDisplayOptions.searchText = filterData.propertyName;//mark results in tree this.searchPropertyName = filterData.propertyName;//mark in table this.hierarchyNavTabs.triggerTabChange('Composition'); this.propertiesNavigationData = []; this.displayClearSearch = true; - }, error => {}); //ignore error + }, (error) => {}); //ignore error - }; + } clearSearch = () => { this.instancesNavigationData = this.instances; @@ -1106,5 +1078,6 @@ export class PropertiesAssignmentComponent { private isInput = (instanceType:string):boolean =>{ return instanceType === ResourceType.VF || instanceType === ResourceType.PNF || instanceType === ResourceType.CVFC || instanceType === ResourceType.CR; } + } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.component.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.component.ts index 7d76904539..5053d52cc8 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.component.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.component.ts @@ -1,79 +1,78 @@ -import * as _ from "lodash"; -import {Component} from '@angular/core'; -import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; -import { DataTypeService } from "app/ng2/services/data-type.service"; -import {PropertyBEModel, DataTypesMap} from "app/models"; -import {PROPERTY_DATA} from "app/utils"; -import {PROPERTY_TYPES} from "../../../../utils"; - +import { Component } from '@angular/core'; +import { DataTypesMap, PropertyBEModel } from 'app/models'; +import { DropdownValue } from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component'; +import { DataTypeService } from 'app/ng2/services/data-type.service'; +import { PROPERTY_DATA } from 'app/utils'; +import * as _ from 'lodash'; +import { PROPERTY_TYPES } from '../../../../utils'; @Component({ selector: 'property-creator', templateUrl: './property-creator.component.html', - styleUrls:['./property-creator.component.less'], + styleUrls: ['./property-creator.component.less'], }) export class PropertyCreatorComponent { - typesProperties: Array<DropdownValue>; - typesSchemaProperties: Array<DropdownValue>; + typesProperties: DropdownValue[]; + typesSchemaProperties: DropdownValue[]; propertyModel: PropertyBEModel; - //propertyNameValidationPattern:RegExp = /^[a-zA-Z0-9_:-]{1,50}$/; - //commentValidationPattern:RegExp = /^[\u0000-\u00BF]*$/; - //types:Array<string>; - dataTypes:DataTypesMap; - isLoading:boolean; + // propertyNameValidationPattern:RegExp = /^[a-zA-Z0-9_:-]{1,50}$/; + // commentValidationPattern:RegExp = /^[\u0000-\u00BF]*$/; + // types:Array<string>; + dataTypes: DataTypesMap; + isLoading: boolean; - constructor(protected dataTypeService:DataTypeService) {} + constructor(protected dataTypeService: DataTypeService) {} ngOnInit() { this.propertyModel = new PropertyBEModel(); this.propertyModel.type = ''; this.propertyModel.schema.property.type = ''; - const types: Array<string> = PROPERTY_DATA.TYPES; //All types - simple type + map + list - this.dataTypes = this.dataTypeService.getAllDataTypes(); //Get all data types in service - const nonPrimitiveTypes :Array<string> = _.filter(Object.keys(this.dataTypes), (type:string)=> { - return types.indexOf(type) == -1; + const types: string[] = PROPERTY_DATA.TYPES; // All types - simple type + map + list + this.dataTypes = this.dataTypeService.getAllDataTypes(); // Get all data types in service + const nonPrimitiveTypes: string[] = _.filter(Object.keys(this.dataTypes), (type: string) => { + return types.indexOf(type) === -1; }); this.typesProperties = _.map(PROPERTY_DATA.TYPES, (type: string) => new DropdownValue(type, type) ); - let typesSimpleProperties = _.map(PROPERTY_DATA.SIMPLE_TYPES, + const typesSimpleProperties = _.map(PROPERTY_DATA.SIMPLE_TYPES, (type: string) => new DropdownValue(type, type) ); - let nonPrimitiveTypesValues = _.map(nonPrimitiveTypes, + const nonPrimitiveTypesValues = _.map(nonPrimitiveTypes, (type: string) => new DropdownValue(type, - type.replace("org.openecomp.datatypes.heat.","")) + type.replace('org.openecomp.datatypes.heat.', '')) ) .sort((a, b) => a.label.localeCompare(b.label)); - this.typesProperties = _.concat(this.typesProperties,nonPrimitiveTypesValues); - this.typesSchemaProperties = _.concat(typesSimpleProperties,nonPrimitiveTypesValues); - this.typesProperties.unshift(new DropdownValue('','Select Type...')); - this.typesSchemaProperties.unshift(new DropdownValue('','Select Schema Type...')); + this.typesProperties = _.concat(this.typesProperties, nonPrimitiveTypesValues); + this.typesSchemaProperties = _.concat(typesSimpleProperties, nonPrimitiveTypesValues); + this.typesProperties.unshift(new DropdownValue('', 'Select Type...')); + this.typesSchemaProperties.unshift(new DropdownValue('', 'Select Schema Type...')); } - checkFormValidForSubmit(){ - const showSchema:boolean = this.showSchema(); - let isSchemaValid: boolean = (showSchema && !this.propertyModel.schema.property.type)? false : true; - if (!showSchema){ + checkFormValidForSubmit() { + const showSchema: boolean = this.showSchema(); + const isSchemaValid: boolean = (showSchema && !this.propertyModel.schema.property.type) ? false : true; + if (!showSchema) { this.propertyModel.schema.property.type = ''; } return this.propertyModel.name && this.propertyModel.type && isSchemaValid; } - showSchema():boolean { + showSchema(): boolean { return [PROPERTY_TYPES.LIST, PROPERTY_TYPES.MAP].indexOf(this.propertyModel.type) > -1; - }; + } - onSchemaTypeChange():void { - if (this.propertyModel.type == PROPERTY_TYPES.MAP) { + onSchemaTypeChange(): void { + if (this.propertyModel.type === PROPERTY_TYPES.MAP) { this.propertyModel.value = JSON.stringify({'': null}); - } else if (this.propertyModel.type == PROPERTY_TYPES.LIST) { + } else if (this.propertyModel.type === PROPERTY_TYPES.LIST) { this.propertyModel.value = JSON.stringify([]); } - }; + } } diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.module.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.module.ts index 92accb26b5..1cbb4e17ec 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.module.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/property-creator/property-creator.module.ts @@ -1,10 +1,10 @@ -import {NgModule} from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {PropertyCreatorComponent} from "./property-creator.component"; -import {FormsModule} from "@angular/forms"; -import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {TranslateModule} from "../../../shared/translator/translate.module"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from '../../../shared/translator/translate.module'; +import { PropertyCreatorComponent } from './property-creator.component'; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts b/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts index 011be41611..bd7ccd1bfd 100644 --- a/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts +++ b/catalog-ui/src/app/ng2/pages/properties-assignment/services/properties.utils.ts @@ -48,7 +48,7 @@ export class PropertiesUtils { let newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE - this.initValueObjectRef(newFEProp); //initialize valueObj. + this.initValueObjectRef(newFEProp); //initialize valueObj AND creates flattened children propertyFeArray.push(newFEProp); newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children this.dataTypeService.checkForCustomBehavior(newFEProp); @@ -79,8 +79,8 @@ export class PropertiesUtils { return instanceFePropertiesMap; } - public convertAddPropertyBAToPropertyFE = (property: PropertyBEModel):PropertyFEModel => { - let newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE + public convertAddPropertyBAToPropertyFE = (property: PropertyBEModel): PropertyFEModel => { + const newFEProp: PropertyFEModel = new PropertyFEModel(property); //Convert property to FE this.initValueObjectRef(newFEProp); newFEProp.updateExpandedChildPropertyId(newFEProp.name); //display only the first level of children this.dataTypeService.checkForCustomBehavior(newFEProp); @@ -108,7 +108,7 @@ export class PropertiesUtils { let tempProps: Array<DerivedFEProperty> = []; let dataTypeObj: DataTypeModel = this.dataTypeService.getDataTypeByTypeName(type); this.dataTypeService.getDerivedDataTypeProperties(dataTypeObj, tempProps, parentName); - return tempProps; + return _.sortBy(tempProps, ['propertiesName']); } /* Sets the valueObj of parent property and its children. diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts index 1e767a5690..104a6d0579 100644 --- a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/capabilities-editor/capabilities-editor.module.ts @@ -5,7 +5,8 @@ import {FormsModule} from "@angular/forms"; import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; +import { SdcUiComponentsModule } from 'onap-ui-angular'; + @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts index 1be8be51af..d38790a8db 100644 --- a/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts +++ b/catalog-ui/src/app/ng2/pages/req-and-capabilities-editor/requirements-editor/requirements-editor.module.ts @@ -4,7 +4,7 @@ import {RequirementsEditorComponent} from "./requirements-editor.component"; import {FormsModule} from "@angular/forms"; import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module"; import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; -import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; +import { SdcUiComponentsModule } from "onap-ui-angular"; @NgModule({ declarations: [ diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts index 2c86cc5c5c..8444c6261a 100644 --- a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.component.ts @@ -14,29 +14,29 @@ * permissions and limitations under the License. */ -import * as _ from "lodash"; import { Component } from '@angular/core'; -import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; import { - Service, - ServiceInstanceObject, - InstanceFePropertiesMap, - InstanceBePropertiesMap, - PropertyBEModel, + Capability, InputBEModel, - OperationModel, + InstanceBePropertiesMap, + InstanceFePropertiesMap, InterfaceModel, - Capability + OperationModel, + PropertyBEModel, + Service } from 'app/models'; -import {ConsumptionInput, ConsumptionInputDetails, ServiceOperation} from 'app/ng2/components/logic/service-consumption/service-consumption.component'; -import {PropertiesUtils} from "app/ng2/pages/properties-assignment/services/properties.utils"; +import { ConsumptionInput, ConsumptionInputDetails, ServiceOperation } from 'app/ng2/components/logic/service-consumption/service-consumption.component'; +import { PropertiesUtils } from 'app/ng2/pages/properties-assignment/services/properties.utils'; +import { ServiceServiceNg2 } from 'app/ng2/services/component-services/service.service'; import { PROPERTY_DATA } from 'app/utils'; - +import * as _ from 'lodash'; +import { ServiceInstanceObject } from '../../../models/service-instance-properties-and-interfaces'; +import { TopologyTemplateService } from '../../services/component-services/topology-template.service'; @Component({ selector: 'service-consumption-editor', templateUrl: './service-consumption-editor.component.html', - styleUrls:['./service-consumption-editor.component.less'], + styleUrls: ['./service-consumption-editor.component.less'], providers: [] }) @@ -45,27 +45,27 @@ export class ServiceConsumptionCreatorComponent { input: { interfaceId: string, serviceOperationIndex: number, - serviceOperations: Array<ServiceOperation>, + serviceOperations: ServiceOperation[], parentService: Service, selectedService: Service, - parentServiceInputs: Array<InputBEModel>, - selectedServiceProperties: Array<PropertyBEModel>, - selectedServiceInstanceId: String, - selectedInstanceSiblings: Array<ServiceInstanceObject>, - selectedInstanceCapabilitisList: Array<Capability>, - siblingsCapabilitiesList: Map<string, Array<Capability>> + parentServiceInputs: InputBEModel[], + selectedServiceProperties: PropertyBEModel[], + selectedServiceInstanceId: string, + selectedInstanceSiblings: ServiceInstanceObject[], + selectedInstanceCapabilitisList: Capability[], + siblingsCapabilitiesList: Map<string, Capability[]> }; - sourceTypes: Array<any> = []; - serviceOperationsList: Array<ServiceOperation>; + sourceTypes: any[] = []; + serviceOperationsList: ServiceOperation[]; serviceOperation: ServiceOperation; currentIndex: number; isLoading: boolean = false; parentService: Service; selectedService: Service; - selectedServiceInstanceId: String; - parentServiceInputs: Array<InputBEModel>; - selectedServiceProperties: Array<PropertyBEModel>; - changedData: Array<ConsumptionInputDetails> = []; + selectedServiceInstanceId: string; + parentServiceInputs: InputBEModel[]; + selectedServiceProperties: PropertyBEModel[]; + changedData: ConsumptionInputDetails[] = []; inputFePropertiesMap: any = []; SOURCE_TYPES = { @@ -75,7 +75,7 @@ export class ServiceConsumptionCreatorComponent { SERVICE_INPUT_LABEL: 'Service Input' }; - constructor(private serviceServiceNg2: ServiceServiceNg2, private propertiesUtils:PropertiesUtils) {} + constructor(private topologyTemplateService: TopologyTemplateService, private propertiesUtils: PropertiesUtils) {} ngOnInit() { this.serviceOperationsList = this.input.serviceOperations; @@ -112,7 +112,7 @@ export class ServiceConsumptionCreatorComponent { capabilities: [] } ]; - _.forEach(this.input.selectedInstanceSiblings, sib => + _.forEach(this.input.selectedInstanceSiblings, (sib) => this.sourceTypes.push({ label: sib.name, value: sib.id, @@ -128,56 +128,84 @@ export class ServiceConsumptionCreatorComponent { } onExpandAll() { - _.forEach(this.serviceOperation.consumptionInputs, coInput => { + _.forEach(this.serviceOperation.consumptionInputs, (coInput) => { coInput.expanded = true; - }) + }); } onCollapseAll() { - _.forEach(this.serviceOperation.consumptionInputs, coInput => { + _.forEach(this.serviceOperation.consumptionInputs, (coInput) => { coInput.expanded = false; - }) + }); } isAllInputExpanded() { - return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === true); + return _.every(this.serviceOperation.consumptionInputs, (coInput) => coInput.expanded === true); } isAllInputCollapsed() { - return _.every(this.serviceOperation.consumptionInputs, coInput => coInput.expanded === false); + return _.every(this.serviceOperation.consumptionInputs, (coInput) => coInput.expanded === false); } onChangePage(newIndex) { if (newIndex >= 0 && newIndex < this.serviceOperationsList.length) { this.currentIndex = newIndex; this.serviceOperation = this.serviceOperationsList[newIndex]; - if(!this.serviceOperation.consumptionInputs || this.serviceOperation.consumptionInputs.length === 0) { + if (!this.serviceOperation.consumptionInputs || this.serviceOperation.consumptionInputs.length === 0) { this.initConsumptionInputs(); } this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs); } } + checkFormValidForSubmit(): boolean { + return this.isValidInputsValues() && this.isMandatoryFieldsValid(); + } + + checkFormValidForNavigation(): boolean { + return this.isMandatoryFieldsValid() && (this.changedData.length === 0 || this.isValidInputsValues()); + } + + onChange(value: any, isValid: boolean, consumptionInput: ConsumptionInputDetails) { + consumptionInput.updateValidity(isValid); + const dataChangedIndex = this.changedData.findIndex((changedItem) => changedItem.inputId === consumptionInput.inputId); + if (value !== consumptionInput.origVal) { + if (dataChangedIndex === -1) { + this.changedData.push(consumptionInput); + } + } else { + if (dataChangedIndex !== -1) { + this.changedData.splice(dataChangedIndex, 1); + } + } + } + + onComplexPropertyChanged(property, consumptionInput) { + consumptionInput.value = JSON.stringify(property.valueObj); + this.onChange(property.valueObj, property.valueObjIsValid , consumptionInput); + } + private initConsumptionInputs() { this.isLoading = true; - this.serviceServiceNg2.getServiceConsumptionInputs(this.parentService, this.selectedServiceInstanceId, this.input.interfaceId, this.serviceOperation.operation).subscribe((result: Array<ConsumptionInput>) => { + this.topologyTemplateService.getServiceConsumptionInputs(this.parentService.uniqueId, this.selectedServiceInstanceId, + this.input.interfaceId, this.serviceOperation.operation).subscribe((result: ConsumptionInput[]) => { this.isLoading = false; this.serviceOperation.consumptionInputs = this.analyzeCurrentConsumptionInputs(result); this.getComplexPropertiesForCurrentInputsOfOperation(this.serviceOperation.consumptionInputs); - }, err=> { + }, (err) => { this.isLoading = false; }); } - private analyzeCurrentConsumptionInputs(result: Array<any>): Array<ConsumptionInputDetails> { - let inputsResult: Array<ConsumptionInputDetails> = []; - let currentOp = this.serviceOperation.operation; - if(currentOp) { - inputsResult = _.map(result, input => { - let sourceVal = input.source || this.SOURCE_TYPES.STATIC; - let consumptionInputDetails: ConsumptionInputDetails = _.cloneDeep(input); + private analyzeCurrentConsumptionInputs(result: any[]): ConsumptionInputDetails[] { + let inputsResult: ConsumptionInputDetails[] = []; + const currentOp = this.serviceOperation.operation; + if (currentOp) { + inputsResult = _.map(result, (input) => { + const sourceVal = input.source || this.SOURCE_TYPES.STATIC; + const consumptionInputDetails: ConsumptionInputDetails = _.cloneDeep(input); consumptionInputDetails.source = sourceVal; consumptionInputDetails.isValid = true; consumptionInputDetails.expanded = false; - let filteredListsObj = this.getFilteredProps(sourceVal, input.type); + const filteredListsObj = this.getFilteredProps(sourceVal, input.type); consumptionInputDetails.assignValueLabel = this.getAssignValueLabel(sourceVal); consumptionInputDetails.associatedProps = filteredListsObj.associatedPropsList; consumptionInputDetails.associatedInterfaces = filteredListsObj.associatedInterfacesList; @@ -190,15 +218,14 @@ export class ServiceConsumptionCreatorComponent { private onSourceChanged(consumptionInput: ConsumptionInputDetails): void { consumptionInput.assignValueLabel = this.getAssignValueLabel(consumptionInput.source); - let filteredListsObj = this.getFilteredProps(consumptionInput.source, consumptionInput.type); + const filteredListsObj = this.getFilteredProps(consumptionInput.source, consumptionInput.type); consumptionInput.associatedProps = filteredListsObj.associatedPropsList; consumptionInput.associatedInterfaces = filteredListsObj.associatedInterfacesList; consumptionInput.associatedCapabilities = filteredListsObj.associatedCapabilitiesList; - if(consumptionInput.source === this.SOURCE_TYPES.STATIC) { - if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(consumptionInput.type) !== -1) { - consumptionInput.value = consumptionInput.defaultValue || ""; - } - else { + if (consumptionInput.source === this.SOURCE_TYPES.STATIC) { + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(consumptionInput.type) !== -1) { + consumptionInput.value = consumptionInput.defaultValue || ''; + } else { consumptionInput.value = null; Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(consumptionInput)); } @@ -206,9 +233,11 @@ export class ServiceConsumptionCreatorComponent { } private getFilteredProps(sourceVal, inputType) { - let currentSourceObj = this.sourceTypes.find(s => s.value === sourceVal); - let associatedInterfacesList = [], associatedPropsList = [], associatedCapabilitiesPropsList: Array<Capability> = []; - if(currentSourceObj) { + const currentSourceObj = this.sourceTypes.find((s) => s.value === sourceVal); + let associatedInterfacesList = []; + let associatedPropsList = []; + let associatedCapabilitiesPropsList: Capability[] = []; + if (currentSourceObj) { if (currentSourceObj.interfaces) { associatedInterfacesList = this.getFilteredInterfaceOutputs(currentSourceObj, inputType); } @@ -221,31 +250,31 @@ export class ServiceConsumptionCreatorComponent { associatedCapabilitiesPropsList = _.reduce(currentSourceObj.capabilities, (filteredCapsList, capability: Capability) => { - let filteredProps = _.filter(capability.properties, prop => prop.type === inputType); + const filteredProps = _.filter(capability.properties, (prop) => prop.type === inputType); if (filteredProps.length) { - let cap = new Capability(capability); + const cap = new Capability(capability); cap.properties = filteredProps; filteredCapsList.push(cap); } - return filteredCapsList + return filteredCapsList; }, []); } return { - associatedPropsList: associatedPropsList, - associatedInterfacesList: associatedInterfacesList, + associatedPropsList, + associatedInterfacesList, associatedCapabilitiesList: associatedCapabilitiesPropsList - } + }; } private getFilteredInterfaceOutputs(currentSourceObj, inputType) { - let currentServiceOperationId = this.serviceOperation.operation.uniqueId; - let filteredInterfacesList = []; - Object.keys(currentSourceObj.interfaces).map(interfId => { - let interfaceObj: InterfaceModel = new InterfaceModel(currentSourceObj.interfaces[interfId]); - Object.keys(interfaceObj.operations).map(opId => { - if(currentServiceOperationId !== opId) { - let operationObj: OperationModel = interfaceObj.operations[opId]; - let filteredOutputsList = _.filter(operationObj.outputs.listToscaDataDefinition, output => output.type === inputType); + const currentServiceOperationId = this.serviceOperation.operation.uniqueId; + const filteredInterfacesList = []; + Object.keys(currentSourceObj.interfaces).map((interfId) => { + const interfaceObj: InterfaceModel = new InterfaceModel(currentSourceObj.interfaces[interfId]); + Object.keys(interfaceObj.operations).map((opId) => { + if (currentServiceOperationId !== opId) { + const operationObj: OperationModel = interfaceObj.operations[opId]; + const filteredOutputsList = _.filter(operationObj.outputs.listToscaDataDefinition, (output) => output.type === inputType); if (filteredOutputsList.length) { filteredInterfacesList.push({ name: `${interfaceObj.type}.${operationObj.name}`, @@ -259,25 +288,23 @@ export class ServiceConsumptionCreatorComponent { return filteredInterfacesList; } - getAssignValueLabel(selectedSource: string): string { - if(selectedSource === this.SOURCE_TYPES.STATIC || selectedSource === "") { + private getAssignValueLabel(selectedSource: string): string { + if (selectedSource === this.SOURCE_TYPES.STATIC || selectedSource === '') { return this.SOURCE_TYPES.STATIC; - } - else { - if(selectedSource === this.parentService.uniqueId) { //parent is the source + } else { + if (selectedSource === this.parentService.uniqueId) { // parent is the source return this.SOURCE_TYPES.SERVICE_INPUT_LABEL; } return this.SOURCE_TYPES.SERVICE_PROPERTY_LABEL; } } - private isValidInputsValues(): boolean { return this.changedData.length > 0 && this.changedData.every((changedItem) => changedItem.isValid); } private isMandatoryFieldsValid(): boolean { - const invalid: Array<ConsumptionInputDetails> = this.serviceOperation.consumptionInputs.filter(item => + const invalid: ConsumptionInputDetails[] = this.serviceOperation.consumptionInputs.filter((item) => item.required && (item.value === null || typeof item.value === 'undefined' || item.value === '')); if (invalid.length > 0) { return false; @@ -285,45 +312,19 @@ export class ServiceConsumptionCreatorComponent { return true; } - checkFormValidForSubmit(): boolean { - return this.isValidInputsValues() && this.isMandatoryFieldsValid(); - } - - checkFormValidForNavigation(): boolean { - return this.isMandatoryFieldsValid() && (this.changedData.length === 0 || this.isValidInputsValues()); - } - - onChange(value: any, isValid: boolean, consumptionInput: ConsumptionInputDetails) { - consumptionInput.updateValidity(isValid); - const dataChangedIndex = this.changedData.findIndex((changedItem) => changedItem.inputId === consumptionInput.inputId); - if (value !== consumptionInput.origVal) { - if (dataChangedIndex === -1) { - this.changedData.push(consumptionInput); - } - } else { - if (dataChangedIndex !== -1) { - this.changedData.splice(dataChangedIndex, 1); - } - } - } - - private getComplexPropertiesForCurrentInputsOfOperation(opInputs: Array<ConsumptionInput>) { - _.forEach(opInputs, input => { - if(PROPERTY_DATA.SIMPLE_TYPES.indexOf(input.type) === -1 && input.source === this.SOURCE_TYPES.STATIC) { + private getComplexPropertiesForCurrentInputsOfOperation(opInputs: ConsumptionInput[]) { + _.forEach(opInputs, (input) => { + if (PROPERTY_DATA.SIMPLE_TYPES.indexOf(input.type) === -1 && input.source === this.SOURCE_TYPES.STATIC) { Object.assign(this.inputFePropertiesMap, this.processPropertiesOfComplexTypeInput(input)); } }); } private processPropertiesOfComplexTypeInput(input: ConsumptionInput): InstanceFePropertiesMap { - let inputBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); + const inputBePropertiesMap: InstanceBePropertiesMap = new InstanceBePropertiesMap(); inputBePropertiesMap[input.name] = [input]; - let originTypeIsVF = false; - return this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(inputBePropertiesMap, originTypeIsVF); //create flattened children and init values + const originTypeIsVF = false; + return this.propertiesUtils.convertPropertiesMapToFEAndCreateChildren(inputBePropertiesMap, originTypeIsVF); // create flattened children and init values } - onComplexPropertyChanged(property, consumptionInput) { - consumptionInput.value = JSON.stringify(property.valueObj); - this.onChange(property.valueObj, property.valueObjIsValid , consumptionInput); - } -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts index e37cd76716..43e88eb0dc 100644 --- a/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts +++ b/catalog-ui/src/app/ng2/pages/service-consumption-editor/service-consumption-editor.module.ts @@ -1,11 +1,11 @@ -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {ServiceConsumptionCreatorComponent} from "./service-consumption-editor.component"; -import {FormsModule} from "@angular/forms"; -import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; -import {PropertyTableModule} from 'app/ng2/components/logic/properties-table/property-table.module'; -import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { PropertyTableModule } from 'app/ng2/components/logic/properties-table/property-table.module'; +import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { TranslateModule } from 'app/ng2/shared/translator/translate.module'; +import { ServiceConsumptionCreatorComponent } from './service-consumption-editor.component'; @NgModule({ declarations: [ @@ -25,4 +25,4 @@ import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; providers: [] }) export class ServiceConsumptionCreatorModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts index 271dd4ada0..708742ae0c 100644 --- a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts +++ b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.component.ts @@ -14,20 +14,23 @@ * permissions and limitations under the License. */ import { Component } from '@angular/core'; -import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; -import {ConstraintObjectUI, OPERATOR_TYPES} from 'app/ng2/components/logic/service-dependencies/service-dependencies.component'; -import {ServiceInstanceObject, PropertyBEModel, InputBEModel} from 'app/models'; +import { InputBEModel, PropertyBEModel } from 'app/models'; +import { ConstraintObjectUI, OPERATOR_TYPES } from 'app/ng2/components/logic/service-dependencies/service-dependencies.component'; +import { DropdownValue } from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component'; +import { ServiceServiceNg2 } from 'app/ng2/services/component-services/service.service'; import { PROPERTY_DATA } from 'app/utils'; -import {DropdownValue} from 'app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component'; +import { ServiceInstanceObject } from '../../../models/service-instance-properties-and-interfaces'; -export class UIDropDownSourceTypesElement extends DropdownValue{ - options: Array<any>; +export class UIDropDownSourceTypesElement extends DropdownValue { + options: any[]; assignedLabel: string; type: string; - constructor(input?: any){ - if(input) { - let value = input.value || ''; - let label = input.label || ''; + constructor(input?: any) { + if (input) { + const value = input.value || ''; + const label = input.label || ''; + // const hidden = input.hidden || ''; + // const selected = input.selected || ''; super(value, label); this.options = input.options; this.assignedLabel = input.assignedLabel; @@ -36,10 +39,11 @@ export class UIDropDownSourceTypesElement extends DropdownValue{ } } +// tslint:disable-next-line:max-classes-per-file @Component({ selector: 'service-dependencies-editor', templateUrl: './service-dependencies-editor.component.html', - styleUrls:['./service-dependencies-editor.component.less'], + styleUrls: ['./service-dependencies-editor.component.less'], providers: [ServiceServiceNg2] }) @@ -47,44 +51,42 @@ export class ServiceDependenciesEditorComponent { input: { serviceRuleIndex: number, - serviceRules: Array<ConstraintObjectUI>, + serviceRules: ConstraintObjectUI[], compositeServiceName: string, currentServiceName: string, - parentServiceInputs: Array<InputBEModel>, - selectedInstanceProperties: Array<PropertyBEModel>, - operatorTypes: Array<DropdownValue>, - selectedInstanceSiblings: Array<ServiceInstanceObject> + parentServiceInputs: InputBEModel[], + selectedInstanceProperties: PropertyBEModel[], + operatorTypes: DropdownValue[], + selectedInstanceSiblings: ServiceInstanceObject[] }; currentServiceName: string; - selectedServiceProperties: Array<PropertyBEModel>; + selectedServiceProperties: PropertyBEModel[]; selectedPropertyObj: PropertyBEModel; - ddValueSelectedServicePropertiesNames: Array<DropdownValue>; - operatorTypes: Array<DropdownValue>; - sourceTypes: Array<UIDropDownSourceTypesElement> = []; + ddValueSelectedServicePropertiesNames: DropdownValue[]; + operatorTypes: DropdownValue[]; + sourceTypes: UIDropDownSourceTypesElement[] = []; currentRule: ConstraintObjectUI; currentIndex: number; - listOfValuesToAssign: Array<DropdownValue>; - listOfSourceOptions: Array<PropertyBEModel>; + listOfValuesToAssign: DropdownValue[]; + listOfSourceOptions: PropertyBEModel[]; assignedValueLabel: string; - serviceRulesList: Array<ConstraintObjectUI>; - + serviceRulesList: ConstraintObjectUI[]; SOURCE_TYPES = { STATIC: {label: 'Static', value: 'static'}, SERVICE_PROPERTY: {label: 'Service Property', value: 'property'} }; - ngOnInit() { this.currentIndex = this.input.serviceRuleIndex; this.serviceRulesList = this.input.serviceRules; this.currentRule = this.serviceRulesList && this.input.serviceRuleIndex >= 0 ? - this.serviceRulesList[this.input.serviceRuleIndex]: - new ConstraintObjectUI({sourceName: this.SOURCE_TYPES.STATIC.value, sourceType: this.SOURCE_TYPES.STATIC.value, value: "", constraintOperator: OPERATOR_TYPES.EQUAL}); + this.serviceRulesList[this.input.serviceRuleIndex] : + new ConstraintObjectUI({sourceName: this.SOURCE_TYPES.STATIC.value, sourceType: this.SOURCE_TYPES.STATIC.value, value: '', constraintOperator: OPERATOR_TYPES.EQUAL}); this.currentServiceName = this.input.currentServiceName; this.operatorTypes = this.input.operatorTypes; this.selectedServiceProperties = this.input.selectedInstanceProperties; - this.ddValueSelectedServicePropertiesNames = _.map(this.input.selectedInstanceProperties, prop => new DropdownValue(prop.name, prop.name)); + this.ddValueSelectedServicePropertiesNames = _.map(this.input.selectedInstanceProperties, (prop) => new DropdownValue(prop.name, prop.name)); this.initSourceTypes(); this.syncRuleData(); this.updateSourceTypesRelatedValues(); @@ -100,7 +102,7 @@ export class ServiceDependenciesEditorComponent { type: this.SOURCE_TYPES.SERVICE_PROPERTY.value, options: this.input.parentServiceInputs }); - _.forEach(this.input.selectedInstanceSiblings, sib => + _.forEach(this.input.selectedInstanceSiblings, (sib) => this.sourceTypes.push({ label: sib.name, value: sib.name, @@ -112,28 +114,27 @@ export class ServiceDependenciesEditorComponent { } syncRuleData() { - if(!this.currentRule.sourceName && this.currentRule.sourceType === this.SOURCE_TYPES.STATIC.value) { + if (!this.currentRule.sourceName && this.currentRule.sourceType === this.SOURCE_TYPES.STATIC.value) { this.currentRule.sourceName = this.SOURCE_TYPES.STATIC.value; } - this.selectedPropertyObj = _.find(this.selectedServiceProperties, prop => prop.name === this.currentRule.servicePropertyName); + this.selectedPropertyObj = _.find(this.selectedServiceProperties, (prop) => prop.name === this.currentRule.servicePropertyName); this.updateOperatorTypesList(); this.updateSourceTypesRelatedValues(); } updateOperatorTypesList() { if (this.selectedPropertyObj && PROPERTY_DATA.SIMPLE_TYPES_COMPARABLE.indexOf(this.selectedPropertyObj.type) === -1) { - this.operatorTypes = [{label: "=", value: OPERATOR_TYPES.EQUAL}]; + this.operatorTypes = [{label: '=', value: OPERATOR_TYPES.EQUAL}]; this.currentRule.constraintOperator = OPERATOR_TYPES.EQUAL; - } - else { + } else { this.operatorTypes = this.input.operatorTypes; } } updateSourceTypesRelatedValues() { - if(this.currentRule.sourceName) { - let selectedSourceType: UIDropDownSourceTypesElement = this.sourceTypes.find( - t => t.value === this.currentRule.sourceName && t.type === this.currentRule.sourceType + if (this.currentRule.sourceName) { + const selectedSourceType: UIDropDownSourceTypesElement = this.sourceTypes.find( + (t) => t.value === this.currentRule.sourceName && t.type === this.currentRule.sourceType ); this.listOfSourceOptions = selectedSourceType.options || []; this.assignedValueLabel = selectedSourceType.assignedLabel || this.SOURCE_TYPES.STATIC.label; @@ -150,7 +151,7 @@ export class ServiceDependenciesEditorComponent { } onServicePropertyChanged() { - this.selectedPropertyObj = _.find(this.selectedServiceProperties, prop => prop.name === this.currentRule.servicePropertyName); + this.selectedPropertyObj = _.find(this.selectedServiceProperties, (prop) => prop.name === this.currentRule.servicePropertyName); this.updateOperatorTypesList(); this.filterOptionsByType(); this.currentRule.value = ''; @@ -165,11 +166,11 @@ export class ServiceDependenciesEditorComponent { } filterOptionsByType() { - if(!this.selectedPropertyObj) { + if (!this.selectedPropertyObj) { this.listOfValuesToAssign = []; return; } - this.listOfValuesToAssign = this.listOfSourceOptions.reduce((result, op:PropertyBEModel) => { + this.listOfValuesToAssign = this.listOfSourceOptions.reduce((result, op: PropertyBEModel) => { if (op.type === this.selectedPropertyObj.type && (!op.schemaType || op.schemaType === this.selectedPropertyObj.schemaType)) { result.push(new DropdownValue(op.name, op.name)); } @@ -182,11 +183,11 @@ export class ServiceDependenciesEditorComponent { } checkFormValidForSubmit() { - if(!this.serviceRulesList) { //for create modal - let isStatic = this.currentRule.sourceName === this.SOURCE_TYPES.STATIC.value; + if (!this.serviceRulesList) { // for create modal + const isStatic = this.currentRule.sourceName === this.SOURCE_TYPES.STATIC.value; return this.currentRule.isValidRule(isStatic); } - //for update all rules - return this.serviceRulesList.every(rule => rule.isValidRule(rule.sourceName === this.SOURCE_TYPES.STATIC.value)); + // for update all rules + return this.serviceRulesList.every((rule) => rule.isValidRule(rule.sourceName === this.SOURCE_TYPES.STATIC.value)); } -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts index 98ac997bf7..7b128f4468 100644 --- a/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts +++ b/catalog-ui/src/app/ng2/pages/service-dependencies-editor/service-dependencies-editor.module.ts @@ -1,9 +1,9 @@ -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {ServiceDependenciesEditorComponent} from "./service-dependencies-editor.component"; -import {FormsModule} from "@angular/forms"; -import {FormElementsModule} from "app/ng2/components/ui/form-components/form-elements.module"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { FormsModule } from '@angular/forms'; +import { FormElementsModule } from 'app/ng2/components/ui/form-components/form-elements.module'; +import { UiElementsModule } from 'app/ng2/components/ui/ui-elements.module'; +import { ServiceDependenciesEditorComponent } from './service-dependencies-editor.component'; @NgModule({ declarations: [ @@ -22,4 +22,4 @@ import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; providers: [] }) export class ServiceDependenciesEditorModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.html b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.html new file mode 100644 index 0000000000..d7cf2f930a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.html @@ -0,0 +1,68 @@ +<!-- + ~ 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="activity-log"> + <div class="sdc-filter-bar-wrapper"> + <sdc-filter-bar + [placeHolder]="'Search...'" + [testId]="activityLogSearchBar" + (keyup)="updateFilter($event)"> + </sdc-filter-bar> + </div> + <ngx-datatable + columnMode="flex" + [footerHeight]="0" + [limit]="50" + [headerHeight]="40" + [rowHeight]="35" + #activityLogTable + [rows]="activities"> + + <ngx-datatable-column name="Time" [flexGrow]="2" [prop]="'TIMESTAMP'"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.TIMESTAMP | date }} | {{row.TIMESTAMP | date:"HH:mm O"}} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Action" [flexGrow]="3" [prop]="'ACTION'"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.ACTION}} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Comment" [flexGrow]="5" [prop]="'COMMENT'"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span sdc-tooltip [tooltip-text]="row.COMMENT">{{ row.COMMENT }}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Modifier" [flexGrow]="3" [prop]="'MODIFIER'"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{ row.MODIFIER }} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Status" [flexGrow]="1" [prop]="'STATUS'"> + <ng-template ngx-datatable-cell-template let-row="row"> + <svg-icon-label + [name]="row.STATUS <= 399 ? 'success' : 'icons_close'" + [mode]="row.STATUS <= 399 ? 'success' : 'error'" + [size]="'medium'" + [label]="row.STATUS" + [labelPlacement]="'left'" + [labelClassName]="'label'" + > + </svg-icon-label> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> + +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.less b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.less new file mode 100644 index 0000000000..4845f4f606 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.less @@ -0,0 +1,8 @@ +.sdc-filter-bar-wrapper { + sdc-filter-bar { + flex: 0 0 30%; + } + display: flex; + justify-content: flex-end; + margin-bottom: 10px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.spec.ts new file mode 100644 index 0000000000..25651e0c1f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.spec.ts @@ -0,0 +1,84 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiServices } from 'onap-ui-angular'; +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Observable'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { ActivityLogService } from '../../../services/activity-log.service'; +import { WorkspaceService } from '../workspace.service'; +import { ActivityLogComponent } from './activity-log.component'; + +describe('activity log component', () => { + + let fixture: ComponentFixture<ActivityLogComponent>; + let activityLogServiceMock: Partial<ActivityLogService>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let componentMetadataMock: ComponentMetadata; + + const mockLogs = '[' + + '{"MODIFIER":"Carlos Santana(m08740)","COMMENT":"comment","STATUS":"200","ACTION":"Checkout","TIMESTAMP":"2018-11-19 13:00:02.388 UTC"},' + + '{"MODIFIER":"John Doe(m08741)","COMMENT":"comment","STATUS":"200","ACTION":"Checkin","TIMESTAMP":"2018-11-20 13:00:02.388 UTC"},' + + '{"MODIFIER":"Jane Doe(m08742)","COMMENT":"comment","STATUS":"200","ACTION":"Checkout","TIMESTAMP":"2018-11-21 13:00:02.388 UTC"}' + + ']'; + + beforeEach( + async(() => { + + componentMetadataMock = new ComponentMetadata(); + componentMetadataMock.uniqueId = 'fake'; + componentMetadataMock.componentType = 'SERVICE'; + + activityLogServiceMock = { + getActivityLog : jest.fn().mockImplementation((type, id) => Observable.of(JSON.parse(mockLogs)) ) + }; + + workspaceServiceMock = { + metadata : componentMetadataMock + }; + + loaderServiceMock = { + activate : jest.fn(), + deactivate: jest.fn() + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [ActivityLogComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: WorkspaceService, useValue: workspaceServiceMock }, + { provide: ActivityLogService, useValue: activityLogServiceMock }, + { provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(ActivityLogComponent); + }); + }) + ); + + it('should see exactly 3 activity logs', () => { + fixture.componentInstance.ngOnInit(); + expect(fixture.componentInstance.activities.length).toBe(3); + }); + + it('should filter out 1 element when searching', () => { + fixture.componentInstance.ngOnInit(); + + const event = { + target : { + value : 'Checkin' + } + }; + + expect(fixture.componentInstance.activities.length).toBe(3); + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.activities.length).toBe(1); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.ts b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.ts new file mode 100644 index 0000000000..84fb81a1ef --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { SdcUiServices } from 'onap-ui-angular'; +import { Activity } from '../../../../models/activity'; +import { ActivityLogService } from '../../../services/activity-log.service'; +import { WorkspaceService } from '../workspace.service'; + +@Component({ + selector: 'activity-log', + templateUrl: './activity-log.component.html', + styleUrls: ['./activity-log.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class ActivityLogComponent implements OnInit { + + activities: Activity[] = []; + temp: Activity[] = []; + + constructor(private workspaceService: WorkspaceService, + private activityLogService: ActivityLogService, + private loaderService: SdcUiServices.LoaderService) { + } + + ngOnInit(): void { + this.loaderService.activate(); + const componentId: string = this.workspaceService.metadata.uniqueId; + const componentType: string = this.workspaceService.metadata.componentType; + this.activityLogService.getActivityLog(componentType, componentId).subscribe((logs) => { + this.activities = logs; + this.temp = [...logs]; + this.loaderService.deactivate(); + }, (error) => { this.loaderService.deactivate(); }); + } + + updateFilter(event) { + const val = event.target.value.toLowerCase(); + + // filter our data + const temp = this.temp.filter((activity: Activity) => { + return !val || + activity.COMMENT.toLowerCase().indexOf(val) !== -1 || + activity.STATUS.toLowerCase().indexOf(val) !== -1 || + activity.ACTION.toLowerCase().indexOf(val) !== -1 || + activity.MODIFIER.toLowerCase().indexOf(val) !== -1; + }); + + // update the rows + this.activities = temp; + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.module.ts b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.module.ts new file mode 100644 index 0000000000..39334d8cde --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/activity-log/activity-log.module.ts @@ -0,0 +1,28 @@ +import {CommonModule} from "@angular/common"; +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; +import {ActivityLogComponent} from "./activity-log.component"; +import {ActivityLogService} from "../../../services/activity-log.service"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; + +@NgModule({ + declarations: [ + ActivityLogComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + GlobalPipesModule, + NgxDatatableModule + ], + exports: [ + ActivityLogComponent + ], + entryComponents: [ + ActivityLogComponent + ], + providers: [ ActivityLogService ] +}) +export class ActivityLogModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.html b/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.html new file mode 100644 index 0000000000..bd30a469e0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.html @@ -0,0 +1,104 @@ +<form> + <div class="attr-container"> + + <div class="attr-col"> + <!-- ATTRIBUTE NAME - MANDATORY --> + <div> + <sdc-input + #attributeName + label="Name" + [required]="true" + [(value)]="attributeToEdit.name" + [disabled]="isEdit" + name="attributeName" + testId="attributeName" + [maxLength]="255"> + </sdc-input> + <sdc-validation [validateElement]="attributeName" (validityChanged)="onValidityChange($event, 'name')"> + <sdc-required-validator message="{{'VALIDATION_ERROR_REQUIRED' | translate : { 'field' : 'Name' } }}"></sdc-required-validator> + <sdc-regex-validator message="{{'VALIDATION_ERROR_SPECIAL_CHARS_NOT_ALLOWED' | translate }}" [pattern]="validationPatterns.propertyName"></sdc-regex-validator> + </sdc-validation> + </div> + + <!-- ATTRIBUTE DESCRIPTION - OPTIONAL --> + <div> + <sdc-textarea #attributeDescription + [(value)]="attributeToEdit.description" + [required]="false" + testId="description" + [maxLength]="256" + label="Description"> + </sdc-textarea> + </div> + </div> + + <div class="attr-col"> + + <div class="attributeType"> + <!-- ATTRIBUTE TYPE - MANDATORY --> + <sdc-dropdown #attributeType [disabled]="false" label="Type" [required]="true" + [selectedOption]="toDropDownOption(this.attributeToEdit.type)" placeHolder="Choose Type" + [options]="types" (changed)="onTypeSelected($event)"> + <sdc-validation [validateElement]="attributeType" (validityChanged)="onValidityChange($event, 'type')"> + <sdc-required-validator message="'required field'"></sdc-required-validator> + </sdc-validation> + </sdc-dropdown> + </div> + + <!-- ATTRIBUTE DEFAULT VALUE TEXT - OPTIONAL --> + <div *ngIf="attributeToEdit.type != 'boolean'"> + <sdc-input + #defaultValue + [required]="false" + label="Default Value" + [(value)]="attributeToEdit.defaultValue" + [disabled]="false" + name="defaultValue" + testId="defaultValue" + [maxLength]="255" + (valueChange)="defaultValueChanged()"> + </sdc-input> + + <sdc-validation [validateElement]="defaultValue" (validityChanged)="onValidityChange($event, 'defaultValue')"> + <sdc-regex-validator *ngIf="this.attributeToEdit.defaultValue && this.attributeToEdit.defaultValue.length > 0" message="{{ this.defaultValueErrorMessage }}" + [pattern]="defaultValuePattern"></sdc-regex-validator> + <sdc-custom-validator *ngIf="this.attributeToEdit.type == 'map' && this.attributeToEdit.schema.property.type" message="{{ 'PROPERTY_EDIT_MAP_UNIQUE_KEYS' | translate }}" + [callback]="isMapUnique" [disabled]="false"></sdc-custom-validator> + </sdc-validation> + </div> + + <!-- ATTRIBUTE DEFAULT VALUE BOOLEAN- OPTIONAL --> + <div *ngIf="attributeToEdit.type == 'boolean'"> + <sdc-dropdown [disabled]="false" label="Default Value" + [required]="false" + [selectedOption]="toDropDownOption(this.attributeToEdit.defaultValue)" placeHolder="Choose Default Value" + [options]="booleanValues" (changed)="onBooleanDefaultValueSelected($event)"> + + </sdc-dropdown> + </div> + + <div *ngIf="attributeToEdit.type == 'list' || attributeToEdit.type == 'map'"> + <!-- ATTRIBUTE ENTRY SCHEMA - MANDATORY --> + <sdc-dropdown #entrySchema + [disabled]="false" label="Entry Schema" [required]="true" + [selectedOption]="toDropDownOption(this.attributeToEdit.schema.property.type)" placeHolder="Choose Schema Type" + [options]="entrySchemaValues" (changed)="onEntrySchemaTypeSelected($event)"> + <sdc-validation [validateElement]="entrySchema" (validityChanged)="onValidityChange($event, 'entrySchema')"> + <sdc-required-validator message="'required !TODO - CHANGE MESSAGE'"></sdc-required-validator> + </sdc-validation> + </sdc-dropdown> + </div> + + <!-- ATTRIBUTE HIDDEN - OPTIONAL --> + <sdc-checkbox + label="Hidden" + [checked]="attributeToEdit.hidden" + [disabled]="false" + testId="hidden" + (checkedChange)="this.onHiddenCheckboxClicked($event)" + > + </sdc-checkbox> + </div> + </div> + +</form>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.ts new file mode 100644 index 0000000000..c703869ad2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attribute-modal.component.ts @@ -0,0 +1,138 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { IDropDownOption } from 'onap-ui-angular/dist/form-elements/dropdown/dropdown-models'; +import { InputComponent } from 'onap-ui-angular/dist/form-elements/text-elements/input/input.component'; +import { Subject } from 'rxjs/Subject'; +import { AttributeModel } from '../../../../models/attributes'; +import { ValidationUtils } from '../../../../utils/validation-utils'; +import { CacheService } from '../../../services/cache.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { AttributeOptions } from './attributes-options'; + +@Component({ + selector: 'attribute-modal', + templateUrl: './attribute-modal.component.html', + styleUrls: ['./attributes.component.less'] +}) +export class AttributeModalComponent implements OnInit { + + @ViewChild('defaultValue') validatedInput: InputComponent; + + public readonly types = AttributeOptions.types; // integer, string, boolean etc. + + public readonly booleanValues = AttributeOptions.booleanValues; // true / false + + public readonly entrySchemaValues = AttributeOptions.entrySchemaValues; // integer, string, boolean, float + + public onValidationChange: Subject<boolean> = new Subject(); + + public validationPatterns: any; + public readonly listPattern = ValidationUtils.getPropertyListPatterns(); + public readonly mapPattern = ValidationUtils.getPropertyMapPatterns(); + + // The current effective default value pattern + public defaultValuePattern: string; + public defaultValueErrorMessage: string; + + // Attribute being Edited + public attributeToEdit: AttributeModel; + + constructor(private translateService: TranslateService, private cacheService: CacheService) { + this.validationPatterns = this.cacheService.get('validation').validationPatterns; + } + + ngOnInit() { + this.revalidateDefaultValue(); + } + + onHiddenCheckboxClicked(event: boolean) { + this.attributeToEdit.hidden = event; + } + + onTypeSelected(selectedElement: IDropDownOption) { + if (this.attributeToEdit.type !== selectedElement.value && selectedElement.value === 'boolean') { + this.attributeToEdit.defaultValue = ''; // Clean old value in case we choose change type to boolean + } + this.attributeToEdit.type = selectedElement.value; + this.revalidateDefaultValue(); + } + + onBooleanDefaultValueSelected(selectedElement: IDropDownOption) { + if (this.attributeToEdit.type === 'boolean') { + this.attributeToEdit.defaultValue = selectedElement.value; + } + } + + onEntrySchemaTypeSelected(selectedElement: IDropDownOption) { + this.attributeToEdit.schema.property.type = selectedElement.value; + this.revalidateDefaultValue(); + } + + onValidityChange(isValid: boolean, field: string) { + const typeIsValid = this.attributeToEdit.type && this.attributeToEdit.type.length > 0; // Make sure type is defined + + // Make sure name is defined when other fields are changed + let nameIsValid = true; + if (field !== 'name') { + nameIsValid = this.attributeToEdit.name && this.attributeToEdit.name.length > 0; + } + this.onValidationChange.next(isValid && nameIsValid && typeIsValid); + } + + defaultValueChanged() { + this.revalidateDefaultValue(); + } + + /** + * Utility function for UI that converts a simple value to IDropDownOption + * @param val + * @returns {{value: any; label: any}} + */ + toDropDownOption(val: string) { + return { value : val, label: val }; + } + + public isMapUnique = () => { + if (this.attributeToEdit && this.attributeToEdit.type === 'map' && this.attributeToEdit.defaultValue) { + return ValidationUtils.validateUniqueKeys(this.attributeToEdit.defaultValue); + } + return true; + } + + private revalidateDefaultValue() { + this.setDefaultValuePattern(this.attributeToEdit.type); + setTimeout(() => { + if (this.validatedInput) { + this.validatedInput.onKeyPress(this.attributeToEdit.defaultValue); + } }, 250); + } + + private setDefaultValuePattern(valueType: string) { + const selectedSchemaType = this.attributeToEdit.schema.property.type; + this.defaultValuePattern = '.*'; + switch (valueType) { + case 'float': + this.defaultValuePattern = this.validationPatterns.number; + this.defaultValueErrorMessage = this.translateService.translate('VALIDATION_ERROR_TYPE', { type : 'float' }); + break; + case 'integer': + this.defaultValuePattern = this.validationPatterns.integerNoLeadingZero; + this.defaultValueErrorMessage = this.translateService.translate('VALIDATION_ERROR_TYPE', { type : 'integer' }); + break; + case 'list': + if (selectedSchemaType != undefined) { + this.defaultValuePattern = this.listPattern[selectedSchemaType]; + const listTypeStr = `list of ${selectedSchemaType}s (v1, v2, ...) `; + this.defaultValueErrorMessage = this.translateService.translate('VALIDATION_ERROR_TYPE', { type : listTypeStr }); + } + break; + case 'map': + if (selectedSchemaType != undefined) { + this.defaultValuePattern = this.mapPattern[selectedSchemaType]; + const mapTypeStr = `map of ${selectedSchemaType}s (k1:v1, k2:v2, ...)`; + this.defaultValueErrorMessage = this.translateService.translate('VALIDATION_ERROR_TYPE', { type : mapTypeStr }); + } + break; + } + } + +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-modal.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-modal.component.spec.ts new file mode 100644 index 0000000000..99aa140dd1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-modal.component.spec.ts @@ -0,0 +1,128 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { AttributeModel } from '../../../../models/attributes'; +import { ValidationUtils } from '../../../../utils/validation-utils'; +import { CacheService } from '../../../services/cache.service'; +import { TranslatePipe } from '../../../shared/translator/translate.pipe'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { AttributeModalComponent } from './attribute-modal.component'; + +describe('attributes modal component', () => { + + let fixture: ComponentFixture<AttributeModalComponent>; + + // Mocks + let translateServiceMock: Partial<TranslateService>; + let cacheServiceMock: Partial<CacheService>; + + const validationPatterns = { + integerNoLeadingZero : 'int_regx', + number : 'number_regx' + }; + + const newAttribute = { + uniqueId: '1', name: 'attr1', description: 'description1', type: 'string', hidden: false, defaultValue: 'val1', schema: null + }; + + beforeEach( + async(() => { + + translateServiceMock = { + translate: jest.fn() + }; + + cacheServiceMock = { + get: jest.fn().mockImplementation((k) => { + return { validationPatterns}; + } ) + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [AttributeModalComponent, TranslatePipe], + imports: [], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: TranslateService, useValue: translateServiceMock}, + {provide: CacheService, useValue: cacheServiceMock}, + ] + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(AttributeModalComponent); + }); + }) + ); + + it('test that when hidden is clicked, hidden attribute is set', async () => { + fixture.componentInstance.attributeToEdit = new AttributeModel(); + const hidden = fixture.componentInstance.attributeToEdit.hidden; + fixture.componentInstance.ngOnInit(); + + expect(hidden).toBe(false); + fixture.componentInstance.onHiddenCheckboxClicked(true); + expect(fixture.componentInstance.attributeToEdit.hidden).toBe(true); + }); + + it('test that when type is set to boolean default value is cleared', async () => { + const component = fixture.componentInstance; + component.attributeToEdit = new AttributeModel(); + component.ngOnInit(); + + component.onTypeSelected({ value : 'string', label : 'string'}); + component.attributeToEdit.defaultValue = 'some_value'; + component.onTypeSelected({ value : 'boolean', label : 'boolean'}); + expect(component.attributeToEdit.defaultValue).toBe(''); + + component.onBooleanDefaultValueSelected({ value : 'true', label : 'true'}); + expect(component.attributeToEdit.defaultValue).toBe('true'); + }); + + it('test that when certain type is selected, the correct regex pattern is chosen', async () => { + const component = fixture.componentInstance; + component.attributeToEdit = new AttributeModel(); + component.ngOnInit(); + + // integer + component.onTypeSelected({ value : 'integer', label : 'integer'}); + expect(component.defaultValuePattern).toBe(validationPatterns.integerNoLeadingZero); + + // float + component.onTypeSelected({ value : 'float', label : 'float'}); + expect(component.defaultValuePattern).toBe(validationPatterns.number); + + // list is chosen with no schema, regex pattern is set to default + component.onTypeSelected({ value : 'list', label : 'list'}); + expect(component.defaultValuePattern).toEqual('.*'); + + // schema is set to list of int + component.onEntrySchemaTypeSelected({ value : 'integer', label : 'integer' }); + expect(component.defaultValuePattern).toEqual(ValidationUtils.getPropertyListPatterns().integer); + + // schema is set to list of float + component.onEntrySchemaTypeSelected({ value : 'float', label : 'float' }); + expect(component.defaultValuePattern).toEqual(ValidationUtils.getPropertyListPatterns().float); + + // map is selected (float schema is still selected from previous line) + component.onTypeSelected({ value : 'map', label : 'map'}); + expect(component.defaultValuePattern).toEqual(ValidationUtils.getPropertyMapPatterns().float); + + // change schema type to boolean + component.onEntrySchemaTypeSelected({ value : 'boolean', label : 'boolean' }); + }); + + it('should detect map with non-unique keys', async () => { + const component = fixture.componentInstance; + component.attributeToEdit = new AttributeModel(); + component.ngOnInit(); + expect(component.isMapUnique()).toBe(true); // map is not selected so return true by default + component.onTypeSelected({ value : 'map', label : 'map'}); + component.onEntrySchemaTypeSelected({ value : 'boolean', label : 'boolean' }); + component.attributeToEdit.defaultValue = '"1":true,"2":false'; + expect(component.isMapUnique()).toBe(true); + component.attributeToEdit.defaultValue = '"1":true,"1":false'; + expect(component.isMapUnique()).toBe(false); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-options.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-options.ts new file mode 100644 index 0000000000..2a6924bc5e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes-options.ts @@ -0,0 +1,60 @@ +import { IDropDownOption } from 'onap-ui-angular/dist/form-elements/dropdown/dropdown-models'; + +export class AttributeOptions { + public static readonly types: IDropDownOption[] = [ + { + label: 'integer', + value: 'integer', + }, + { + label: 'string', + value: 'string', + }, + { + label: 'float', + value: 'float' + }, + { + label: 'boolean', + value: 'boolean' + }, + { + label: 'list', + value: 'list' + }, + { + label: 'map', + value: 'map' + } + ]; + + public static readonly booleanValues: IDropDownOption[] = [ + { + label: 'true', + value: 'true', + }, + { + label: 'false', + value: 'false', + } + ]; + + public static readonly entrySchemaValues: IDropDownOption[] = [ + { + label: 'integer', + value: 'integer', + }, + { + label: 'string', + value: 'string', + }, + { + label: 'float', + value: 'float' + }, + { + label: 'boolean', + value: 'boolean' + } + ]; +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.html b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.html new file mode 100644 index 0000000000..00a7a5cec0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.html @@ -0,0 +1,93 @@ +<!-- + ~ 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="workspace-attributes"> + + <div class="action-bar-wrapper"> + <svg-icon-label + *ngIf="!(this.isViewOnly$ | async)" + class="add-attr-icon" + [name]="'plus'" + [mode]="'primary'" + [size]="'medium'" + [label]="'Add'" + [labelPlacement]="'right'" + [labelClassName]="'externalActionLabel'" + (click)="onAddAttribute()"> + </svg-icon-label> + </div> + + <ngx-datatable + columnMode="flex" + [footerHeight]="0" + [limit]="50" + [headerHeight]="40" + [rowHeight]="35" + [rows]="attributes" + #componentAttributesTable + (activate)="onExpandRow($event)"> + + <ngx-datatable-row-detail [rowHeight]="80"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> + <div>{{row.description}}</div> + </ng-template> + </ngx-datatable-row-detail> + + <ngx-datatable-column [resizeable]="false" name="Name" [flexGrow]="2"> + + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded"> + <div class="expand-collapse-cell"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'"></svg-icon> + <span>{{ row.name }}</span> + </div> + </ng-template> + + </ngx-datatable-column> + + <ngx-datatable-column [resizeable]="false" name="Type" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.type}} + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-column [resizeable]="false" name="Default Value" [flexGrow]="3"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.defaultValue}} + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-column *ngIf="!(this.isViewOnly$ | async)" [resizeable]="false" name="Action" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row" let-rowIndex="rowIndex"> + <div class="actionColumn"> + <svg-icon [clickable]="true" + [mode]="'primary2'" + [name]="'edit-o'" + [size]="'medium'" + (click)="onEditAttribute($event, row)"> + </svg-icon> + <svg-icon [clickable]="true" + [mode]="'primary2'" + [name]="'trash-o'" + (click)="onDeleteAttribute($event, row)" + [size]="'medium'"> + </svg-icon> + </div> + </ng-template> + </ngx-datatable-column> + + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.less b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.less new file mode 100644 index 0000000000..3e91ae4689 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.less @@ -0,0 +1,36 @@ +.action-bar-wrapper { + flex: 0 0 30%; + display: flex; + justify-content: flex-end; + margin-bottom: 10px; +} + +.add-attr-icon{ + cursor: pointer; +} + +.attr-container { + display: flex; + justify-content: space-between; + + .attr-col { + display: flex; + flex-direction: column; + max-width: 275px; + flex-grow: 1; + } + +} + +.attributeType { + margin-bottom: 10px; +} + +sdc-checkbox { + margin-top: 20px; +} + +.actionColumn { + text-align: center; + padding: 5px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts new file mode 100644 index 0000000000..f676e2b4d9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.spec.ts @@ -0,0 +1,182 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import 'rxjs/add/observable/of'; +import { Observable } from 'rxjs/Rx'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { ModalsHandler } from '../../../../utils'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { WorkspaceService } from '../workspace.service'; +import { AttributesComponent } from './attributes.component'; + +describe('attributes component', () => { + + let fixture: ComponentFixture<AttributesComponent>; + + // Mocks + let workspaceServiceMock: Partial<WorkspaceService>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let componentMetadataMock: ComponentMetadata; + let modalServiceMock: Partial<SdcUiServices.ModalService>; + + const mockAttributesList = [ + { uniqueId: '1', name: 'attr1', description: 'description1', type: 'string', hidden: false, defaultValue: 'val1', schema: null }, + { uniqueId : '2', name : 'attr2', description: 'description2', type : 'int', hidden : false, defaultValue : 1, schema : null}, + { uniqueId : '3', name : 'attr3', description: 'description3', type : 'double', hidden : false, defaultValue : 1.0, schema : null}, + { uniqueId : '4', name : 'attr4', description: 'description4', type : 'boolean', hidden : false, defaultValue : true, schema : null}, + ]; + + const newAttribute = { + uniqueId : '5', name : 'attr5', description: 'description5', type : 'string', hidden : false, defaultValue : 'val5', schema : null + }; + const updatedAttribute = { + uniqueId : '2', name : 'attr2', description: 'description_new', type : 'string', hidden : false, defaultValue : 'new_val2', schema : null + }; + const errorAttribute = { + uniqueId : '99', name : 'attr99', description: 'description_error', type : 'string', hidden : false, defaultValue : 'error', schema : null + }; + + beforeEach( + async(() => { + + componentMetadataMock = new ComponentMetadata(); + componentMetadataMock.uniqueId = 'fake'; + componentMetadataMock.componentType = 'VL'; + + topologyTemplateServiceMock = { + getComponentAttributes: jest.fn().mockResolvedValue({ attributes : mockAttributesList }), + addAttributeAsync: jest.fn().mockImplementation( + (compType, cUid, attr) => { + if (attr === errorAttribute) { + return Observable.throwError('add_error').toPromise(); + } else { + return Observable.of(newAttribute).toPromise(); + } + } + ), + updateAttributeAsync: jest.fn().mockImplementation( + (compType, cUid, attr) => { + if (attr === errorAttribute) { + return Observable.throwError('update_error').toPromise(); + } else { + return Observable.of(updatedAttribute).toPromise(); + } + } + ), + deleteAttributeAsync: jest.fn().mockImplementation((cid, ctype, attr) => Observable.of(attr)) + }; + + workspaceServiceMock = { + metadata: componentMetadataMock + }; + + const customModalInstance = { innerModalContent: { instance: { onValidationChange: { subscribe: jest.fn()}}}}; + + modalServiceMock = { + openInfoModal: jest.fn(), + openCustomModal: jest.fn().mockImplementation(() => customModalInstance) + }; + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [AttributesComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock}, + {provide: ModalsHandler, useValue: {}}, + {provide: TranslateService, useValue: { translate: jest.fn() }}, + {provide: SdcUiServices.ModalService, useValue: modalServiceMock }, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(AttributesComponent); + }); + }) + ); + + it('should see exactly 1 attributes on init', async () => { + await fixture.componentInstance.asyncInitComponent(); + expect(fixture.componentInstance.getAttributes().length).toEqual(4); + }); + + it('should see exactly 5 attributes when adding', async () => { + await fixture.componentInstance.asyncInitComponent(); + expect(fixture.componentInstance.getAttributes().length).toEqual(4); + + await fixture.componentInstance.addOrUpdateAttribute(newAttribute, false); + expect(fixture.componentInstance.getAttributes().length).toEqual(5); + }); + + it('should see exactly 3 attributes when deleting', async () => { + await fixture.componentInstance.asyncInitComponent(); + expect(fixture.componentInstance.getAttributes().length).toEqual(4); + const attrToDelete = mockAttributesList[0]; + expect(fixture.componentInstance.getAttributes().filter((attr) => attr.uniqueId === attrToDelete.uniqueId).length).toEqual(1); + await fixture.componentInstance.deleteAttribute(attrToDelete); + expect(fixture.componentInstance.getAttributes().length).toEqual(3); + expect(fixture.componentInstance.getAttributes().filter((attr) => attr.uniqueId === attrToDelete.uniqueId).length).toEqual(0); + }); + + it('should see updated attribute', async () => { + await fixture.componentInstance.asyncInitComponent(); + + await fixture.componentInstance.addOrUpdateAttribute(updatedAttribute, true); + expect(fixture.componentInstance.getAttributes().length).toEqual(4); + const attribute = fixture.componentInstance.getAttributes().filter( (attr) => { + return attr.uniqueId === updatedAttribute.uniqueId; + })[0]; + expect(attribute.description).toEqual( 'description_new'); + }); + + it('Add fails, make sure loader is deactivated and attribute is not added', async () => { + await fixture.componentInstance.asyncInitComponent(); + const numAttributes = fixture.componentInstance.getAttributes().length; + await fixture.componentInstance.addOrUpdateAttribute(errorAttribute, false); // Add + expect(loaderServiceMock.deactivate).toHaveBeenCalled(); + expect(fixture.componentInstance.getAttributes().length).toEqual(numAttributes); + }); + + it('Update fails, make sure loader is deactivated', async () => { + await fixture.componentInstance.asyncInitComponent(); + const numAttributes = fixture.componentInstance.getAttributes().length; + await fixture.componentInstance.addOrUpdateAttribute(errorAttribute, true); // Add + expect(loaderServiceMock.deactivate).toHaveBeenCalled(); + expect(fixture.componentInstance.getAttributes().length).toEqual(numAttributes); + }); + + it('on delete modal shell be opened', async () => { + await fixture.componentInstance.asyncInitComponent(); + const event = { stopPropagation: jest.fn() }; + fixture.componentInstance.onDeleteAttribute(event, fixture.componentInstance.getAttributes()[0]); + expect(event.stopPropagation).toHaveBeenCalled(); + expect(modalServiceMock.openInfoModal).toHaveBeenCalled(); + }); + + it('on add modal shell be opened', async () => { + await fixture.componentInstance.asyncInitComponent(); + fixture.componentInstance.onAddAttribute(); + expect(modalServiceMock.openCustomModal).toHaveBeenCalled(); + }); + + it('on edit modal shell be opened', async () => { + await fixture.componentInstance.asyncInitComponent(); + const event = { stopPropagation: jest.fn() }; + fixture.componentInstance.onEditAttribute(event, fixture.componentInstance.getAttributes()[0]); + expect(event.stopPropagation).toHaveBeenCalled(); + expect(modalServiceMock.openCustomModal).toHaveBeenCalled(); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.ts new file mode 100644 index 0000000000..bc47f1456b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.component.ts @@ -0,0 +1,188 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Select } from '@ngxs/store'; +import { IAttributeModel } from 'app/models'; +import * as _ from 'lodash'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { ModalComponent } from 'onap-ui-angular/dist/modals/modal.component'; +import { AttributeModel } from '../../../../models'; +import { Resource } from '../../../../models'; +import { ModalsHandler } from '../../../../utils'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { WorkspaceState } from '../../../store/states/workspace.state'; +import { WorkspaceService } from '../workspace.service'; +import { AttributeModalComponent } from './attribute-modal.component'; + +@Component({ + selector: 'attributes', + templateUrl: './attributes.component.html', + styleUrls: ['./attributes.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class AttributesComponent implements OnInit { + + @Select(WorkspaceState.isViewOnly) + isViewOnly$: boolean; + + @ViewChild('componentAttributesTable') + private table: any; + + private componentType: string; + private componentUid: string; + + private attributes: IAttributeModel[] = []; + private temp: IAttributeModel[] = []; + private customModalInstance: ModalComponent; + + constructor(private workspaceService: WorkspaceService, + private topologyTemplateService: TopologyTemplateService, + private modalsHandler: ModalsHandler, + private modalService: SdcUiServices.ModalService, + private loaderService: SdcUiServices.LoaderService, + private translateService: TranslateService) { + + this.componentType = this.workspaceService.metadata.componentType; + this.componentUid = this.workspaceService.metadata.uniqueId; + } + + ngOnInit(): void { + this.asyncInitComponent(); + } + + async asyncInitComponent() { + this.loaderService.activate(); + const response = await this.topologyTemplateService.getComponentAttributes(this.componentType, this.componentUid); + this.attributes = response.attributes; + this.temp = [...response.attributes]; + this.loaderService.deactivate(); + } + + getAttributes(): IAttributeModel[] { + return this.attributes; + } + + addOrUpdateAttribute = async (attribute: AttributeModel, isEdit: boolean) => { + this.loaderService.activate(); + let attributeFromServer: AttributeModel; + this.temp = [...this.attributes]; + + const deactivateLoader = () => { + this.loaderService.deactivate(); + return undefined; + }; + + if (isEdit) { + attributeFromServer = await this.topologyTemplateService + .updateAttributeAsync(this.componentType, this.componentUid, attribute) + .catch(deactivateLoader); + if (attributeFromServer) { + const indexOfUpdatedAttribute = _.findIndex(this.temp, (e) => e.uniqueId === attributeFromServer.uniqueId); + this.temp[indexOfUpdatedAttribute] = attributeFromServer; + } + } else { + attributeFromServer = await this.topologyTemplateService + .addAttributeAsync(this.componentType, this.componentUid, attribute) + .catch(deactivateLoader); + if (attributeFromServer) { + this.temp.push(attributeFromServer); + } + } + this.attributes = this.temp; + this.loaderService.deactivate(); + } + + deleteAttribute = async (attributeToDelete: AttributeModel) => { + this.loaderService.activate(); + this.temp = [...this.attributes]; + const res = await this.topologyTemplateService.deleteAttributeAsync(this.componentType, this.componentUid, attributeToDelete); + _.remove(this.temp, (attr) => attr.uniqueId === attributeToDelete.uniqueId); + this.attributes = this.temp; + this.loaderService.deactivate(); + }; + + openAddEditModal(selectedRow: AttributeModel, isEdit: boolean) { + const component = new Resource(undefined, undefined, undefined); + component.componentType = this.componentType; + component.uniqueId = this.componentUid; + + const title: string = this.translateService.translate('ATTRIBUTE_DETAILS_MODAL_TITLE'); + const attributeModalConfig = { + title, + size: 'md', + type: SdcUiCommon.ModalType.custom, + buttons: [ + { + id: 'save', + text: 'Save', + // spinner_position: Placement.left, + size: 'sm', + callback: () => this.modalCallBack(isEdit), + closeModal: true, + disabled: false, + } + ] as SdcUiCommon.IModalButtonComponent[] + }; + + this.customModalInstance = this.modalService.openCustomModal(attributeModalConfig, AttributeModalComponent, { attributeToEdit: selectedRow }); + this.customModalInstance.innerModalContent.instance. + onValidationChange.subscribe((isValid) => this.customModalInstance.getButtonById('save').disabled = !isValid); + } + + /*********************** + * Call Backs from UI * + ***********************/ + + /** + * Called when 'Add' is clicked + */ + onAddAttribute() { + this.openAddEditModal(new AttributeModel(), false); + } + + /** + * Called when 'Edit' button is clicked + */ + onEditAttribute(event, row) { + event.stopPropagation(); + + const attributeToEdit: AttributeModel = new AttributeModel(row); + this.openAddEditModal(attributeToEdit, true); + } + + /** + * Called when 'Delete' button is clicked + */ + onDeleteAttribute(event, row: AttributeModel) { + event.stopPropagation(); + const onOk = () => { + this.deleteAttribute(row); + }; + + const title: string = this.translateService.translate('ATTRIBUTE_VIEW_DELETE_MODAL_TITLE'); + const message: string = this.translateService.translate('ATTRIBUTE_VIEW_DELETE_MODAL_TEXT'); + const okButton = new SdcUiComponents.ModalButtonComponent(); + okButton.testId = 'OK'; + okButton.text = 'OK'; + okButton.type = SdcUiCommon.ButtonType.info; + okButton.closeModal = true; + okButton.callback = onOk; + + this.modalService.openInfoModal(title, message, 'delete-modal', [okButton]); + } + + onExpandRow(event) { + if (event.type === 'click') { + this.table.rowDetail.toggleExpandRow(event.row); + } + } + + /** + * Callback from Modal after "Save" is clicked + * + * @param {boolean} isEdit - Whether modal is edit or add attribute + */ + modalCallBack = (isEdit: boolean) => { + const attribute: AttributeModel = this.customModalInstance.innerModalContent.instance.attributeToEdit; + this.addOrUpdateAttribute(attribute, isEdit); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.module.ts b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.module.ts new file mode 100644 index 0000000000..5abb952e37 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/attributes/attributes.module.ts @@ -0,0 +1,32 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { SdcUiComponentsModule } from 'onap-ui-angular'; +import { GlobalPipesModule } from '../../../pipes/global-pipes.module'; +import { AttributesComponent } from './attributes.component'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { AttributeModalComponent } from './attribute-modal.component'; +import { TranslateModule } from '../../../shared/translator/translate.module'; + +@NgModule({ + declarations: [ + AttributesComponent, + AttributeModalComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + GlobalPipesModule, + NgxDatatableModule, + TranslateModule + ], + exports: [ + AttributesComponent + ], + entryComponents: [ + AttributesComponent, AttributeModalComponent + ], + providers: [TopologyTemplateService] +}) +export class AttributesModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/__snapshots__/deployment-artifacts-page.spec.ts.snap b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/__snapshots__/deployment-artifacts-page.spec.ts.snap new file mode 100644 index 0000000000..b53674497c --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/__snapshots__/deployment-artifacts-page.spec.ts.snap @@ -0,0 +1,24 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`deployment artifacts page should match current snapshot of informational artifact pages component 1`] = ` +<deployment-artifact-page + addOrUpdateArtifact={[Function Function]} + artifactsService={[Function Object]} + cacheService={[Function Object]} + deleteArtifact={[Function Function]} + getEnvArtifact={[Function Function]} + modalService={[Function Object]} + openGenericArtifactBrowserModal={[Function Function]} + openPopOver={[Function Function]} + popoverContentComponent="undefined" + popoverService={[Function Object]} + sortArtifacts={[Function Function]} + store={[Function Store]} + table="undefined" + translateService={[Function Object]} + updateEnvParams={[Function Function]} + workspaceService={[Function Object]} +> + +</deployment-artifact-page> +`; diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.html b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.html new file mode 100644 index 0000000000..35592d846a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.html @@ -0,0 +1,73 @@ +<div class="deployment-artifact-page" *ngIf="(workspaceState$ | async) as state"> + <svg-icon-label *ngIf="!state.isViewOnly" class="add-artifact-btn" [clickable]="true" [mode]="'primary'" [labelPlacement]="'right'" + [label]="'Add'" [name]="'plus'" + (click)="addOrUpdateArtifact()"></svg-icon-label> + <ngx-datatable + columnMode="flex" + [headerHeight]="40" + [footerHeight]="'undefined'" + [reorderable]="false" + [swapColumns]="false" + [rows]="deploymentArtifacts$ | async" + #deploymentArtifactsTable> + <ngx-datatable-column [resizeable]="false" name="Name" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div *ngIf="row.generatedFromId" class="env-artifact-container"> + <div class="env-artifact"></div> + </div> + <span sdc-tooltip [tooltip-text]="row.artifactDisplayName" [tooltip-placement]="3" [attr.data-tests-id]="'artifactDisplayName_' + row.artifactDisplayName">{{row.artifactDisplayName}}</span> + <span *ngIf="row.description.length > 0" class="info"> + <svg-icon [clickable]="true" [name]="'comment'" [mode]="'primary2'" (click)="openPopOver('Description',row.description,{x:$event.pageX , y:$event.pageY },'bottom')"></svg-icon> + </span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="Type" [flexGrow]="0.6"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span sdc-tooltip [tooltip-text]="row.artifactType" [tooltip-placement]="3" [attr.data-tests-id]="'artifactType_' + row.artifactDisplayName">{{row.artifactType}}</span> + </ng-template> + </ngx-datatable-column> exactly 2 tosca artifacts + <ngx-datatable-column [resizeable]="false" name="Version" [flexGrow]="0.3"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span [attr.data-tests-id]="'artifactVersion_' + row.artifactDisplayName">{{ row.artifactVersion }}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="UUID" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span sdc-tooltip [tooltip-text]="row.artifactUUID" [tooltip-placement]="3">{{ row.artifactUUID }}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [flexGrow]="0.6"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class="download-artifact-button"> + <svg-icon *ngIf="!row.heatParameters?.length && !state.isViewOnly" class="action-icon" [mode]="'primary2'" [name]="'edit-o'" + testId="edit_{{row.artifactDisplayName}}" clickable="true" size="medium" + (click)="addOrUpdateArtifact(row, state.isViewOnly)"></svg-icon> + <svg-icon *ngIf="row.heatParameters?.length && !state.isViewOnly" class="action-icon" [mode]="'primary2'" [name]="'indesign_status'" + testId="update_heat_params_{{row.artifactDisplayName}}" clickable="true" size="medium" + (click)="updateEnvParams(row, state.isViewOnly)"></svg-icon> + <svg-icon *ngIf="!row.isFromCsar && !state.isViewOnly" class="action-icon" [mode]="'primary2'" [name]="'trash-o'" + testId="delete_{{row.artifactDisplayName}}" clickable="true" size="medium" (click)="deleteArtifact(row)"></svg-icon> + <svg-icon *ngIf="row.isGenericBrowseable()" class="action-icon" [mode]="'primary2'" [name]="'search-o'" + testId="gab-{{row.artifactDisplayName}}" clickable="true" size="medium" (click)="openGenericArtifactBrowserModal(row)"></svg-icon> + + <!--Download--> + </div> + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-footer> + <ng-template ngx-datatable-footer-template> + <div class="table-footer-container"> + <sdc-button *ngIf="!state.isViewOnly" [type]="'secondary'" + [testId]="'add_artifact_btn'" + [text]="'DEPLOYMENT_ARTIFACT_BUTTON_ADD_OTHER' | translate" + [icon_name]="'plus-circle-o'" + [icon_mode] = "'secondary'" + [icon_position]="'left'" + (click)="addOrUpdateArtifact()"> + </sdc-button> + </div> + </ng-template> + </ngx-datatable-footer> + </ngx-datatable> +</div> diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.less b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.less new file mode 100644 index 0000000000..22ceb96653 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.less @@ -0,0 +1,55 @@ +.deployment-artifact-page { + + + .env-artifact-container { + margin-left: -25px; + margin-top: -21px; + padding-left: 10px; + position: absolute; + background-color: white; + .env-artifact { + border-left: 1px #848586 solid; + height: 33px; + + border-top: 1px #848586 solid; + border-bottom: 1px #848586 solid; + width: 10px; + float: left; + + } + } + .add-artifact-btn { + display: flex; + cursor: pointer; + justify-content: flex-end; + margin-bottom: 10px; + } + .download-artifact-button { + display: flex; + justify-content: center; + + .action-icon { + margin-right: 10px; + } + } + + .table-footer-container { + display: flex; + align-items: center; + width: 100%; + justify-content: center; + margin: 20px 0px; + } +} + +:host ::ng-deep { + + .ngx-datatable { + //border: 1px solid red; + .datatable-body-cell { + .info { + float: right; + } + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.ts new file mode 100644 index 0000000000..53b21b34b6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.component.ts @@ -0,0 +1,155 @@ +import { Component, OnInit, ViewChild } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { ArtifactModel } from 'app/models'; +import * as _ from 'lodash'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/index'; +import { map } from 'rxjs/operators'; +import { GabConfig } from '../../../../models/gab-config'; +import { PathsAndNamesDefinition } from '../../../../models/paths-and-names'; +import { GenericArtifactBrowserComponent } from '../../../../ng2/components/logic/generic-artifact-browser/generic-artifact-browser.component'; +import { ArtifactGroupType, ArtifactType } from '../../../../utils/constants'; +import { ArtifactsService } from '../../../components/forms/artifacts-form/artifacts.service'; +import { PopoverContentComponent } from '../../../components/ui/popover/popover-content.component'; +import { CacheService } from '../../../services/cache.service'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { GetArtifactsByTypeAction } from '../../../store/actions/artifacts.action'; +import { ArtifactsState } from '../../../store/states/artifacts.state'; +import { WorkspaceState, WorkspaceStateModel } from '../../../store/states/workspace.state'; +import { WorkspaceService } from '../workspace.service'; +import { ModalService } from 'app/ng2/services/modal.service'; + +export interface IPoint { + x: number; + y: number; +} + +@Component({ + selector: 'deployment-artifact-page', + templateUrl: './deployment-artifacts-page.component.html', + styleUrls: ['./deployment-artifacts-page.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class DeploymentArtifactsPageComponent implements OnInit { + + public componentId: string; + public componentType: string; + public deploymentArtifacts$: Observable<ArtifactModel[]>; + public isComponentInstanceSelected: boolean; + + @Select(WorkspaceState) workspaceState$: Observable<WorkspaceStateModel>; + @ViewChild('informationArtifactsTable') table: any; + @ViewChild('popoverForm') popoverContentComponent: PopoverContentComponent; + + constructor(private workspaceService: WorkspaceService, + private artifactsService: ArtifactsService, + private store: Store, + private popoverService: SdcUiServices.PopoverService, + private cacheService: CacheService, + private modalService: SdcUiServices.ModalService, + private translateService: TranslateService) { + } + + private getEnvArtifact = (heatArtifact: ArtifactModel, artifacts: ArtifactModel[]): ArtifactModel => { + return _.find(artifacts, (item: ArtifactModel) => { + return item.generatedFromId === heatArtifact.uniqueId; + }); + }; + + // we need to sort the artifact in a way that the env artifact is always under the artifact he is connected to- this is cause of the way the ngx databale work + private sortArtifacts = ((artifacts) => { + const sortedArtifacts = []; + _.forEach(artifacts, (artifact: ArtifactModel): void => { + const envArtifact = this.getEnvArtifact(artifact, artifacts); + if (!artifact.generatedFromId) { + sortedArtifacts.push(artifact); + } + if (envArtifact) { + sortedArtifacts.push(envArtifact); + } + }); + return sortedArtifacts; + }) + + ngOnInit(): void { + this.componentId = this.workspaceService.metadata.uniqueId; + this.componentType = this.workspaceService.metadata.componentType; + + this.store.dispatch(new GetArtifactsByTypeAction({ + componentType: this.componentType, + componentId: this.componentId, + artifactType: ArtifactGroupType.DEPLOYMENT + })); + this.deploymentArtifacts$ = this.store.select(ArtifactsState.getArtifactsByType).pipe(map((filterFn) => filterFn(ArtifactType.DEPLOYMENT))).pipe(map(artifacts => { + return this.sortArtifacts(artifacts); + })); + } + + onActivate(event) { + if (event.type === 'click') { + this.table.rowDetail.toggleExpandRow(event.row); + } + } + + public addOrUpdateArtifact = (artifact: ArtifactModel, isViewOnly: boolean) => { + this.artifactsService.openArtifactModal(this.componentId, this.componentType, artifact, ArtifactGroupType.DEPLOYMENT, isViewOnly); + } + + public deleteArtifact = (artifactToDelete) => { + this.artifactsService.deleteArtifact(this.componentType, this.componentId, artifactToDelete); + } + + private openPopOver = (title: string, content: string, positionOnPage: IPoint, location: string) => { + this.popoverService.createPopOver(title, content, positionOnPage, location); + } + + public updateEnvParams = (artifact: ArtifactModel, isViewOnly: boolean) => { + this.artifactsService.openUpdateEnvParams(this.componentType, this.componentId, artifact ); + } + + private openGenericArtifactBrowserModal = (artifact: ArtifactModel): void => { + const titleStr = 'Generic Artifact Browser'; + const modalConfig = { + size: 'sdc-xl', + title: titleStr, + type: SdcUiCommon.ModalType.custom, + buttons: [{ + id: 'closeGABButton', + text: 'Close', + size: 'sm', + closeModal: true + }] as SdcUiCommon.IModalButtonComponent[] + }; + + const uiConfiguration: any = this.cacheService.get('UIConfiguration'); + let noConfig: boolean = false; + let pathsandnamesArr: PathsAndNamesDefinition[] = []; + + if (typeof uiConfiguration.gab === 'undefined') { + noConfig = true; + } else { + const gabConfig: GabConfig = uiConfiguration.gab + .find((config) => config.artifactType === artifact.artifactType); + if (typeof gabConfig === 'undefined') { + noConfig = true; + } else { + pathsandnamesArr = gabConfig.pathsAndNamesDefinitions; + } + } + + + if (noConfig) { + const msg = this.translateService.translate('DEPLOYMENT_ARTIFACT_GAB_NO_CONFIG'); + this.modalService.openAlertModal(titleStr, msg); + } + + const modalInputs = { + pathsandnames: pathsandnamesArr, + artifactid: artifact.esId, + resourceid: this.componentId + }; + + this.modalService.openCustomModal(modalConfig, GenericArtifactBrowserComponent, modalInputs); + + } + +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.module.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.module.ts new file mode 100644 index 0000000000..398e9d3f4d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.module.ts @@ -0,0 +1,35 @@ +import {CommonModule} from "@angular/common"; +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {UiElementsModule} from "../../../components/ui/ui-elements.module"; +import {ArtifactFormModule} from "../../../components/forms/artifacts-form/artifact-form.module"; +import {ArtifactsService} from "../../../components/forms/artifacts-form/artifacts.service"; +import {DeploymentArtifactsPageComponent} from "./deployment-artifacts-page.component"; +import {TranslatePipe} from "../../../shared/translator/translate.pipe"; +import {TranslateModule} from "../../../shared/translator/translate.module"; +import {GenericArtifactBrowserModule} from "../../../components/logic/generic-artifact-browser/generic-artifact-browser.module"; + +@NgModule({ + declarations: [ + DeploymentArtifactsPageComponent + ], + imports: [ + TranslateModule, + CommonModule, + SdcUiComponentsModule, + NgxDatatableModule, + UiElementsModule, + ArtifactFormModule, + GenericArtifactBrowserModule + ], + exports: [ + DeploymentArtifactsPageComponent + ], + entryComponents: [ + DeploymentArtifactsPageComponent + ], + providers:[ArtifactsService] +}) +export class DeploymentArtifactsPageModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.spec.ts new file mode 100644 index 0000000000..056efdc5d4 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment-artifacts/deployment-artifacts-page.spec.ts @@ -0,0 +1,86 @@ +// import ' rxjs/add/observable/of'; +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { async, ComponentFixture } from '@angular/core/testing'; +import { NgxsModule, Store } from '@ngxs/store'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiServices } from 'onap-ui-angular'; +import { Observable } from 'rxjs/Observable'; +import { deploymentArtifactMock } from '../../../../../jest/mocks/artifacts-mock'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { ArtifactsService } from '../../../components/forms/artifacts-form/artifacts.service'; +import { CacheService } from '../../../services/cache.service'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { TranslateModule } from '../../../shared/translator/translate.module'; +import { TranslateService } from '../../../shared/translator/translate.service'; +import { ArtifactsState } from '../../../store/states/artifacts.state'; +import { WorkspaceService } from '../workspace.service'; +import { DeploymentArtifactsPageComponent } from './deployment-artifacts-page.component'; +import {ModalService} from "../../../services/modal.service"; + +describe('deployment artifacts page', () => { + + let fixture: ComponentFixture<DeploymentArtifactsPageComponent>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let store: Store; + + beforeEach( + async(() => { + + topologyTemplateServiceMock = { + getArtifactsByType: jest.fn().mockImplementation((componentType, id, artifactType) => Observable.of(deploymentArtifactMock)) + }; + workspaceServiceMock = { + metadata: <ComponentMetadata>{ + uniqueId: 'service_unique_id', + componentType: 'SERVICE' + } + } + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + } + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [DeploymentArtifactsPageComponent], + imports: [NgxDatatableModule, TranslateModule, NgxsModule.forRoot([ArtifactsState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock}, + {provide: ArtifactsService, useValue: {}}, + {provide: SdcUiServices.PopoverService, useValue: {}}, + {provide: CacheService, useValue: {}}, + {provide: SdcUiServices.ModalService, useValue: {}}, + {provide: ModalService, useValue: {}}, + {provide: TranslateService, useValue: {}} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(DeploymentArtifactsPageComponent); + store = testBed.get(Store); + }); + }) + ); + + it('should match current snapshot of informational artifact pages component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should see exactly 2 tosca artifacts', () => { + fixture.componentInstance.ngOnInit(); + fixture.componentInstance.deploymentArtifacts$.subscribe((artifacts) => { + expect(artifacts.length).toEqual(8); + }) + store.selectOnce((state) => state.artifacts.deploymentArtifacts).subscribe((artifacts) => { + expect(artifacts.length).toEqual(8); + }); + }); + +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.html b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.html new file mode 100644 index 0000000000..885277217d --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.html @@ -0,0 +1,11 @@ +<div class="deployment-page"> + <deployment-graph></deployment-graph> + <panel-wrapper-component> + <sdc-tabs class="deployment-tabs" [iconsSize]="'large'" [isVertical]="true"> + <sdc-tab *ngFor="let tab of tabs" [titleIcon]="tab.titleIcon" [active]="tab.isActive" + [tooltipText]="tab.tooltipText"> + <hierarchy-tab *ngIf="isDataAvailable" [isViewOnly]="(isViewOnly$ | async)"></hierarchy-tab> + </sdc-tab> + </sdc-tabs> + </panel-wrapper-component> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.less b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.less new file mode 100644 index 0000000000..4b7a1e7e9f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.less @@ -0,0 +1,24 @@ +@import './../../../../../assets/styles/variables.less'; +@import './../../../../../assets/styles/override.less'; +.deployment-page { + width: 100%; + height: 100%; + + /deep/ .sdc-tabs { + height: 100%; + } + /deep/ .sdc-tabs-list { + position: absolute; + top: 22px; + right: 303px; + background-color: @sdcui_color_silver; + + svg-icon-label { + vertical-align: middle; + } + } + /deep/ .sdc-tab-content { + height: 100%; + } +} + diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.ts new file mode 100644 index 0000000000..12bd5369c7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.component.ts @@ -0,0 +1,78 @@ +/*- + * ============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} from "@angular/core"; +import {HierarchyTabComponent} from "./panel/panel-tabs/hierarchy-tab/hierarchy-tab.component"; +import {ComponentGenericResponse} from "../../../services/responses/component-generic-response"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../workspace.service"; +import {Module} from "app/models"; +import {SdcUiServices} from "onap-ui-angular"; +import {Select} from "@ngxs/store"; +import {WorkspaceState} from "../../../store/states/workspace.state"; +import {DeploymentGraphService} from "../../composition/deployment/deployment-graph.service"; + +const tabs = + { + hierarchyTab: { + titleIcon: 'composition-o', + component: HierarchyTabComponent, + input: {}, + isActive: true, + tooltipText: 'Hierarchy' + } + }; + +@Component({ + selector: 'deployment-page', + templateUrl: './deployment-page.component.html', + styleUrls: ['deployment-page.component.less'] +}) + +export class DeploymentPageComponent { + public tabs: Array<any>; + public resourceType: string; + public modules: Array<Module>; + public isDataAvailable: boolean; + + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + + constructor(private topologyTemplateService: TopologyTemplateService, + private workspaceService: WorkspaceService, + private deploymentService: DeploymentGraphService, + private loaderService: SdcUiServices.LoaderService) { + this.tabs = []; + this.isDataAvailable = false; + } + + ngOnInit(): void { + this.topologyTemplateService.getDeploymentGraphData(this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId).subscribe((response: ComponentGenericResponse) => { + this.deploymentService.componentInstances = response.componentInstances; + this.deploymentService.componentInstancesRelations = response.componentInstancesRelations; + this.deploymentService.modules = response.modules; + this.isDataAvailable = true; + this.loaderService.deactivate(); + }); + + this.loaderService.activate(); + this.resourceType = this.workspaceService.getMetadataType(); + this.tabs.push(tabs.hierarchyTab); + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.module.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.module.ts new file mode 100644 index 0000000000..3635e8f2cf --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/deployment-page.module.ts @@ -0,0 +1,30 @@ +/** + * Created by ob0695 on 6/4/2018. + */ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {DeploymentPageComponent} from "./deployment-page.component"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {UiElementsModule} from "../../../components/ui/ui-elements.module"; +import {TranslateModule} from "../../../shared/translator/translate.module"; +import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; +import {HierarchyTabModule} from "./panel/panel-tabs/hierarchy-tab/hierarchy-tab.module"; +import {DeploymentGraphService} from "../../composition/deployment/deployment-graph.service"; +import {DeploymentGraphModule} from "../../composition/deployment/deployment-graph.module"; + +@NgModule({ + declarations: [DeploymentPageComponent], + imports: [CommonModule, + DeploymentGraphModule, + SdcUiComponentsModule, + UiElementsModule, + TranslateModule, + GlobalPipesModule, + HierarchyTabModule + ], + exports: [DeploymentPageComponent], + entryComponents: [DeploymentPageComponent], + providers: [DeploymentGraphService] +}) +export class DeploymentPageModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.html b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.html new file mode 100644 index 0000000000..d5b9d9e9b2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.html @@ -0,0 +1,29 @@ +<div class="edit-module-name"> + <div class="edit-module-name-label vfInstance-name" data-tests-id="popover-vfinstance-name" sdc-tooltip [tooltip-text]="selectModule.vfInstanceName">{{selectModule.vfInstanceName}}</div> + <div class="edit-module-name-heatName"> + <sdc-input #heatName [maxLength]="50" + [(value)]="selectModule.heatName" + [testId]="'popover-heat-name'" + [placeHolder]="'Enter Name'"> + </sdc-input> + <sdc-validation [validateElement]="heatName"> + <sdc-regex-validator [message]="'Special characters not allowed.'" [pattern]="pattern"></sdc-regex-validator> + </sdc-validation> + </div> + <div class="edit-module-name-label module-name" data-tests-id="'popover-module-name'" sdc-tooltip [tooltip-text]="selectModule.moduleName">{{selectModule.moduleName}}</div> + <sdc-button class="edit-module-name-btn cancel-button" + [text]="'Cancel'" + [testId]="'popover-close-button'" + [type]="'primary'" + [size] = "'small'" + (click)="clickButton(false)"> + </sdc-button> + <sdc-button class="edit-module-name-btn save-button" + [text]="'Save'" + [testId]="'popover-save-button'" + [type]="'primary'" + [size] = "'small'" + (click)="clickButton(true)" + [disabled]="selectModule.heatName == originalName"> + </sdc-button> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.less b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.less new file mode 100644 index 0000000000..721ad53bc3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.less @@ -0,0 +1,20 @@ +.edit-module-name-btn{ + float:right; + margin-left: 10px; + margin-bottom: 20px; +} +.save-button { + margin-left: 30px; +} +.cancel-button { + margin-left: 20px; +} +.edit-module-name-heatName { + margin-bottom: 15px; +} +.edit-module-name-label { + text-overflow: ellipsis; + display: block; + white-space: nowrap; + margin-bottom: 10px; +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.ts new file mode 100644 index 0000000000..819182c75f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/edit-module-name/edit-module-name.component.ts @@ -0,0 +1,24 @@ +import { Component, Input, Output, OnInit } from "@angular/core"; +import { EventEmitter } from "@angular/core"; +import { DisplayModule } from "../../../../../../../models/modules/base-module"; +import { ValidationConfiguration } from "../../../../../../../models/validation-config"; + +@Component({ + selector: 'edit-module-name', + templateUrl: './edit-module-name.component.html', + styleUrls: ['edit-module-name.component.less'] +}) +export class EditModuleName implements OnInit{ + @Input() selectModule:DisplayModule; + @Output() clickButtonEvent: EventEmitter<String> = new EventEmitter(); + private pattern = ValidationConfiguration.validation.validationPatterns.stringOrEmpty; + private originalName: string; + constructor(){} + public ngOnInit(): void { + this.originalName = this.selectModule.heatName; + } + + private clickButton(saveOrCancel: boolean) : void { + this.clickButtonEvent.emit(saveOrCancel ? this.selectModule.heatName : null); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.html b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.html new file mode 100644 index 0000000000..7c0e60b814 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.html @@ -0,0 +1,119 @@ +<div class="sdc-hierarchy-tab" ng-class=""> + <sdc-loader [global]="false" [testId]="'hierarchy-tab-loader'" [active]="isLoading" [relative]="true" [size]="'medium'"></sdc-loader> + <div class="sdc-hierarchy-tab-title" + [attr.data-tests-id]="'tab-header'">{{'DEPLOYMENT_TAB_TITLE' | translate }}</div> + <div [ngClass]="{'scroll-module-list': selectedModule}"> + <div *ngIf="topologyTemplateType != 'SERVICE'; else isService" class="modules-list"> + <div> + <div class="sdc-hierarchy-tab-sub-title" data-tests-id="tab-sub-header">{{topologyTemplateName}}</div> + <div *ngFor="let module of modules; index as i"> + <sdc-accordion [title]="module.name" [arrow-direction]="'left'" + [css-class]="'expand-collapse-container'" + [ngClass]="{'selected': selectedModule !== undefined && selectedModule.uniqueId === module.uniqueId}" + [testId]="'hierarchy-module-' + i + '-title'" tooltip="{{module.name}}" + (click)="onModuleSelected(module)"> + <div *ngFor="let memberId of getKeys(module.members)"> + <div class="expand-collapse-sub-title" tooltip="{{memberId}}">{{memberId}}</div> + </div> + </sdc-accordion> + </div> + </div> + </div> + + <ng-template #isService> + <div class="module-list"> + <div *ngFor="let instance of componentInstances; index as instanceIndex"> + <sdc-accordion [title]="instance.name" [arrow-direction]="'left'" + [css-class]="'expand-collapse-container outer-container'" + [testId]="'hierarchy-instance-' + instanceIndex + '-title'" + tooltip="{{instance.name}}"> + <div *ngFor="let module of instance.groupInstances; index as moduleIndex"> + <sdc-accordion [title]="module.name" [arrow-direction]="'left'" + [css-class]="'expand-collapse-container inner-container'" + [ngClass]="{'selected': selectedModule && selectedModule.groupInstanceUniqueId === module.uniqueId}" + [testId]="'hierarchy-module-' + moduleIndex + '-title'" + tooltip="{{module.uniqueId}}" + (click)="onModuleSelected(module, instance.uniqueId)"> + <div *ngFor="let memberId of getKeys(module.members)"> + <div class="expand-collapse-sub-title" tooltip="{{memberId}}">{{memberId}}</div> + </div> + </sdc-accordion> + </div> + </sdc-accordion> + </div> + </div> + </ng-template> + + <!--TODO: Add Resizable--> + <div *ngIf="selectedModule"class="module-data-container" [attr.data-tests-id]="'selected-module-data'"> + <div class="module-data"> + <div class="module-name-container"> + <div class="module-name module-text-overflow" [attr.data-tests-id]="'selected-module-name'" + tooltip="{{selectedModule.name}}">{{selectedModule.name}}</div> + <div class="edit-name-container" *ngIf="topologyTemplateType != 'SERVICE'"> + <svg-icon name="edit-o" [size]="'medium'" [ngClass]="{'hand-pointer': !isViewOnly}" (click)="openEditModuleNamePopup($event)"></svg-icon> + </div> + </div> + <div [attr.data-tests-id]="'selected-module-group-uuid'" tooltip="{{selectedModule.groupUUID}}"> + <div class="selected-module-property-header">Module ID:</div> + <div class="selected-module-property-value small-font">{{selectedModule.groupUUID}}</div> + </div> + <div [attr.data-tests-id]="'selected-module-group-customization-uuid'" + *ngIf="topologyTemplateType == 'SERVICE' && isViewOnly" + tooltip="{{selectedModule.customizationUUID}}"> + <div class="selected-module-property-header">Customization ID:</div> + <div class="selected-module-property-value small-font">{{selectedModule.customizationUUID}}</div> + </div> + <div [attr.data-tests-id]="'selected-module-group-invariant-uuid'" + tooltip="{{selectedModule.invariantUUID}}"> + <div class="selected-module-property-header">Invariant UUID:</div> + <div class="selected-module-property-value small-font">{{selectedModule.invariantUUID}}</div> + </div> + <div [attr.data-tests-id]="'selected-module-version'" class="selected-module-property-container"> + <div class="selected-module-property-header">Version:</div> + <div class="selected-module-property-value same-row">{{selectedModule.version}}</div> + </div> + <div data-tests-id="selected-module-is-base" class="selected-module-property-container"> + <div class="selected-module-property-header">IsBase:</div> + <div class="selected-module-property-value same-row">{{selectedModule.isBase}}</div> + </div> + + </div> + <sdc-accordion [title]="'Properties'" [arrow-direction]="'right'" + [css-class]="'expand-collapse-module-data-container'"> + <div *ngFor="let property of selectedModule.properties | orderBy:['name']:['asc']"> + <div class="module-data-list-item"> + <div class="module-data-list-item-value property-name" + [attr.data-tests-id]="'selected-module-property-name'"> + <span tooltip="{{property.name}}" [ngClass]="{'hand-pointer': !isViewOnly}" + (click)="!isViewOnly && openEditPropertyModal(property)">{{property.name}}</span> + </div> + <div class="module-data-list-item-value property-info" + [attr.data-tests-id]="'selected-module-property-type'"> Type: {{property.type}}</div> + <div class="module-data-list-item-value property-info" + [attr.data-tests-id]="'selected-module-property-schema-type'"> + Value: {{property.value}}</div> + </div> + </div> + </sdc-accordion> + <sdc-accordion [title]="'Artifacts'" [arrow-direction]="'right'" + [css-class]="'expand-collapse-module-data-container'"> + <div *ngFor="let artifact of selectedModule.artifacts | orderBy:['artifactName']:['asc']"> + <div class="module-data-list-item"> + <div class="artifact-list-item"> + <div class="module-data-list-item-value" + [attr.data-tests-id]="'selected-module-artifact-name'" + tooltip="{{artifact.artifactName}}">{{artifact.artifactName}}</div> + <div class="module-data-list-item-value artifact-info" + [attr.data-tests-id]="'selected-module-artifact-uuid'" + tooltip="{{artifact.artifactUUID}}">UUID: {{artifact.artifactUUID}}</div> + <div class="module-data-list-item-value artifact-info" + [attr.data-tests-id]="'selected-module-artifact-version'"> + Version: {{artifact.artifactVersion}}</div> + </div> + </div> + </div> + </sdc-accordion> + </div> + </div> +</div> diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.less b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.less new file mode 100644 index 0000000000..269ca0aee0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.less @@ -0,0 +1,222 @@ +@import './../../../../../../../../assets/styles/variables.less'; +.sdc-hierarchy-tab { + padding: 15px 0 0 0; + background-color: #f8f8f8; + height: 100%; + box-shadow: 0.3px 1px 3px rgba(24, 24, 25, 0.42); + display: flex; + flex-flow: column; + + .sdc-hierarchy-tab-title { + color: @main_color_a; + padding: 0 0 15px 20px; + border-bottom: 1px solid #d2d2d2; + } + + .sdc-hierarchy-tab-sub-title { + color: @main_color_a; + padding: 15px 20px 15px 20px; + } + + .scroll-module-list { + overflow-y: auto; + display: flex; + height: 100%; + flex-direction: column; + } + + /deep/ .expand-collapse-container { + margin-bottom: 0; + + .sdc-accordion-header { + white-space: nowrap; + line-height: 22px; + background-color: @tlv_color_u; + padding: 8px 20px 8px 8px; + box-shadow: inset 0px -1px 0px 0px rgba(255, 255, 255, 0.7); + height: 40px; + + .title { + overflow: hidden; + text-overflow: ellipsis; + max-width: 215px; + } + } + + .sdc-accordion-body.open { + padding: 0 0 5px 0; + } + + .sdc-accordion-header:hover { + background-color: @main_color_o; + } + + &.outer-container { + .sdc-accordion-body { + padding-left: 0; + } + } + + &.inner-container { + margin-bottom: 0; + + .sdc-accordion-header { + padding: 8px 20px 8px 30px; + background-color: @tlv_color_t + } + } + } + + sdc-accordion.selected { + /deep/ .expand-collapse-container { + .sdc-accordion-header { + background-color: @main_color_a; + color: @main_color_p; + + .svg-icon { + fill: @main_color_p; + } + } + } + } + + .expand-collapse-sub-title { + max-width: 225px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + padding: 10px 0 0 33px; + } + + .expand-collapse-content { + .expand-collapse-title { + padding: 0 10px 0 30px; + } + } + + .module-data-container { + width: 100%; + overflow-y: overlay; + background-color: @tlv_color_v; + border: 1px solid @main_color_a; + border-top: 4px solid @main_color_a; + box-shadow: 0.3px 1px 2px rgba(24, 24, 25, 0.32); + .module-data { + color: @main_color_a; + padding: 10px 0 10px 0; + margin: 0 20px 0 20px; + + .selected-module-property-header { + font-weight: bold; + } + + .selected-module-property-value { + font-family: @font-opensans-regular; + + &.small-font { + font-size: 12px; + } + } + + .module-name-container { + + display: flex; + flex-direction: row; + + .module-name { + font-size: 14px; + width: 75%; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } + + .edit-name-container { + float: right; + border-left: 1px solid @main_color_a; + height: 20px; + padding-left: 12px; + + svg-icon { + padding-top: 3px; + fill: @main_color_s; + + &.hand-pointer { + cursor: pointer; + } + + } + } + } + } + + .selected-module-property-container { + flex-direction: row; + display: flex; + + .selected-module-property-value { + text-indent: 2px; + } + } + + /deep/ .expand-collapse-module-data-container { + margin-bottom: 0; + + .sdc-accordion-header { + white-space: nowrap; + line-height: 22px; + padding: 8px 20px 8px 16px; + height: 40px; + background-color: @tlv_color_w; + color: @main_color_l; + border-top: 1px solid @main_color_a; + border-bottom: 1px solid @main_color_a; + width: 100%; + } + + } + + .module-data-list-item { + padding-bottom: 10px; + margin: 0 20px 0 20px; + + .artifact-list-item { + color: @main_color_m; + } + + .module-data-list-item-value { + width: 100%; + max-width: 240px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + + &.artifact-info { + font-family: @font-opensans-regular; + font-size: 12px; + } + + &.property-name { + font-weight: 400; + color: @main_color_a; + + .hand-pointer { + cursor: pointer; + } + } + + &.property-info { + color: @func_color_s; + font-family: @font-opensans-regular; + } + } + } + } +} + +.modules-list { + overflow-y: overlay; + flex-grow: 1; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.spec.ts new file mode 100644 index 0000000000..ab88867cc0 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.spec.ts @@ -0,0 +1,133 @@ +import {async, ComponentFixture} from "@angular/core/testing"; +import {HierarchyTabComponent} from "./hierarchy-tab.component"; +import {ConfigureFn, configureTests} from "../../../../../../../../jest/test-config.helper"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {TranslateModule} from "../../../../../../shared/translator/translate.module"; +import {TopologyTemplateService} from "../../../../../../services/component-services/topology-template.service"; +import {WorkspaceService} from "../../../../workspace.service"; +import {ModulesService} from "../../../../../../services/modules.service"; +import {GlobalPipesModule} from "../../../../../../pipes/global-pipes.module"; +import {TranslateService} from "../../../../../../shared/translator/translate.service"; +import {ModalsHandler} from "../../../../../../../utils/modals-handler"; +import {ComponentFactory} from "../../../../../../../utils/component-factory"; +import {NgxsModule} from "@ngxs/store"; +import { SdcUiServices } from "onap-ui-angular"; +import {Observable} from "rxjs"; +import {DisplayModule, Module} from "../../../../../../../models/modules/base-module"; +import {DeploymentGraphService} from "../../../../../composition/deployment/deployment-graph.service"; +import {ComponentMetadata} from "../../../../../../../models/component-metadata"; + +describe('HierarchyTabComponent', () => { + + let fixture: ComponentFixture<HierarchyTabComponent>; + let workspaceService: Partial<WorkspaceService>; + let popoverServiceMock: Partial<SdcUiServices.PopoverService>; + let modulesServiceMock: Partial<ModulesService>; + + let editModuleNameInstanceMock = {innerPopoverContent:{instance: { clickButtonEvent: Observable.of("new heat name")}}, + closePopover: jest.fn()}; + let eventMock = {x: 1650, y: 350}; + let moduleMock: Array<Module> = [{name: "NewVf2..base_vepdg..module-0", uniqueId: '1'}]; + let selectedModuleMock: DisplayModule = {name: "NewVf2..base_vepdg..module-0", vfInstanceName: "NewVf2", moduleName:"module-0", + heatName: "base_vepdg", uniqueId: '1', updateName: jest.fn().mockImplementation(() => { + selectedModuleMock.name = selectedModuleMock.vfInstanceName + '..' + selectedModuleMock.heatName + '..' + + selectedModuleMock.moduleName;})} + let updateSelectedModuleMock = () => { + selectedModuleMock.heatName = "base_vepdg"; + selectedModuleMock.name = "NewVf2..base_vepdg..module-0"; + fixture.componentInstance.selectedModule = selectedModuleMock; + fixture.componentInstance.modules = moduleMock; + } + beforeEach( + async(() => { + + workspaceService ={ + metadata: <ComponentMetadata> { + name: '', + componentType: '' + } + } + popoverServiceMock = { + createPopOverWithInnerComponent: jest.fn().mockImplementation(() => {return editModuleNameInstanceMock}) + } + modulesServiceMock = { + updateModuleMetadata: jest.fn().mockReturnValue(Observable.of({})) + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [HierarchyTabComponent], + schemas: [NO_ERRORS_SCHEMA], + imports: [TranslateModule, NgxsModule.forRoot([]), GlobalPipesModule], + providers: [ + {provide: DeploymentGraphService, useValue: {}}, + {provide: ComponentFactory, useValue: {}}, + {provide: TopologyTemplateService, useValue: {}}, + {provide: WorkspaceService, useValue: workspaceService}, + {provide: ModulesService, useValue: modulesServiceMock}, + {provide: TranslateService, useValue: {}}, + {provide: ModalsHandler, useValue: {}}, + {provide: SdcUiServices.PopoverService, useValue: popoverServiceMock} + ] + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(HierarchyTabComponent); + }); + }) + ); + + it('expected heirarchy component to be defined', () => { + expect(fixture).toBeDefined(); + }); + + it('Update heat name and name sucessfully', () => { + updateSelectedModuleMock(); + fixture.componentInstance.openEditModuleNamePopup(eventMock); + expect(fixture.componentInstance.selectedModule.updateName).toHaveBeenCalled(); + expect(modulesServiceMock.updateModuleMetadata).toHaveBeenCalled(); + expect(fixture.componentInstance.selectedModule.name).toEqual('NewVf2..new heat name..module-0'); + expect(fixture.componentInstance.modules[0].name).toEqual('NewVf2..new heat name..module-0'); + expect(fixture.componentInstance.selectedModule.heatName).toEqual('new heat name'); + }) + it('Try to update heat name and name and get error from server', () => { + updateSelectedModuleMock(); + modulesServiceMock.updateModuleMetadata.mockImplementation(() => Observable.throwError({})); + fixture.componentInstance.openEditModuleNamePopup(eventMock); + expect(fixture.componentInstance.selectedModule.updateName).toHaveBeenCalled(); + expect(modulesServiceMock.updateModuleMetadata).toHaveBeenCalled(); + expect(fixture.componentInstance.modules[0].name).toEqual('NewVf2..base_vepdg..module-0'); + expect(fixture.componentInstance.selectedModule.heatName).toEqual('base_vepdg'); + expect(fixture.componentInstance.selectedModule.name).toEqual('NewVf2..base_vepdg..module-0'); + }) + it('Try to update heat name and name but not find the module with the same uniqueId', () => { + selectedModuleMock.uniqueId = '2' + updateSelectedModuleMock(); + fixture.componentInstance.openEditModuleNamePopup(eventMock); + expect(fixture.componentInstance.selectedModule.updateName).toHaveBeenCalled(); + expect(modulesServiceMock.updateModuleMetadata).not.toHaveBeenCalled(); + expect(fixture.componentInstance.modules[0].name).toEqual('NewVf2..base_vepdg..module-0'); + expect(fixture.componentInstance.selectedModule.heatName).toEqual('base_vepdg'); + expect(fixture.componentInstance.selectedModule.name).toEqual('NewVf2..base_vepdg..module-0'); + selectedModuleMock.uniqueId = '1' + }) + it('Open edit module name popover and change the heat name', () => { + updateSelectedModuleMock(); + spyOn(fixture.componentInstance, 'updateHeatName'); + spyOn(fixture.componentInstance, 'updateOriginalHeatName'); + fixture.componentInstance.openEditModuleNamePopup(eventMock); + expect(popoverServiceMock.createPopOverWithInnerComponent).toHaveBeenCalled(); + expect(fixture.componentInstance.selectedModule.heatName).toEqual("new heat name"); + expect(fixture.componentInstance.updateHeatName).toHaveBeenCalled(); + }) + + + it('Open edit module name popover and not change the heat name', () => { + updateSelectedModuleMock(); + editModuleNameInstanceMock.innerPopoverContent.instance.clickButtonEvent = Observable.of(null); + fixture.componentInstance.openEditModuleNamePopup(eventMock); + expect(popoverServiceMock.createPopOverWithInnerComponent).toHaveBeenCalled(); + expect(fixture.componentInstance.selectedModule.heatName).toEqual("base_vepdg"); + }) +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.ts new file mode 100644 index 0000000000..604b194283 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.component.ts @@ -0,0 +1,139 @@ +import {Component, Input} from "@angular/core"; +import {Component as TopologyTemplate, ComponentInstance, DisplayModule, Module, PropertyModel} from "app/models"; +import {TranslateService} from "app/ng2/shared/translator/translate.service"; +import {ComponentType} from "app/utils/constants"; +import {WorkspaceService} from "../../../../workspace.service"; +import {ModulesService} from "../../../../../../services/modules.service"; +import * as _ from "lodash"; +import {ModalsHandler} from "../../../../../../../utils/modals-handler"; +import {ComponentFactory} from "../../../../../../../utils/component-factory"; +import {Select, Store} from "@ngxs/store"; +import { SdcUiServices } from "onap-ui-angular"; +import { EditModuleName } from "../edit-module-name/edit-module-name.component"; +import {GraphState} from "../../../../../composition/common/store/graph.state"; +import {DeploymentGraphService} from "../../../../../composition/deployment/deployment-graph.service"; +import {OnSidebarOpenOrCloseAction} from "../../../../../composition/common/store/graph.actions"; + +@ Component({ + selector: 'hierarchy-tab', + templateUrl: './hierarchy-tab.component.html', + styleUrls: ['./hierarchy-tab.component.less'], +}) +export class HierarchyTabComponent { + + @Select(GraphState.withSidebar) withSidebar$: boolean; + @Input() isViewOnly: boolean; + public selectedIndex: number; + public selectedModule: DisplayModule; + public isLoading: boolean; + public topologyTemplateName: string; + public topologyTemplateType: string; + public modules: Array<Module> = []; + public componentInstances: Array<ComponentInstance> = []; + private editPropertyModalTopologyTemplate: TopologyTemplate; + + constructor(private translateService: TranslateService, + private workspaceService: WorkspaceService, + private deploymentService: DeploymentGraphService, + private modulesService: ModulesService, + private ModalsHandler: ModalsHandler, + private componentFactory: ComponentFactory, + private store: Store, + private popoverService: SdcUiServices.PopoverService) { + this.isLoading = false; + this.topologyTemplateName = this.workspaceService.metadata.name; + this.topologyTemplateType = this.workspaceService.metadata.componentType; + } + + ngOnInit() { + this.modules = this.deploymentService.modules; + this.componentInstances = this.deploymentService.componentInstances; + this.editPropertyModalTopologyTemplate = this.componentFactory.createEmptyComponent(this.topologyTemplateType); + this.editPropertyModalTopologyTemplate.componentInstances = this.deploymentService.componentInstances; + } + + onModuleSelected(module: Module, componentInstanceId?: string): void { + + let onSuccess = (module: DisplayModule) => { + console.log("Module Loaded: ", module); + this.selectedModule = module; + this.isLoading = false; + }; + + let onFailed = () => { + this.isLoading = false; + }; + + if (!this.selectedModule || (this.selectedModule && this.selectedModule.uniqueId != module.uniqueId)) { + this.isLoading = true; + if (this.topologyTemplateType == ComponentType.SERVICE) { + // this.selectedInstanceId = componentInstanceId; + this.modulesService.getComponentInstanceModule(this.topologyTemplateType, this.workspaceService.metadata.uniqueId, componentInstanceId, module.uniqueId).subscribe((resultModule: DisplayModule) => { + onSuccess(resultModule); + }, () => { + onFailed(); + }); + } else { + this.modulesService.getModuleForDisplay(this.topologyTemplateType, this.workspaceService.metadata.uniqueId, module.uniqueId).subscribe((resultModule: DisplayModule) => { + onSuccess(resultModule); + }, () => { + onFailed(); + }); + } + } + } + + updateHeatName(): void { + this.isLoading = true; + let originalName: string = this.selectedModule.name; + + this.selectedModule.updateName(); + let moduleIndex: number = _.indexOf(this.modules, _.find(this.modules, (module: Module) => { + return module.uniqueId === this.selectedModule.uniqueId; + })); + + if (moduleIndex !== -1) { + this.modules[moduleIndex].name = this.selectedModule.name; + this.modulesService.updateModuleMetadata(this.topologyTemplateType, this.workspaceService.metadata.uniqueId, this.modules[moduleIndex]).subscribe(() => { + this.isLoading = false; + }, () => { + this.updateOriginalHeatName(originalName, moduleIndex); + this.modules[moduleIndex].name = originalName; + }); + } else { + this.updateOriginalHeatName(originalName, moduleIndex); + } + }; + + private updateOriginalHeatName(originalName: string, moduleIndex: number){ + this.isLoading = false; + this.selectedModule.name = originalName; + this.selectedModule.heatName = this.selectedModule.name.split('..')[1]; + } + + openEditPropertyModal(property: PropertyModel): void { + this.editPropertyModalTopologyTemplate.setComponentMetadata(this.workspaceService.metadata); + this.ModalsHandler.openEditModulePropertyModal(property, this.editPropertyModalTopologyTemplate, this.selectedModule, this.selectedModule.properties).then(() => { + }); + } + + private getKeys(map: Map<any, any>) { + return _.keys(map); + } + + private toggleSidebarDisplay = () => { + // this.withSidebar = !this.withSidebar; + this.store.dispatch(new OnSidebarOpenOrCloseAction()); + } + + public openEditModuleNamePopup($event) { + const editModuleNameInstance = this.popoverService.createPopOverWithInnerComponent('Edit Module Name', '', {x:$event.x , y:$event.y }, EditModuleName, {selectModule: _.cloneDeep(this.selectedModule)}, 'top'); + editModuleNameInstance.innerPopoverContent.instance.clickButtonEvent.subscribe((newHeatName) => { + if(newHeatName != null){ + this.selectedModule.heatName = newHeatName; + this.updateHeatName(); + } + editModuleNameInstance.closePopover(); + }) + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.module.ts b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.module.ts new file mode 100644 index 0000000000..048ca0c65f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/deployment/panel/panel-tabs/hierarchy-tab/hierarchy-tab.module.ts @@ -0,0 +1,24 @@ +/** + * Created by ob0695 on 6/4/2018. + */ +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {HierarchyTabComponent} from "./hierarchy-tab.component"; +import {UiElementsModule} from "../../../../../../components/ui/ui-elements.module"; +import {TranslateModule} from "../../../../../../shared/translator/translate.module"; +import {CommonModule} from "@angular/common"; +import {GlobalPipesModule} from "../../../../../../pipes/global-pipes.module"; +import { EditModuleName } from "../edit-module-name/edit-module-name.component"; + +@NgModule({ + declarations: [HierarchyTabComponent, EditModuleName], + imports: [CommonModule, + UiElementsModule, + SdcUiComponentsModule, + TranslateModule, + GlobalPipesModule], + entryComponents: [HierarchyTabComponent, EditModuleName], + exports: [HierarchyTabComponent, EditModuleName], +}) +export class HierarchyTabModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html new file mode 100644 index 0000000000..574f2d1bb4 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.html @@ -0,0 +1,62 @@ +<div class="status-page"> + <ngx-datatable + class="material" + [columnMode]="'standard'" + [rowHeight]="'auto'" + [reorderable]="false" + [swapColumns]="false" + [rows]="artifacts" + [scrollbarH]="true" + #statusTable> + <ngx-datatable-row-detail [rowHeight]="'auto'"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> + <div *ngFor="let status of row.statuses"> + <span class = "status" [attr.data-tests-id]="generateDataTestID('statusTimeStamp_',componentName, row.name, status.status)">{{ status.timeStamp | date:'short':'UTC'}}</span> + <span class = "status" [attr.data-tests-id]="generateDataTestID('statusValue_',componentName, row.name, status.status)">{{ status.status }}</span> + </div> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column name="Component ID" [resizeable]="false" [width]="250"> + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded" > + <div> + <span class="urlValue"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'" (click)="expandRow(row)" [attr.data-tests-id]="generateDataTestID('expandIcon_compID_', componentName, row.name)"></svg-icon> + </span> + <span class="urlValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('compID_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="componentName"> + {{ componentName }} + </span> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [width]="280" name="Artifact Name"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('artName_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="row.name">{{ row.name }}</div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [width]="380" name="URL"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div> + <span class="urlValue ellipsisCell" id="urlCell" [attr.data-tests-id]="generateDataTestID('url_',componentName, row.name)">{{ row.url }}</span> + <span class="urlCopyIcon" title="Copy URL"> + <svg-icon-label [clickable]="true" [mode]="'primary'" [labelPlacement]="'right'" + [label]="" [name]="'copy-o'" [testId]="'copyToClipboard'" + (click)="copyToClipboard(row.url)"> + </svg-icon-label> + </span> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [width]="180" name="Time(UTC)"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('time_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="getLatestArtifact(row.name).timeStamp | date:'short':'UTC'">{{ getLatestArtifact(row.name).timeStamp | date:'short':'UTC'}}</div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [width]="280" name="Status"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('status_',componentName, row.name)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="getLatestArtifact(row.name).status">{{ getLatestArtifact(row.name).status }}</div> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less new file mode 100644 index 0000000000..81b8805792 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.less @@ -0,0 +1,78 @@ +:host ::ng-deep { + .ngx-datatable { + > div { + min-height: 5px; + } + } +} + +.datatable-header-cell { + text-align: left; + color: red; +} + +.statusHeaderTable { + color: #000000; + font-family: OpenSans-Bold, sans-serif; + font-size: 12px; + font-weight: bold; + float: left; +} + +.status { + padding-right: 30px; + color: #5a5a5a; + font-family: OpenSans-Regular, sans-serif; + font-size: 12px; +} + +.distributionIDBlock { + display: inline-block; +} + +.distributionRowContainer{ + background-color: #eaeaea; + text-align: center; +} + +.distributionRowLabel { + overflow: hidden; + padding-top: 10px; + color: #000000; + font-family: OpenSans-Semibold, sans-serif; + font-size: 12px; + font-weight: bold; +} + +.distributionRowValue { + color: #263d4d; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; +} + +.urlValue { + float: left; + color: #263d4d; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; +} + +.urlCopyIcon { + float: right; + width: 8%; +} + +.ellipsisCell { + width: 92%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + + + + + + + + diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts new file mode 100644 index 0000000000..72b930b6b8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.spec.ts @@ -0,0 +1,90 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiServices } from 'onap-ui-angular'; +import { ConfigureFn, configureTests } from '../../../../../../../jest/test-config.helper'; +import { DistributionService } from '../../distribution.service'; +import { DistributionComponentArtifactTableComponent } from './distribution-component-artifact-table.component'; + +describe('DistributionComponentArtifactTableComponent', () => { + let fixture: ComponentFixture<DistributionComponentArtifactTableComponent>; + let distibutionServiceMock: Partial<DistributionService>; + + const mockArtifactsForDistributionAndComponentName = [ + { + name: 'Artifact1', + statuses: [ + {timeStamp: '7/25/2019 12:48AM', status: 'DEPLOY_OK'}, + {timeStamp: '7/25/2019 12:48AM', status: 'DOWNLOAD_OK'}, + {timeStamp: '7/25/2019 12:48AM', status: 'NOTIFIED'} + ], + url: 'URL1', + }, + { + name: 'Artifact2', + statuses: [ + {timeStamp: '7/26/2019 12:48AM', status: 'STATUS_TO_DISPLAY'}, + {timeStamp: '7/25/2019 12:48AM', status: 'DOWNLOAD_OK'}, + {timeStamp: '7/25/2019 12:48AM', status: 'NOTIFIED'} + ], + url: 'URL2', + }, + { + name: 'ArtifactWithNoStatuses', + url: 'URL2', + } + ]; + + beforeEach(() => { + + distibutionServiceMock = { + getArtifactstByDistributionIDAndComponentsName: jest.fn().mockReturnValue(mockArtifactsForDistributionAndComponentName), + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [DistributionComponentArtifactTableComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: DistributionService, useValue: distibutionServiceMock} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(DistributionComponentArtifactTableComponent); + }); + + }); + + it('Get Latest Artifact (status and timeStamp) - So the Component Table will display the last time stamp of the notification', async () => { + await fixture.componentInstance.ngOnInit(); + expect(fixture.componentInstance.getLatestArtifact('Artifact2')).toEqual({status: 'STATUS_TO_DISPLAY', timeStamp: '7/26/2019 12:48AM'}); + expect(fixture.componentInstance.getLatestArtifact('ArtifactWithNoStatuses')).toEqual(null); + }); + + it('Once the Distribution Component Artifact Table Component is created - artifacts will keep the relevant artifacts for a specific distributionID and Component Name', async () => { + await fixture.componentInstance.ngOnInit(); + // tslint:disable:no-string-literal + expect(fixture.componentInstance.artifacts.length).toBe(3); + expect(fixture.componentInstance.artifacts[0].name).toBe('Artifact1'); + expect(fixture.componentInstance.artifacts[0].url).toBe('URL1'); + expect(fixture.componentInstance.artifacts[0].statuses.length).toBe(3); + + expect(fixture.componentInstance.artifacts[1].name).toBe('Artifact2'); + }); + + it('Once the Distribution Component Artifact Table Component is created for Modal- artifacts will keep the relevant artifacts for a ' + + 'specific distributionID and Component Name filtered by Status', async () => { + fixture.componentInstance.statusFilter = 'DOWNLOAD_OK'; + await fixture.componentInstance.ngOnInit(); + expect(fixture.componentInstance.artifacts.length).toBe(3); + expect(fixture.componentInstance.artifacts[0].name).toBe('Artifact1'); + expect(fixture.componentInstance.artifacts[0].url).toBe('URL1'); + + expect(fixture.componentInstance.artifacts[0].statuses.length).toBe(1); + expect(fixture.componentInstance.artifacts[0].statuses[0]).toEqual({status: 'DOWNLOAD_OK', timeStamp: '7/25/2019 12:48AM'}); + + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts new file mode 100644 index 0000000000..af9aef5c64 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component.ts @@ -0,0 +1,68 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import * as _ from 'lodash'; +import { DistributionService } from '../../distribution.service'; + +// tslint:disable:no-string-literal + +@Component({ + selector: 'app-distribution-component-artifact-table', + templateUrl: './distribution-component-artifact-table.component.html', + styleUrls: ['./distribution-component-artifact-table.component.less'] +}) +export class DistributionComponentArtifactTableComponent implements OnInit { + + @ViewChild('statusTable', {}) table: any; + + @Input() componentName: string; + @Input() rowDistributionID: string; + @Input() statusFilter: string; + + public artifacts = []; + + constructor(private distributionService: DistributionService) { + } + + ngOnInit() { + this.artifacts = this.distributionService.getArtifactstByDistributionIDAndComponentsName(this.rowDistributionID, this.componentName); + if (this.statusFilter) { + this.artifacts.forEach( + (artifact) => { + artifact.statuses = _.filter(artifact.statuses, {status: this.statusFilter}); + }); + } + } + + public getLatestArtifact(artifactName: string) { + const selectedArtifact = this.artifacts.filter((artifact) => artifact.name === artifactName); + if (selectedArtifact && selectedArtifact[0] && selectedArtifact[0]['statuses'] && selectedArtifact[0]['statuses'][0]) { + return selectedArtifact[0]['statuses'][0]; + } else { + return null; + } + } + + private copyToClipboard(urlToCopy: any) { + + const inputForCopyToClipboard = document.getElementById('inputForCopyToClipboard') as HTMLInputElement; + inputForCopyToClipboard.value = urlToCopy; + /* Select the text field */ + inputForCopyToClipboard.select(); + + /* Copy the text inside the text field */ + document.execCommand('copy'); + + } + + private generateDataTestID(preFix: string, componentName: string, artifactName: string, status?: string) { + if (!status) { + return preFix + componentName + '_' + artifactName; + } else { + return preFix + status + '_' + componentName + '_' + artifactName; + } + } + + private expandRow(row: any) { + this.table.rowDetail.toggleExpandRow(row); + } + +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html new file mode 100644 index 0000000000..fa5a9ad7fb --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.html @@ -0,0 +1,47 @@ +<div > + <div class="distributionSummary" *ngIf="!isModal"> + <span class= "rightVerticalSeperator titleSummaryFontSettings" data-tests-id="totalDistributionArtifactsLabel">Total Artifacts <span class="blue" data-tests-id="totalDistributionArtifactsValue">{{ getTotalArtifactsForDistributionID(rowDistributionID) }} </span></span> + <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'NOTIFIED')" data-tests-id="totalDistributionNotifiedArtifactsLabel">Notified <span data-tests-id="totalDistributionNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOTIFIED') }}</span></span> + <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'DOWNLOAD_OK')" data-tests-id="totalDistributionDownloadedArtifactsLabel">Downloaded <span data-tests-id="totalDistributionDownloadedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_OK') }}</span></span> + <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'DEPLOY_OK')" data-tests-id="totalDistributionDeployedArtifactsLabel">Deployed <span data-tests-id="totalDistributionDeployedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_OK') }}</span></span> + <span class="blue rightVerticalSeperator" (click)="openModal(rowDistributionID,'NOT_NOTIFIED')" data-tests-id="totalDistributionNotNotifiedArtifactsLabel">Not Notified <span data-tests-id="totalDistributionNotNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOT_NOTIFIED') }}</span></span> + <span class="blue rightVerticalSeperator floatRight" (click)="openModal(rowDistributionID,'DEPLOY_ERROR')" data-tests-id="totalDistributionDeployErrorArtifactsLabel">Deploy Errors <span class="red" data-tests-id="totalDistributionDeployErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_ERROR') }}</span></span> + <span class="blue rightVerticalSeperator floatRight" (click)="openModal(rowDistributionID,'DOWNLOAD_ERROR ')" data-tests-id="totalDistributionDownloadErrorArtifactsLabel">Download Errors <span class="red" data-tests-id="totalDistributionDownloadErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_ERROR') }}</span></span> + </div> + + <div class="distributionSummary" *ngIf="isModal"> + <span data-tests-id="modalStatusLabel"><a>Status {{ statusFilter }} <span class="blue" data-tests-id="statusValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, statusFilter) }}</span></a></span> + </div> + + + <div class="componentShiftLeft" *ngFor="let component of components"> + <div class="componentSummary" *ngIf="!isModal"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="isExpanded(component) ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'" [attr.data-tests-id]="generateExpandDataTestID(component)" (click)="expandRow(component)"></svg-icon> + + + <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, '')">{{ component }} <span class="blue" data-tests-id="totalComponentArtifactsValue">{{ getTotalArtifactsForDistributionID(rowDistributionID, component) }}</span></span> + <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Notified')">Notified <span class="blue" data-tests-id="totalComponentNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOTIFIED', component) }}</span></span> + <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Downloaded')">Downloaded <span class="blue" data-tests-id="totalComponentDownloadedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_OK', component) }}</span></span> + <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'Deployed')">Deployed <span class="blue" data-tests-id="totalComponentDeployedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_OK', component) }}</span></span> + <span class="rightVerticalSeperatorComponent titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'NotNotified')">Not Notified <span class="blue" data-tests-id="totalComponentNotNotifiedArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'NOT_NOTIFIED', component) }}</span></span> + <span class="msoStatus" [ngClass]="{'red': getMSOStatus (rowDistributionID, component) === 'COMPONENT_DONE_ERROR', 'green': getMSOStatus (rowDistributionID, component) === 'COMPONENT_DONE_OK'}">{{ getMSOStatus (rowDistributionID, component) }}</span> + <span class="rightVerticalSeperatorComponent floatRight titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'DeployErrors')">Deploy Errors <span class="red" data-tests-id="totalComponentDeployErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DEPLOY_ERROR', component) }}</span></span> + <span class="rightVerticalSeperatorComponent floatRight titleSummaryFontSettings" [attr.data-tests-id]="generateTotalComponentArtifactsLabel(component, 'DownloadErrors')">Download Errors <span class="red" data-tests-id="totalComponentDownloadErrorArtifactsValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, 'DOWNLOAD_ERROR', component) }}</span></span> + </div> + + <div class="componentSummary" *ngIf="isModal"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="isExpanded(component) ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'" [attr.data-tests-id]="generateExpandDataTestID(component+'_ForModal')" (click)="expandRow(component)"></svg-icon> + <span data-tests-id="modalComponentLabel"><a>{{ component }} <span class="blue" data-tests-id="modalComponentValue">{{ getLengthArtifactsForDistributionIDByStatus(rowDistributionID, statusFilter, component) }} </span></a></span> + </div> + + <div *ngIf="isExpanded(component)"> + <app-distribution-component-artifact-table [rowDistributionID]= rowDistributionID [componentName]=component + [statusFilter]="statusFilter"></app-distribution-component-artifact-table> + </div> + </div> +</div> + diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less new file mode 100644 index 0000000000..3eab18ca14 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.less @@ -0,0 +1,66 @@ +.red { + color: red; +} + +.green { + color: green; +} + +.msoStatus { + padding-left: 5px; +} + +.blue { + color: #009fdb; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; +} + +.rightVerticalSeperator { + border-right: 1px solid #d2d2d2; + padding-left: 5px; + padding-right: 5px; + cursor: pointer; +} + +.rightVerticalSeperatorComponent { + border-right: 1px solid #d2d2d2; + padding-left: 5px; + padding-right: 5px; +} + +.floatRight{ + float: right; +} + +.distributionSummary { + padding-top: 5px; + padding-bottom: 5px; + background-color: #eaeaea; + padding-left: 25px; + padding-right: 25px; +} + +.componentSummary { + margin-top: 5px; + margin-bottom: 5px; + padding-top: 5px; + padding-bottom: 5px; + background-color: #eaeaea; + padding-left: 25px; + padding-right: 25px; +} + +.componentShiftLeft { + margin-left: 15px; +} + +.titleSummaryFontSettings { + color: #191919; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; +} + + + + diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts new file mode 100644 index 0000000000..ff89b92fd8 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.spec.ts @@ -0,0 +1,47 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiServices } from 'onap-ui-angular'; +import { ConfigureFn, configureTests } from '../../../../../../jest/test-config.helper'; +import { DistributionService } from '../distribution.service'; +import { DistributionComponentTableComponent } from './distribution-component-table.component'; + +describe('DistributionComponentTableComponent', () => { + let fixture: ComponentFixture<DistributionComponentTableComponent>; + let distibutionServiceMock: Partial<DistributionService>; + + const mockComponentsForDistribution = ['Consumer1', 'Consumer2']; + + beforeEach(() => { + + distibutionServiceMock = { + getComponentsByDistributionID: jest.fn().mockReturnValue(mockComponentsForDistribution), + getArtifactstByDistributionIDAndComponentsName: jest.fn(), + getArtifactsForDistributionIDAndComponentByStatus: jest.fn() + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [DistributionComponentTableComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: DistributionService, useValue: distibutionServiceMock}, + {provide: SdcUiServices.ModalService, useValue: {}} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(DistributionComponentTableComponent); + }); + + }); + + it('Once the Distribution Component Table Component is created - components will keep the relevant components for a specific distributionID', async () => { + await fixture.componentInstance.ngOnInit(); + expect(fixture.componentInstance.components.length).toBe(2); + expect(fixture.componentInstance.components[0]).toBe('Consumer1'); + expect(fixture.componentInstance.components[1]).toBe('Consumer2'); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts new file mode 100644 index 0000000000..e3aaf9d639 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution-component-table/distribution-component-table.component.ts @@ -0,0 +1,104 @@ +import { Component, Input, OnInit } from '@angular/core'; +import { SdcUiCommon, SdcUiComponents, SdcUiServices } from 'onap-ui-angular'; +import { ModalComponent } from 'onap-ui-angular/dist/modals/modal.component'; +import { DistributionComponent } from '../distribution.component'; +import { DistributionService } from '../distribution.service'; + +@Component({ + selector: 'app-distribution-component-table', + templateUrl: './distribution-component-table.component.html', + styleUrls: ['./distribution-component-table.component.less'] +}) +export class DistributionComponentTableComponent implements OnInit { + + @Input() rowDistributionID: string; + @Input() isModal: boolean = false; + @Input() statusFilter: string; + public components = []; + private customModalInstance: ModalComponent; + private expanded = []; + constructor(private distributionService: DistributionService, + private modalService: SdcUiServices.ModalService) { + } + + ngOnInit() { + this.initComponents(); + } + + private generateTotalComponentArtifactsLabel(componentName: any, status: string): string { + return 'total' + componentName + status + 'ArtifactsLabel'; + } + + private generateExpandDataTestID(componentName: string) { + return 'expandIcon_' + componentName; + } + + private initComponents() { + this.components = this.distributionService.getComponentsByDistributionID(this.rowDistributionID); + this.components.map((component) => this.expanded.push({componentName: component, expanded: false})); + } + + private getTotalArtifactsForDistributionID(distributionID: string, componentName?: string): number { + return this.distributionService.getArtifactstByDistributionIDAndComponentsName(distributionID, componentName).length; + } + + private getLengthArtifactsForDistributionIDByStatus(distributionID: string, statusToSerach: string, componentName?: string): number { + if (componentName) { + return this.distributionService.getArtifactsForDistributionIDAndComponentByStatus(distributionID, statusToSerach, componentName).length; + } else { + return this.distributionService.getArtifactsForDistributionIDAndComponentByStatus(distributionID, statusToSerach).length; + } + } + + private openModal(rowDistributionID: string, statusFilter: string) { + + const title: string = 'Distribution by Status'; + const attributeModalConfig = { + title, + size: 'sdc-xl', + type: SdcUiCommon.ModalType.custom, + buttons: [ + { + id: 'close', + text: 'Close', + size: 'sm', + closeModal: true, + disabled: false, + } + ] as SdcUiCommon.IModalButtonComponent[] + }; + + this.customModalInstance = this.modalService.openCustomModal(attributeModalConfig, DistributionComponent, { + // inputs + rowDistributionID, + statusFilter, + isModal: true, + }); + } + + private expandRow(componentName: string) { + console.log('Should expand componentSummary for componentName = ' + componentName); + const selectedComponent = this.expanded.find((component) => component.componentName === componentName); + // tslint:disable:no-string-literal + const selectedComponentExpandedVal = selectedComponent['expanded']; + // this.expanded = !this.expanded; + for (const i in this.expanded) { + if (this.expanded[i].componentName === componentName) { + this.expanded[i].expanded = !this.expanded[i].expanded; + break; //Stop this loop, we found it! + } + } + const selectedComponentAfter = this.expanded.find((component) => component.componentName === componentName); + const selectedComponentExpandedValAfter = selectedComponentAfter['expanded']; + } + + private isExpanded(componentName: string) { + const selectedComponent = this.expanded.find((component) => component.componentName === componentName); + return selectedComponent['expanded']; + } + + + private getMSOStatus(rowDistributionID: string, componentName: string): string { + return this.distributionService.getMSOStatus(rowDistributionID, componentName); + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html new file mode 100644 index 0000000000..d0cacb054e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.html @@ -0,0 +1,80 @@ +<div *ngIf="!isModal"> + <div *ngIf="serviceHasDistibutions" class="w-sdc-distribution-view-header"> + <div class="w-sdc-distribution-view-title" data-tests-id="DistributionsLabel">DISTRIBUTION <span class="blue-font" data-tests-id="totalArtifacts">[{{distributions.length}}]</span></div> + <div class="header-spacer"></div> + <input type="text" value="GeeksForGeeks" id="inputForCopyToClipboard" [ngStyle]="{'z-index': '-2', 'width': '25px'}"> + <div class="top-search"> + <input type="text" + style="width: auto;" + class="search-text" + data-tests-id="searchTextbox" + placeholder="Search" + data-ng-model="searchBind" + ng-model-options="{ debounce: 500 }" + (keyup)="updateFilter($event)"/> + </div> + <div class="sprite-new refresh-btn" data-tests-id="refreshButton" (click)="refreshDistributions()" title="Refresh"></div> + </div> + <div class="w-sdc-distribution-view-header w-sdc-distribution-view-title" data-tests-id="noDistributionsLabel" *ngIf="!serviceHasDistibutions">No Distributions To Present</div> +</div> + +<div *ngIf="serviceHasDistibutions"> + <ngx-datatable + [columnMode]="'flex'" + [rowHeight]="'auto'" + [reorderable]="false" + [swapColumns]="false" + [scrollbarV]="false" + [rows]="distributions" + [sorts]="[{prop: 'timestamp', dir: 'desc'}]" + + #distributionTable> + <ngx-datatable-row-detail [rowHeight]="'auto'"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> + <app-distribution-component-table [rowDistributionID]=row.distributionID [isModal]="isModal" + [statusFilter]="statusFilter"></app-distribution-component-table> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column [resizeable]="false" [flexGrow]="2" name="Distribution ID"> + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded" > + <div class="expand-collapse-cell"> + <a><svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'" (click)="expandRow(row, expanded)" [attr.data-tests-id]="generateDataTestID('expandIcon_', row.distributionID, isModal)"></svg-icon></a> + + </div> + <div class="distributionIDBlock"> + <div class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('distID_', row.distributionID, isModal)">{{ row.distributionID }}</div> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [flexGrow]="1" name="User id"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class = "distributionRowValue ellipsisCell" [attr.data-tests-id]="generateDataTestID('userID_', row.distributionID)" sdc-tooltip [tooltip-placement]="3" [tooltip-text]="row.userId">{{ row.userId }}</div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [flexGrow]="1" name="Time[UTC]"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('timeStamp_', row.distributionID)">{{ row.timestamp }} </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false"[flexGrow]="1" name="Status" > + <ng-template ngx-datatable-cell-template let-row="row"> + <div> + <span class="statusIcon"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]= "getIconName(row.deployementStatus)" [mode]="'primary'" + [size]="'medium'"></svg-icon> + </span> + <span class = "distributionRowValue" [attr.data-tests-id]="generateDataTestID('status_', row.distributionID)"> + {{ row.deployementStatus }} + </span> + <span class="btnMarkAsDistributed" (click)="markDeploy(row.distributionID, row.deployementStatus)"> + <svg-icon [clickable]="true" [name]= "'success'" [mode]="getIconMode(row.deployementStatus)" + [size]="'medium'"></svg-icon> + </span> + </div> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div> diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less new file mode 100644 index 0000000000..b630881fdc --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.less @@ -0,0 +1,92 @@ +:host ::ng-deep { + .ngx-datatable { + > div { + min-height: 500px; + datatable-body { + max-height: max-content; + } + } + } +} + +.w-sdc-distribution-view-header { + display: flex; + -webkit-justify-content: space-between; + margin: 0 25px 5px 40px; + + .header-spacer { + flex-grow: 5; + } + + .w-sdc-distribution-view-title{ + color: #191919; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; + line-height: 30px; + + + .blue-font { + color: #009fdb; + font-family: OpenSans-Semibold, sans-serif; + font-size: 14px; + } + } + +} + +.distribution-page { + max-height: 150px; +} + + .distributionIDBlock { + display: inline-block; + } + + .expand-collapse-cell { + display: inline-block; + } + + .statusIcon { + display: inline-block; + margin-right: 10px; + } + + .btnMarkAsDistributed { + float: right; + background-color: #E5F3FF; + border: 1px solid #8DCCD5; + width: 55px; + height: 21px; + text-align: center; + } + + .distributionRowContainer{ + background-color: #eaeaea; + text-align: center; + } + + .distributionRowLabel { + overflow: hidden; + padding-top: 10px; + color: #000000; + font-family: OpenSans-Semibold, sans-serif; + font-size: 12px; + font-weight: bold; + } + + .distributionRowValue { + color: #263d4d; + font-family: OpenSans-Regular, sans-serif; + font-size: 14px; + } + +.ellipsisCell { + width: 92%; + white-space: nowrap; + text-overflow: ellipsis; + overflow: hidden; +} + + + + diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts new file mode 100644 index 0000000000..e6c9c239e1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.spec.ts @@ -0,0 +1,92 @@ +import { NO_ERRORS_SCHEMA } from '@angular/core'; +import { ComponentFixture } from '@angular/core/testing'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiServices } from 'onap-ui-angular'; +import { ConfigureFn, configureTests } from '../../../../../jest/test-config.helper'; +import { ComponentMetadata } from '../../../../models/component-metadata'; +import { AuthenticationService } from '../../../services/authentication.service'; +import { WorkspaceService } from '../workspace.service'; +import { DistributionComponent } from './distribution.component'; +import { DistributionService } from './distribution.service'; +import {EventListenerService} from "../../../../services/event-listener-service"; + +describe('DistributionComponent', () => { + let fixture: ComponentFixture<DistributionComponent>; + let distibutionServiceMock: Partial<DistributionService>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let authenticationServiceMock: Partial <AuthenticationService>; + let eventListenerService: Partial <EventListenerService>; + + const mockDistributionListFromService = [ + { + deployementStatus: 'Distributed', + distributionID: '1', + timestamp: '2019-07-21 08:37:02.834 UTC', + userId: 'Aretha Franklin(op0001)' + }, { + deployementStatus: 'Distributed', + distributionID: '2', + timestamp: '2019-07-21 09:37:02.834 UTC', + userId: 'Aretha Franklin(op0001)' + }]; + + beforeEach(() => { + + distibutionServiceMock = { + initDistributionsList: jest.fn(), + getDistributionList: jest.fn().mockReturnValue(mockDistributionListFromService), + initDistributionsStatusForDistributionID: jest.fn() + }; + + const componentMetadata = new ComponentMetadata(); + componentMetadata.uuid = '111'; + + workspaceServiceMock = { + metadata : componentMetadata + }; + + authenticationServiceMock = { + getLoggedinUser: jest.fn().mockReturnValue({role: 'designer'}) + }; + + eventListenerService = { + registerObserverCallback: jest.fn(), + unRegisterObserver: jest.fn() + } + + loaderServiceMock = { + activate: jest.fn(), + deactivate: jest.fn() + }; + + const configure: ConfigureFn = (testBed) => { + testBed.configureTestingModule({ + declarations: [DistributionComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: DistributionService, useValue: distibutionServiceMock}, + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock}, + {provide: AuthenticationService, useValue: authenticationServiceMock}, + {provide: EventListenerService, useValue: eventListenerService} + ], + }); + }; + + configureTests(configure).then((testBed) => { + fixture = testBed.createComponent(DistributionComponent); + }); + + }); + + it('Once the Distribution Component is created - distributionsResponseFromServer save all the distributions from the Service', async () => { + fixture.componentInstance.componentUuid = 'componentUid'; + fixture.componentInstance.rowDistributionID = null; + fixture.componentInstance.isModal = false; + + await fixture.componentInstance.ngOnInit(); + expect(fixture.componentInstance.distributions.length).toBe(2); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts new file mode 100644 index 0000000000..ca1b6292d3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.component.ts @@ -0,0 +1,117 @@ +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { SdcUiCommon, SdcUiServices } from 'onap-ui-angular'; +import { EventListenerService } from '../../../../services/event-listener-service'; +import { AuthenticationService } from '../../../services/authentication.service'; +import { WorkspaceService } from '../workspace.service'; +import { DistributionService } from './distribution.service'; +import { EVENTS } from '../../../../utils/constants'; + +@Component({ + selector: 'distribution', + templateUrl: './distribution.component.html', + styleUrls: ['../../../../../assets/styles/table-style.less', './distribution.component.less'] +}) +export class DistributionComponent implements OnInit { + + @ViewChild('distributionTable', { }) table: any; + + @Input() isModal: boolean = false; + @Input() statusFilter: string; + @Input() rowDistributionID: string; + public componentUuid: string; + public distributions = []; + private expanded: any = {}; + private serviceHasDistibutions: boolean = false; + private readonly uniqueId: string; + private userRole: string; + + constructor(private workspaceService: WorkspaceService, + private distributionService: DistributionService, + private loaderService: SdcUiServices.LoaderService, + private authService: AuthenticationService, + private eventListenerService: EventListenerService) { + this.componentUuid = this.workspaceService.metadata.uuid; + this.uniqueId = this.workspaceService.metadata.uniqueId; + } + + + + async ngOnInit() { + this.userRole = this.authService.getLoggedinUser().role; + this.eventListenerService.registerObserverCallback(EVENTS.ON_DISTRIBUTION_SUCCESS, async () => { + await this.refreshDistributions(); + }); + await this.initDistributions(this.componentUuid, this.rowDistributionID); + } + + ngOnDestroy(): void { + this.eventListenerService.unRegisterObserver(EVENTS.ON_DISTRIBUTION_SUCCESS); + } + + async initDistributions(componentUuid: string, specificDistributionID?: string) { + this.loaderService.activate(); + await this.distributionService.initDistributionsList(componentUuid); + this.distributions = this.distributionService.getDistributionList(); + this.distributions.length > 0 ? this.serviceHasDistibutions = true : this.serviceHasDistibutions = false; + if (specificDistributionID) { + this.distributions = this.distributionService.getDistributionList(specificDistributionID); + } + this.loaderService.deactivate(); + } + + getIconName(rowStatus: string ) { + if (rowStatus === 'Distributed') { + return 'distributed'; + } + if (rowStatus === 'Deployed') { + return 'v-circle'; + } + } + + getIconMode(rowStatus: string) { + if (rowStatus === 'Distributed') { + return 'primary'; + } + if (rowStatus === 'Deployed') { + return 'secondary'; + } + } + + private async markDeploy(distributionID: string, status: string) { + if (status === 'Distributed') { + console.log('Should send MarkDeploy POST Request ServiceID:' + this.uniqueId + ' DISTID:' + distributionID); + await this.distributionService.markDeploy(this.uniqueId, distributionID); + this.refreshDistributions(); + } + } + + private async refreshDistributions() { + await this.initDistributions(this.componentUuid); + } + + private updateFilter(event) { + const val = event.target.value.toLowerCase(); + + // filter our data + this.distributions = this.distributionService.getDistributionList().filter((distribution: any[]) => { + return !val || + // tslint:disable:no-string-literal + distribution['distributionID'].toLowerCase().indexOf(val) !== -1; + }); + } + + private generateDataTestID(preFix: string, distributionID: string, isModal?: boolean ): string { + if (isModal) { + return preFix + distributionID.substring(0, 5) + '_Modal'; + } else { + return preFix + distributionID.substring(0, 5); + } + } + + private async expandRow(row: any, expanded: boolean) { + if (!expanded) { + await this.distributionService.initDistributionsStatusForDistributionID(row.distributionID); + } + this.table.rowDetail.toggleExpandRow(row); + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts new file mode 100644 index 0000000000..723a6d8c0a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.module.ts @@ -0,0 +1,34 @@ +import { CommonModule } from '@angular/common'; +import { NgModule } from '@angular/core'; +import { NgxDatatableModule } from '@swimlane/ngx-datatable'; +import { SdcUiComponentsModule } from 'onap-ui-angular'; +import { DistributionComponentArtifactTableComponent } from './distribution-component-table/distribution-component-artifact-table/distribution-component-artifact-table.component'; +import { DistributionComponentTableComponent } from './distribution-component-table/distribution-component-table.component'; +import { DistributionComponent } from './distribution.component'; +import { DistributionService } from './distribution.service'; + +@NgModule({ + declarations: [ + DistributionComponent, + DistributionComponentTableComponent, + DistributionComponentArtifactTableComponent, + ], + imports: [ + // TranslateModule, + CommonModule, + SdcUiComponentsModule, + NgxDatatableModule, + ], + exports: [ + DistributionComponent, + DistributionComponentTableComponent + ], + entryComponents: [ + DistributionComponent, + DistributionComponentTableComponent, + DistributionComponentArtifactTableComponent + ], + providers: [DistributionService] +}) +export class DistributionModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts new file mode 100644 index 0000000000..ed6791c5c1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/disribution/distribution.service.ts @@ -0,0 +1,233 @@ +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { tap } from 'rxjs/operators'; +import { Distribution } from '../../../../models/distribution'; +import { ISdcConfig, SdcConfigToken } from '../../../config/sdc-config.config'; + +@Injectable() +export class DistributionService { + protected baseUrl; + private distributionList = []; + private distributionStatusesMap = {}; + + // tslint:disable:no-string-literal + + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + } + + // Once the distribution page is loaded or when the user wants to refresh the list + async initDistributionsList(componentUuid: string): Promise<object> { + const distributionsListURL = this.baseUrl + 'services/' + componentUuid + '/distribution'; + const res = this.http.get<Distribution[]>(distributionsListURL).pipe(tap( (result) => { + this.distributionList = result['distributionStatusOfServiceList']; + this.insertDistrbutionsToMap(); + } )); + return res.toPromise(); + } + + // Once the user click on the relevant distribution ID in the distribution table (open and close) + async initDistributionsStatusForDistributionID(distributionID: string): Promise<object> { + const distributionStatus = this.baseUrl + 'services/distribution/' + distributionID; + const res = this.http.get<object>(distributionStatus).pipe(tap( (result) => { + this.insertDistributionStatusToDistributionsMap(distributionID, result['distributionStatusList']); + } )); + return res.toPromise(); + } + + public getDistributionList(specificDistributionID?: string) { + if (specificDistributionID) { + return this.distributionList.filter((distribution) => { + return distribution['distributionID'] === specificDistributionID; + }); + } else { + return this.distributionList; + } + } + + public getComponentsByDistributionID(distributionID: string) { + const components = []; + const distributionStatusMap = this.getStatusMapForDistributionID(distributionID); + if (distributionStatusMap) { + distributionStatusMap.forEach((component) => components.push(component.componentID)); + } + return components; + } + + // get array of artifacts per distributionID w/o componentName, sliced by artifact status + public getArtifactsForDistributionIDAndComponentByStatus(distributionID: string, statusToSearch: string, componentName?: string) { + const filteredArtifactsByStatus = []; + + if (componentName) { + this.getArtifactstByDistributionIDAndComponentsName(distributionID, componentName).forEach ( (artifact) => { + if (this.artifactStatusHasMatch(artifact, statusToSearch)) { + filteredArtifactsByStatus.push(artifact); + } + } ); + } else { + this.getArtifactstByDistributionIDAndComponentsName(distributionID).forEach ( (artifact) => { + if (this.artifactStatusHasMatch(artifact, statusToSearch)) { + filteredArtifactsByStatus.push(artifact); + } + } ); + } + return filteredArtifactsByStatus; + } + + public getArtifactstByDistributionIDAndComponentsName(distributionID: string, componentName?: string): any[] { + const artifacts = []; + if (this.getStatusMapForDistributionID(distributionID)) { + if (componentName) { + if (this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName).length > 0) { + const artifactsArr = this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName)[0]['artifacts'] + if (artifactsArr.length > 0) { + artifactsArr.forEach((artifact) => { + const artifactObj = { + url: artifact.artifactUrl, + name: artifact.artifactName, + statuses: artifact.statuses + }; + artifacts.push(artifactObj); + }); + } + } + } else { + const components = this.getComponentsByDistributionID(distributionID); + components.forEach((componentName) => { + if (this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName).length > 0) { + const artifactsArr = this.getStatusMapForDistributionID(distributionID).filter((component) => component.componentID === componentName)[0]['artifacts'] + if (artifactsArr.length > 0) { + artifactsArr.forEach((artifact) => { + const artifactObj = { + url: artifact.artifactUrl, + name: artifact.artifactName, + statuses: artifact.statuses + }; + artifacts.push(artifactObj); + }); + } + } + }); + } + } + return artifacts; + } + + public getStatusMapForDistributionID(distributionID: string) { + return this.distributionStatusesMap[distributionID]; + } + + public markDeploy(uniqueId: string, distributionID: string): Promise<object> { + const distributionStatus = this.baseUrl + 'services/' + uniqueId + '/distribution/' + distributionID + '/markDeployed'; + const res = this.http.post<object>(distributionStatus, {}).pipe(tap( (result) => { + console.log(result); + } )); + return res.toPromise(); + } + + public getMSOStatus(distributionID: string, componentName: string): string { + const msoStatus = this.distributionStatusesMap[distributionID].filter((component) => component.componentID === componentName)[0].msoStatus; + return msoStatus ? msoStatus : ''; + } + + private artifactStatusHasMatch(artifact: any, statusToSerach: string) { + for (let i = 0; i < artifact.statuses.length; i++) { + if (artifact.statuses[i].status === statusToSerach) { + return true; + } + } + return false; + } + + private insertDistributionStatusToDistributionsMap(distributionID: string, distributionStatusMapResponseFromServer: object[]) { + + // // Clear the Distribution ID array - to avoid statuses duplications + const distribution = this.distributionStatusesMap[distributionID]; + distribution.length = 0; + + // Sort the response of statuses from Server, so it will be easy to pop the latest status when it will be required + const sortedResponseByTimeStamp = distributionStatusMapResponseFromServer.sort((a, b) => b['timestamp'] - a['timestamp']) + + sortedResponseByTimeStamp.map((distributionStatus) => { + const formattedDate = this.formatDate(distributionStatus['timestamp']); + + // if (distributionStatus['url'] === null) { + // distributionStatus['url'] = ""; + // } + + const detailedArtifactStatus = { + componentID: distributionStatus['omfComponentID'], + artifactName: distributionStatus['url']? distributionStatus['url'].split('/').pop() : '', + url: distributionStatus['url'], + time: distributionStatus['timestamp'], + status: distributionStatus['status'], + }; + + + + // Add Component to this.distributionStatusesMap in case not exist. + let componentPosition = _.findIndex(distribution, {componentID: detailedArtifactStatus.componentID}) + + if (componentPosition === -1) { + this.addComponentIdToDistributionStatusMap(distributionID, detailedArtifactStatus.componentID); + componentPosition = distribution.length - 1; + } + + const component = distribution[componentPosition]; + + + // Add Artifact to this.distributionStatusesMap[componentID] in case not exist. + let artifactPosition = _.findIndex(component.artifacts, {artifactUrl: detailedArtifactStatus.url}) + + if (artifactPosition === -1) { + this.addArtifactToComponentId(distributionID, componentPosition, detailedArtifactStatus.artifactName, detailedArtifactStatus.url); + artifactPosition = component.artifacts.length - 1; + } + + + // Add status to relevat artifact in relevent componentID. + if (detailedArtifactStatus.url) { + // Case where there is a url -> should add its status + component.artifacts[artifactPosition].statuses.push({ + timeStamp: detailedArtifactStatus.time, + status: detailedArtifactStatus.status + }); + } else { + // Should update the Component -> status from MSO + this.distributionStatusesMap[distributionID][componentPosition].msoStatus = detailedArtifactStatus.status; + } + + + }); + } + + private addComponentIdToDistributionStatusMap(distributionID: string, componentIDValue: string) { + this.distributionStatusesMap[distributionID].push({ + componentID: componentIDValue, + msoStatus: null, + artifacts: [] + }); + } + + private addArtifactToComponentId(distributionID: string, componentPosition: number, artifactNameValue: string, artifactURLValue: any) { + if (artifactNameValue) { + this.distributionStatusesMap[distributionID][componentPosition].artifacts.push({ + artifactName: artifactNameValue, + artifactUrl: artifactURLValue, + statuses: [] + }); + } + } + + private insertDistrbutionsToMap() { + this.distributionList.map((distribution) => this.distributionStatusesMap[distribution.distributionID] = []); + } + + private formatDate(epochTime: string) { + const intEpochTime = new Date(parseInt(epochTime, 10)); + const amOrPm = (intEpochTime.getHours() + 24) % 24 > 12 ? 'PM' : 'AM'; + const formattedDate = (intEpochTime.getMonth() + 1) + '/' + intEpochTime.getDate() + '/' + intEpochTime.getFullYear() + ' ' + intEpochTime.getHours() + ':' + + intEpochTime.getMinutes() + amOrPm; + return formattedDate; + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/__snapshots__/informational-artifact-page.spec.ts.snap b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/__snapshots__/informational-artifact-page.spec.ts.snap new file mode 100644 index 0000000000..1a19b36cfb --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/__snapshots__/informational-artifact-page.spec.ts.snap @@ -0,0 +1,40 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`informational artifacts page should match current snapshot of informational artifact pages component 1`] = ` +<information-artifact-page + addOrUpdateArtifact={[Function Function]} + artifactsService={[Function Object]} + deleteArtifact={[Function Function]} + store={[Function Store]} + table={[Function DatatableComponent]} + workspaceService={[Function Object]} +> + <div + class="information-artifact-page" + > + <svg-icon-label + class="add-artifact-btn" + /> + <ngx-datatable + class="ngx-datatable" + columnmode="flex" + > + <div + visibilityobserver="" + > + + <datatable-body + class="datatable-body" + > + <datatable-selection> + + + + </datatable-selection> + </datatable-body> + + </div> + </ngx-datatable> + </div> +</information-artifact-page> +`; diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.html b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.html new file mode 100644 index 0000000000..cff33258ae --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.html @@ -0,0 +1,82 @@ +<div class="information-artifact-page"> + <svg-icon-label class="add-artifact-btn" [clickable]="true" [mode]="'primary'" [labelPlacement]="'right'" + [label]="'Add'" [name]="'plus'" [testId]="'add-information-artifact-button'" + (click)="addOrUpdateArtifact()"></svg-icon-label> + <ngx-datatable + columnMode="flex" + [headerHeight]="40" + [reorderable]="false" + [swapColumns]="false" + [rows]="informationArtifacts$ | async" + [footerHeight]="'undefined'" + [sorts]="[{prop: 'artifactDisplayName', dir: 'desc'}]" + #informationArtifactsTable + (activate)="onActivate($event)"> + <ngx-datatable-row-detail [rowHeight]="80"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> + <div [attr.data-tests-id]="row.artifactDisplayName+'Description'">{{row.description}}</div> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column [resizeable]="false" name="Name" [flexGrow]="3" + [prop]="'artifactDisplayName'"> + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded"> + <div class="expand-collapse-cell"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'"></svg-icon> + <span [attr.data-tests-id]="'artifactDisplayName_' + row.artifactDisplayName">{{row.artifactDisplayName }}</span> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="Type" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.artifactType}} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="Version" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{ row.artifactVersion }} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="UUID" [flexGrow]="2"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{ row.artifactUUID }} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class="download-artifact-button"> + <svg-icon class="action-icon" *ngIf="!row.isThirdParty()" [mode]="'primary2'" + [disabled]="isViewOnly$ | async" [name]="'edit-o'" + testId="edit_{{row.artifactDisplayName}}" clickable="true" size="medium" + (click)="addOrUpdateArtifact(row)"></svg-icon> + <svg-icon class="action-icon" *ngIf="!row.isThirdParty()" [mode]="'primary2'" + [disabled]="isViewOnly$ | async" [name]="'trash-o'" + testId="delete_{{row.artifactDisplayName}}" clickable="true" size="medium" (click)="deleteArtifact(row)"></svg-icon> + <download-artifact class="action-icon" [disabled]="isViewOnly$ | async" [artifact]="row" + [componentId]="componentId" + [componentType]="componentType" + testId="download_{{row.artifactDisplayName}}"></download-artifact> + </div> + </ng-template> + </ngx-datatable-column> + + <ngx-datatable-footer> + <ng-template ngx-datatable-footer-template> + <div class="add-artifacts-dynamic-btn-list"> + <sdc-button *ngFor="let artifact of informationArtifactsAsButtons$ | async" + class="add-artifacts-dynamic-btn" + testId="add_artifact_{{artifact.artifactDisplayName}}" + [type]="'secondary'" + [size]="'xx-large'" + [text]="'ADD ' + artifact.artifactDisplayName" + [icon_name]="'plus-circle-o'" + [icon_mode] = "'secondary'" + [icon_position]="'left'" + (click)="addOrUpdateArtifact(artifact)"> + </sdc-button> + </div> + </ng-template> + </ngx-datatable-footer> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.less b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.less new file mode 100644 index 0000000000..b69e511f70 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.less @@ -0,0 +1,29 @@ +.information-artifact-page { + + .add-artifact-btn { + display: flex; + cursor: pointer; + justify-content: flex-end; + margin-bottom: 10px; + } + .download-artifact-button { + display: flex; + justify-content: center; + + .action-icon{ + margin-right: 10px; + } + } + + .add-artifacts-dynamic-btn-list { + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + margin: 20px 0px; + .add-artifacts-dynamic-btn{ + width: 350px; + margin-top: 15px; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.ts b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.ts new file mode 100644 index 0000000000..a6804a43c6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.component.ts @@ -0,0 +1,69 @@ +import {Component, OnInit, ViewChild} from "@angular/core"; +import {WorkspaceService} from "../workspace.service"; +import {SdcUiCommon, SdcUiComponents, SdcUiServices} from "onap-ui-angular"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import * as _ from "lodash"; +import {ArtifactGroupType, ArtifactType} from "../../../../utils/constants"; +import {ArtifactsService} from "../../../components/forms/artifacts-form/artifacts.service"; +import {DeleteArtifactAction, GetArtifactsByTypeAction} from "../../../store/actions/artifacts.action"; +import {Select, Store} from "@ngxs/store"; +import {Observable} from "rxjs/index"; +import {ArtifactsState} from "../../../store/states/artifacts.state"; +import {map} from "rxjs/operators"; +import {WorkspaceState} from "../../../store/states/workspace.state"; +import {ArtifactModel} from "../../../../models/artifacts"; + +@Component({ + selector: 'information-artifact-page', + templateUrl: './information-artifact-page.component.html', + styleUrls: ['./information-artifact-page.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class InformationArtifactPageComponent implements OnInit { + + public componentId: string; + public componentType: string; + public informationArtifacts$: Observable<ArtifactModel[]>; + public informationArtifactsAsButtons$: Observable<ArtifactModel[]>; + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + @ViewChild('informationArtifactsTable') table: any; + + constructor(private workspaceService: WorkspaceService, + private artifactsService: ArtifactsService, + private store: Store) { + } + + ngOnInit(): void { + this.componentId = this.workspaceService.metadata.uniqueId; + this.componentType = this.workspaceService.metadata.componentType; + + this.store.dispatch(new GetArtifactsByTypeAction({ + componentType: this.componentType, + componentId: this.componentId, + artifactType: ArtifactGroupType.INFORMATION + })); + + let artifacts = this.store.select(ArtifactsState.getArtifactsByType).pipe(map(filterFn => filterFn(ArtifactType.INFORMATION))); + this.informationArtifacts$ = artifacts.pipe(map(artifacts => _.filter(artifacts, (artifact) => { + return artifact.esId; + }))); + + this.informationArtifactsAsButtons$ = artifacts.pipe(map(artifacts => _.filter(artifacts, (artifact) => { + return !artifact.esId; + }))); + } + + onActivate(event) { + if (event.type === 'click') { + this.table.rowDetail.toggleExpandRow(event.row); + } + } + + public addOrUpdateArtifact = (artifact: ArtifactModel, isViewOnly?: boolean) => { + this.artifactsService.openArtifactModal(this.componentId, this.componentType, artifact, ArtifactGroupType.INFORMATION, isViewOnly); + } + + public deleteArtifact = (artifactToDelete) => { + this.artifactsService.deleteArtifact(this.componentType, this.componentId, artifactToDelete) + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.module.ts b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.module.ts new file mode 100644 index 0000000000..5eb9e5851b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/information-artifact-page.module.ts @@ -0,0 +1,30 @@ +import {CommonModule} from "@angular/common"; +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {UiElementsModule} from "../../../components/ui/ui-elements.module"; +import {InformationArtifactPageComponent} from "./information-artifact-page.component"; +import {ArtifactFormModule} from "../../../components/forms/artifacts-form/artifact-form.module"; +import {ArtifactsService} from "../../../components/forms/artifacts-form/artifacts.service"; + +@NgModule({ + declarations: [ + InformationArtifactPageComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + NgxDatatableModule, + UiElementsModule, + ArtifactFormModule + ], + exports: [ + InformationArtifactPageComponent + ], + entryComponents: [ + InformationArtifactPageComponent + ], + providers:[ArtifactsService] +}) +export class InformationArtifactPageModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/information-artifact/informational-artifact-page.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/informational-artifact-page.spec.ts new file mode 100644 index 0000000000..10fd14739b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/information-artifact/informational-artifact-page.spec.ts @@ -0,0 +1,77 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {WorkspaceService} from "../workspace.service"; +import {SdcUiServices} from "onap-ui-angular"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {Observable} from "rxjs/Observable"; +import {ComponentMetadata} from "../../../../models/component-metadata"; +import 'rxjs/add/observable/of'; +import {NgxsModule, Store} from "@ngxs/store"; +import {ArtifactsState} from "../../../store/states/artifacts.state"; +import {InformationArtifactPageComponent} from "./information-artifact-page.component"; +import { informationalArtifactsMock} from "../../../../../jest/mocks/artifacts-mock"; +import {ArtifactsService} from "../../../components/forms/artifacts-form/artifacts.service"; + +describe('informational artifacts page', () => { + + let fixture: ComponentFixture<InformationArtifactPageComponent>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let store: Store; + + beforeEach( + async(() => { + + topologyTemplateServiceMock = { + getArtifactsByType: jest.fn().mockImplementation((componentType, id, artifactType) => Observable.of(informationalArtifactsMock)) + }; + workspaceServiceMock = {metadata: <ComponentMetadata>{uniqueId: 'service_unique_id', componentType: 'SERVICE'}} + + loaderServiceMock = { + activate : jest.fn(), + deactivate: jest.fn() + } + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [InformationArtifactPageComponent], + imports: [NgxDatatableModule, NgxsModule.forRoot([ArtifactsState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock }, + {provide: ArtifactsService, useValue: {}}, + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(InformationArtifactPageComponent); + store = testBed.get(Store); + }); + }) + ); + + it('should match current snapshot of informational artifact pages component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should see exactly 3 informational artifacts and six buttons to add artifact by template', () => { + fixture.componentInstance.ngOnInit(); + fixture.componentInstance.informationArtifacts$.subscribe((artifacts)=> { + expect(artifacts.length).toEqual(3); + }) + fixture.componentInstance.informationArtifactsAsButtons$.subscribe((artifacts)=> { + expect(artifacts.length).toEqual(6); + }) + + store.selectOnce(state => state.artifacts.artifacts).subscribe(artifacts => { + expect(artifacts.length).toEqual(9); + }); + }) + + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.html new file mode 100644 index 0000000000..f496e64c17 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.html @@ -0,0 +1,22 @@ +<div class="capabilities-properties-table"> + <ngx-datatable #componentsMetadataTable + columnMode="flex" + [headerHeight]="40" + [rowHeight]="35" + [rows]="capabilitiesProperties" + [sorts]="[{prop: 'name', dir: 'desc'}]"> + <ngx-datatable-column *ngFor="let column of capabilityPropertiesColumns" [ngSwitch]="column.prop" [resizeable]="false" + [draggable]="false" name={{column.name}} [flexGrow]="column.flexGrow"> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchCase="'name'"> + <a data-tests-id="row[column.prop]" sdc-tooltip [tooltip-text]="row[column.prop]" (click)="updateProperty(row)">{{row[column.prop]}}</a> + </ng-template> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchCase="'schema'"> + <span *ngIf="row[column.prop] && row[column.prop].property" data-tests-id="row[column.prop].property.type" + sdc-tooltip [tooltip-text]="row[column.prop].property.type">{{row[column.prop].property.type}}</span> + </ng-template> + <ng-template ngx-datatable-cell-template let-row="row" *ngSwitchDefault> + <span data-tests-id="row[column.prop]" sdc-tooltip [tooltip-text]="row[column.prop]" [tooltip-placement]="3">{{row[column.prop]}}</span> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.less new file mode 100644 index 0000000000..007f509538 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.less @@ -0,0 +1,9 @@ + +:host ::ng-deep { + .ngx-datatable { + > div { + min-height: auto !important; + } + + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.ts new file mode 100644 index 0000000000..2a1a16e265 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities-properties/capabilities-properties.ts @@ -0,0 +1,33 @@ + +import { ViewChild, Input, OnInit, Component } from "@angular/core"; +import {SdcUiServices} from "onap-ui-angular"; +import { ModalsHandler } from "../../../../../../utils/modals-handler"; +import { WorkspaceService } from "../../../workspace.service"; +import { PropertyModel } from "../../../../../../models/properties"; + + +@Component({ + selector: 'capabilities-properties', + templateUrl: './capabilities-properties.html', + styleUrls: ['./capabilities-properties.less', '../../../../../../../assets/styles/table-style.less'] +}) +export class CapabilitiesPropertiesComponent { + @Input() public capabilitiesProperties: Array<PropertyModel> = []; + + private capabilityPropertiesColumns = [ + {name: 'Name', prop: 'name', flexGrow: 1}, + {name: 'Type', prop: 'type', flexGrow: 1}, + {name: 'Schema', prop: 'schema', flexGrow: 1}, + {name: 'Description', prop: 'description', flexGrow: 1}, + ]; + constructor(private modalsHandler: ModalsHandler, + private workspaceService: WorkspaceService) {} + + private updateProperty(property: PropertyModel): void { + _.forEach(this.capabilitiesProperties, (prop: PropertyModel) => { + prop.readonly = true; + }); + this.modalsHandler.openEditPropertyModal(property, this.workspaceService.metadata, this.capabilitiesProperties, false, 'component', + this.workspaceService.metadata.uniqueId); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.html new file mode 100644 index 0000000000..819eb84849 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.html @@ -0,0 +1,59 @@ +<div class="capabilities-table"> + <div class="expand-collapse-all-rows"> + <svg-icon class="selected-all-capabilities" + [mode]="'primary'" [clickable]="true" [name]="'expand-o'" + [size]="'medium'" (click)="capabilitiesTable.rowDetail.expandAllRows()"> + </svg-icon> + <svg-icon class="unselected-all-capabilities" + [mode]="'primary'" [clickable]="true" [name]="'minimize-o'" + [size]="'medium'" (click)="capabilitiesTable.rowDetail.collapseAllRows()"> + </svg-icon> + </div> + <ngx-datatable #capabilitiesTable + columnMode="flex" + [headerHeight]="40" + [rowHeight]="35" + [rows]="capabilities" + (select)="onSelectCapabilities($event)" + [selectionType]="'single'"> + <ngx-datatable-row-detail [rowHeight]="undefiend"> + <ng-template let-row="row" ngx-datatable-row-detail-template> + <div class="properties-title">Properties</div> + <capabilities-properties [capabilitiesProperties]="row.properties"></capabilities-properties> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column name="Name" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded"> + <div class="expand-collapse-cell"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'" (click)="expendRow(row)"></svg-icon> + <span data-tests-id="row.name" sdc-tooltip [tooltip-text]="row.name" [tooltip-placement]="3" (click)="editCapability(row)">{{row.name}}</span> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Type" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.type" sdc-tooltip [tooltip-text]="row.type" [tooltip-placement]="3">{{row.type ? row.type.replace("tosca.capabilities.",""): ''}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Description" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.description" sdc-tooltip [tooltip-text]="row.description" [tooltip-placement]="3">{{row.description}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Valid Source" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.validSourceTypes.join(',')" sdc-tooltip [tooltip-text]="row.validSourceTypes ? row.validSourceTypes.join(',') : null" [tooltip-placement]="3"> + {{row.validSourceTypes ? row.validSourceTypes.join(','): ''}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Occurrences" [flexGrow]="1" [prop]="'minOccurrences'" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.minOccurrences+','+row.maxOccurrences" sdc-tooltip + [tooltip-text]="row.minOccurrences+','+row.maxOccurrences" [tooltip-placement]="3"> + {{row.minOccurrences}},{{row.maxOccurrences}}</span> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.less new file mode 100644 index 0000000000..0c520a8135 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.less @@ -0,0 +1,16 @@ +:host ::ng-deep { + .datatable-row-detail { + width: 1260px; + } + .datatable-body-row { + cursor: pointer; + } +} +.expand-collapse-all-rows { + position: absolute; + top: 172px; + left: 890px; +} +.properties-title { + padding-bottom: 10px; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.ts new file mode 100644 index 0000000000..02db5d3aee --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilities.component.ts @@ -0,0 +1,79 @@ +import {Capability, CapabilityUI} from "../../../../../models/capability"; +import { ViewChild, Input, OnInit, Component } from "@angular/core"; +import {SdcUiServices} from "onap-ui-angular"; +import {CapabilitiesEditorComponent} from "./capabilityEditor/capabilities-editor.component"; +import {WorkspaceService} from "../../workspace.service"; +import {TopologyTemplateService} from "../../../../services/component-services/topology-template.service"; +import {ReqAndCapabilitiesService} from "../req-and-capabilities.service"; +import {ModalComponent} from "onap-ui-angular/dist/modals/modal.component"; +import {EventListenerService} from "../../../../../services/event-listener-service"; + + +@Component({ + selector: 'capabilities', + templateUrl: './capabilities.component.html', + styleUrls: ['./capabilities.component.less','../../../../../../assets/styles/table-style.less'] +}) +export class CapabilitiesComponent { + @Input() public capabilities: Array<Capability>; + @ViewChild('capabilitiesTable') capabilitiesTable: any; + private customModalInstance: ModalComponent; + + constructor( + private workspaceService: WorkspaceService, + private loaderService: SdcUiServices.LoaderService, + private topologyTemplateService: TopologyTemplateService, + private reqAndCapabilitiesService : ReqAndCapabilitiesService, + private modalService: SdcUiServices.ModalService, + private eventListenerService: EventListenerService) { + } + + private onSelectCapabilities({ selected }) { + } + + editCapability(cap: CapabilityUI) { + let modalConfig = { + size: 'md', + title: 'Update Capability', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: ('Update'), + size: "'x-small'", + callback: () => this.updateCapability(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + capability: cap, + capabilityTypesList: this.reqAndCapabilitiesService.getCapabilityTypesList(), + }; + + this.customModalInstance = this.modalService.openCustomModal(modalConfig, CapabilitiesEditorComponent, {input: modalInputs}); + this.customModalInstance.innerModalContent.instance. + onValidationChange.subscribe((isValid) => this.customModalInstance.getButtonById('saveButton').disabled = !isValid); + } + + expendRow(row) { + this.capabilitiesTable.rowDetail.toggleExpandRow(row); + } + + private updateCapability() { + const capability = this.customModalInstance.innerModalContent.instance.capabilityData; + this.loaderService.activate(); + if (capability.uniqueId) { + this.topologyTemplateService.updateCapability(this.workspaceService.metadata.getTypeUrl(), this.workspaceService.metadata.uniqueId, capability).subscribe((result) => { + let index = this.capabilities.findIndex((cap) => result[0].uniqueId === cap.uniqueId); + this.capabilities[index] = new CapabilityUI(result[0], this.workspaceService.metadata.uniqueId); + this.loaderService.deactivate(); + this.eventListenerService.notifyObservers('CAPABILITIES_UPDATED'); + }, () => { + this.loaderService.deactivate(); + }); + } + } + + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.html new file mode 100644 index 0000000000..bc15d4d228 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.html @@ -0,0 +1,93 @@ +<div class="capability-editor"> + <form class="w-sdc-form"> + <div class="i-sdc-form-content-capability-content"> + <div class="content-row"> + <div class="i-sdc-form-item"> + <sdc-input + label="{{ 'CAP_NAME' | translate }}" + required="true" + class="i-sdc-form-input" + testId="capName" + [disabled]="isReadonly" + [(value)]="capabilityData.name" + (valueChange)="validityChanged()"> + </sdc-input> + </div> + </div> + + <div class="group-with-border"> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'CAP_TYPE' | translate }}" + required="true" + class="i-sdc-form-select" + testId="capType" + [disabled]="isReadonly" + [options]="capabilityTypesMappedList" + selectedOption="{{ capabilityData.type }}" + [placeHolder] = "capabilityData.type" + (changed)="onSelectCapabilityType($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <label class="i-sdc-form-label"> {{ 'CAP_DESCRIPTION' | translate }} </label> + <textarea + rows="3" + class="i-sdc-form-input description" + data-tests-id="capDesc" + disabled + value="{{capabilityData.description}}"> + </textarea> + </div> + <div class="content-row i-sdc-form-item"> + <label class="i-sdc-form-label valid-source-label"> {{ 'CAP_VALID_SOURCE' | translate }} </label> + <textarea + rows="2" + class="i-sdc-form-input" + data-tests-id="capValidSrc" + disabled + value="{{capabilityData.validSourceTypes}}"> + </textarea> + </div> + </div> + + <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate }} </label> + <div class="content-row occurrences-section"> + <div class="min-occurrences-value"> + <sdc-input + label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate }}" + class="i-sdc-form-input" + testId="capOccurrencesMin" + [disabled]="isReadonly" + [(value)]="capabilityData.minOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + <div class="sdc-input"> + <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate }} </label> + <div class="max-occurrences-value"> + <sdc-checkbox + class="checkbox-label unbounded-value" + testId="capOccurrencesMaxUnbounded" + label="{{translatedUnboundTxt.toLowerCase()}}" + (checkedChange)="onUnboundedChanged()" + [checked]="isUnboundedChecked" + [disabled]="isReadonly"> + </sdc-checkbox> + + <sdc-input + *ngIf="!isUnboundedChecked" + class="i-sdc-form-input" + testId="capOccurrencesMax" + [disabled]="isReadonly" + [(value)]="capabilityData.maxOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + </div> + </div> + </div> + </form> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.less new file mode 100644 index 0000000000..324dc6c4d2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.less @@ -0,0 +1,38 @@ +@import '../../../../../../../assets/styles/variables.less'; + +.capability-editor { + .i-sdc-form-content-capability-content { + padding: 10px 25px; + .group-with-border { + margin: 25px 0; + padding: 15px 0; + border-top: 1px solid @tlv_color_u; + border-bottom: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } + } + + .occurrences-label { + font-family: @font-opensans-bold; + margin-bottom: 19px; + } + .occurrences-section, /deep/ .max-occurrences-value { + display: flex; + .min-occurrences-value { + padding-right: 30px; + } + .unbounded-value { + padding-top: 7px; + padding-right: 20px; + .sdc-checkbox__label { + text-transform: capitalize; + } + } + } + textarea { + min-height: unset; + height: unset; + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.ts new file mode 100644 index 0000000000..3bafa42e0f --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.component.ts @@ -0,0 +1,81 @@ +import {Component} from '@angular/core'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {Capability, CapabilityTypeModel} from 'app/models'; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {TranslateService} from 'app/ng2/shared/translator/translate.service'; +import {Subject} from "rxjs"; + +@Component({ + selector: 'capabilities-editor', + templateUrl: './capabilities-editor.component.html', + styleUrls: ['./capabilities-editor.component.less'], + providers: [ServiceServiceNg2] +}) + +export class CapabilitiesEditorComponent { + input: { + test: string, + capability: Capability, + capabilityTypesList: Array<CapabilityTypeModel>, + isReadonly: boolean; + }; + capabilityData: Capability; + capabilityTypesMappedList: Array<DropdownValue>; + isUnboundedChecked: boolean; + isReadonly: boolean; + translatedUnboundTxt: string; + + public onValidationChange: Subject<boolean> = new Subject(); + + constructor(private translateService: TranslateService) { + } + + ngOnInit() { + this.capabilityData = new Capability(this.input.capability); + this.translatedUnboundTxt = ''; + this.capabilityData.minOccurrences = this.capabilityData.minOccurrences || 0; + this.translateService.languageChangedObservable.subscribe(lang => { + this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED'); + this.capabilityData.maxOccurrences = this.capabilityData.maxOccurrences || this.translatedUnboundTxt; + this.isUnboundedChecked = this.capabilityData.maxOccurrences === this.translatedUnboundTxt; + }); + this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type)); + this.isReadonly = this.input.isReadonly; + this.validityChanged(); + } + + onUnboundedChanged() { + this.isUnboundedChecked = !this.isUnboundedChecked; + this.capabilityData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null; + this.validityChanged(); + } + + checkFormValidForSubmit() { + return this.capabilityData.name && this.capabilityData.name.length && + this.capabilityData.type && this.capabilityData.type.length && !_.isEqual(this.capabilityData.minOccurrences, "") && this.capabilityData.minOccurrences >= 0 && + ( + this.isUnboundedChecked || + (this.capabilityData.maxOccurrences && (this.capabilityData.minOccurrences <= parseInt(this.capabilityData.maxOccurrences))) + ); + } + + onSelectCapabilityType(selectedCapType: DropdownValue) { + this.capabilityData.type = selectedCapType && selectedCapType.value; + if (selectedCapType && selectedCapType.value) { + let selectedCapabilityTypeObj: CapabilityTypeModel = this.input.capabilityTypesList.find(capType => capType.toscaPresentation.type === selectedCapType.value); + this.capabilityData.description = selectedCapabilityTypeObj.toscaPresentation.description; + this.capabilityData.validSourceTypes = selectedCapabilityTypeObj.toscaPresentation.validTargetTypes; + this.capabilityData.properties = _.forEach( + _.toArray(selectedCapabilityTypeObj.properties), + prop => prop.uniqueId = null //a requirement for the BE + ); + } + this.validityChanged(); + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.onValidationChange.next(validState); + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.module.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.module.ts new file mode 100644 index 0000000000..38b104a0f6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/capabilities/capabilityEditor/capabilities-editor.module.ts @@ -0,0 +1,29 @@ +import {NgModule} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {CapabilitiesEditorComponent} from './capabilities-editor.component'; +import {FormsModule} from '@angular/forms'; +import {FormElementsModule} from 'app/ng2/components/ui/form-components/form-elements.module'; +import {UiElementsModule} from 'app/ng2/components/ui/ui-elements.module'; +import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import {SdcUiComponentsModule} from 'onap-ui-angular'; +// import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; + +@NgModule({ + declarations: [ + CapabilitiesEditorComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + UiElementsModule, + TranslateModule, + SdcUiComponentsModule + ], + exports: [], + entryComponents: [ + CapabilitiesEditorComponent + ], + providers: [] +}) +export class CapabilitiesEditorModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.html new file mode 100644 index 0000000000..73e0ae52ae --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.html @@ -0,0 +1,21 @@ +<div class="workspace-req-and-cap"> + <div> + <span class="addTitle" *ngIf="selectTabName === 'REQUIREMENTS'" (click)="addRequiremnet()">Add Requirement</span> + <span class="addTitle" *ngIf="selectTabName !== 'REQUIREMENTS'" (click)="addCapability()">Add Capability</span> + <span class="req-and-cap-filter" *ngIf="notEmptyTable"> + <sdc-filter-bar + [placeHolder]="'Search'" + (keyup)="updateFilter($event)" + [testId]="'search-box'"> + </sdc-filter-bar> + </span> + </div> + <sdc-tabs (selectedTab)="selectTab($event)" [tabStyle]="'sdc-table-tab'"> + <sdc-tab [title]="'Requirements('+(requirements.length||'0')+')'" [active]="true" [testId]="'req-tab'"> + <div #requirmentsContainer></div> + </sdc-tab> + <sdc-tab [title]="'Capabilities('+(capabilities.length||'0')+')'" [active]="false" [testId]="'cap-tab'"> + <div #capabilitiesContainer></div> + </sdc-tab> + </sdc-tabs> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.less new file mode 100644 index 0000000000..f3d39cacd6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.less @@ -0,0 +1,19 @@ +.req-and-cap-filter { + width: 336px; + float: right; + margin-right: 10px; +} + +.addTitle { + float: right; + text-transform: uppercase; + font-family: OpenSans-Semibold, sans-serif; + color: #009fdb; + cursor: pointer; +} + +:host ::ng-deep .sdc-tabs { + .sdc-tab-content { + margin-top: 0; + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.spec.ts new file mode 100644 index 0000000000..b7fad045d3 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.spec.ts @@ -0,0 +1,127 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import { NO_ERRORS_SCHEMA} from "@angular/core"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; + +import {Observable} from "rxjs/Observable"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {SdcUiServices, SdcUiCommon} from "onap-ui-angular"; +import 'rxjs/add/observable/of'; +import {ReqAndCapabilitiesComponent} from "./req-and-capabilities.component"; +import {ReqAndCapabilitiesService} from "./req-and-capabilities.service"; +import {WorkspaceService} from "../workspace.service"; +import { + capabilitiesMock, + filterRequirmentsMock, + requirementMock +} from "../../../../../jest/mocks/req-and-capabilities.mock"; +import {ComponentMetadata} from "../../../../models/component-metadata"; +import { TopologyTemplateService } from "../../../services/component-services/topology-template.service"; +import {EventListenerService} from "../../../../services/event-listener-service"; + +describe('req and capabilities component', () => { + + let fixture: ComponentFixture<ReqAndCapabilitiesComponent>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let createDynamicComponentServiceMock: Partial<SdcUiServices.CreateDynamicComponentService> + let reqAndCapabilitiesService: Partial<ReqAndCapabilitiesService>; + let modalService: Partial<SdcUiServices.ModalService>; + let eventListenerService: Partial<EventListenerService>; + + + + beforeEach( + async(() => { + + workspaceServiceMock = { + metadata: new ComponentMetadata() + }; + + topologyTemplateServiceMock = { + getRequirementsAndCapabilitiesWithProperties: jest.fn().mockImplementation(() => + Observable.of({requirements: {'tosca.requirements.Node': requirementMock}, + capabilities: {'tosca.capabilities.Node': capabilitiesMock}})) + }; + + loaderServiceMock = { + activate : jest.fn(), + deactivate: jest.fn() + } + createDynamicComponentServiceMock = { + insertComponentDynamically: jest.fn() + } + + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [ReqAndCapabilitiesComponent], + imports: [NgxDatatableModule], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + { provide: WorkspaceService, useValue: workspaceServiceMock }, + { provide: SdcUiServices.LoaderService, useValue: loaderServiceMock }, + { provide: TopologyTemplateService, useValue: topologyTemplateServiceMock }, + { provide: SdcUiServices.CreateDynamicComponentService, useValue: createDynamicComponentServiceMock }, + { provide: ReqAndCapabilitiesService, useValue: reqAndCapabilitiesService }, + { provide: SdcUiServices.ModalService, useValue: modalService }, + { provide: EventListenerService, useValue: eventListenerService } + ], + }); + }; + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(ReqAndCapabilitiesComponent); + }); + }) + ); + + it('should see exactly 2 requirement in requirements table when call initCapabilitiesAndRequirements and meta data requirements null', () => { + workspaceServiceMock.metadata.requirements = null; + fixture.componentInstance.initCapabilitiesAndRequirements(); + expect(workspaceServiceMock.metadata.requirements["tosca.requirements.Node"].length).toBe(3); + }); + it('should see exactly 2 capabilities in capabilities table when call initCapabilitiesAndRequirements and meta data capabilities null', () => { + workspaceServiceMock.metadata.capabilities = null; + fixture.componentInstance.initCapabilitiesAndRequirements(); + expect(workspaceServiceMock.metadata.capabilities["tosca.capabilities.Node"].length).toBe(2); + }); + + it('capabilities array papulated when call populateReqOrCap with capabilities', () => { + workspaceServiceMock.metadata.capabilities = {"tosca.capabilities.Node": capabilitiesMock, "tosca.capabilities.Scalable": capabilitiesMock}; + fixture.componentInstance.populateReqOrCap("capabilities"); + expect(fixture.componentInstance.capabilities.length).toBe(4); + }); + + it('create requirements component when call loadReqOrCap with true', () => { + createDynamicComponentServiceMock.insertComponentDynamically.mockImplementation(() => { return {instance: {requirements: requirementMock}}}); + fixture.componentInstance.requirements = requirementMock; + fixture.componentInstance.loadReqOrCap(true); + expect(fixture.componentInstance.instanceRef.instance.requirements.length).toEqual(3); + }); + + it('create capabilities component when call loadReqOrCap with false', () => { + fixture.componentInstance.instanceRef = {instance: {requirements: null}}; + createDynamicComponentServiceMock.insertComponentDynamically.mockImplementation(() => { return {instance: {capabilities: capabilitiesMock}}}); + fixture.componentInstance.capabilities = capabilitiesMock; + fixture.componentInstance.requirementsUI = filterRequirmentsMock; + let event = { + target : { + value : 'root' + } + } + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.instanceRef.instance.requirements.length).toBe(1); + }); + + it('should filter 1 capabilities when searching and call updateFilter function and instanceRef is capabilities component', () => { + fixture.componentInstance.instanceRef = {instance: {capabilities: null}}; + fixture.componentInstance.capabilities = capabilitiesMock; + fixture.componentInstance.selectTabName = 'CAPABILITIES'; + let event = { + target : { + value : '1source' + } + } + fixture.componentInstance.updateFilter(event); + expect(fixture.componentInstance.instanceRef.instance.capabilities[0].type).toBe("tosca.capabilities.Node"); + }); +}); diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.ts new file mode 100644 index 0000000000..69999bfb86 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.component.ts @@ -0,0 +1,229 @@ +import { Component, ComponentRef, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; +import * as _ from 'lodash'; +import { SdcUiServices } from 'onap-ui-angular'; +import { Capability, CapabilityUI } from '../../../../models/capability'; +import { Requirement, RequirementUI } from '../../../../models/requirement'; +import { TopologyTemplateService } from '../../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../../services/responses/component-generic-response'; +import { WorkspaceService } from '../workspace.service'; +import { CapabilitiesComponent } from './capabilities/capabilities.component'; +import { RequirmentsComponent } from './requirements/requirments.components'; +import {ReqAndCapabilitiesService} from "./req-and-capabilities.service"; +import {CapabilitiesEditorComponent} from "./capabilities/capabilityEditor/capabilities-editor.component"; +import {ModalComponent} from "onap-ui-angular/dist/modals/modal.component"; +import {EventListenerService} from "../../../../services/event-listener-service"; +import {RequirementsEditorComponent} from "./requirements/requirementEditor/requirements-editor.component"; + +@Component({ + selector: 'req-and-capabilities', + templateUrl: './req-and-capabilities.component.html', + styleUrls: ['./req-and-capabilities.component.less'] +}) +export class ReqAndCapabilitiesComponent implements OnInit { + + @ViewChild('requirmentsContainer', { read: ViewContainerRef }) requirmentsContainer: ViewContainerRef; + @ViewChild('capabilitiesContainer', { read: ViewContainerRef }) capabilitiesContainer: ViewContainerRef; + private requirements: Requirement[] = []; + private requirementsUI: RequirementUI[] = []; + private capabilities: Capability[] = []; + private selectTabName: string = 'REQUIREMENTS'; + private notEmptyTable: boolean = true; + private instanceRef: ComponentRef<any>; + private customModalInstance: ModalComponent; + readonly INPUTS_FOR_CAPABILITIES: string = 'INPUTS_FOR_CAPABILITIES'; + readonly INPUTS_FOR_REQUIREMENTS: string = 'INPUTS_FOR_REQUIREMENTS'; + + constructor(private workspaceService: WorkspaceService, + private loaderService: SdcUiServices.LoaderService, + private topologyTemplateService: TopologyTemplateService, + private createDynamicComponentService: SdcUiServices.CreateDynamicComponentService, + private reqAndCapabilitiesService : ReqAndCapabilitiesService, + private modalService: SdcUiServices.ModalService, + private eventListenerService: EventListenerService) { + } + + ngOnInit(): void { + this.initCapabilitiesAndRequirements(); + + this.eventListenerService.registerObserverCallback('CAPABILITIES_UPDATED', () => { + this.loadReqOrCap(); + }); + + this.eventListenerService.registerObserverCallback('REQUIREMENTS_UPDATED', () => { + this.loadReqOrCap(); + }); + } + + + + private initCapabilitiesAndRequirements(): void { + if (!this.workspaceService.metadata.capabilities || !this.workspaceService.metadata.requirements) { + this.loaderService.activate(); + this.topologyTemplateService.getRequirementsAndCapabilitiesWithProperties + (this.workspaceService.metadata.componentType, this.workspaceService.metadata.uniqueId) + .subscribe((response: ComponentGenericResponse) => { + this.workspaceService.metadata.capabilities = response.capabilities; + this.workspaceService.metadata.requirements = response.requirements; + this.initReqOrCap(); + this.loaderService.deactivate(); + }, (error) => { + this.loaderService.deactivate(); + }); + } else { + this.initReqOrCap(); + } + } + + private initReqOrCap() { + this.populateReqOrCap('requirements'); + this.extendRequirementsToRequiremnetsUI(this.requirements); + this.populateReqOrCap('capabilities'); + this.loadReqOrCap(); + } + + private populateReqOrCap(instanceName: string) { + _.forEach(this.workspaceService.metadata[instanceName], (concatArray: any[], name) => { + this[instanceName] = this[instanceName].concat(concatArray); + }); + } + + private updateFilter(event) { + const val = event.target.value.toLowerCase(); + if (this.selectTabName === 'REQUIREMENTS') { + this.instanceRef.instance.requirements = this.requirementsUI.filter((req: Requirement) => { + return !val || this.filterRequirments(req, val); + }); + } else { + this.instanceRef.instance.capabilities = this.capabilities.filter((cap: Capability) => { + return !val || this.filterCapabilities(cap, val); + }); + } + + } + + private selectTab($event) { + this.selectTabName = $event.title.contains('Requirement') ? 'REQUIREMENTS' : 'CATPABILITIES'; + this.loadReqOrCap(); + } + + private async loadReqOrCap() { + if (this.instanceRef) { + this.instanceRef.destroy(); + } + + if (this.selectTabName === 'REQUIREMENTS') { + this.notEmptyTable = this.requirementsUI.length !== 0; + this.instanceRef = this.createDynamicComponentService. + insertComponentDynamically(RequirmentsComponent, {requirements: this.requirementsUI}, this.requirmentsContainer); + // TODO - Keep the initInputs, so it will be called only for the first time - no need to wait to thse API's every time that a user switches tab + await this.reqAndCapabilitiesService.initInputs(this.INPUTS_FOR_REQUIREMENTS); + } else { + this.notEmptyTable = this.capabilities.length !== 0; + this.instanceRef = this.createDynamicComponentService. + insertComponentDynamically(CapabilitiesComponent, {capabilities: this.capabilities}, this.capabilitiesContainer); + // TODO - Keep the initInputs, so it will be called only for the first time - no need to wait to thse API's every time that a user switches tab + await this.reqAndCapabilitiesService.initInputs(this.INPUTS_FOR_CAPABILITIES); + } + } + + private filterCapabilities(capability: Capability, val: string): boolean { + return _.includes([capability.name, capability.description, capability.validSourceTypes.join(), + capability.minOccurrences, capability.maxOccurrences].join('').toLowerCase(), val) || + (capability.type && capability.type.replace('tosca.capabilities.', '').toLowerCase().indexOf(val) !== -1); + } + + private filterRequirments(requirement: Requirement, val: string): boolean { + return _.includes([requirement.name, requirement.minOccurrences, requirement.maxOccurrences].join('').toLowerCase(), val) || + (requirement.capability && requirement.capability.substring('tosca.capabilities.'.length).toLowerCase().indexOf(val) !== -1) || + (requirement.node && requirement.node.substring('tosca.node.'.length).toLowerCase().indexOf(val) !== -1) || + (requirement.relationship && requirement.relationship.substring('tosca.relationship.'.length) + .toLowerCase().indexOf(val) !== -1); + } + + private addCapability() { + let modalConfig = { + size: 'md', + title: 'Add Capability', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: ('Create'), + size: "'x-small'", + callback: () => this.createCapability(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + capabilityTypesList: this.reqAndCapabilitiesService.getCapabilityTypesList(), + }; + + this.customModalInstance = this.modalService.openCustomModal(modalConfig, CapabilitiesEditorComponent, {input: modalInputs}); + this.customModalInstance.innerModalContent.instance. + onValidationChange.subscribe((isValid) => this.customModalInstance.getButtonById('saveButton').disabled = !isValid); + } + + private createCapability() { + const capability = this.customModalInstance.innerModalContent.instance.capabilityData; + this.loaderService.activate(); + if (!capability.uniqueId) { + this.topologyTemplateService.createCapability(this.workspaceService.metadata.getTypeUrl(), this.workspaceService.metadata.uniqueId, capability).subscribe((result) => { + this.capabilities.unshift(new CapabilityUI(result[0], this.workspaceService.metadata.uniqueId)); + this.loadReqOrCap(); + this.loaderService.deactivate(); + }, () => { + this.loaderService.deactivate(); + }); + } + } + + private addRequiremnet () { + let modalConfig = { + size: 'md', + title: 'Add Requirement', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: ('Create'), + size: "'x-small'", + callback: () => this.createRequirement(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + // requirement: req, + relationshipTypesList: this.reqAndCapabilitiesService.getRelationsShipeTypeList(), + nodeTypesList: this.reqAndCapabilitiesService.getNodeTypesList(), + capabilityTypesList: this.reqAndCapabilitiesService.getCapabilityTypesList(), + // isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(), + }; + + this.customModalInstance = this.modalService.openCustomModal(modalConfig, RequirementsEditorComponent, {input: modalInputs}); + this.customModalInstance.innerModalContent.instance. + onValidationChange.subscribe((isValid) => this.customModalInstance.getButtonById('saveButton').disabled = !isValid); + } + + + private createRequirement() { + const requirement = this.customModalInstance.innerModalContent.instance.requirementData; + this.loaderService.activate(); + if (!requirement.uniqueId) { + this.topologyTemplateService.createRequirement(this.workspaceService.metadata.getTypeUrl(), this.workspaceService.metadata.uniqueId, requirement).subscribe(result => { + this.requirementsUI.unshift(new RequirementUI(result[0], this.workspaceService.metadata.uniqueId)); + this.loadReqOrCap(); + this.loaderService.deactivate(); + }, () => { + this.loaderService.deactivate(); + }); + } + } + + private extendRequirementsToRequiremnetsUI(requirements: Requirement[]) { + this.requirements.map((requirement) => { + this.requirementsUI.push(new RequirementUI(requirement, this.workspaceService.metadata.uniqueId)); + }); + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.module.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.module.ts new file mode 100644 index 0000000000..aacb3a5bd1 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.module.ts @@ -0,0 +1,49 @@ +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; + +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import { ReqAndCapabilitiesComponent } from "./req-and-capabilities.component"; +import { CommonModule } from "@angular/common"; + +import {RequirmentsComponent } from "./requirements/requirments.components"; +import { CapabilitiesComponent } from "./capabilities/capabilities.component"; +import { CapabilitiesPropertiesComponent } from "./capabilities/capabilities-properties/capabilities-properties"; +import {ReqAndCapabilitiesService} from "./req-and-capabilities.service"; +import {RequirementsEditorComponent} from "./requirements/requirementEditor/requirements-editor.component"; +import {CapabilitiesEditorComponent} from "./capabilities/capabilityEditor/capabilities-editor.component"; +import {TranslateModule} from "../../../shared/translator/translate.module"; +import {ToscaTypesServiceNg2} from "../../../services/tosca-types.service"; + +@NgModule({ + declarations: [ + ReqAndCapabilitiesComponent, + CapabilitiesComponent, + RequirmentsComponent, + CapabilitiesPropertiesComponent, + RequirementsEditorComponent, + CapabilitiesEditorComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + NgxDatatableModule, + TranslateModule + ], + exports: [ + ReqAndCapabilitiesComponent, + CapabilitiesComponent, + RequirmentsComponent, + CapabilitiesPropertiesComponent + ], + entryComponents: [ + ReqAndCapabilitiesComponent, + CapabilitiesComponent, + RequirmentsComponent, + CapabilitiesPropertiesComponent, + RequirementsEditorComponent, + CapabilitiesEditorComponent + ], + providers: [ ReqAndCapabilitiesService] +}) +export class reqAndCapabilitiesModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.service.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.service.ts new file mode 100644 index 0000000000..470aac75a6 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/req-and-capabilities.service.ts @@ -0,0 +1,80 @@ +import { Injectable } from "@angular/core"; +import { TopologyTemplateService } from "../../../services/component-services/topology-template.service"; +import { Store } from "@ngxs/store"; +import { SdcUiServices } from "onap-ui-angular"; +import { CapabilityTypeModel } from "../../../../models/capability-types"; +import { RelationshipTypeModel } from "../../../../models/relationship-types"; +import { NodeTypeModel } from "../../../../models/node-types"; +import { WorkspaceService } from "../workspace.service"; +import { ToscaTypesServiceNg2 } from "../../../services/tosca-types.service"; + + + +@Injectable() +export class ReqAndCapabilitiesService { + + private capabilityTypesList: CapabilityTypeModel[]; + private relationshipTypesList: RelationshipTypeModel[]; + private nodeTypesList: NodeTypeModel[]; + private capabilitiesListUpdated: boolean = false; + private requirementsListUpdated: boolean = false; + private nodeTypeListUpdated: boolean = false; + + readonly INPUTS_FOR_REQUIREMENTS: string = 'INPUTS_FOR_REQUIREMENTS'; + readonly INPUTS_FOR_CAPABILITIES: string = 'INPUTS_FOR_CAPABILITIES'; + + constructor( + private workspaceService: WorkspaceService, + private modalService: SdcUiServices.ModalService, + private loaderService: SdcUiServices.LoaderService, + private topologyTemplateService: TopologyTemplateService, + private store: Store, + private toscaTypesServiceNg2: ToscaTypesServiceNg2){} + + public isViewOnly = (): boolean => { + return this.store.selectSnapshot((state) => state.workspace.isViewOnly); + } + + public isDesigner = (): boolean => { + return this.store.selectSnapshot((state) => state.workspace.isDesigner); + } + + public async initInputs(initInputsFor: string) { + + if (!this.capabilitiesListUpdated){ + // -- COMMON for both -- + this.capabilityTypesList = []; + let capabilityTypesResult = await this.toscaTypesServiceNg2.fetchCapabilityTypes(); + Object.keys(capabilityTypesResult).forEach(key => {this.capabilityTypesList.push(capabilityTypesResult[key])}) + this.capabilitiesListUpdated = true; + } + + if (initInputsFor === 'INPUTS_FOR_REQUIREMENTS') { + if (!this.requirementsListUpdated){ + this.relationshipTypesList = []; + let relationshipTypesResult = await this.toscaTypesServiceNg2.fetchRelationshipTypes(); + Object.keys(relationshipTypesResult).forEach(key => {this.relationshipTypesList.push(relationshipTypesResult[key])}); + this.requirementsListUpdated = true; + } + + if (!this.nodeTypeListUpdated){ + this.nodeTypesList = []; + let nodeTypesResult = await this.toscaTypesServiceNg2.fetchNodeTypes(); + Object.keys(nodeTypesResult).forEach(key => {this.nodeTypesList.push(nodeTypesResult[key])}) + this.nodeTypeListUpdated = true; + } + } + } + + getCapabilityTypesList() { + return this.capabilityTypesList; + } + + getRelationsShipeTypeList() { + return this.relationshipTypesList; + } + + getNodeTypesList() { + return this.nodeTypesList; + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.html new file mode 100644 index 0000000000..330680d3ed --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.html @@ -0,0 +1,91 @@ +<div class="requirement-editor"> + <form class="w-sdc-form"> + <div class="i-sdc-form-content-requirement-content"> + <div class="content-row"> + <div class="i-sdc-form-item"> + <sdc-input + label="{{ 'REQ_NAME' | translate}}" + required="true" + testId="reqName" + [disabled]="isReadonly" + [(value)]="requirementData.name" + (valueChange)="validityChanged()"> + </sdc-input> + </div> + </div> + + <div class="group-with-border"> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_RELATED_CAPABILITY' | translate }}" + testId="reqRelatedCapability" + required="true" + [disabled]="isReadonly" + [options]="capabilityTypesMappedList" + selectedOption="{{requirementData.capability}}" + [placeHolder] = "requirementData.capability" + (changed)="onCapabilityChanged($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_NODE' | translate }}" + testId="reqNode" + [disabled]="isReadonly" + [options]="nodeTypesMappedList" + selectedOption="{{requirementData.node}}" + [placeHolder] = "requirementData.node" + (changed)="onNodeChanged($event)"> + </sdc-dropdown> + </div> + <div class="content-row i-sdc-form-item"> + <sdc-dropdown + label="{{ 'REQ_RELATIONSHIP' | translate }}" + testId="reqRelationship" + [disabled]="isReadonly" + [options]="relationshipTypesMappedList" + selectedOption="{{requirementData.relationship}}" + [placeHolder] = "requirementData.relationship" + (changed)="onRelationshipChanged($event)"> + </sdc-dropdown> + </div> + </div> + + <label class="i-sdc-form-label occurrences-label"> {{ 'REQ_CAP_OCCURRENCES' | translate}} </label> + <div class="content-row occurrences-section"> + <div class="min-occurrences-value"> + <sdc-input + label="{{ 'REQ_CAP_OCCURRENCES_MIN' | translate}}" + testId="reqOccurrencesMin" + [disabled]="isReadonly" + [(value)]="requirementData.minOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + <div class="sdc-input"> + <label class="sdc-input__label"> {{ 'REQ_CAP_OCCURRENCES_MAX' | translate}} </label> + <div class="max-occurrences-value"> + <sdc-checkbox + class="checkbox-label unbounded-value" + testId="reqOccurrencesMaxUnbounded" + label="{{translatedUnboundTxt.toLowerCase()}}" + (checkedChange)="onUnboundedChanged()" + [checked]="isUnboundedChecked" + [disabled]="isReadonly"> + </sdc-checkbox> + <sdc-input + *ngIf="!isUnboundedChecked" + testId="reqOccurrencesMax" + [disabled]="isReadonly" + [(value)]="requirementData.maxOccurrences" + (valueChange)="validityChanged()" + type="number"> + </sdc-input> + </div> + + </div> + </div> + </div> + </form> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.less new file mode 100644 index 0000000000..6e50eb79f5 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.less @@ -0,0 +1,35 @@ +@import '../../../../../../../assets/styles/variables.less'; + +.requirement-editor { + .i-sdc-form-content-requirement-content { + padding: 10px 25px; + + .group-with-border { + margin: 25px 0; + padding: 15px 0; + border-top: 1px solid @tlv_color_u; + border-bottom: 1px solid @tlv_color_u; + .content-row:not(:last-of-type) { + padding-bottom: 13px; + } + } + + .occurrences-label { + font-family: @font-opensans-bold; + margin-bottom: 19px; + } + .occurrences-section, /deep/ .max-occurrences-value { + display: flex; + .min-occurrences-value { + padding-right: 30px; + } + .unbounded-value { + padding-top: 7px; + padding-right: 20px; + .sdc-checkbox__label { + text-transform: capitalize; + } + } + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.ts new file mode 100644 index 0000000000..2c5c96f3da --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.component.ts @@ -0,0 +1,90 @@ +import {Component} from '@angular/core'; +import {ServiceServiceNg2} from "app/ng2/services/component-services/service.service"; +import {Requirement, RelationshipTypeModel, NodeTypeModel, CapabilityTypeModel} from 'app/models'; +import {TranslateService} from 'app/ng2/shared/translator/translate.service'; +import {DropdownValue} from "app/ng2/components/ui/form-components/dropdown/ui-element-dropdown.component"; +import {Subject} from "rxjs"; + +@Component({ + selector: 'requirements-editor', + templateUrl: 'requirements-editor.component.html', + styleUrls: ['requirements-editor.component.less'], + providers: [ServiceServiceNg2, TranslateService] +}) + +export class RequirementsEditorComponent { + + input: { + requirement: Requirement, + relationshipTypesList: Array<RelationshipTypeModel>; + nodeTypesList: Array<NodeTypeModel>; + capabilityTypesList: Array<CapabilityTypeModel>; + isReadonly: boolean; + }; + requirementData: Requirement; + capabilityTypesMappedList: Array<DropdownValue>; + relationshipTypesMappedList: Array<DropdownValue>; + nodeTypesMappedList: Array<DropdownValue>; + isUnboundedChecked: boolean; + isReadonly: boolean; + translatedUnboundTxt: string; + + public onValidationChange: Subject<boolean> = new Subject(); + + constructor(private translateService: TranslateService) { + } + + ngOnInit() { + this.requirementData = new Requirement(this.input.requirement); + this.requirementData.minOccurrences = this.requirementData.minOccurrences || 0; + this.translatedUnboundTxt = ''; + this.capabilityTypesMappedList = _.map(this.input.capabilityTypesList, capType => new DropdownValue(capType.toscaPresentation.type, capType.toscaPresentation.type)); + this.relationshipTypesMappedList = _.map(this.input.relationshipTypesList, rType => new DropdownValue(rType.toscaPresentation.type, rType.toscaPresentation.type)); + this.nodeTypesMappedList = _.map(this.input.nodeTypesList, nodeType => { + return new DropdownValue( + nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName, + nodeType.componentMetadataDefinition.componentMetadataDataDefinition.toscaResourceName) + }); + this.translateService.languageChangedObservable.subscribe(lang => { + this.translatedUnboundTxt = this.translateService.translate('REQ_CAP_OCCURRENCES_UNBOUNDED'); + this.requirementData.maxOccurrences = this.requirementData.maxOccurrences || this.translatedUnboundTxt; + this.isUnboundedChecked = this.requirementData.maxOccurrences === this.translatedUnboundTxt; + }); + this.isReadonly = this.input.isReadonly; + this.validityChanged(); + } + + onUnboundedChanged() { + this.isUnboundedChecked = !this.isUnboundedChecked; + this.requirementData.maxOccurrences = this.isUnboundedChecked ? this.translatedUnboundTxt : null; + this.validityChanged(); + } + + onCapabilityChanged(selectedCapability: DropdownValue) { + this.requirementData.capability = selectedCapability && selectedCapability.value; + this.validityChanged(); + } + + onNodeChanged(selectedNode: DropdownValue) { + this.requirementData.node = selectedNode && selectedNode.value; + } + + onRelationshipChanged(selectedRelationship: DropdownValue) { + this.requirementData.relationship = selectedRelationship && selectedRelationship.value; + } + + checkFormValidForSubmit() { + return this.requirementData.name && this.requirementData.name.length && + this.requirementData.capability && this.requirementData.capability.length && !_.isEqual(this.requirementData.minOccurrences, "") && this.requirementData.minOccurrences >= 0 && + ( + this.isUnboundedChecked || + (this.requirementData.maxOccurrences && (this.requirementData.minOccurrences <= parseInt(this.requirementData.maxOccurrences))) + ); + } + + validityChanged = () => { + let validState = this.checkFormValidForSubmit(); + this.onValidationChange.next(validState); + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.module.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.module.ts new file mode 100644 index 0000000000..b1d8db54aa --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirementEditor/requirements-editor.module.ts @@ -0,0 +1,28 @@ +import {NgModule} from "@angular/core"; +import {CommonModule} from "@angular/common"; +import {RequirementsEditorComponent} from "./requirements-editor.component"; +import {FormsModule} from "@angular/forms"; +// import {FormElementsModule} from "../../../components/ui/form-components/form-elements.module"; +import {TranslateModule} from 'app/ng2/shared/translator/translate.module'; +import {SdcUiComponentsModule} from "onap-ui-angular/"; +import {FormElementsModule} from 'app/ng2/components/ui/form-components/form-elements.module'; +// import {SdcUiComponentsModule} from "sdc-ui/lib/angular/index"; + +@NgModule({ + declarations: [ + RequirementsEditorComponent + ], + imports: [CommonModule, + FormsModule, + FormElementsModule, + TranslateModule, + SdcUiComponentsModule + ], + exports: [], + entryComponents: [ + RequirementsEditorComponent + ], + providers: [] +}) +export class RequirementsEditorModule { +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirements.component.less b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirements.component.less new file mode 100644 index 0000000000..19f1c9b55a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirements.component.less @@ -0,0 +1,4 @@ +/deep/ .importedFromFile { + background-color: #f8f8f8; + color: #959595; + }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.html b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.html new file mode 100644 index 0000000000..7606ed189a --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.html @@ -0,0 +1,38 @@ +<div class="requirements-table"> + <ngx-datatable #capabilitiesTable + columnMode="flex" + [headerHeight]="40" + [rowHeight]="35" + [rowClass]="getRowClass" + [rows]="requirements"> + <ngx-datatable-column name="Name" [flexGrow]="1" [resizeable]="false" > + <ng-template ngx-datatable-cell-template let-row="row"> + <span [ngStyle]="{'cursor':row.isCreatedManually ? 'pointer' : 'null' }" data-tests-id="row.name" sdc-tooltip [tooltip-text]="row.name" [tooltip-placement]="3" (click)="editRequirement(row)">{{row.name}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Capability" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.capability" sdc-tooltip [tooltip-text]="row.capability" [tooltip-placement]="3">{{row.capability ? row.capability.substring("tosca.capabilities.".length) : ''}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Node" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.node" sdc-tooltip [tooltip-text]="row.node" [tooltip-placement]="3">{{row.node ? row.node.substring("tosca.nodes.".length) : ''}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Relationship" [flexGrow]="1" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.relationship" sdc-tooltip [tooltip-text]="row.relationship" [tooltip-placement]="3">{{row.relationship ? row.relationship.substring("tosca.relationships.".length): ''}}</span> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column name="Connected To" [flexGrow]="1" [resizeable]="false"> + </ngx-datatable-column> + <ngx-datatable-column name="Occurrences" [flexGrow]="1" [prop]="'minOccurrences'" [resizeable]="false"> + <ng-template ngx-datatable-cell-template let-row="row"> + <span data-tests-id="row.minOccurrences+','+row.maxOccurrences" sdc-tooltip + [tooltip-text]="row.minOccurrences+','+row.maxOccurrences" [tooltip-placement]="3"> + {{row.minOccurrences}},{{row.maxOccurrences}}</span> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.ts b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.ts new file mode 100644 index 0000000000..b65489ce4e --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/req-and-capabilities/requirements/requirments.components.ts @@ -0,0 +1,103 @@ +import {Input, Component, OnInit} from "@angular/core"; +import {Requirement, RequirementUI} from "../../../../../models/requirement"; +import {RequirementsEditorComponent} from "./requirementEditor/requirements-editor.component"; +import {WorkspaceService} from "../../workspace.service"; +import {TopologyTemplateService} from "../../../../services/component-services/topology-template.service"; +import {ReqAndCapabilitiesService} from "../req-and-capabilities.service"; +import {EventListenerService} from "../../../../../services/event-listener-service"; +import {ModalComponent} from "onap-ui-angular/dist/modals/modal.component"; +import {SdcUiServices} from "onap-ui-angular"; +import sortedIndexBy = require("lodash/sortedIndexBy"); + +@Component({ + selector: 'requirments', + templateUrl: './requirments.components.html', + styleUrls: ['../../../../../../assets/styles/table-style.less', './requirements.component.less'] +}) + + + +export class RequirmentsComponent implements OnInit { + @Input() public requirements: Array<RequirementUI>; + private customModalInstance: ModalComponent; + + constructor( + private workspaceService: WorkspaceService, + private loaderService: SdcUiServices.LoaderService, + private topologyTemplateService: TopologyTemplateService, + private reqAndCapabilitiesService : ReqAndCapabilitiesService, + private modalService: SdcUiServices.ModalService, + private eventListenerService: EventListenerService) { + } + + + ngOnInit(): void { + let isCreatedManually: RequirementUI[] = []; + let isImportedFromFile: RequirementUI[] = []; + + isCreatedManually = this.requirements.filter((requirement) => requirement.isCreatedManually); + isImportedFromFile = this.requirements.filter((requirement) => !requirement.isCreatedManually); + + this.requirements = []; + + isCreatedManually.map((requirement) => this.requirements.push(requirement)); + isImportedFromFile.map((requirement) => this.requirements.push(requirement)); + + } + + + + editRequirement(req) { + + let modalConfig = { + size: 'md', + title: 'Update Requirement', + type: 'custom', + buttons: [ + { + id: 'saveButton', + text: ('Update'), + size: "'x-small'", + callback: () => this.updateRequirement(), + closeModal: true + }, + {text: "Cancel", size: "'x-small'", closeModal: true}] + }; + let modalInputs = { + requirement: req, + relationshipTypesList: this.reqAndCapabilitiesService.getRelationsShipeTypeList(), + nodeTypesList: this.reqAndCapabilitiesService.getNodeTypesList(), + capabilityTypesList: this.reqAndCapabilitiesService.getCapabilityTypesList(), + // isReadonly: this.$scope.isViewMode() || !this.$scope.isDesigner(), + }; + + this.customModalInstance = this.modalService.openCustomModal(modalConfig, RequirementsEditorComponent, {input: modalInputs}); + this.customModalInstance.innerModalContent.instance. + onValidationChange.subscribe((isValid) => this.customModalInstance.getButtonById('saveButton').disabled = !isValid); + + } + + private updateRequirement() { + const requirement = this.customModalInstance.innerModalContent.instance.requirementData; + this.loaderService.activate(); + if (requirement.uniqueId) { + this.topologyTemplateService.updateRequirement(this.workspaceService.metadata.getTypeUrl(), this.workspaceService.metadata.uniqueId, requirement).subscribe(result => { + let index = this.requirements.findIndex(req => result[0].uniqueId === req.uniqueId); + this.requirements[index] = new RequirementUI(result[0], this.workspaceService.metadata.uniqueId); + this.eventListenerService.notifyObservers('REQUIREMENTS_UPDATED'); + this.loaderService.deactivate(); + }, () => { + this.loaderService.deactivate(); + }); + } + } + + getRowClass(row) { + if (!row.isCreatedManually) { + return { + 'importedFromFile': true + }; + } + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/__snapshots__/tosca-artifact-page.spec.ts.snap b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/__snapshots__/tosca-artifact-page.spec.ts.snap new file mode 100644 index 0000000000..14146d51d2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/__snapshots__/tosca-artifact-page.spec.ts.snap @@ -0,0 +1,35 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`tosca artifacts page should match current snapshot of tosca artifact pages component 1`] = ` +<tosca-artifact-page + serviceLoader={[Function Object]} + store={[Function Store]} + table={[Function DatatableComponent]} + workspaceService={[Function Object]} +> + <div + class="tosca-artifact-page" + > + <ngx-datatable + class="ngx-datatable" + columnmode="flex" + > + <div + visibilityobserver="" + > + + <datatable-body + class="datatable-body" + > + <datatable-selection> + + + + </datatable-selection> + </datatable-body> + + </div> + </ngx-datatable> + </div> +</tosca-artifact-page> +`; diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.html b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.html new file mode 100644 index 0000000000..fece92ee37 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.html @@ -0,0 +1,50 @@ +<div class="tosca-artifact-page"> + <ngx-datatable + columnMode="flex" + [headerHeight]="40" + [rowHeight]="35" + [reorderable]="false" + [swapColumns]="false" + [rows]="toscaArtifacts$ | async" + [sorts]="[{prop: 'artifactDisplayName', dir: 'desc'}]" + #toscaArtifactsTable + (activate)="onActivate($event)"> + <ngx-datatable-row-detail [rowHeight]="80"> + <ng-template let-row="row" let-expanded="expanded" ngx-datatable-row-detail-template> + <div>Label: {{row.artifactLabel}}</div> + <div>UUID: {{row.artifactUUID}}</div> + <div>Description: {{row.description}}</div> + </ng-template> + </ngx-datatable-row-detail> + <ngx-datatable-column [resizeable]="false" name="Name" [flexGrow]="3" + [prop]="'artifactDisplayName'"> + <ng-template ngx-datatable-cell-template let-row="row" let-expanded="expanded"> + <div class="expand-collapse-cell"> + <svg-icon [clickable]="true" class="expand-collapse-icon" + [name]="expanded ? 'caret1-up-o': 'caret1-down-o'" [mode]="'primary'" + [size]="'medium'"></svg-icon> + <span>{{row.artifactDisplayName }}</span> + </div> + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false"name="Type" [flexGrow]="3"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{row.artifactType}} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false" name="Version" [flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + {{ row.artifactVersion }} + </ng-template> + </ngx-datatable-column> + <ngx-datatable-column [resizeable]="false"[flexGrow]="1"> + <ng-template ngx-datatable-cell-template let-row="row"> + <div class="download-artifact-button"> + <download-artifact [artifact]="row" [componentId]="componentId" + [componentType]="componentType" + testId="download_{{row.artifactDisplayName}}"></download-artifact> + </div> + </ng-template> + </ngx-datatable-column> + </ngx-datatable> +</div>
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.less b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.less new file mode 100644 index 0000000000..9c5dd47585 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.less @@ -0,0 +1,7 @@ +.tosca-artifact-page { + .download-artifact-button { + text-align: center; + padding-top: 4px; + + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.ts b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.ts new file mode 100644 index 0000000000..e74e5db668 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.component.ts @@ -0,0 +1,46 @@ +import {Component, OnInit, ViewChild} from "@angular/core"; +import {WorkspaceService} from "../workspace.service"; +import {SdcUiServices} from "onap-ui-angular"; +import {ArtifactModel} from "../../../../models"; +import {Select, Store} from "@ngxs/store"; +import {WorkspaceState} from "../../../store/states/workspace.state"; +import * as _ from "lodash"; +import {ArtifactGroupType, COMPONENT_FIELDS} from "../../../../utils"; +import {GetArtifactsByTypeAction} from "../../../store/actions/artifacts.action"; +import {Observable} from "rxjs/index"; +import {ArtifactsState} from "../../../store/states/artifacts.state"; +import {ArtifactType} from "../../../../utils/constants"; +import {map} from "rxjs/operators"; + +@Component({ + selector: 'tosca-artifact-page', + + templateUrl: './tosca-artifact-page.component.html', + styleUrls: ['./tosca-artifact-page.component.less', '../../../../../assets/styles/table-style.less'] +}) +export class ToscaArtifactPageComponent implements OnInit { + + @Select(WorkspaceState.isViewOnly) isViewOnly$: boolean; + @ViewChild('toscaArtifactsTable') table: any; + public toscaArtifacts$: Observable<ArtifactModel[]>; + public componentId: string; + public componentType:string; + + constructor(private serviceLoader: SdcUiServices.LoaderService, private workspaceService: WorkspaceService, private store: Store) { + } + + + ngOnInit(): void { + this.componentId = this.workspaceService.metadata.uniqueId; + this.componentType = this.workspaceService.metadata.componentType; + + this.store.dispatch(new GetArtifactsByTypeAction({componentType:this.componentType, componentId:this.componentId, artifactType:ArtifactGroupType.TOSCA})); + this.toscaArtifacts$ = this.store.select(ArtifactsState.getArtifactsByType).pipe(map(filterFn => filterFn(ArtifactGroupType.TOSCA))); + } + + onActivate(event) { + if(event.type === 'click'){ + this.table.rowDetail.toggleExpandRow(event.row); + } + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.module.ts b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.module.ts new file mode 100644 index 0000000000..00c7b0b371 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.module.ts @@ -0,0 +1,28 @@ +import {CommonModule} from "@angular/common"; +import {NgModule} from "@angular/core"; +import {SdcUiComponentsModule} from "onap-ui-angular"; +import {GlobalPipesModule} from "../../../pipes/global-pipes.module"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {ToscaArtifactPageComponent} from "./tosca-artifact-page.component"; +import {UiElementsModule} from "../../../components/ui/ui-elements.module"; + +@NgModule({ + declarations: [ + ToscaArtifactPageComponent + ], + imports: [ + CommonModule, + SdcUiComponentsModule, + NgxDatatableModule, + UiElementsModule + ], + exports: [ + ToscaArtifactPageComponent + ], + entryComponents: [ + ToscaArtifactPageComponent + ], + +}) +export class ToscaArtifactPageModule { +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.spec.ts b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.spec.ts new file mode 100644 index 0000000000..af3558e15b --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/tosca-artifacts/tosca-artifact-page.spec.ts @@ -0,0 +1,71 @@ +import {async, ComponentFixture, TestBed} from "@angular/core/testing"; +import {NO_ERRORS_SCHEMA} from "@angular/core"; +import {ToscaArtifactPageComponent} from "./tosca-artifact-page.component"; +import {ConfigureFn, configureTests} from "../../../../../jest/test-config.helper"; +import {NgxDatatableModule} from "@swimlane/ngx-datatable"; +import {WorkspaceService} from "../workspace.service"; +import {SdcUiServices} from "onap-ui-angular"; +import {TopologyTemplateService} from "../../../services/component-services/topology-template.service"; +import {Observable} from "rxjs/Observable"; +import {ComponentMetadata} from "../../../../models/component-metadata"; +import 'rxjs/add/observable/of'; +import {NgxsModule, Store} from "@ngxs/store"; +import {ArtifactsState} from "../../../store/states/artifacts.state"; +import {toscaArtifactMock} from "../../../../../jest/mocks/artifacts-mock"; + +describe('tosca artifacts page', () => { + + let fixture: ComponentFixture<ToscaArtifactPageComponent>; + let topologyTemplateServiceMock: Partial<TopologyTemplateService>; + let workspaceServiceMock: Partial<WorkspaceService>; + let loaderServiceMock: Partial<SdcUiServices.LoaderService>; + let store: Store; + + + beforeEach( + async(() => { + + topologyTemplateServiceMock = { + getArtifactsByType: jest.fn().mockImplementation((componentType, id, artifactType) => Observable.of(toscaArtifactMock)) + }; + workspaceServiceMock = {metadata: <ComponentMetadata>{uniqueId: 'service_unique_id', componentType: 'SERVICE'}} + + loaderServiceMock = { + activate : jest.fn(), + deactivate: jest.fn() + } + const configure: ConfigureFn = testBed => { + testBed.configureTestingModule({ + declarations: [ToscaArtifactPageComponent], + imports: [NgxDatatableModule, NgxsModule.forRoot([ArtifactsState])], + schemas: [NO_ERRORS_SCHEMA], + providers: [ + {provide: WorkspaceService, useValue: workspaceServiceMock}, + {provide: TopologyTemplateService, useValue: topologyTemplateServiceMock}, + {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock } + ], + }); + }; + + configureTests(configure).then(testBed => { + fixture = testBed.createComponent(ToscaArtifactPageComponent); + store = testBed.get(Store); + }); + }) + ); + + it('should match current snapshot of tosca artifact pages component', () => { + expect(fixture).toMatchSnapshot(); + }); + + it('should see exactly 2 tosca artifacts', () => { + fixture.componentInstance.ngOnInit(); + fixture.componentInstance.toscaArtifacts$.subscribe((artifacts)=> { + expect(artifacts.length).toEqual(2); + }) + store.selectOnce(state => state.artifacts.toscaArtifacts).subscribe(artifacts => { + expect(artifacts.length).toEqual(9); + }); + }) + +});
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.module.ts b/catalog-ui/src/app/ng2/pages/workspace/workspace-ng1-bridge-service.ts index 96af247086..3d93b459a2 100644 --- a/catalog-ui/src/app/ng2/components/logic/service-path/service-path.module.ts +++ b/catalog-ui/src/app/ng2/pages/workspace/workspace-ng1-bridge-service.ts @@ -1,3 +1,6 @@ +/** + * Created by ob0695 on 6/24/2018. + */ /*- * ============LICENSE_START======================================================= * SDC @@ -17,23 +20,18 @@ * limitations under the License. * ============LICENSE_END========================================================= */ +import {Store} from "@ngxs/store"; +import {Injectable} from "@angular/core"; +import {UpdateIsViewOnly} from "../../store/actions/workspace.action"; + +@Injectable() +export class WorkspaceNg1BridgeService { + + constructor(private store: Store) { + }; -import { NgModule } from "@angular/core"; -import {CommonModule} from "@angular/common"; -import {ServicePathComponent} from "./service-path.component"; -import {UiElementsModule} from "app/ng2/components/ui/ui-elements.module"; + public updateIsViewOnly = (isViewOnly: boolean):void => { + this.store.dispatch(new UpdateIsViewOnly(isViewOnly)); + } -@NgModule({ - declarations: [ - ServicePathComponent - ], - imports: [CommonModule, - UiElementsModule], - exports: [], - entryComponents: [ - ServicePathComponent - ], - providers: [] -}) -export class ServicePathModule { -}
\ No newline at end of file +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/workspace.component.ts b/catalog-ui/src/app/ng2/pages/workspace/workspace.component.ts new file mode 100644 index 0000000000..a209406a53 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/workspace.component.ts @@ -0,0 +1,3 @@ +/** + * Created by ob0695 on 6/11/2018. + */ diff --git a/catalog-ui/src/app/ng2/pages/workspace/workspace.module.ts b/catalog-ui/src/app/ng2/pages/workspace/workspace.module.ts new file mode 100644 index 0000000000..cb646379d2 --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/workspace.module.ts @@ -0,0 +1,50 @@ +/** + * Created by ob0695 on 6/4/2018. + */ +/** + * Created by ob0695 on 6/4/2018. + */ +import {NgModule} from "@angular/core"; +import {CompositionPageModule} from "../composition/composition-page.module"; + +import {NgxsModule} from "@ngxs/store"; +import {TopologyTemplateService} from "../../services/component-services/topology-template.service"; +import {WorkspaceState} from "../../store/states/workspace.state"; +import {WorkspaceService} from "./workspace.service"; +import {DeploymentPageModule} from "./deployment/deployment-page.module"; +import {ToscaArtifactPageModule} from "./tosca-artifacts/tosca-artifact-page.module"; +import {InformationArtifactPageModule} from "./information-artifact/information-artifact-page.module"; +import { reqAndCapabilitiesModule } from "./req-and-capabilities/req-and-capabilities.module"; +import {AttributesModule} from "./attributes/attributes.module"; +import {ArtifactsState} from "../../store/states/artifacts.state"; +import {InstanceArtifactsState} from "../../store/states/instance-artifacts.state"; +import {DeploymentArtifactsPageModule} from "./deployment-artifacts/deployment-artifacts-page.module"; +import { DistributionModule } from './disribution/distribution.module'; +import { ActivityLogModule } from './activity-log/activity-log.module'; + +@NgModule({ + declarations: [], + imports: [ + DeploymentPageModule, + CompositionPageModule, + AttributesModule, + reqAndCapabilitiesModule, + ToscaArtifactPageModule, + DeploymentArtifactsPageModule, + InformationArtifactPageModule, + DistributionModule, + ActivityLogModule, + NgxsModule.forFeature([WorkspaceState, ArtifactsState, InstanceArtifactsState]) + ], + + exports: [], + entryComponents: [], + providers: [TopologyTemplateService, WorkspaceService] +}) + +export class WorkspaceModule { + + constructor() { + + } +} diff --git a/catalog-ui/src/app/ng2/pages/workspace/workspace.service.ts b/catalog-ui/src/app/ng2/pages/workspace/workspace.service.ts new file mode 100644 index 0000000000..9f985016ec --- /dev/null +++ b/catalog-ui/src/app/ng2/pages/workspace/workspace.service.ts @@ -0,0 +1,70 @@ +/** + * Created by ob0695 on 6/5/2018. + */ +/*- + * ============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========================================================= + */ + +/** + * Created by rc2122 on 5/23/2017. + */ +import { Injectable } from '@angular/core'; +import {WorkspaceMode, ComponentState, Role} from "../../../utils/constants"; +import {Component as TopologyTemplate, ComponentMetadata} from "app/models"; +import {CacheService} from "../../services/cache.service"; +import {IComponentMetadata} from "../../../models/component-metadata"; +import {ComponentType} from "../../../utils"; + +@Injectable() +export class WorkspaceService { + + public metadata:ComponentMetadata; + + constructor(private cacheService:CacheService) { + + } + + public setComponentMetadata = (metadata: ComponentMetadata) => { + this.metadata = metadata; + } + + public getMetadataType(): string { + switch (this.metadata.componentType) { + case ComponentType.SERVICE: + return ComponentType.SERVICE; + default: + return this.metadata.resourceType; + } + } + + public getComponentMode = (component:TopologyTemplate):WorkspaceMode => {//return if is edit or view for resource or service + let mode = WorkspaceMode.VIEW; + + let user = this.cacheService.get("user"); + if (component.lifecycleState === ComponentState.NOT_CERTIFIED_CHECKOUT && + component.lastUpdaterUserId === user.userId) { + if ((component.isService() || component.isResource()) && user.role == Role.DESIGNER) { + mode = WorkspaceMode.EDIT; + } + } + return mode; + } +} + +
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/pipes/entity-filter.pipe.ts b/catalog-ui/src/app/ng2/pipes/entity-filter.pipe.ts new file mode 100644 index 0000000000..af107ed006 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/entity-filter.pipe.ts @@ -0,0 +1,151 @@ +/*- + * ============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 {Pipe, PipeTransform} from "@angular/core"; +import {Component, Resource} from "app/models"; +import {ComponentType} from "app/utils/constants"; + +export interface ISearchFilter { + [key:string]: string; +} + +export interface IEntityFilterObject { + // Types + selectedComponentTypes?:Array<string>; + selectedResourceSubTypes?:Array<string>; + // Categories + selectedCategoriesModel?:Array<string>; + // Statuses + selectedStatuses?:Array<string>; + // distributed + distributed?:Array<string>; + // search + search?:ISearchFilter; + +} + +@Pipe({name: 'entity-filter'}) +export class EntityFilterPipe implements PipeTransform{ + constructor() { + } + + public static transform(components:Array<Component>, filter:IEntityFilterObject) { + let filteredComponents:Array<Component> = components; + + // filter by type + // -------------------------------------------------------------------------- + if ((filter.selectedComponentTypes && filter.selectedComponentTypes.length > 0) || (filter.selectedResourceSubTypes && filter.selectedResourceSubTypes.length > 0)) { + let filteredTypes = []; + angular.forEach(components, (component:Component):void => { + // Filter by component type + let typeLower:string = component.componentType.toLowerCase(); + let typeFirstCapital:string = typeLower.charAt(0).toUpperCase() + typeLower.slice(1); + if (filter.selectedComponentTypes.indexOf(typeFirstCapital) !== -1) { + filteredTypes.push(component); + } + + // Filter by resource sub type, only in case the resource checkbox was not selected (because in this case we already added all the components in above section). + if (component.isResource() && filter.selectedComponentTypes.indexOf("Resource") === -1 && filter.selectedResourceSubTypes.length > 0) { + //filteredComponents.pop(); // Remove the last inserted component. + let resource:Resource = <Resource>component; + if (filter.selectedResourceSubTypes.indexOf(resource.getComponentSubType()) !== -1) { + filteredTypes.push(component); + } + } + }); + filteredComponents = filteredTypes; + } + + // filter by categories & subcategories & groupings + // -------------------------------------------------------------------------- + if (filter.selectedCategoriesModel && filter.selectedCategoriesModel.length > 0) { + let filteredCategories = []; + angular.forEach(filteredComponents, (component:Component):void => { + let componentCategory = component.categoryNormalizedName + + ((component.subCategoryNormalizedName) ? '.' + component.subCategoryNormalizedName : ''); + if (component.componentType === ComponentType.RESOURCE) { + componentCategory = 'resourceNewCategory.' + componentCategory; + } else if (component.componentType === ComponentType.SERVICE) { + componentCategory = 'serviceNewCategory.' + componentCategory; + } + if (filter.selectedCategoriesModel.indexOf(componentCategory) !== -1) { + filteredCategories.push(component); + } + }); + filteredComponents = filteredCategories; + } + + // filter by statuses + // -------------------------------------------------------------------------- + if (filter.selectedStatuses && filter.selectedStatuses.length > 0) { + + let filteredStatuses = []; + angular.forEach(filteredComponents, (component:Component):void => { + if (filter.selectedStatuses.indexOf(component.lifecycleState) > -1) { + filteredStatuses.push(component); + } + //if status DISTRIBUTED && CERTIFIED are selected the component will added in CERTIFIED status , not need to add twice + if (filter.selectedStatuses.indexOf('DISTRIBUTED') > -1 && !(filter.selectedStatuses.indexOf('CERTIFIED') > -1)) { + if (component.distributionStatus && component.distributionStatus.indexOf('DISTRIBUTED') > -1 && component.lifecycleState.indexOf('CERTIFIED') > -1) { + filteredStatuses.push(component); + } + } + }); + filteredComponents = filteredStatuses; + } + + // filter by statuses and distributed + // -------------------------------------------------------------------------- + if (filter.distributed != undefined && filter.distributed.length > 0) { + let filterDistributed:Array<any> = filter.distributed; + let filteredDistributed = []; + angular.forEach(filteredComponents, (entity) => { + filterDistributed.forEach((distribute) => { + let distributeItem = distribute.split(','); + distributeItem.forEach((item) => { + if (item !== undefined && entity.distributionStatus === item) { + filteredDistributed.push(entity); + } + }) + }); + }); + filteredComponents = filteredDistributed; + } + + // filter by search + // -------------------------------------------------------------------------- + if (filter.search != undefined) { + Object.keys(filter.search).forEach((searchKey) => { + let searchVal = filter.search[searchKey]; + if (searchVal) { + searchVal = searchVal.toLowerCase(); + filteredComponents = filteredComponents.filter((component:Component) => + component[searchKey].toLowerCase().indexOf(searchVal) !== -1); + } + }); + } + + return filteredComponents; + } + + public transform(components:Array<Component>, filter:IEntityFilterObject) { + return EntityFilterPipe.transform(components, filter); + } +} diff --git a/catalog-ui/src/app/ng2/pipes/global-pipes.module.ts b/catalog-ui/src/app/ng2/pipes/global-pipes.module.ts index c44d71b30d..66f9518a47 100644 --- a/catalog-ui/src/app/ng2/pipes/global-pipes.module.ts +++ b/catalog-ui/src/app/ng2/pipes/global-pipes.module.ts @@ -26,7 +26,10 @@ import {GroupByPipe} from "./groupBy.pipe"; import {ResourceNamePipe} from "./resource-name.pipe"; import {NgModule} from "@angular/core"; import {SafeUrlSanitizerPipe} from "./safeUrlSanitizer.pipe"; -import {OrderByPipe} from "./orderBy.pipe"; +import {EntityFilterPipe} from "./entity-filter.pipe"; +import {KeyValuePipe} from "./key-value.pipe"; +import {PropertiesOrderByPipe} from "./properties-order-by.pipe"; +import {OrderByPipe} from "./order-by.pipe"; @NgModule({ declarations: [ @@ -36,6 +39,9 @@ import {OrderByPipe} from "./orderBy.pipe"; SafeUrlSanitizerPipe, SearchFilterPipe, ResourceNamePipe, + EntityFilterPipe, + KeyValuePipe, + PropertiesOrderByPipe, OrderByPipe ], exports: [ @@ -45,7 +51,20 @@ import {OrderByPipe} from "./orderBy.pipe"; SafeUrlSanitizerPipe, SearchFilterPipe, ResourceNamePipe, - OrderByPipe + EntityFilterPipe, + PropertiesOrderByPipe, + OrderByPipe, + KeyValuePipe + ], + providers: [ + ContentAfterLastDotPipe, + GroupByPipe, + KeysPipe, + SafeUrlSanitizerPipe, + SearchFilterPipe, + ResourceNamePipe, + EntityFilterPipe, + KeyValuePipe ] }) diff --git a/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts index 90dce23352..19811f2f08 100644 --- a/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts +++ b/catalog-ui/src/app/ng2/pipes/groupBy.pipe.ts @@ -26,6 +26,9 @@ import {Pipe, PipeTransform} from '@angular/core'; @Pipe({name: 'groupBy'}) export class GroupByPipe implements PipeTransform { transform(value: Array<any>, field: string): Array<any> { + if(!value) { + return null; + } const groupedObj = value.reduce((prev, cur)=> { if(!prev[cur[field]]) { prev[cur[field]] = [cur]; diff --git a/catalog-ui/src/app/ng2/pipes/key-value.pipe.ts b/catalog-ui/src/app/ng2/pipes/key-value.pipe.ts new file mode 100644 index 0000000000..4adb0b12f7 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/key-value.pipe.ts @@ -0,0 +1,41 @@ +/** + * Created by ob0695 on 7/3/2018. + */ +/*- + * ============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========================================================= + */ + +/** + * Created by rc2122 on 5/17/2017. + */ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({name: 'keyValue'}) +export class KeyValuePipe implements PipeTransform { + transform(value, field: string): Array<any> { + if(!value) { + return null; + } + let keyValueObject = []; + for (let key in value) { + keyValueObject.push({key:key, value: value[key]}); + } + return keyValueObject; + } +} diff --git a/catalog-ui/src/app/ng2/pipes/keys.pipe.ts b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts index 349e9334f7..f8663aca2e 100644 --- a/catalog-ui/src/app/ng2/pipes/keys.pipe.ts +++ b/catalog-ui/src/app/ng2/pipes/keys.pipe.ts @@ -23,6 +23,9 @@ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'keys'}) export class KeysPipe implements PipeTransform { transform(value, args:string[]) : any { + if(!value) { + return null; + } let keys = []; for (let key in value) { keys.push(key); diff --git a/catalog-ui/src/app/ng2/pipes/orderBy.pipe.ts b/catalog-ui/src/app/ng2/pipes/order-by.pipe.ts index 4edbd60f60..6dc5d47863 100644 --- a/catalog-ui/src/app/ng2/pipes/orderBy.pipe.ts +++ b/catalog-ui/src/app/ng2/pipes/order-by.pipe.ts @@ -20,10 +20,10 @@ import { Pipe, PipeTransform } from '@angular/core'; -@Pipe({ name: 'orderBy' }) +@Pipe({name: 'orderBy'}) export class OrderByPipe implements PipeTransform { transform(records: Array<any>, args?: any): any { - if (!records || !args.path) return records; + if (!records || !args.path) { return records; } let len = args.path.length; return records.sort((itemIdx1, itemIdx2) => { var i = 0; diff --git a/catalog-ui/src/app/ng2/pipes/properties-order-by.pipe.ts b/catalog-ui/src/app/ng2/pipes/properties-order-by.pipe.ts new file mode 100644 index 0000000000..98debd28d9 --- /dev/null +++ b/catalog-ui/src/app/ng2/pipes/properties-order-by.pipe.ts @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * SDC + * ================================================================================ + * Copyright (C) 2017 Huawei 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 { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ name: 'propertiesOrderBy' }) +export class PropertiesOrderByPipe implements PipeTransform { + transform(records: Array<any>, args?: any): any { + if (!records || !args.path) return records; + let len = args.path.length; + return records.sort((itemIdx1, itemIdx2) => { + var i = 0; + while (i < len) { itemIdx1 = itemIdx1[args.path[i]]; itemIdx2 = itemIdx2[args.path[i]]; i++; } + // Order * (-1): We change our order + return itemIdx1 + "" > itemIdx2 + "" ? args.direction : args.direction * (- 1); + }) + }; +} diff --git a/catalog-ui/src/app/ng2/pipes/resource-name.pipe.ts b/catalog-ui/src/app/ng2/pipes/resource-name.pipe.ts index fdf9526375..0fdbbcaade 100644 --- a/catalog-ui/src/app/ng2/pipes/resource-name.pipe.ts +++ b/catalog-ui/src/app/ng2/pipes/resource-name.pipe.ts @@ -20,15 +20,20 @@ import { Pipe, PipeTransform } from '@angular/core'; +import * as _ from 'lodash'; @Pipe({name: 'resourceName'}) export class ResourceNamePipe implements PipeTransform { + + public static getDisplayName (value:string): string { + const newName:string = + _.last(value.split(/tosca\.nodes\..*network\..*relationships\..*org\.openecomp\..*resource\.nfv\..*nodes\.module\..*cp\..*vl\./)); + return (newName) ? newName : value; + } + transform(value) : any { if (value) { - //newName = _.last(newName.split('.')); - const newName:string = - _.last(value.split(/tosca\.nodes\..*network\..*relationships\..*org\.openecomp\..*resource\.nfv\..*nodes\.module\..*cp\..*vl\./)); - return (newName) ? newName : value; + return ResourceNamePipe.getDisplayName(value); } } } diff --git a/catalog-ui/src/app/ng2/services/activity-log.service.ts b/catalog-ui/src/app/ng2/services/activity-log.service.ts new file mode 100644 index 0000000000..deabbee0bd --- /dev/null +++ b/catalog-ui/src/app/ng2/services/activity-log.service.ts @@ -0,0 +1,48 @@ +/*- + * ============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 { HttpClient } from '@angular/common/http'; +import { Inject, Injectable} from '@angular/core'; +import { Observable } from 'rxjs/Observable'; +import { Activity } from '../../models/activity'; +import { ServerTypeUrl } from '../../utils/constants'; +import { ISdcConfig, SdcConfigToken } from '../config/sdc-config.config'; +import { HttpHelperService } from './http-hepler.service'; + +@Injectable() +export class ActivityLogService { + url: string; + + constructor(private httpClient: HttpClient, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { + this.url = this.sdcConfig.api.root + this.sdcConfig.api.GET_activity_log; + } + + public getActivityLog(componentType: string, uid: string): Observable<Activity[]> { + + // Compose URL: audit-records/services_or_resources/uid + const url = HttpHelperService.replaceUrlParams(this.url, { + type: ServerTypeUrl.toServerTypeUrl(componentType), + id: uid + }); + + return this.httpClient.get<Activity[]>(url); + } + +} diff --git a/catalog-ui/src/app/ng2/services/archive.service.ts b/catalog-ui/src/app/ng2/services/archive.service.ts deleted file mode 100644 index 83f1c502c2..0000000000 --- a/catalog-ui/src/app/ng2/services/archive.service.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 { Injectable, Inject } from "@angular/core"; -import { Observable } from "rxjs/Observable"; -import { HttpService } from "./http.service"; -import { SdcConfigToken, ISdcConfig } from "../config/sdc-config.config"; -import { Component } from "../../models"; -import { ComponentFactory } from 'app/utils/component-factory'; - - -@Injectable() -export class ArchiveService { - protected baseUrl; - - constructor(private http: HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig, private componentFactory:ComponentFactory/*, @Inject(ComponentFactory) componentFactory */) { - this.baseUrl = sdcConfig.api.root ; - } - - public getArchiveCatalog() { - let archiveCatalogItems:Component[] = []; - let archiveCatalogResourceItems:Component[] = []; - let archiveCatalogServiceItems:Component[] = []; - - return this.http.get(this.baseUrl + '/v1/catalog/archive/', {}).map(res => { - let archiveCatalogObject = res.json(); - if (archiveCatalogObject.resources) archiveCatalogResourceItems = this.getResourceItems(archiveCatalogObject.resources); - if (archiveCatalogObject.services) archiveCatalogServiceItems = this.getServiceItems(archiveCatalogObject.services); - archiveCatalogItems = [].concat(archiveCatalogResourceItems, archiveCatalogServiceItems); - - return archiveCatalogItems; - }); - } - - - private getResourceItems(resources){ - let resourceItems = resources.map((resource)=>{ - return this.componentFactory.createResource(resource) - }) - return resourceItems; - } - - private getServiceItems(services){ - let serviceItems = services.map((service)=>{ - return this.componentFactory.createService(service) - }) - return serviceItems; - } - -}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/authentication.service.ts b/catalog-ui/src/app/ng2/services/authentication.service.ts index 1c6502dd0d..52ca643833 100644 --- a/catalog-ui/src/app/ng2/services/authentication.service.ts +++ b/catalog-ui/src/app/ng2/services/authentication.service.ts @@ -20,38 +20,44 @@ import {Injectable, Inject} from '@angular/core'; import {IAppConfigurtaion, ICookie} from "../../models/app-config"; -import {Response, Headers, RequestOptions, Http} from '@angular/http'; import {Cookie2Service} from "./cookie.service"; import { Observable } from 'rxjs/Observable'; import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; +import { IUserProperties } from "app/models"; +import { CacheService } from "app/ng2/services/cache.service"; +import { HttpClient, HttpHeaders } from '@angular/common/http'; @Injectable() export class AuthenticationService { - constructor(private cookieService:Cookie2Service, private http: Http, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { + private _loggedinUser:IUserProperties; + + constructor(private cookieService:Cookie2Service, private http: HttpClient, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig, private cacheService: CacheService) { this.cookieService = cookieService; - this.http = http; } - private getAuthHeaders():any { + private getAuthHeaders():HttpHeaders { let cookie:ICookie = this.sdcConfig.cookie; - let authHeaders:any = {}; - authHeaders[cookie.userFirstName] = this.cookieService.getFirstName(); - authHeaders[cookie.userLastName] = this.cookieService.getLastName(); - authHeaders[cookie.userEmail] = this.cookieService.getEmail(); - authHeaders[cookie.userIdSuffix] = this.cookieService.getUserId(); + let authHeaders: HttpHeaders = new HttpHeaders(); + authHeaders = authHeaders.set(cookie.userFirstName, this.cookieService.getFirstName()) + .set(cookie.userLastName, this.cookieService.getLastName()) + .set(cookie.userEmail, this.cookieService.getEmail()) + .set(cookie.userIdSuffix, this.cookieService.getUserId()) return authHeaders; } - public authenticate(): Observable<JSON> { - let options = new RequestOptions({ - headers: new Headers(this.getAuthHeaders()) - }); - + public authenticate(): Observable<IUserProperties> { let authUrl = this.sdcConfig.api.root + this.sdcConfig.api.GET_user_authorize; - return this.http - .get(authUrl, options) - .map((res: Response) => res.json()); + return this.http.get<IUserProperties>(authUrl, {headers: this.getAuthHeaders()}); + } + + public getLoggedinUser():IUserProperties { + return this._loggedinUser; } + public setLoggedinUser(loggedinUser:IUserProperties) { + this._loggedinUser = loggedinUser; + this.cacheService.set('user', loggedinUser); + }; + } diff --git a/catalog-ui/src/app/ng2/services/cache.service.ts b/catalog-ui/src/app/ng2/services/cache.service.ts new file mode 100644 index 0000000000..e876ec1098 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/cache.service.ts @@ -0,0 +1,49 @@ +/*- + * ============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 {Injectable} from "@angular/core"; +import {Dictionary} from "app/utils"; + +@Injectable() +export class CacheService { + private storage:Dictionary<string, any>; + + constructor() { + this.storage = new Dictionary<string, any>(); + }; + + public get(key:string): any { + return this.storage.getValue(key); + } + + public set(key:string, value:any): void { + this.storage.setValue(key, value); + } + + public remove(key:string): void { + if (this.storage.containsKey(key)) { + this.storage.remove(key); + } + } + + public contains(key:string): boolean { + return this.storage.containsKey(key); + } +} diff --git a/catalog-ui/src/app/ng2/services/catalog.service.ts b/catalog-ui/src/app/ng2/services/catalog.service.ts new file mode 100644 index 0000000000..bbdfa1b420 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/catalog.service.ts @@ -0,0 +1,85 @@ +/*- + * ============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 { Injectable, Inject } from "@angular/core"; +import { Observable } from "rxjs/Observable"; +import { SdcConfigToken, ISdcConfig } from "../config/sdc-config.config"; +import { Component, IApi, IComponentsArray } from "app/models"; +import { ComponentFactory } from 'app/utils/component-factory'; +import {ResourceType} from "../../utils/constants"; +import {SharingService} from "./sharing.service"; +import { HttpClient, HttpParams } from "@angular/common/http"; + +@Injectable() +export class CatalogService { + protected api:IApi; + protected baseUrl:string; + protected baseMicroServiceUrl:string; + + constructor(private http: HttpClient, + @Inject(SdcConfigToken) sdcConfig:ISdcConfig, + private componentFactory:ComponentFactory, + private sharingService:SharingService) { + this.api = sdcConfig.api; + this.baseUrl = sdcConfig.api.root ; + this.baseMicroServiceUrl = sdcConfig.api.uicache_root; + } + + public getCatalog(): Observable<Array<Component>> { + let searchParams = new HttpParams(); + searchParams = searchParams.append('excludeTypes', ResourceType.VFCMT).append('excludeTypes', ResourceType.CONFIGURATION); + return this.http.get<IComponentsArray>(this.baseMicroServiceUrl + this.api.GET_uicache_catalog, {params: searchParams}) + .map(res => this.processComponentsResponse(res, true)); + } + + public getArchiveCatalog() { + return this.http.get<IComponentsArray>(this.baseUrl + '/v1/catalog/archive/', {}) + .map(res => this.processComponentsResponse(res)); + } + + private processComponentsResponse(componentsArr: IComponentsArray, addSharing:boolean = false) { + const componentsList: Component[] = []; + if (componentsArr.resources) { + componentsList.push(...this.getResourceItems(componentsArr.resources)); + } + if (componentsArr.services) { + componentsList.push(...this.getServiceItems(componentsArr.services)); + } + if (addSharing) { + componentsList.forEach((item) => this.sharingService.addUuidValue(item.uniqueId, item.uuid)); + } + return componentsList; + } + + private getResourceItems(resources){ + let resourceItems = resources.map((resource)=>{ + return this.componentFactory.createResource(resource) + }) + return resourceItems; + } + + private getServiceItems(services){ + let serviceItems = services.map((service)=>{ + return this.componentFactory.createService(service) + }) + return serviceItems; + } + +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts index 15750020dc..cc382a3df0 100644 --- a/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts +++ b/catalog-ui/src/app/ng2/services/component-instance-services/component-instance.service.ts @@ -19,62 +19,102 @@ */ import {Injectable, Inject} from '@angular/core'; -import {Response, RequestOptions, Headers} from '@angular/http'; import { Observable } from 'rxjs/Observable'; import {PropertyFEModel, PropertyBEModel} from "app/models"; -import {CommonUtils} from "app/utils"; -import {Component, ComponentInstance, Capability, PropertyModel} from "app/models"; -import { HttpService } from '../http.service'; +import {CommonUtils, ComponentType, ServerTypeUrl, ComponentInstanceFactory} from "app/utils"; +import {Component, ComponentInstance, Capability, PropertyModel, ArtifactGroupModel, ArtifactModel, AttributeModel, IFileDownload} from "app/models"; import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config"; +import { HttpClient, HttpHeaders } from '@angular/common/http'; +import { InputBEModel } from '../../../models/properties-inputs/input-be-model'; +import { HttpHelperService } from '../http-hepler.service'; @Injectable() export class ComponentInstanceServiceNg2 { protected baseUrl; - constructor(private http: HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { + constructor(private http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; } + private getServerTypeUrl = (componentType:string):string => { + switch (componentType) { + case ComponentType.SERVICE: + return ServerTypeUrl.SERVICES; + default: + return ServerTypeUrl.RESOURCES; + } + } getComponentInstanceProperties(component: Component, componentInstanceId: string): Observable<Array<PropertyBEModel>> { - - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/properties') - .map((res: Response) => { - return CommonUtils.initBeProperties(res.json()); + return this.http.get<Array<PropertyBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/properties') + .map(res => { + return CommonUtils.initBeProperties(res); }) } getComponentInstanceInputs(component: Component, componentInstance: ComponentInstance): Observable<Array<PropertyBEModel>> { - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/inputs') - .map((res: Response) => { - return CommonUtils.initInputs(res.json()); + return this.http.get<Array<InputBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstance.uniqueId + '/' + componentInstance.componentUid + '/inputs') + .map(res => { + return CommonUtils.initInputs(res); }) } - updateInstanceProperties(component: Component, componentInstanceId: string, properties: PropertyBEModel[]) { + getComponentInstanceArtifactsByGroupType = (componentType:string, componentId:string, componentInstanceId:string, artifactGroupType:string):Observable<ArtifactGroupModel> => { + + return this.http.get<ArtifactGroupModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/resourceInstances/" + componentInstanceId + "/artifactsByType/" + artifactGroupType) + .map(res => { + return new ArtifactGroupModel(res); + }); + }; + + getArtifactByGroupType = (componentType:string, componentId:string, artifactGroupType:string):Observable<ArtifactGroupModel> => { + + return this.http.get<ArtifactGroupModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/artifactsByType/" + artifactGroupType) + .map(response => new ArtifactGroupModel(response)); + }; + + changeResourceInstanceVersion = (componentType:string, componentId:string, componentInstanceId:string, componentUid:string):Observable<ComponentInstance> => { + return this.http.post<ComponentInstance>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + componentInstanceId + '/changeVersion', {'componentUid': componentUid}) + .map((res) => { + return ComponentInstanceFactory.createComponentInstance(res); + }) + }; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/properties', properties) - .map((res: Response) => { - return res.json().map((resProperty) => new PropertyBEModel(resProperty)); + updateComponentInstance = (componentType:string, componentId:string, componentInstance:ComponentInstance):Observable<ComponentInstance> => { + return this.http.post<ComponentInstance>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + componentInstance.uniqueId, componentInstance.toJSON()) + .map((response) => { + return ComponentInstanceFactory.createComponentInstance(response); + }); + }; + + updateInstanceProperties(componentType:string, componentId:string, componentInstanceId: string, properties: PropertyBEModel[]) { + + return this.http.post<Array<PropertyModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + componentInstanceId + '/properties', properties) + .map((res) => { + return res.map((resProperty) => { + let newProp = new PropertyModel(resProperty); + newProp.resourceInstanceUniqueId = componentInstanceId + return newProp; + }); }); } - getInstanceCapabilityProperties(component: Component, componentInstanceId: string, capability: Capability): Observable<Array<PropertyModel>> { + getInstanceCapabilityProperties(componentType: string, componentId: string, componentInstanceId: string, capability: Capability): Observable<Array<PropertyModel>> { - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/capability/' + capability.type + + return this.http.get<Array<PropertyModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/componentInstances/' + componentInstanceId + '/capability/' + capability.type + '/capabilityName/' + capability.name + '/ownerId/' + capability.ownerId + '/properties') - .map((res: Response) => { - capability.properties = res.json().map((capProp) => new PropertyModel(capProp)); // update capability properties + .map((res) => { + capability.properties = res.map((capProp) => new PropertyModel(capProp)); // update capability properties return capability.properties; }) } updateInstanceCapabilityProperties(component: Component, componentInstanceId: string, capability: Capability, properties: PropertyBEModel[]): Observable<Array<PropertyModel>> { - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/capability/' + capability.type + + return this.http.put<Array<PropertyModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/componentInstances/' + componentInstanceId + '/capability/' + capability.type + '/capabilityName/' + capability.name + '/ownerId/' + capability.ownerId + '/properties', properties) - .map((res: Response) => { - const savedProperties: PropertyModel[] = res.json().map((resProperty) => new PropertyModel(resProperty)); + .map((res) => { + const savedProperties: PropertyModel[] = res.map((resProperty) => new PropertyModel(resProperty)); savedProperties.forEach((savedProperty) => { const propIdx = capability.properties.findIndex((p) => p.uniqueId === savedProperty.uniqueId); if (propIdx !== -1) { @@ -87,37 +127,87 @@ export class ComponentInstanceServiceNg2 { updateInstanceInputs(component: Component, componentInstanceId: string, inputs: PropertyBEModel[]): Observable<PropertyBEModel[]> { - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/inputs', inputs) - .map((res: Response) => { - return res.json().map((resInput) => new PropertyBEModel(resInput)); + return this.http.post<Array<PropertyModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstanceId + '/inputs', inputs) + .map((res) => { + return res.map((resInput) => new PropertyBEModel(resInput)); }); } getComponentGroupInstanceProperties(component: Component, groupInstanceId: string): Observable<Array<PropertyBEModel>> { - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/groups/' + groupInstanceId + '/properties') - .map((res: Response) => { - return CommonUtils.initBeProperties(res.json()); + return this.http.get<Array<PropertyBEModel>>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/groups/' + groupInstanceId + '/properties') + .map((res) => { + return CommonUtils.initBeProperties(res); + }); + } + + updateComponentGroupInstanceProperties(componentType:string, componentId:string, groupInstanceId: string, properties: PropertyBEModel[]): Observable<Array<PropertyBEModel>> { + return this.http.put<Array<PropertyBEModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/groups/' + groupInstanceId + '/properties', properties) + .map((res) => { + return res.map((resProperty) => new PropertyBEModel(resProperty)); }); } - updateComponentGroupInstanceProperties(component: Component, groupInstanceId: string, properties: PropertyBEModel[]): Observable<Array<PropertyBEModel>> { - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/groups/' + groupInstanceId + '/properties', properties) - .map((res: Response) => { - return res.json().map((resProperty) => new PropertyBEModel(resProperty)); + getComponentPolicyInstanceProperties(componentType:string, componentId:string, policyInstanceId: string): Observable<Array<PropertyBEModel>> { + return this.http.get<Array<PropertyBEModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/policies/' + policyInstanceId + '/properties') + .map((res) => { + return CommonUtils.initBeProperties(res); }); } - getComponentPolicyInstanceProperties(component: Component, policyInstanceId: string): Observable<Array<PropertyBEModel>> { - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policyInstanceId + '/properties') - .map((res: Response) => { - return CommonUtils.initBeProperties(res.json()); + updateComponentPolicyInstanceProperties(componentType:string, componentId:string, policyInstanceId: string, properties: PropertyBEModel[]): Observable<Array<PropertyBEModel>> { + return this.http.put<Array<PropertyBEModel>>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/policies/' + policyInstanceId + '/properties', properties) + .map((res) => { + return res.map((resProperty) => new PropertyBEModel(resProperty)); }); } - updateComponentPolicyInstanceProperties(component: Component, policyInstanceId: string, properties: PropertyBEModel[]): Observable<Array<PropertyBEModel>> { - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policyInstanceId + '/properties', properties) - .map((res: Response) => { - return res.json().map((resProperty) => new PropertyBEModel(resProperty)); + addInstanceArtifact = (componentType:string, componentId:string, instanceId:string, artifact:ArtifactModel):Observable<ArtifactModel> => { + // let deferred = this.$q.defer<ArtifactModel>(); + let headerObj = new HttpHeaders(); + if (artifact.payloadData) { + headerObj = headerObj.set('Content-MD5', HttpHelperService.getHeaderMd5(artifact)); + } + return this.http.post<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + instanceId + '/artifacts', JSON.stringify(artifact), {headers: headerObj}) + .map((res) => { + return new ArtifactModel(res); + }); + }; + + updateInstanceArtifact = (componentType:string, componentId:string, instanceId:string, artifact:ArtifactModel):Observable<ArtifactModel> => { + return this.http.post<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + instanceId + '/artifacts/' + artifact.uniqueId, artifact).map((res) => { + return new ArtifactModel(res); + });; + }; + + deleteInstanceArtifact = (componentId:string, componentType:string, instanceId:string, artifactId:string, artifactLabel:string):Observable<ArtifactModel> => { + return this.http.delete<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/resourceInstance/" + instanceId + '/artifacts/' + artifactId + '?operation=' + artifactLabel) + .map((res) => { + return new ArtifactModel(res); }); } + + downloadInstanceArtifact = (componentType:string, componentId:string, instanceId:string, artifactId:string):Observable<IFileDownload> => { + return this.http.get<IFileDownload>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/resourceInstances/" + instanceId + "/artifacts/" + artifactId); + }; + + uploadInstanceEnvFile = (componentType:string, componentId:string, instanceId:string, artifact:ArtifactModel):Observable<ArtifactModel> => { + let headerObj = new HttpHeaders(); + if (artifact.payloadData) { + headerObj = headerObj.set('Content-MD5', HttpHelperService.getHeaderMd5(artifact)); + } + return this.http.post<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + instanceId + '/artifacts/' + artifact.uniqueId, JSON.stringify(artifact), {headers: headerObj}); + }; + + + updateInstanceAttribute = (componentType:string, componentId:string, attribute:AttributeModel):Observable<AttributeModel> => { + let instanceId = attribute.resourceInstanceUniqueId; + return this.http.post<AttributeModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + "/resourceInstance/" + instanceId + "/attribute", attribute) + .map((response) => { + let newAttribute = new AttributeModel(response); + newAttribute.readonly = true; + newAttribute.resourceInstanceUniqueId = instanceId; + return newAttribute; + }); + }; + } diff --git a/catalog-ui/src/app/ng2/services/component-services/component-mode.service.ts b/catalog-ui/src/app/ng2/services/component-services/component-mode.service.ts index b0cc1b8f1a..9952b03c7c 100644 --- a/catalog-ui/src/app/ng2/services/component-services/component-mode.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/component-mode.service.ts @@ -24,7 +24,7 @@ import { Injectable } from '@angular/core'; import {WorkspaceMode, ComponentState, Role} from "../../../utils/constants"; import { Component as ComponentData } from "app/models"; -import { CacheService } from "app/services/cache-service" +import { CacheService } from "app/services-ng2"; @Injectable() diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.factory.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.factory.ts index 6e9d0e8031..15e5a7960a 100644 --- a/catalog-ui/src/app/ng2/services/component-services/component.service.factory.ts +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.factory.ts @@ -20,15 +20,18 @@ import {Injectable} from "@angular/core"; -import {Component} from "../../../models/components/component"; +import {Component} from "app/models"; import {ComponentServiceNg2} from "./component.service"; import {ServiceServiceNg2} from "./service.service"; +import {CacheService} from "app/services-ng2"; @Injectable() export class ComponentServiceFactoryNg2 { + componentService: ComponentServiceNg2; serviceService: ServiceServiceNg2; - constructor(componentService: ComponentServiceNg2, serviceService: ServiceServiceNg2) { + + constructor(componentService: ComponentServiceNg2, serviceService: ServiceServiceNg2, private cacheService:CacheService) { this.serviceService = serviceService; this.componentService = componentService; } diff --git a/catalog-ui/src/app/ng2/services/component-services/component.service.ts b/catalog-ui/src/app/ng2/services/component-services/component.service.ts index 445e1231f9..760bfc591b 100644 --- a/catalog-ui/src/app/ng2/services/component-services/component.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/component.service.ts @@ -23,51 +23,52 @@ import {Injectable, Inject} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; -import {Response, URLSearchParams, Headers} from '@angular/http'; -import { Component, ComponentInstance, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, - PropertyBEModel, InterfaceModel, OperationModel, BEOperationModel, Capability, Requirement, PolicyInstance} from "app/models"; -import {COMPONENT_FIELDS, CommonUtils, SERVICE_FIELDS} from "app/utils"; -import {downgradeInjectable} from '@angular/upgrade/static'; +import { Component, InputBEModel, InstancePropertiesAPIMap, FilterPropertiesAssignmentData, OperationModel, CreateOperationResponse, ArtifactModel} from "app/models"; +import {COMPONENT_FIELDS} from "app/utils"; import {ComponentGenericResponse} from "../responses/component-generic-response"; import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map"; import {API_QUERY_PARAMS} from "app/utils"; -import { ComponentType, ServerTypeUrl } from "../../../utils/constants"; -import { HttpService } from '../http.service'; +import {ComponentType, ServerTypeUrl, SERVICE_FIELDS} from "../../../utils/constants"; import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config"; -import {ConstraintObject} from 'app/ng2/components/logic/service-dependencies/service-dependencies.component'; import {IDependenciesServerResponse} from "../responses/dependencies-server-response"; import {AutomatedUpgradeGenericResponse} from "../responses/automated-upgrade-response"; import {IAutomatedUpgradeRequestObj} from "../../pages/automated-upgrade/automated-upgrade.service"; - -declare var angular:angular.IAngularStatic; - +import {ComponentInstance} from "../../../models/componentsInstances/componentInstance"; +import {CommonUtils} from "../../../utils/common-utils"; +import {RelationshipModel} from "../../../models/graph/relationship"; +import { HttpClient, HttpParams, HttpHeaders } from "@angular/common/http"; +import { BEOperationModel, InterfaceModel } from "../../../models/operation"; +import { PropertyBEModel } from "../../../models/properties-inputs/property-be-model"; +import { PolicyInstance } from "../../../models/graph/zones/policy-instance"; +import { ConstraintObject } from "../../components/logic/service-dependencies/service-dependencies.component"; +import { Requirement } from "../../../models/requirement"; +import { Capability } from "../../../models/capability"; + +/* +PLEASE DO NOT USE THIS SERVICE IN ANGULAR2! Use the topology-template.service instead + */ @Injectable() export class ComponentServiceNg2 { protected baseUrl; - constructor(protected http:HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; } - protected getComponentDataByFieldsName(componentType:string, componentId: string, fields:Array<string>):Observable<ComponentGenericResponse> { + protected getComponentDataByFieldsName(componentType:string, componentId:string, fields:Array<string>):Observable<ComponentGenericResponse> { - let params:URLSearchParams = new URLSearchParams(); + let params: HttpParams = new HttpParams(); _.forEach(fields, (field:string):void => { - params.append(API_QUERY_PARAMS.INCLUDE, field); + params = params.append(API_QUERY_PARAMS.INCLUDE, field); }); - return this.http.get(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {search: params}) - .map((res:Response) => { - return this.analyzeComponentDataResponse(res); + return this.http.get<ComponentGenericResponse>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {params: params}) + .map((res) => { + return new ComponentGenericResponse().deserialize(res); }); } - - protected analyzeComponentDataResponse(res: Response):ComponentGenericResponse { - return new ComponentGenericResponse().deserialize(res.json()); - } - - private getServerTypeUrl = (componentType:string):string => { + protected getServerTypeUrl = (componentType:string):string => { switch (componentType) { case ComponentType.SERVICE: return ServerTypeUrl.SERVICES; @@ -76,15 +77,23 @@ export class ComponentServiceNg2 { } } - getComponentMetadata(component:Component):Observable<ComponentGenericResponse> { - return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_METADATA]); + getFullComponent(uniqueId:string):Observable<ComponentGenericResponse> { + return this.http.get<ComponentGenericResponse>(this.baseUrl + uniqueId) + .map((res) => { + return new ComponentGenericResponse().deserialize(res); + }); } + getComponentMetadata(uniqueId:string, type:string):Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(type, uniqueId, [COMPONENT_FIELDS.COMPONENT_METADATA]); + } + + getComponentInstanceAttributesAndProperties(component:Component):Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES]); } - getComponentInstanceProperties(component:Component):Observable<ComponentGenericResponse> { + getComponentInstanceProperties(component:Component): Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES]); } @@ -132,17 +141,15 @@ export class ComponentServiceNg2 { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_PROPERTIES]); } - getInterfaces(component:Component):Observable<ComponentGenericResponse> { + getInterfaceOperations(component:Component):Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INTERFACE_OPERATIONS]); } getInterfaceOperation(component:Component, operation:OperationModel):Observable<OperationModel> { - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaces/' + operation.interfaceId + '/operations/' + operation.uniqueId) - .map((res:Response) => { - return res.json(); - }); + return this.http.get<OperationModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaceOperations/' + operation.uniqueId); } + // tslint:disable-next-line:member-ordering createInterfaceOperation(component:Component, operation:OperationModel):Observable<OperationModel> { const operationList = { 'interfaces': { @@ -154,9 +161,9 @@ export class ComponentServiceNg2 { } } }; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaceOperations', operationList) - .map((res:Response) => { - const interf:InterfaceModel = _.find(res.json().interfaces, interf => interf.type === operation.interfaceType); + return this.http.post<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaceOperations', operationList) + .map((res:any) => { + const interf:InterfaceModel = _.find(res.interfaces, interf => interf.type === operation.interfaceType); const newOperation:OperationModel = _.find(interf.operations, op => op.name === operation.name); return new OperationModel({ ...newOperation, @@ -167,21 +174,22 @@ export class ComponentServiceNg2 { }); } + // tslint:disable-next-line:member-ordering updateInterfaceOperation(component:Component, operation:OperationModel):Observable<OperationModel> { const operationList = { - 'interfaces': { + interfaces: { [operation.interfaceType]: { - 'type': operation.interfaceType, - 'operations': { + type: operation.interfaceType, + operations: { [operation.name]: new BEOperationModel(operation) } } } }; - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaceOperations', operationList) - .map((res:Response) => { - const interf:InterfaceModel = _.find(res.json().interfaces, interf => interf.type === operation.interfaceType); - const newOperation:OperationModel = _.find(interf.operations, op => op.name === operation.name); + return this.http.put<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaceOperations', operationList) + .map((res: any) => { + const interf: InterfaceModel = _.find(res.interfaces, interf => interf.type === operation.interfaceType); + const newOperation: OperationModel = _.find(interf.operations, op => op.name === operation.name); return new OperationModel({ ...newOperation, interfaceType: interf.type, @@ -191,18 +199,16 @@ export class ComponentServiceNg2 { }); } - deleteInterfaceOperation(component:Component, operation:OperationModel):Observable<OperationModel> { - return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaces/' + operation.interfaceId + '/operations/' + operation.uniqueId) - .map((res:Response) => { - return res.json(); - }); + + deleteInterfaceOperation(component: Component, operation:OperationModel):Observable<OperationModel> { + return this.http.delete<OperationModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/interfaces/' + operation.interfaceId + '/operations/' + operation.uniqueId); } getInterfaceTypes(component:Component):Observable<{[id:string]: Array<string>}> { - return this.http.get(this.baseUrl + 'interfaceLifecycleTypes') - .map((res:Response) => { + return this.http.get<any>(this.baseUrl + 'interfaceLifecycleTypes') + .map((res: any) => { const interfaceMap = {}; - _.forEach(res.json(), (interf:any) => { + _.forEach(res, (interf: any) => { interfaceMap[interf.toscaPresentation.type] = _.keys(interf.toscaPresentation.operations); }); return interfaceMap; @@ -217,19 +223,18 @@ export class ComponentServiceNg2 { payloadData: oldOperation.artifactData }; - const headers = new Headers(); + const headers = new HttpHeaders(); JSON.stringify(payload); const payloadString = JSON.stringify(payload, null, ' '); const md5Result = md5(payloadString).toLowerCase(); headers.append('Content-MD5', btoa(md5Result)); return this.http.post(this.baseUrl + component.getTypeUrl() + component.uuid + '/interfaces/' + newOperation.interfaceId + '/operations/' + newOperation.uniqueId + '/artifacts/' + newOperation.implementation.artifactUUID, - payload, - {headers} - ).map((res: Response) => { - const fileName = res.json().artifactDisplayName || res.json().artifactName; + payload, {headers} + ).map((res: any) => { + const fileName = res.artifactDisplayName || res.artifactName; newOperation.artifactFileName = fileName; - return res.json(); + return res; }); } @@ -237,78 +242,17 @@ export class ComponentServiceNg2 { return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]); } - createCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> { - let capBEObj = { - 'capabilities': { - [capabilityData.type]: [capabilityData] - } - }; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj) - .map((res: Response) => { - return res.json(); - }); - } - updateCapability(component: Component, capabilityData: Capability): Observable<Array<Capability>> { - let capBEObj = { - 'capabilities': { - [capabilityData.type]: [capabilityData] - } - }; - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities', capBEObj) - .map((res: Response) => { - return res.json(); - }); - } - deleteCapability(component: Component, capId: string): Observable<Capability> { - return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities/' + capId) - .map((res: Response) => { - return res.json(); - }); - } - createRequirement(component: Component, requirementData: Requirement): Observable<any> { - let reqBEObj = { - 'requirements': { - [requirementData.capability]: [requirementData] - } - }; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj) - .map((res: Response) => { - return res.json(); - }); - } - - updateRequirement(component: Component, requirementData: Requirement): Observable<any> { - let reqBEObj = { - 'requirements': { - [requirementData.capability]: [requirementData] - } - }; - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements', reqBEObj) - .map((res: Response) => { - return res.json(); - }); - } - - deleteRequirement(component: Component, reqId: string): Observable<Requirement> { - return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements/' + reqId) - .map((res: Response) => { - return res.json(); - }); - } getDeploymentGraphData(component:Component):Observable<ComponentGenericResponse> { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]); } createInput(component:Component, inputsToCreate:InstancePropertiesAPIMap, isSelf:boolean):Observable<any> { - let inputs = isSelf ? { serviceProperties: inputsToCreate.componentInstanceProperties } : inputsToCreate; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputs) - .map(res => { - return res.json(); - }) + const inputs = isSelf ? { serviceProperties: inputsToCreate.componentInstanceProperties } : inputsToCreate; + return this.http.post<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputs); } createListInput(component:Component, input:any, isSelf:boolean):Observable<any> { @@ -324,10 +268,7 @@ export class ComponentServiceNg2 { } else { inputs = input; } - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/listInput', inputs) - .map(res => { - return res.json(); - }) + return this.http.post<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/listInput', inputs); } createPolicy(component:Component, policiesToCreate:InstancePropertiesAPIMap, isSelf:boolean):Observable<any> { @@ -341,78 +282,71 @@ export class ComponentServiceNg2 { ...policiesToCreate.componentInstanceProperties } }; - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/policies', policiesList) - .map(res => { - return res.json(); - }); + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/policies', policiesList); } deletePolicy(component:Component, policy: PolicyInstance) { - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policy.uniqueId + '/undeclare', policy) - .map(res => { - return res.json(); - }); + return this.http.put<PolicyInstance>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policy.uniqueId + '/undeclare', policy); } - restoreComponent(componentType:string, componentId:string){ + restoreComponent(componentType:string, componentId:string) { return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {}) } - archiveComponent(componentType:string, componentId:string){ + archiveComponent(componentType:string, componentId:string) { return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/archive', {}) } deleteInput(component:Component, input:InputBEModel):Observable<InputBEModel> { - return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input') - .map((res:Response) => { - return new InputBEModel(res.json()); - }); + + return this.http.delete<InputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input') + .map((res) => { + return new InputBEModel(res); + }) } updateComponentInputs(component:Component, inputs:InputBEModel[]):Observable<InputBEModel[]> { - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', inputs) - .map((res:Response) => { - return res.json().map((input) => new InputBEModel(input)); + + return this.http.post<InputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', inputs) + .map((res) => { + return res.map((input) => new InputBEModel(input)); }) } - filterComponentInstanceProperties(component: Component, filterData:FilterPropertiesAssignmentData): Observable<InstanceBePropertiesMap> {//instance-property-be-map - let params: URLSearchParams = new URLSearchParams(); + filterComponentInstanceProperties(component:Component, filterData:FilterPropertiesAssignmentData):Observable<InstanceBePropertiesMap> {//instance-property-be-map + let params: HttpParams = new HttpParams(); _.forEach(filterData.selectedTypes, (type:string) => { - params.append('resourceType', type); + params = params.append('resourceType', type); }); - return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {search: params}) - .map((res: Response) => { - return res.json(); - }); + return this.http.get<InstanceBePropertiesMap>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {params: params}); } createServiceProperty(component: Component, propertyModel:PropertyBEModel): Observable<PropertyBEModel> { let serverObject = {}; serverObject[propertyModel.name] = propertyModel; return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/properties', serverObject) - .map(res => { - let property:PropertyBEModel = new PropertyBEModel(res.json()); + .map((res: PropertyBEModel) => { + const property: PropertyBEModel = new PropertyBEModel(res); return property; }) } - getServiceProperties(component: Component): Observable<Array<PropertyBEModel>> { + getServiceProperties(component: Component): Observable<PropertyBEModel[]> { return this.http.get(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/properties') - .map((res: Response) => { - if (!res.text()){ + .map((res: PropertyBEModel[]) => { + if (!res) { return new Array<PropertyBEModel>(); } - return CommonUtils.initBeProperties(res.json()); + return CommonUtils.initBeProperties(res); }); } updateServiceProperties(component: Component, properties: PropertyBEModel[]) { - return this.http.put( this.baseUrl + component.getTypeUrl() + component.uniqueId + '/properties', properties) - .map((res: Response) => { - const resJson = res.json(); + return this.http.put<PropertyBEModel[]>( this.baseUrl + component.getTypeUrl() + component.uniqueId + '/properties', properties) + .map((res) => { + const resJson = res; return _.map(resJson, (resValue:PropertyBEModel) => new PropertyBEModel(resValue)); }); @@ -425,25 +359,16 @@ export class ComponentServiceNg2 { }) } - getDependencies(componentType:string, componentId: string):Observable<Array<IDependenciesServerResponse>> { - return this.http.get(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/dependencies') - .map((res:Response) => { - return res.json(); - }); + getDependencies(componentType:string, componentId: string):Observable<IDependenciesServerResponse[]> { + return this.http.get<IDependenciesServerResponse[]>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/dependencies'); } - automatedUpgrade(componentType:string, componentId: string, componentsIdsToUpgrade:Array<IAutomatedUpgradeRequestObj>):Observable<AutomatedUpgradeGenericResponse> { - return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/automatedupgrade', componentsIdsToUpgrade) - .map((res:Response) => { - return res.json(); - }); + automatedUpgrade(componentType:string, componentId:string, componentsIdsToUpgrade:IAutomatedUpgradeRequestObj[]):Observable<AutomatedUpgradeGenericResponse> { + return this.http.post<AutomatedUpgradeGenericResponse>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/automatedupgrade', componentsIdsToUpgrade); } - updateComponentInstance(component:Component, componentInstance:ComponentInstance):Observable<ComponentInstance> { - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstance/' + componentInstance.uniqueId, componentInstance) - .map((res:Response) => { - return res.json(); - }); + updateMultipleComponentInstances(componentId:string, instances:ComponentInstance[]):Observable<ComponentInstance[]> { + return this.http.post<ComponentInstance[]>(this.baseUrl + componentId + '/resourceInstance/multipleComponentInstance', instances); } getServiceFilterConstraints(component:Component):Observable<ComponentGenericResponse> { @@ -451,24 +376,18 @@ export class ComponentServiceNg2 { } createServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraint:ConstraintObject):Observable<any> { - return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter', constraint) - .map((res:Response) => { - return res.json(); - }); + return this.http.post<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter', constraint); } - updateServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraints:Array<ConstraintObject>):Observable<any> { - return this.http.put(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/', constraints) - .map((res:Response) => { - return res.json(); - }); + updateServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraints:ConstraintObject[]):Observable<any> { + return this.http.put<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/', constraints); } deleteServiceFilterConstraints(component:Component, componentInstance:ComponentInstance, constraintIndex:number) { - return this.http.delete(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/' + constraintIndex) - .map((res:Response) => { - return res.json(); - }); + return this.http.delete<any>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/resourceInstances/' + componentInstance.uniqueId + '/nodeFilter/' + constraintIndex); } -} + protected analyzeComponentDataResponse(res: Response):ComponentGenericResponse { + return new ComponentGenericResponse().deserialize(res); + } +} diff --git a/catalog-ui/src/app/ng2/services/component-services/resource.service.ts b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts index 699e762a60..d20f5415bc 100644 --- a/catalog-ui/src/app/ng2/services/component-services/resource.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/resource.service.ts @@ -21,14 +21,14 @@ import { Injectable } from '@angular/core'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; -import { Http, Response, Headers, RequestOptions } from '@angular/http'; +import { HttpClient } from '@angular/common/http'; @Injectable() export class ResourceServiceNg2 { protected baseUrl = ""; - constructor(private http: Http) { + constructor(private http: HttpClient) { } diff --git a/catalog-ui/src/app/ng2/services/component-services/service.service.ts b/catalog-ui/src/app/ng2/services/component-services/service.service.ts index dce4e814ec..9460a32323 100644 --- a/catalog-ui/src/app/ng2/services/component-services/service.service.ts +++ b/catalog-ui/src/app/ng2/services/component-services/service.service.ts @@ -22,9 +22,7 @@ import { Injectable, Inject } from '@angular/core'; import { Observable } from 'rxjs/Observable'; import 'rxjs/add/operator/map'; import 'rxjs/add/operator/toPromise'; -import { Response, URLSearchParams } from '@angular/http'; -import {Service, OperationModel} from "app/models"; -import { HttpService } from '../http.service'; +import {Service} from "app/models"; import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config"; import {ForwardingPath} from "app/models/forwarding-path"; @@ -34,77 +32,53 @@ import {Component} from "app/models/components/component"; import {ComponentGenericResponse} from "app/ng2/services/responses/component-generic-response"; import {COMPONENT_FIELDS, SERVICE_FIELDS} from "app/utils/constants"; import {ComponentServiceNg2} from "./component.service"; -import {ServiceGenericResponse} from "app/ng2/services/responses/service-generic-response"; import {ServicePathMapItem} from "app/models/graph/nodes-and-links-map"; -import {ConsumptionInput} from 'app/ng2/components/logic/service-consumption/service-consumption.component'; - +import { HttpClient, HttpParams } from '@angular/common/http'; +import { OperationModel } from '../../../models/operation'; +import { ConsumptionInput } from '../../components/logic/service-consumption/service-consumption.component'; @Injectable() export class ServiceServiceNg2 extends ComponentServiceNg2 { protected baseUrl = ""; - constructor(protected http: HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { super(http, sdcConfig); this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; } validateConformanceLevel(service: Service): Observable<boolean> { - return this.http.get(this.baseUrl + service.getTypeUrl() + service.uuid + '/conformanceLevelValidation') - .map((res: Response) => { - return res.json(); - }); - } - - getNodesAndLinksMap(service: Service):Observable<Array<ServicePathMapItem>> { - return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/linksMap').map(res => { - return <Array<ServicePathMapItem>>res.json(); - }); - } - - getServicePath(service: Service, id: string):Observable<any> { - return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths/' + id) - .map(res => { - return res.json(); - }) + return this.http.get<boolean>(this.baseUrl + service.getTypeUrl() + service.uuid + '/conformanceLevelValidation'); } - getServicePaths(service: Service):Observable<any> { - return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths') - .map(res => { - return res.json(); - }) + getNodesAndLinksMap(serviceId: string):Observable<Array<ServicePathMapItem>> { + return this.http.get<Array<ServicePathMapItem>>(this.baseUrl + 'services/' + serviceId + '/linksMap'); } - createOrUpdateServicePath(service: Service, inputsToCreate: ForwardingPath):Observable<ForwardingPath> { + createOrUpdateServicePath(serviceId: string, inputsToCreate: ForwardingPath):Observable<ForwardingPath> { if (inputsToCreate.uniqueId) { - return this.updateServicePath(service, inputsToCreate); + return this.updateServicePath(serviceId, inputsToCreate); } else { - return this.createServicePath(service, inputsToCreate); + return this.createServicePath(serviceId, inputsToCreate); } } - createServicePath(service: Service, inputsToCreate: ForwardingPath):Observable<ForwardingPath> { + createServicePath(serviceId: string, inputsToCreate: ForwardingPath):Observable<ForwardingPath> { let input = new ServicePathRequestData(inputsToCreate); - - return this.http.post(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths', input) - .map(res => { - return this.parseServicePathResponse(res); - }); + return this.http.post<ForwardingPath>(this.baseUrl + 'services/' + serviceId + '/paths', input).map((res:any) => { + return this.parseServicePathResponse(res); + }); } - deleteServicePath(service: Service, id: string):Observable<any> { - return this.http.delete(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths/' + id ) - .map((res) => { - return res.json(); - }); + deleteServicePath(serviceId: string, id: string):Observable<any> { + return this.http.delete<any>(this.baseUrl + 'services/' + serviceId + '/paths/' + id); } - updateServicePath(service: Service, inputsToUpdate:ForwardingPath):Observable<ForwardingPath> { + updateServicePath(serviceId: string, inputsToUpdate:ForwardingPath):Observable<ForwardingPath> { let input = new ServicePathRequestData(inputsToUpdate); - return this.http.put(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths', input) + return this.http.put<{[key:string]:ForwardingPath}>(this.baseUrl + 'services/' + serviceId + '/paths', input) .map((res) => { return this.parseServicePathResponse(res); }); @@ -122,28 +96,25 @@ export class ServiceServiceNg2 extends ComponentServiceNg2 { } getServiceConsumptionInputs(service: Service, serviceInstanceId: String, interfaceId: string, operation: OperationModel): Observable<any> { - return this.http.get(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId + '/interfaces/' + interfaceId + '/operations/' + operation.uniqueId + '/inputs') - .map(res => { - return res.json(); - }); + return this.http.get<any>(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId + '/interfaces/' + interfaceId + + '/operations/' + operation.uniqueId + '/inputs'); } createOrUpdateServiceConsumptionInputs(service: Service, serviceInstanceId: String, consumptionInputsList: Array<{[id: string]: Array<ConsumptionInput>}>): Observable<any> { return this.http.post(this.baseUrl + service.getTypeUrl() + service.uniqueId + '/consumption/' + serviceInstanceId, consumptionInputsList); } - checkComponentInstanceVersionChange(service: Service, newVersionId: string):Observable<Array<string>> { - let instanceId = service.selectedInstance.uniqueId; - let queries = {componentInstanceId: instanceId, newComponentInstanceId: newVersionId}; + checkComponentInstanceVersionChange(componentType:string, componentId:string, instanceId:string, newInstanceId:string):Observable<Array<string>> { + let queries = {componentInstanceId: instanceId, newComponentInstanceId: newInstanceId}; - let params:URLSearchParams = new URLSearchParams(); + let params:HttpParams = new HttpParams(); _.map(_.keys(queries), (key:string):void => { - params.append(key, queries[key]); + params = params.append(key, queries[key]); }); - let url = this.baseUrl + service.getTypeUrl() + service.uniqueId + '/paths-to-delete'; - return this.http.get(url, {search: params}).map((res: Response) => { - return res.json().forwardingPathToDelete; + let url = this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/paths-to-delete'; + return this.http.get<any>(url, {params: params}).map((res) => { + return res.forwardingPathToDelete; }); } @@ -151,12 +122,8 @@ export class ServiceServiceNg2 extends ComponentServiceNg2 { return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, SERVICE_FIELDS.FORWARDING_PATHS, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_POLICIES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]); } - protected analyzeComponentDataResponse(res: Response):ComponentGenericResponse { - return new ServiceGenericResponse().deserialize(res.json()); - } - - private parseServicePathResponse(res: Response):ForwardingPath { - let resJSON = res.json(); + private parseServicePathResponse(res: { [key:string]:ForwardingPath }):ForwardingPath { + let resJSON = res; let pathId = Object.keys(resJSON.forwardingPaths)[0]; let forwardingPath = resJSON.forwardingPaths[pathId]; let path:ForwardingPath = new ForwardingPath(); diff --git a/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts new file mode 100644 index 0000000000..0abb163404 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/topology-template.service.ts @@ -0,0 +1,515 @@ +/** + * Created by ob0695 on 6/26/2018. + */ +/*- + * ============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 {Injectable, Inject} from '@angular/core'; +import {Observable} from 'rxjs/Observable'; +import 'rxjs/add/operator/map'; +import 'rxjs/add/operator/toPromise'; +import { + Component, + InputBEModel, + InstancePropertiesAPIMap, + FilterPropertiesAssignmentData, + ArtifactModel, + PropertyModel, + IFileDownload, + AttributeModel, + IAttributeModel, Capability, Requirement +} from "app/models"; +import {ArtifactGroupType, COMPONENT_FIELDS} from "app/utils"; +import {ComponentGenericResponse} from "../responses/component-generic-response"; +import {InstanceBePropertiesMap} from "../../../models/properties-inputs/property-fe-map"; +import {API_QUERY_PARAMS} from "app/utils"; +import {ComponentType, ServerTypeUrl, SERVICE_FIELDS} from "../../../utils/constants"; +import {SdcConfigToken, ISdcConfig} from "../../config/sdc-config.config"; +import {IDependenciesServerResponse} from "../responses/dependencies-server-response"; +import {AutomatedUpgradeGenericResponse} from "../responses/automated-upgrade-response"; +import {IAutomatedUpgradeRequestObj} from "../../pages/automated-upgrade/automated-upgrade.service"; +import {ComponentInstance} from "../../../models/componentsInstances/componentInstance"; +import {CommonUtils} from "../../../utils/common-utils"; +import {RelationshipModel} from "../../../models/graph/relationship"; +import {ServiceGenericResponse} from "../responses/service-generic-response"; +import { HttpClient, HttpParams, HttpHeaders } from "@angular/common/http"; +import { HttpHelperService } from "../http-hepler.service"; +import { + Component as TopologyTemplate, + FullComponentInstance, + Service, + OperationModel, +} from 'app/models'; +import { ConsumptionInput } from "../../components/logic/service-consumption/service-consumption.component"; +import { ConstraintObject } from "../../components/logic/service-dependencies/service-dependencies.component"; +import { ComponentMetadata } from "../../../models/component-metadata"; +import { PolicyInstance } from "../../../models/graph/zones/policy-instance"; +import { PropertyBEModel } from "../../../models/properties-inputs/property-be-model"; + +/* we need to use this service from now, we will remove component.service when we finish remove the angular1. + The service is duplicated since we can not use downgrades service with NGXS*/ + +@Injectable() +export class TopologyTemplateService { + + protected baseUrl; + + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + } + + getFullComponent(componentType: string, uniqueId: string): Observable<Component> { + return this.http.get<Component>(this.baseUrl + this.getServerTypeUrl(componentType) + uniqueId); + } + + getComponentMetadata(uniqueId: string, type: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(type, uniqueId, [COMPONENT_FIELDS.COMPONENT_METADATA]); + } + + getComponentInstanceAttributesAndProperties(uniqueId: string, type: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(type, uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_INSTANCES_ATTRIBUTES]); + } + + async getComponentAttributes(componentType: string, componentId: string): Promise<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_ATTRIBUTES]).toPromise(); + } + + getComponentCompositionData(componentUniqueId: string, componentType: string): Observable<ComponentGenericResponse> { + const params: string[] = [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, + COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_POLICIES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]; + if (componentType === ComponentType.SERVICE) { + params.push(COMPONENT_FIELDS.FORWARDING_PATHS); + } + return this.getComponentDataByFieldsName(componentType, componentUniqueId, params); + } + + getComponentResourcePropertiesData(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, + [COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_POLICIES, COMPONENT_FIELDS.COMPONENT_NON_EXCLUDED_GROUPS]); + } + + getComponentResourceInstances(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentInputs(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INPUTS]); + } + + getComponentInputsWithProperties(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, + [COMPONENT_FIELDS.COMPONENT_INPUTS, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, COMPONENT_FIELDS.COMPONENT_PROPERTIES]); + } + + getComponentDeploymentArtifacts(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS]); + } + + getComponentInformationalArtifacts(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS]); + } + + getComponentInformationalArtifactsAndInstances(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS, COMPONENT_FIELDS.COMPONENT_INSTANCES]); + } + + getComponentToscaArtifacts(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_TOSCA_ARTIFACTS]); + } + + getComponentProperties(component: Component): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(component.componentType, component.uniqueId, [COMPONENT_FIELDS.COMPONENT_PROPERTIES]); + } + + getCapabilitiesAndRequirements(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES]); + } + + getRequirementsAndCapabilitiesWithProperties(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, + [COMPONENT_FIELDS.COMPONENT_REQUIREMENTS, COMPONENT_FIELDS.COMPONENT_CAPABILITIES, COMPONENT_FIELDS.COMPONENT_CAPABILITIES_PROPERTIES]); + } + + getDeploymentGraphData(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_RELATION, COMPONENT_FIELDS.COMPONENT_INSTANCES, COMPONENT_FIELDS.COMPONENT_GROUPS]); + } + + createInput(component: Component, inputsToCreate: InstancePropertiesAPIMap, isSelf: boolean): Observable<any> { + const inputs = isSelf ? { serviceProperties: inputsToCreate.componentInstanceProperties } : inputsToCreate; + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/inputs', inputs); + } + + restoreComponent(componentType: string, componentId: string) { + return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/restore', {}); + } + + archiveComponent(componentType: string, componentId: string) { + return this.http.post(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/archive', {}); + } + + deleteInput(component: Component, input: InputBEModel): Observable<InputBEModel> { + return this.http.delete<InputBEModel>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/delete/' + input.uniqueId + '/input') + .map((res) => { + return new InputBEModel(res); + }); + } + + updateComponentInputs(component: Component, inputs: InputBEModel[]): Observable<InputBEModel[]> { + return this.http.post<InputBEModel[]>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/update/inputs', inputs) + .map((res) => { + return res.map((input) => new InputBEModel(input)); + }); + } + + filterComponentInstanceProperties(component: Component, filterData: FilterPropertiesAssignmentData): Observable<InstanceBePropertiesMap> {// instance-property-be-map + let params: HttpParams = new HttpParams(); + _.forEach(filterData.selectedTypes, (type: string) => { + params = params.append('resourceType', type); + }); + + // tslint:disable-next-line:object-literal-shorthand + return this.http.get<InstanceBePropertiesMap>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/filteredproperties/' + filterData.propertyName, {params: params}); + } + + createServiceProperty(componentId: string, propertyModel: PropertyBEModel): Observable<PropertyBEModel> { + const serverObject = {}; + serverObject[propertyModel.name] = propertyModel; + return this.http.post<PropertyBEModel>(this.baseUrl + 'services/' + componentId + '/properties', serverObject) + .map((res) => { + const property: PropertyBEModel = new PropertyBEModel(res); + return property; + }); + } + + getServiceProperties(componentId: string): Observable<PropertyBEModel[]> { + return this.http.get<any>(this.baseUrl + 'services/' + componentId + '/properties') + .map((res) => { + if (!res) { + return new Array<PropertyBEModel>(); + } + return CommonUtils.initBeProperties(res); + }); + } + + updateServiceProperties(componentId: string, properties: PropertyBEModel[]) { + return this.http.put<any>( this.baseUrl + 'services/' + componentId + '/properties', properties) + .map((res) => { + const resJson = res; + return _.map(resJson, + (resValue: PropertyBEModel) => new PropertyBEModel(resValue)); + }); + } + + deleteServiceProperty(componentId: string, property: PropertyBEModel): Observable<string> { + return this.http.delete(this.baseUrl + 'services/' + componentId + '/properties/' + property.uniqueId ) + .map((res: Response) => { + return property.uniqueId; + }); + } + + getDependencies(componentType: string, componentId: string): Observable<IDependenciesServerResponse[]> { + return this.http.get<IDependenciesServerResponse[]>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/dependencies'); + } + + automatedUpgrade(componentType: string, componentId: string, componentsIdsToUpgrade: IAutomatedUpgradeRequestObj[]): Observable<AutomatedUpgradeGenericResponse> { + return this.http.post<AutomatedUpgradeGenericResponse>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/automatedupgrade', componentsIdsToUpgrade); + } + + updateComponentInstance(componentMetaDataId: string, componentInstance:ComponentInstance): Observable<ComponentInstance> { + return this.http.post<ComponentInstance>(this.baseUrl + 'services/' + componentMetaDataId + '/resourceInstance/' + componentInstance.uniqueId, componentInstance); + } + + updateMultipleComponentInstances(componentId: string, componentType: string, instances: ComponentInstance[]): Observable<ComponentInstance[]> { + return this.http.post<ComponentInstance[]>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/multipleComponentInstance', instances) + .map((res) => { + return CommonUtils.initComponentInstances(res); + }); + } + + createRelation(componentId: string, componentType: string, link: RelationshipModel): Observable<RelationshipModel> { + return this.http.post<RelationshipModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/associate', link) + .map((res) => { + return new RelationshipModel(res); + }); + } + + deleteRelation(componentId: string, componentType: string, link: RelationshipModel): Observable<RelationshipModel> { + return this.http.put<RelationshipModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/dissociate', link) + .map((res) => { + return new RelationshipModel(res); + }); + } + + createComponentInstance(componentType: string, componentId: string, componentInstance: ComponentInstance): Observable<ComponentInstance> { + return this.http.post<ComponentInstance>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance', componentInstance) + .map((res) => { + return new ComponentInstance(res); + }); + } + + deleteComponentInstance(componentType: string, componentId: string, componentInstanceId: string): Observable<ComponentInstance> { + return this.http.delete<ComponentInstance>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/resourceInstance/' + componentInstanceId) + .map((res) => { + return new ComponentInstance(res); + }); + } + + fetchRelation(componentType: string, componentId: string, linkId: string): Observable<RelationshipModel> { + return this.http.get<RelationshipModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/relationId/' + linkId) + .map((res) => { + return new RelationshipModel(res); + }); + } + + addOrUpdateArtifact = (componentType: string, componentId: string, artifact: ArtifactModel): Observable<ArtifactModel> => { + let headerObj: HttpHeaders = new HttpHeaders(); + if (artifact.payloadData) { + headerObj = headerObj.append('Content-MD5', HttpHelperService.getHeaderMd5(artifact)); + } + + let artifactID: string = ''; + if (artifact.uniqueId) { + artifactID = '/' + artifact.uniqueId; + } + return this.http.post<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/artifacts' + artifactID, JSON.stringify(artifact), {headers: headerObj}).map( + (res) => new ArtifactModel(res) + ); + } + + deleteArtifact = (componentId: string, componentType: string, artifactId: string, artifactLabel: string): Observable<ArtifactModel> => { + return this.http.delete<ArtifactModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/artifacts/' + artifactId + '?operation=' + artifactLabel) + .map((res) => new ArtifactModel(res)); + } + + downloadArtifact = (componentType: string, componentId: string, artifactId: string): Observable<IFileDownload> => { + return this.http.get<IFileDownload>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/artifacts/' + artifactId); + } + + // ------------------------------------------------ Properties API --------------------------------------------------// + addProperty = (componentType: string, componentId: string, property: PropertyModel):Observable<PropertyModel> => { + return this.http.post<PropertyModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/properties', property.convertToServerObject()).map((response) => { + return new PropertyModel(response[Object.keys(response)[0]]); + }); + } + + updateProperty = (componentType: string, componentId: string, property: PropertyModel): Observable<PropertyModel> => { + var propertiesList:PropertyBEModel[] = [property]; + return this.http.put<PropertyModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/properties', propertiesList) + .map((response) => { + return new PropertyModel(response[Object.keys(response)[0]]); + }); + } + + deleteProperty = (componentType: string, componentId: string, propertyId: string): Observable<PropertyModel> => { + return this.http.delete<PropertyModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/properties/' + propertyId); + } + + // ------------------------------------------------ Attributes API --------------------------------------------------// + addAttribute = (componentType: string, componentId: string, attribute: AttributeModel): Observable<AttributeModel> => { + return this.http.post<AttributeModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/attributes', attribute.convertToServerObject()) + .map((response) => { + return new AttributeModel(response); + }); + } + + updateAttribute = (componentType: string, componentId: string, attribute: AttributeModel): Observable<AttributeModel> => { + const payload = attribute.convertToServerObject(); + + return this.http.put<AttributeModel>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/attributes/' + attribute.uniqueId, payload) + .map((response) => { + return new AttributeModel(response); + }); + } + + // Async Methods + addAttributeAsync = async (componentType: string, componentId: string, attribute: AttributeModel): Promise<AttributeModel> => { + return this.addAttribute(componentType, componentId, attribute).toPromise(); + } + + updateAttributeAsync = async (componentType: string, componentId: string, attribute: AttributeModel): Promise<AttributeModel> => { + return this.updateAttribute(componentType, componentId, attribute).toPromise(); + } + + deleteAttributeAsync = async (componentType: string, componentId: string, attribute: AttributeModel): Promise<any> => { + return this.http.delete<any>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/attributes/' + attribute.uniqueId, {}).toPromise(); + } + + getArtifactsByType(componentType: string, componentId: string, artifactsType: ArtifactGroupType) { + return this.getComponentDataByFieldsName(componentType, componentId, [this.convertArtifactTypeToUrl(artifactsType)]); + } + + getServiceConsumptionData(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [ + // COMPONENT_FIELDS.COMPONENT_INSTANCES_INTERFACES, + COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES, + // COMPONENT_FIELDS.COMPONENT_INSTANCES_INPUTS, + COMPONENT_FIELDS.COMPONENT_INPUTS, + COMPONENT_FIELDS.COMPONENT_INSTANCES, + COMPONENT_FIELDS.COMPONENT_CAPABILITIES + ]); + } + + getServiceConsumptionInputs(componentMetaDataId: string, serviceInstanceId: string, interfaceId: string, operation: OperationModel): Observable<ConsumptionInput[]> { + return this.http.get<ConsumptionInput[]> + (this.baseUrl + 'services/' + componentMetaDataId + '/consumption/' + serviceInstanceId + '/interfaces/' + interfaceId + '/operations/' + operation.uniqueId + '/inputs'); + } + + createOrUpdateServiceConsumptionInputs(componentMetaDataId: string, serviceInstanceId: string, consumptionInputsList: Array<{[id: string]: ConsumptionInput[]}>): Observable<any> { + return this.http.post(this.baseUrl + 'services/' + componentMetaDataId + '/consumption/' + serviceInstanceId, consumptionInputsList); + } + + getServiceFilterConstraints(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [SERVICE_FIELDS.NODE_FILTER]); + } + + getComponentInstanceProperties(componentType: string, componentId: string): Observable<ComponentGenericResponse> { + return this.getComponentDataByFieldsName(componentType, componentId, [COMPONENT_FIELDS.COMPONENT_INSTANCES_PROPERTIES]); + } + + createServiceFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraint: ConstraintObject): Observable<any> { + return this.http.post<any>(this.baseUrl + 'services/' + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/nodeFilter', constraint); + } + + updateServiceFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraints: ConstraintObject[]):Observable<any> { + return this.http.put<any>(this.baseUrl + 'services/' + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/nodeFilter', constraints) + } + + deleteServiceFilterConstraints(componentMetaDataId: string, componentInstanceId: string, constraintIndex: number): Observable<any>{ + return this.http.delete<any>(this.baseUrl + 'services/' + componentMetaDataId + '/resourceInstances/' + componentInstanceId + '/nodeFilter/' + constraintIndex) + } + + deletePolicy(component: Component, policy: PolicyInstance): Observable<PolicyInstance> { + return this.http.put<PolicyInstance>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/policies/' + policy.uniqueId + '/undeclare', policy) + } + + createListInput(componentId: string, input: any, isSelf: boolean): Observable<any> { + let inputs: any; + if (isSelf) { + // change componentInstanceProperties -> serviceProperties + inputs = { + componentInstInputsMap: { + serviceProperties: input.componentInstInputsMap.componentInstanceProperties + }, + listInput: input.listInput + }; + } else { + inputs = input; + } + return this.http.post<any>(this.baseUrl + 'services/' + componentId + '/create/listInput', inputs); + } + + createPolicy(component: Component, policiesToCreate: InstancePropertiesAPIMap, isSelf: boolean): Observable<any> { + const policiesList = + isSelf ? + // tslint:disable-next-line:object-literal-key-quotes + {'componentPropertiesToPolicies': { + ...policiesToCreate.componentInstanceProperties + } + } : + // tslint:disable-next-line:object-literal-key-quotes + {'componentInstancePropertiesToPolicies': { + ...policiesToCreate.componentInstanceProperties + } + }; + return this.http.post(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/create/policies', policiesList); + } + + protected getComponentDataByFieldsName(componentType: string, componentId: string, fields: string[]): Observable<ComponentGenericResponse> { + let params: HttpParams = new HttpParams(); + _.forEach(fields, (field: string): void => { + params = params.append(API_QUERY_PARAMS.INCLUDE, field); + }); + // tslint:disable-next-line:object-literal-shorthand + return this.http.get<ComponentGenericResponse>(this.baseUrl + this.getServerTypeUrl(componentType) + componentId + '/filteredDataByParams', {params: params}) + .map((res) => { + return componentType === ComponentType.SERVICE ? new ServiceGenericResponse().deserialize(res) : + new ComponentGenericResponse().deserialize(res); + }); + } + + private getServerTypeUrl = (componentType: string): string => { + switch (componentType) { + case ComponentType.SERVICE: + case ComponentType.SERVICE_PROXY: + return ServerTypeUrl.SERVICES; + default: + return ServerTypeUrl.RESOURCES; + } + } + + private convertArtifactTypeToUrl = (artifactType: ArtifactGroupType): string => { + switch (artifactType) { + case ArtifactGroupType.TOSCA: + return COMPONENT_FIELDS.COMPONENT_TOSCA_ARTIFACTS; + case ArtifactGroupType.INFORMATION: + return COMPONENT_FIELDS.COMPONENT_INFORMATIONAL_ARTIFACTS; + case ArtifactGroupType.DEPLOYMENT: + return COMPONENT_FIELDS.COMPONENT_DEPLOYMENT_ARTIFACTS; + case ArtifactGroupType.SERVICE_API: + return COMPONENT_FIELDS.SERVICE_API_ARTIFACT; + } + } + + // createCapability(component: Component, capabilityData: Capability): Observable<Capability[]> { + createCapability(type: string, uniqueId: string, capabilityData: Capability): Observable<Capability[]> { + let capBEObj = { + 'capabilities': { + [capabilityData.type]: [capabilityData] + } + }; + return this.http.post<any>(this.baseUrl + type + uniqueId + '/capabilities', capBEObj); + } + + updateCapability(type: string, uniqueId: string, capabilityData: Capability): Observable<Capability[]> { + let capBEObj = { + 'capabilities': { + [capabilityData.type]: [capabilityData] + } + }; + return this.http.put<any>(this.baseUrl + type + uniqueId + '/capabilities', capBEObj); + } + + deleteCapability(component: Component, capId: string): Observable<Capability> { + return this.http.delete<Capability>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/capabilities/' + capId); + } + + createRequirement(type: string, uniqueId: string, requirementData: Requirement): Observable<any> { + let reqBEObj = { + 'requirements': { + [requirementData.capability]: [requirementData] + } + }; + return this.http.post(this.baseUrl + type + uniqueId + '/requirements', reqBEObj); + } + + updateRequirement(type: string, uniqueId: string, requirementData: Requirement): Observable<any> { + let reqBEObj = { + 'requirements': { + [requirementData.capability]: [requirementData] + } + }; + return this.http.put(this.baseUrl + type + uniqueId + '/requirements', reqBEObj); + } + + deleteRequirement(component: Component, reqId: string): Observable<Requirement> { + return this.http.delete<Requirement>(this.baseUrl + component.getTypeUrl() + component.uniqueId + '/requirements/' + reqId); + } +} diff --git a/catalog-ui/src/app/ng2/services/config.service.ts b/catalog-ui/src/app/ng2/services/config.service.ts index 11fe395811..2b9b49cf62 100644 --- a/catalog-ui/src/app/ng2/services/config.service.ts +++ b/catalog-ui/src/app/ng2/services/config.service.ts @@ -16,62 +16,80 @@ * */ -import { Injectable, Inject } from '@angular/core'; -import { Http, Response } from '@angular/http'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable, Injector } from '@angular/core'; +import { IAppConfigurtaion, Plugins, PluginsConfiguration, ValidationConfiguration, Validations } from 'app/models'; +import { IApi } from 'app/models/app-config'; import 'rxjs/add/operator/toPromise'; -import {IAppConfigurtaion, ValidationConfiguration, Validations, Plugins, PluginsConfiguration} from "app/models"; -import {IApi} from "app/models/app-config"; -import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; +import { ISdcConfig, SdcConfigToken } from '../config/sdc-config.config'; +import { CacheService } from './cache.service'; @Injectable() export class ConfigService { - private baseUrl; public configuration: IAppConfigurtaion; - public api:IApi; + public api: IApi; + private baseUrl; + + constructor( + @Inject(SdcConfigToken) private sdcConfig: ISdcConfig, + private cacheService: CacheService, + private injector: Injector, + private http: HttpClient + ) { + this.api = this.sdcConfig.api; + this.baseUrl = this.api.root + this.sdcConfig.api.component_api_root; + } - constructor(private http: Http, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { - this.api = this.sdcConfig.api; - this.baseUrl = this.api.root + this.sdcConfig.api.component_api_root; + loadSdcSetupData = (): Promise<void> => { + const url: string = this.api.root + this.api.GET_SDC_Setup_Data; + const promise: Promise<any> = this.http.get<any>(url).toPromise(); + promise.then((response) => { + this.cacheService.set('version', response.version); + this.cacheService.set('serviceCategories', response.categories.serviceCategories); + this.cacheService.set('resourceCategories', response.categories.resourceCategories); + this.cacheService.set('UIConfiguration', response.configuration); + }); + return promise; } loadValidationConfiguration(): Promise<ValidationConfiguration> { - let url: string = this.sdcConfig.validationConfigPath; - let promise: Promise<ValidationConfiguration> = this.http.get(url).map((res: Response) => res.json()).toPromise(); + const url: string = this.sdcConfig.validationConfigPath; + const promise: Promise<ValidationConfiguration> = this.http.get<ValidationConfiguration>(url).toPromise(); promise.then((validationData: Validations) => { ValidationConfiguration.validation = validationData; + this.cacheService.set('validation', validationData); }).catch((ex) => { console.error('Error loading validation.json configuration file, using fallback data', ex); - let fallback:Validations = { - "propertyValue": { - "max": 2500, - "min": 0 + const fallback = { + propertyValue: { + max: 2500, + min: 0 }, - - "validationPatterns": { - "string": "^[\\sa-zA-Z0-9+-]+$", - "comment": "^[\\sa-zA-Z0-9+-_\\{\\}\"]+$", - "integer": "^(([-+]?\\d+)|([-+]?0x[0-9a-fA-F]+))$" + validationPatterns: { + string: '^[\\sa-zA-Z0-9+-]+$', + stringOrEmpty: '^[\\sa-zA-Z0-9&-]*$', + comment: '^[\\sa-zA-Z0-9+-_\\{\\}"]+$', + integer: '^(([-+]?\\d+)|([-+]?0x[0-9a-fA-F]+))$' } }; - ValidationConfiguration.validation = fallback; - + this.cacheService.set('validation', fallback); }); return promise; } - loadPluginsConfiguration(): Promise<PluginsConfiguration> { - let url:string = this.api.no_proxy_root + this.api.GET_plugins_configuration; - let promise: Promise<any> = this.http.get(url).map((res: Response) => res.json()).toPromise(); + loadPluginsConfiguration = (): Promise<PluginsConfiguration> => { + const url: string = this.api.no_proxy_root + this.api.GET_plugins_configuration; + const promise: Promise<any> = this.http.get<PluginsConfiguration>(url).toPromise(); return new Promise<PluginsConfiguration>((resolve) => { promise.then((pluginsData: Plugins) => { PluginsConfiguration.plugins = pluginsData; resolve(); }).catch((ex) => { - console.error("Error loading plugins configuration from FE", ex); + console.error('Error loading plugins configuration from FE', ex); PluginsConfiguration.plugins = [] as Plugins; resolve(); diff --git a/catalog-ui/src/app/ng2/services/cookie.service.ts b/catalog-ui/src/app/ng2/services/cookie.service.ts index 2a783fdd48..61d13186fa 100644 --- a/catalog-ui/src/app/ng2/services/cookie.service.ts +++ b/catalog-ui/src/app/ng2/services/cookie.service.ts @@ -19,7 +19,7 @@ */ import {Injectable, Inject} from '@angular/core'; -import {IAppConfigurtaion, ICookie} from "../../models/app-config"; +import {ICookie} from "../../models/app-config"; import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; @Injectable() diff --git a/catalog-ui/src/app/ng2/services/data-type.service.ts b/catalog-ui/src/app/ng2/services/data-type.service.ts index cabaccd1d5..0559f35ae2 100644 --- a/catalog-ui/src/app/ng2/services/data-type.service.ts +++ b/catalog-ui/src/app/ng2/services/data-type.service.ts @@ -20,9 +20,9 @@ import * as _ from "lodash"; import { Injectable } from '@angular/core'; -import { DataTypeModel, DataTypesMap, PropertyBEModel, PropertyFEModel, DerivedFEProperty, DerivedFEPropertyMap } from "app/models"; +import { DataTypeModel, DataTypesMap, PropertyFEModel, DerivedFEProperty} from "app/models"; import { DataTypesService } from "app/services/data-types-service"; -import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils"; +import { PROPERTY_DATA } from "app/utils"; /** This is a new service for NG2, to eventually replace app/services/data-types-service.ts * @@ -32,13 +32,16 @@ import { PROPERTY_DATA, PROPERTY_TYPES } from "app/utils"; @Injectable() export class DataTypeService { - private dataTypes: DataTypesMap; + public dataTypes: DataTypesMap; constructor(private dataTypeService: DataTypesService) { this.dataTypes = dataTypeService.getAllDataTypes(); //This should eventually be replaced by an NG2 call to the backend instead of utilizing Angular1 downgraded component. } public getDataTypeByTypeName(typeName: string): DataTypeModel { + if(!this.dataTypes){ + this.dataTypes = this.dataTypeService.getAllDataTypes(); + } if (!this.dataTypes[typeName]) console.log("MISSING Datatype: " + typeName); return this.dataTypes[typeName]; } @@ -47,6 +50,13 @@ export class DataTypeService { return this.dataTypes; } + public getConstraintsByParentTypeAndUniqueID(rootPropertyType, propertyName){ + // const property = this.dataTypes[rootPropertyType].properties.filter(property => + // property.name == propertyName); + // return property[0] && property[0].constraints ? property[0].constraints[0].validValues : null; + return null; + } + public getDerivedDataTypeProperties(dataTypeObj: DataTypeModel, propertiesArray: Array<DerivedFEProperty>, parentName: string) { //push all child properties to array diff --git a/catalog-ui/src/app/ng2/services/event-bus.service.ts b/catalog-ui/src/app/ng2/services/event-bus.service.ts index cc53d7978b..2a15ca25db 100644 --- a/catalog-ui/src/app/ng2/services/event-bus.service.ts +++ b/catalog-ui/src/app/ng2/services/event-bus.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {BasePubSub, IPubSubEvent} from 'sdc-pubsub'; +import {BasePubSub, IPubSubEvent} from "sdc-pubsub"; @Injectable() export class EventBusService extends BasePubSub { diff --git a/catalog-ui/src/app/ng2/services/file-utils.service.ts b/catalog-ui/src/app/ng2/services/file-utils.service.ts new file mode 100644 index 0000000000..57897492b0 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/file-utils.service.ts @@ -0,0 +1,48 @@ +import { Injectable } from "@angular/core"; +import { WindowRef } from "./window.service"; + +@Injectable() +export class FileUtilsService { + constructor(private windowRef: WindowRef){} + + public byteCharactersToBlob = (byteCharacters, contentType):any => { + contentType = contentType || ''; + let sliceSize = 1024; + let bytesLength = byteCharacters.length; + let slicesCount = Math.ceil(bytesLength / sliceSize); + let byteArrays = new Array(slicesCount); + + for (let sliceIndex = 0; sliceIndex < slicesCount; ++sliceIndex) { + let begin = sliceIndex * sliceSize; + let end = Math.min(begin + sliceSize, bytesLength); + + let bytes = new Array(end - begin); + for (let offset = begin, i = 0; offset < end; ++i, ++offset) { + bytes[i] = byteCharacters[offset].charCodeAt(0); + } + byteArrays[sliceIndex] = new Uint8Array(bytes); + } + return new Blob(byteArrays, {type: contentType}); + }; + + public base64toBlob = (base64Data, contentType):any => { + let byteCharacters = atob(base64Data); + return this.byteCharactersToBlob(byteCharacters, contentType); + }; + + public downloadFile = (blob, fileName):void=> { + let url = this.windowRef.nativeWindow.URL.createObjectURL(blob); + let downloadLink = document.createElement("a"); + + downloadLink.setAttribute('href', url); + downloadLink.setAttribute('download', fileName); + document.body.appendChild(downloadLink); + + var clickEvent = new MouseEvent("click", { + "view": window, + "bubbles": true, + "cancelable": true + }); + downloadLink.dispatchEvent(clickEvent); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/gab.service.ts b/catalog-ui/src/app/ng2/services/gab.service.ts index d903d20ade..b62c566dd6 100644 --- a/catalog-ui/src/app/ng2/services/gab.service.ts +++ b/catalog-ui/src/app/ng2/services/gab.service.ts @@ -18,12 +18,13 @@ * ============LICENSE_END========================================================= */ -import {Injectable, Inject} from "@angular/core"; -import {Response} from '@angular/http'; -import {HttpService} from "./http.service"; -import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; -import {Observable} from "rxjs"; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { Observable } from 'rxjs'; +import { ISdcConfig, SdcConfigToken } from '../config/sdc-config.config'; +// tslint:disable-next-line:interface-name export interface IServerResponse { data: [{ [key: string]: string }]; } @@ -36,24 +37,25 @@ export class GabRequest { } } +// tslint:disable-next-line:max-classes-per-file @Injectable() export class GabService { baseUrl: string; gabUrl: string; - constructor(@Inject(SdcConfigToken) sdcConfig: ISdcConfig, private http: HttpService) { + constructor(@Inject(SdcConfigToken) sdcConfig: ISdcConfig, protected http: HttpClient) { this.baseUrl = sdcConfig.api.root; this.gabUrl = sdcConfig.api.POST_GAB_Search; } public getArtifact(artifactUniqueId: string, resourceId: string, columns: string[]): Observable<Response> { - let finalUrl: string = this.baseUrl + this.gabUrl; - let request: GabRequest = { + const finalUrl: string = this.baseUrl + this.gabUrl; + const request: GabRequest = { fields: columns, parentId: resourceId, - artifactUniqueId: artifactUniqueId + artifactUniqueId }; - return this.http.post(finalUrl, request); + return this.http.post<Response>(finalUrl, request); } }
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/groups.service.ts b/catalog-ui/src/app/ng2/services/groups.service.ts index e3b3d85b50..8b2bcd3415 100644 --- a/catalog-ui/src/app/ng2/services/groups.service.ts +++ b/catalog-ui/src/app/ng2/services/groups.service.ts @@ -1,11 +1,11 @@ import {IZoneInstanceAssignment} from '../../models/graph/zones/zone-instance'; import {Injectable, Inject} from "@angular/core"; import {Observable} from "rxjs/Observable"; -import {HttpService} from "./http.service"; import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; import {GroupInstance} from '../../models/graph/zones/group-instance'; import {UiBaseObject} from "../../models/ui-models/ui-base-object"; import {IZoneService} from "../../models/graph/zones/zone"; +import { HttpClient } from '@angular/common/http'; @Injectable() export class GroupsService implements IZoneService { @@ -17,13 +17,13 @@ export class GroupsService implements IZoneService { 'SERVICE': 'services' } - constructor(private http:HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { + constructor(private http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { this.baseUrl = sdcConfig.api.root; } - public createGroupInstance(componentType:string, componentUniqueId:string, groupType:string) { - return this.http.post(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[componentType.toUpperCase()] + '/' + componentUniqueId + '/groups/' + groupType, {}).map(resp => { - return resp.json(); + public createGroupInstance(componentType:string, componentUniqueId:string, groupType:string): Observable<GroupInstance>{ + return this.http.post<GroupInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[componentType.toUpperCase()] + '/' + componentUniqueId + '/groups/' + groupType, {}).map(resp => { + return new GroupInstance(resp); }); }; @@ -40,8 +40,7 @@ export class GroupsService implements IZoneService { } public updateGroupMembers(topologyTemplateType:string, topologyTemplateId:string, groupId:string, members:Array<string>):Observable<Array<string>> { - return this.http.post(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId + '/members', members) - .map(response => response.json()); + return this.http.post<Array<string>>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId + '/members', members); } public updateMembers(topologyTemplateType:string, topologyTemplateId:string, groupId:string, members:Array<UiBaseObject>):Observable<Array<string>> { @@ -50,22 +49,18 @@ export class GroupsService implements IZoneService { } public getSpecificGroup(topologyTemplateType:string, topologyTemplateId:string, groupId:string):Observable<GroupInstance> { - return this.http.get(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId) + return this.http.get<GroupInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId) .map(res => { - return new GroupInstance(res.json()); + return new GroupInstance(res); }); } public updateName(topologyTemplateType:string, topologyTemplateId:string, groupId:string, newName:string):Observable<GroupInstance> { - return this.http.put(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId, {name: newName}).map(resp => { - return resp.json(); - }); + return this.http.put<GroupInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId, {name: newName}); }; public deleteGroup(topologyTemplateType:string, topologyTemplateId:string, groupId:string) { - return this.http.delete(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId).map(resp => { - return resp.json(); - }); + return this.http.delete<GroupInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/groups/' + groupId); }; public updateZoneInstanceAssignments(topologyTemplateType:string, topologyTemplateId:string, policyId:string, members:Array<IZoneInstanceAssignment>):Observable<any> { @@ -75,4 +70,6 @@ export class GroupsService implements IZoneService { public deleteZoneInstance(topologyTemplateType:string, topologyTemplateId:string, policyId:string):Observable<any> { return this.deleteGroup(topologyTemplateType, topologyTemplateId, policyId); }; -}
\ No newline at end of file + + +} diff --git a/catalog-ui/src/app/ng2/services/home.service.ts b/catalog-ui/src/app/ng2/services/home.service.ts new file mode 100644 index 0000000000..c472aa80c1 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/home.service.ts @@ -0,0 +1,74 @@ +/*- + * ============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 { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { Component, IApi, Resource, Service } from 'app/models'; +import { ComponentFactory } from 'app/utils/component-factory'; +import { Observable } from 'rxjs'; +import { ISdcConfig, SdcConfigToken } from '../config/sdc-config.config'; +import { SharingService } from './sharing.service'; + +// tslint:disable-next-line:interface-name +interface IComponentsArray { + services: Service[]; + resources: Resource[]; +} + +@Injectable() +export class HomeService { + private api: IApi; + private smallObjectAttributes = [ + 'uniqueId', 'name', 'componentType', 'resourceType', 'lastUpdateDate', 'lifecycleState', 'distributionStatus', + 'icon', 'version' + ]; + + constructor(private http: HttpClient, + @Inject(SdcConfigToken) private sdcConfig: ISdcConfig, + private sharingService: SharingService, + private componentFactory: ComponentFactory) { + this.api = sdcConfig.api; + } + + public getAllComponents(smallObjects?: boolean): Observable<Component[]> { + return this.http.get<IComponentsArray>(this.api.root + this.api.GET_element) + .map((response) => { + const componentResponse: IComponentsArray = response; + let componentsList: Component[] = []; + + componentResponse.services && componentResponse.services.forEach((serviceResponse: Service) => { + serviceResponse = (smallObjects) ? _.pick(serviceResponse, this.smallObjectAttributes) : serviceResponse; + const component: Service = this.componentFactory.createService(serviceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + componentResponse.resources && componentResponse.resources.forEach((resourceResponse: Resource) => { + resourceResponse = (smallObjects) ? _.pick(resourceResponse, this.smallObjectAttributes) : resourceResponse; + const component: Resource = this.componentFactory.createResource(resourceResponse); + componentsList.push(component); + this.sharingService.addUuidValue(component.uniqueId, component.uuid); + }); + + componentsList = _.orderBy(componentsList, ['lastUpdateDate'], ['desc']); + + return componentsList; + }); + } +} diff --git a/catalog-ui/src/app/ng2/services/http-hepler.service.ts b/catalog-ui/src/app/ng2/services/http-hepler.service.ts new file mode 100644 index 0000000000..2b11067a24 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/http-hepler.service.ts @@ -0,0 +1,31 @@ +import { Injectable, Inject } from "@angular/core"; +import { Dictionary } from "../../utils/dictionary/dictionary"; +import { SharingService } from "../services/sharing.service"; +import { SdcConfigToken, ISdcConfig } from "../config/sdc-config.config"; + + +@Injectable() +export class HttpHelperService { + constructor( private sharingService: SharingService, + @Inject(SdcConfigToken) private sdcConfig: ISdcConfig){} + + public getUuidValue = (url: string): string => { + let map: Dictionary<string, string> = this.sharingService.getUuidMap(); + if (map && url.indexOf(this.sdcConfig.api.root) > 0) { + map.forEach((key: string) => { + if (url.indexOf(key) !== -1) { + return this.sharingService.getUuidValue(key); + } + }); + } + return ''; + } + public static replaceUrlParams(url: string, urlParams: { [index: string]: any }): string { + return url.replace(/:(\w+)/g, (m, p1): string => urlParams[p1] || ''); + } + public static getHeaderMd5 = (object:any):string => { + let componentString:string = JSON.stringify(object); + let md5Result = md5(componentString).toLowerCase(); + return btoa(md5Result); + }; +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/http.service.ts b/catalog-ui/src/app/ng2/services/http.service.ts deleted file mode 100644 index 2785688ace..0000000000 --- a/catalog-ui/src/app/ng2/services/http.service.ts +++ /dev/null @@ -1,100 +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 {Injectable, Inject} from '@angular/core'; -import {Http, XHRBackend, RequestOptions, Request, RequestOptionsArgs, Response, Headers} from '@angular/http'; -import {Observable} from 'rxjs/Observable'; -import {UUID} from 'angular2-uuid'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/catch'; -import 'rxjs/add/observable/throw'; -import {Dictionary} from "../../utils/dictionary/dictionary"; -import {SharingService, CookieService} from "app/services"; -import { ModalService } from "app/ng2/services/modal.service"; -import { ServerErrorResponse } from "app/models"; -import {ErrorMessageComponent} from "../components/ui/modal/error-message/error-message.component"; -import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; - -@Injectable() -export class HttpService extends Http { - - constructor(backend: XHRBackend, options: RequestOptions, private sharingService: SharingService, private cookieService: CookieService, private modalService: ModalService, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { - super(backend, options); - this._defaultOptions.withCredentials = true; - this._defaultOptions.headers.append(cookieService.getUserIdSuffix(), cookieService.getUserId()); - } - - request(request:string|Request, options?:RequestOptionsArgs):Observable<Response> { - /** - * For every request to the server, that the service id, or resource id is sent in the URL, need to pass UUID in the header. - * Check if the unique id exists in uuidMap, and if so get the UUID and add it to the header. - */ - if (typeof request === 'string') { // meaning we have to add the token to the options, not in url - if (!options) { - // make option object - options = {headers: new Headers()}; - } - - var uuidValue = this.getUuidValue(request); - if(uuidValue!= ''){ - options.headers['X-ECOMP-ServiceID'] = uuidValue; - - } - options.headers.set('X-ECOMP-RequestID', UUID.UUID()); - - } else { - // we have to add the token to the url object - var uuidValue = this.getUuidValue((<Request>request).url); - if(uuidValue!= ''){ - request.headers.set('X-ECOMP-ServiceID',uuidValue); - - } - request.headers.set('X-ECOMP-RequestID', UUID.UUID()); - } - return super.request(request, options).catch((err) => this.catchError(err)); - } - - private getUuidValue = (url: string) :string => { - let map:Dictionary<string, string> = this.sharingService.getUuidMap(); - if (map && url.indexOf(this.sdcConfig.api.root) > 0) { - map.forEach((key:string) => { - if (url.indexOf(key) !== -1) { - return this.sharingService.getUuidValue(key); - } - }); - } - return ''; - } - - private catchError = (response: Response): Observable<any> => { - - let modalInstance = this.modalService.createErrorModal("OK"); - let errorResponse: ServerErrorResponse = new ServerErrorResponse(response); - this.modalService.addDynamicContentToModal(modalInstance, ErrorMessageComponent, errorResponse); - modalInstance.instance.open(); - - return Observable.throw(response); - }; - - public static replaceUrlParams(url:string, urlParams:{[index:string]:any}):string { - return url.replace(/:(\w+)/g, (m, p1):string => urlParams[p1] || ''); - } - -} diff --git a/catalog-ui/src/app/ng2/services/modules.service.ts b/catalog-ui/src/app/ng2/services/modules.service.ts new file mode 100644 index 0000000000..857f0718d6 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/modules.service.ts @@ -0,0 +1,34 @@ +import {Inject, Injectable} from "@angular/core"; +import {HttpClient} from "@angular/common/http"; +import {ISdcConfig, SdcConfigToken} from "../config/sdc-config.config"; +import {DisplayModule, Module} from "../../models/modules/base-module"; +import {Observable} from "rxjs/Observable"; +import {ServerTypeUrl} from "../../utils/constants"; + +@Injectable() +export class ModulesService { + + protected baseUrl; + + constructor(private http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + } + + getComponentInstanceModule = (topologyTemplateType: string, topologyTemplateId: string, componentInstanceId: string, moduleId: string):Observable<DisplayModule> => { + return this.http.get<DisplayModule>(this.baseUrl + ServerTypeUrl.toServerTypeUrl(topologyTemplateType) + "/" + topologyTemplateId + "/resourceInstance/" + componentInstanceId + "/groupInstance/" + moduleId) + .map((response) => { + return new DisplayModule(response); + }) + }; + + getModuleForDisplay = (topologyTemplateType: string, topologyTemplateId: string, moduleId: string):Observable<DisplayModule> => { + return this.http.get<DisplayModule>(this.baseUrl + ServerTypeUrl.toServerTypeUrl(topologyTemplateType) + "/" + topologyTemplateId + "/groups/" + moduleId) + .map((response) => { + return new DisplayModule(response); + }) + }; + + public updateModuleMetadata = (topologyTemplateType: string, topologyTemplateId: string, module: Module):Observable<Module> => { + return this.http.put<Module>(this.baseUrl + ServerTypeUrl.toServerTypeUrl(topologyTemplateType) + "/" + topologyTemplateId + "/groups/" + module.uniqueId + "/metadata", module) + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/services/onboarding.service.ts b/catalog-ui/src/app/ng2/services/onboarding.service.ts new file mode 100644 index 0000000000..0ec4875d91 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/onboarding.service.ts @@ -0,0 +1,151 @@ +/** + * Created by rc2122 on 6/4/2018. + */ +/*- + * ============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========================================================= + */ + +'use strict'; +import {Inject, Injectable} from "@angular/core"; +import {SdcConfigToken, ISdcConfig} from "app/ng2/config/sdc-config.config"; +import {Observable} from "rxjs/Observable"; +import { HttpClient, HttpResponse } from "@angular/common/http"; +import { ComponentFactory } from "../../utils/component-factory"; +import { DEFAULT_ICON, ComponentType } from "../../utils/constants"; +import { ICsarComponent } from "../../models/csar-component"; +import { IApi } from "../../models/app-config"; +import { CacheService } from "./cache.service"; +import { IComponentMetadata, ComponentMetadata } from "../../models/component-metadata"; +import { IMainCategory, ISubCategory } from "../../models/category"; +import { Resource } from "../../models/components/resource"; + +export interface OnboardingComponents { + listCount: number; + results: Array<ICsarComponent> +} + +@Injectable() +export class OnboardingService { + private api:IApi; + + constructor(protected http: HttpClient, + private cacheService:CacheService, + @Inject(SdcConfigToken) sdcConfig:ISdcConfig, + private componentFactory: ComponentFactory) { + this.api = sdcConfig.api; + } + + getOnboardingVSPs = (): Observable<Array<ICsarComponent>> =>{ + return this.http.get<OnboardingComponents>(this.api.GET_onboarding).map((onboardingVSPs) =>{ + return onboardingVSPs.results + }); + } + + getOnboardingComponents = ():Observable<Array<IComponentMetadata>> => { + return this.getOnboardingVSPs().map((onboardingComponents) => { + let componentsMetadataList: Array<IComponentMetadata> = new Array(); + onboardingComponents.forEach((obc:ICsarComponent) => { + let componentMetaData: ComponentMetadata = this.createFromCsarComponent(obc); + componentsMetadataList.push(componentMetaData); + }); + return componentsMetadataList; + }); + }; + + public createFromCsarComponent = (csar:ICsarComponent): ComponentMetadata => { + let newMetadata = new ComponentMetadata(); + newMetadata.name = csar.vspName; + + /** + * Onboarding CSAR contains category and sub category that are uniqueId. + * Need to find the category and sub category and extract the name from them. + * First concat all sub categories to one array. + * Then find the selected sub category and category. + * @type {any} + */ + let availableCategories = angular.copy(this.cacheService.get('resourceCategories')); + let allSubs = []; + _.each(availableCategories, (main:IMainCategory)=> { + if (main.subcategories) { + allSubs = allSubs.concat(main.subcategories); + } + }); + + let selectedCategory:IMainCategory = _.find(availableCategories, function (main:IMainCategory) { + return main.uniqueId === csar.category; + }); + + let selectedSubCategory:ISubCategory = _.find(allSubs, (sub:ISubCategory)=> { + return sub.uniqueId === csar.subCategory; + }); + + // Build the categories and sub categories array (same format as component category) + let categories:Array<IMainCategory> = new Array(); + let subcategories:Array<ISubCategory> = new Array(); + if (selectedCategory && selectedSubCategory) { + subcategories.push(selectedSubCategory); + selectedCategory.subcategories = subcategories; + categories.push(selectedCategory); + } + + // Fill the component with details from CSAR + + newMetadata.categories = categories; + newMetadata.vendorName = csar.vendorName; + newMetadata.vendorRelease = csar.vendorRelease; + newMetadata.csarUUID = csar.packageId; + newMetadata.csarPackageType = csar.packageType; + newMetadata.csarVersion = csar.version; + newMetadata.packageId = csar.packageId; + newMetadata.description = csar.description; + newMetadata.selectedCategory = selectedCategory && selectedSubCategory ? selectedCategory.name + "_#_" + selectedSubCategory.name : ''; + newMetadata.filterTerm = newMetadata.name + ' ' + newMetadata.description + ' ' + newMetadata.vendorName + ' ' + newMetadata.csarVersion; + newMetadata.resourceType = "VF"; + newMetadata.componentType = ComponentType.RESOURCE; + newMetadata.tags = []; + newMetadata.icon = DEFAULT_ICON; + newMetadata.iconSprite = "sprite-resource-icons"; + return newMetadata; + }; + + downloadOnboardingCsar = (packageId:string):Observable<HttpResponse<Blob>> => { + return this.http.get(this.api.GET_onboarding + "/" + packageId, {observe: 'response', responseType: 'blob'}); + }; + + getComponentFromCsarUuid = (csarUuid:string):Observable<ComponentMetadata> => { + return this.http.get<ComponentMetadata>(this.api.root + this.api.GET_component_from_csar_uuid.replace(':csar_uuid', csarUuid)) + .map((response: any) => { + // If the status is 400, this means that the component not found. + // I do not want to return error from server, because a popup will appear in client with the error. + // So returning success (200) with status 400. + if (response.status !== 400) { + let componentMetadata = new ComponentMetadata(); + componentMetadata = response; + return componentMetadata; + } + }); + }; + + //TODO remove when workspace page convert to angular5 + convertMetaDataToComponent(componentMetadata: ComponentMetadata) { + let newResource: Resource = <Resource>this.componentFactory.createEmptyComponent(ComponentType.RESOURCE); + newResource.setComponentMetadata(componentMetadata); + return newResource; + } +} diff --git a/catalog-ui/src/app/ng2/services/plugins.service.ts b/catalog-ui/src/app/ng2/services/plugins.service.ts index 2a3b68fe25..0f71eee1f0 100644 --- a/catalog-ui/src/app/ng2/services/plugins.service.ts +++ b/catalog-ui/src/app/ng2/services/plugins.service.ts @@ -1,40 +1,40 @@ -import { Injectable, Inject } from '@angular/core'; +import {Inject, Injectable} from '@angular/core'; import {Observable} from 'rxjs/Observable'; import {Http, Response} from '@angular/http'; -import {IApi, IAppConfigurtaion, Plugin, Plugins, PluginsConfiguration} from "app/models"; +import {IApi, IAppConfigurtaion, Plugin, PluginsConfiguration} from "app/models"; import {ISdcConfig, SdcConfigToken} from "../config/sdc-config.config"; @Injectable() export class PluginsService { - private baseUrl; public configuration: IAppConfigurtaion; public api: IApi; + private baseUrl; - constructor(private http: Http, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { + constructor(private http: Http, @Inject(SdcConfigToken) private sdcConfig: ISdcConfig) { this.api = this.sdcConfig.api; this.baseUrl = this.api.root + this.sdcConfig.api.component_api_root; } public getPluginByStateUrl = (stateUrl: string) => { - let pluginKey: any = _.findKey(PluginsConfiguration.plugins, (pluginConfig: Plugin) =>{ - return pluginConfig.pluginStateUrl === stateUrl; + let pluginKey: any = _.findKey(PluginsConfiguration.plugins, (pluginConfig: Plugin) => { + return pluginConfig.pluginStateUrl === stateUrl; }); return PluginsConfiguration.plugins[pluginKey]; }; - public isPluginDisplayedInContext = (plugin: Plugin ,userRole: string, contextType: string) => { + public isPluginDisplayedInContext = (plugin: Plugin, userRole: string, contextType: string) => { return plugin.pluginDisplayOptions["context"] && - plugin.pluginDisplayOptions["context"].displayRoles.includes(userRole) && - plugin.pluginDisplayOptions["context"].displayContext.indexOf(contextType) !== -1 + plugin.pluginDisplayOptions["context"].displayRoles.includes(userRole) && + plugin.pluginDisplayOptions["context"].displayContext.indexOf(contextType) !== -1 }; public isPluginOnline = (pluginId: string): Observable<boolean> => { - let url:string = this.api.no_proxy_root + this.api.GET_plugin_online_state.replace(':pluginId', pluginId); + let url: string = this.api.no_proxy_root + this.api.GET_plugin_online_state.replace(':pluginId', pluginId); return this.http.get(url).map((res: Response) => { return res.json() }) - .catch(error => Observable.of(false)); + .catch(error => Observable.of(false)); } } diff --git a/catalog-ui/src/app/ng2/services/policies.service.ts b/catalog-ui/src/app/ng2/services/policies.service.ts index 3675a7b9ab..a1a9013303 100644 --- a/catalog-ui/src/app/ng2/services/policies.service.ts +++ b/catalog-ui/src/app/ng2/services/policies.service.ts @@ -20,13 +20,13 @@ import {Injectable, Inject} from "@angular/core"; import {Observable} from "rxjs/Observable"; -import {HttpService} from "./http.service"; import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; import {PolicyInstance, PolicyTargetsRequest} from '../../models/graph/zones/policy-instance'; import {IZoneInstanceAssignment} from "../../models/graph/zones/zone-instance"; import {IZoneService} from "../../models/graph/zones/zone"; import {TargetUiObject} from "../../models/ui-models/ui-target-object"; import {TargetOrMemberType} from "../../utils/constants"; +import { HttpClient } from "@angular/common/http"; @Injectable() @@ -38,14 +38,12 @@ export class PoliciesService implements IZoneService { 'SERVICE': 'services' } - constructor(private http:HttpService, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { + constructor(private http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { this.baseUrl = sdcConfig.api.root; } - public createPolicyInstance(topologyTemplateType:string, topologyTemplateId:string, policyType:string) { - return this.http.post(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyType, {}).map(resp => { - return resp.json(); - }); + public createPolicyInstance(topologyTemplateType:string, topologyTemplateId:string, policyType:string): Observable<PolicyInstance> { + return this.http.post<PolicyInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyType, {}); } public addPolicyTarget(topologyTemplateType:string, topologyTemplateId:string, policy:PolicyInstance, targetId:string, targetType:TargetOrMemberType) { @@ -76,8 +74,8 @@ export class PoliciesService implements IZoneService { } public updatePolicyTargets(topologyTemplateType:string, topologyTemplateId:string, policyId:string, targets:PolicyTargetsRequest): Observable<PolicyInstance> { - return this.http.post(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId + '/targets', targets.requestItems) - .map(response => new PolicyInstance(response.json())); + return this.http.post<PolicyInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId + '/targets', targets.requestItems) + .map(response => new PolicyInstance(response)); } public updateTargets(topologyTemplateType:string, topologyTemplateId:string, policyId:string, targets:Array<TargetUiObject>):Observable<PolicyInstance> { @@ -94,22 +92,18 @@ export class PoliciesService implements IZoneService { } public getSpecificPolicy(topologyTemplateType:string, topologyTemplateId:string, policyId:string):Observable<PolicyInstance> { - return this.http.get(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId) + return this.http.get<PolicyInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId) .map(res => { - return new PolicyInstance(res.json()); + return new PolicyInstance(res); }); } public updateName(topologyTemplateType:string, topologyTemplateId:string, policyId:string, newName:string):Observable<any> { - return this.http.put(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId, {name: newName}).map(res => { - return res.json(); - }); + return this.http.put<PolicyInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId, {name: newName}); }; public deletePolicy(topologyTemplateType:string, topologyTemplateId:string, policyId:string) { - return this.http.delete(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId).map(resp => { - return resp.json(); - }); + return this.http.delete<PolicyInstance>(this.baseUrl + '/v1/catalog/' + this.mapApiDirections[topologyTemplateType.toUpperCase()] + '/' + topologyTemplateId + '/policies/' + policyId); }; public updateZoneInstanceAssignments(topologyTemplateType:string, topologyTemplateId:string, policyId:string, targets:Array<IZoneInstanceAssignment>):Observable<PolicyInstance>{ diff --git a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts index d297ea0874..f161babfa6 100644 --- a/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts +++ b/catalog-ui/src/app/ng2/services/responses/component-generic-response.ts @@ -22,12 +22,15 @@ * Created by ob0695 on 4/18/2017. */ -import { ArtifactGroupModel, PropertyModel, PropertiesGroup, InputsGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel, - InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup, InterfaceModel} from "app/models"; +import { ArtifactGroupModel, PropertyModel, PropertiesGroup, AttributeModel, AttributesGroup, ComponentInstance, OperationModel, + InputBEModel, Module, ComponentMetadata, RelationshipModel, RequirementsGroup, CapabilitiesGroup} from "app/models"; import {CommonUtils} from "app/utils"; import {Serializable} from "../utils/serializable"; +import {PropertyBEModel} from "../../../models/properties-inputs/property-be-model"; import { PolicyInstance } from "app/models/graph/zones/policy-instance"; import { GroupInstance } from "../../../models/graph/zones/group-instance"; +import { InputsGroup } from "../../../models/inputs"; +import { InterfaceModel } from "../../../models/operation"; export class ComponentGenericResponse implements Serializable<ComponentGenericResponse> { @@ -40,7 +43,7 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR public componentInstancesAttributes:AttributesGroup; public componentInstancesRelations:Array<RelationshipModel>; public componentInstances:Array<ComponentInstance>; - public componentInstancesInterfaces:Map<string,Array<InterfaceModel>>; + public componentInstancesInterfaces: Map<string, Array<InterfaceModel>>; public inputs:Array<InputBEModel>; public capabilities:CapabilitiesGroup; public requirements:RequirementsGroup; @@ -75,7 +78,7 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR if(response.deploymentArtifacts) { this.deploymentArtifacts = new ArtifactGroupModel(response.deploymentArtifacts); } - if(response.inputs) { + if(response.inputs) { this.inputs = CommonUtils.initInputs(response.inputs); } if(response.attributes) { @@ -97,10 +100,9 @@ export class ComponentGenericResponse implements Serializable<ComponentGenericR this.toscaArtifacts = new ArtifactGroupModel(response.toscaArtifacts); } if(response.interfaces) { - this.interfaces = CommonUtils.initInterfaces(response.interfaces); this.interfaceOperations = CommonUtils.initInterfaceOperations(response.interfaces); } - if(response.componentInstancesInterfaces) { + if (response.componentInstancesInterfaces) { this.componentInstancesInterfaces = new Map(); for (let resourceId in response.componentInstancesInterfaces) { this.componentInstancesInterfaces[resourceId] = CommonUtils.initInterfaces(response.componentInstancesInterfaces[resourceId]); diff --git a/catalog-ui/src/app/ng2/services/responses/service-generic-response.ts b/catalog-ui/src/app/ng2/services/responses/service-generic-response.ts index d32ed26bb2..0fe85715be 100644 --- a/catalog-ui/src/app/ng2/services/responses/service-generic-response.ts +++ b/catalog-ui/src/app/ng2/services/responses/service-generic-response.ts @@ -2,12 +2,15 @@ import * as _ from "lodash"; import {Serializable} from "../utils/serializable"; import {ComponentGenericResponse} from "./component-generic-response"; import {ForwardingPath} from "../../../models/forwarding-path"; +import {ArtifactGroupModel} from "../../../models/artifacts"; export class ServiceGenericResponse extends ComponentGenericResponse implements Serializable<ServiceGenericResponse> { public forwardingPaths: { [key:string]:ForwardingPath } = {}; + public serviceApiArtifacts: ArtifactGroupModel; deserialize (response): ServiceGenericResponse { super.deserialize(response); + this.serviceApiArtifacts = new ArtifactGroupModel(response.serviceApiArtifacts); if(response.forwardingPaths) { _.forEach(response.forwardingPaths, (pathResponse, id) => { let pathId = id; 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/services/sharing.service.ts index 3639639c88..0a6b8cb1af 100644 --- a/catalog-ui/src/app/ng2/pages/composition/panel/panel-tabs/policies/policy-information-tab.component.ts +++ b/catalog-ui/src/app/ng2/services/sharing.service.ts @@ -18,22 +18,25 @@ * ============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'; +import {Injectable} from "@angular/core"; +import {Dictionary} from "app/utils"; -@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; +@Injectable() +export class SharingService { + private uuidMap:Dictionary<string, string> = new Dictionary<string,string>(); - constructor(private translateService:TranslateService) { + constructor() { } + public getUuidValue(uniqueId:string): string { + return this.uuidMap.getValue(uniqueId); + } + + public addUuidValue(uniqueId:string, uuid:string): void { + this.uuidMap.setValue(uniqueId, uuid); + } + + public getUuidMap() :Dictionary<string, string> { + return this.uuidMap; + } } diff --git a/catalog-ui/src/app/ng2/services/tosca-types.service.ts b/catalog-ui/src/app/ng2/services/tosca-types.service.ts index 66826c0fef..83b833b1ab 100644 --- a/catalog-ui/src/app/ng2/services/tosca-types.service.ts +++ b/catalog-ui/src/app/ng2/services/tosca-types.service.ts @@ -14,12 +14,19 @@ * permissions and limitations under the License. */ -import {Injectable, Inject} from '@angular/core'; -import {Observable} from 'rxjs/Observable'; -import {HttpService} from './http.service'; -import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; -import {CapabilityTypesMap, NodeTypesMap, RelationshipTypesMap} from "app/models"; -import {Response} from '@angular/http'; +import { HttpClient } from '@angular/common/http'; +import { Inject, Injectable } from '@angular/core'; +import { Response } from '@angular/http'; +import { + CapabilityTypeModel, + CapabilityTypesMap, + IComponentsArray, + NodeTypesMap, + RelationshipTypesMap +} from 'app/models'; +import { Observable } from 'rxjs/Observable'; +import { ISdcConfig, SdcConfigToken } from '../config/sdc-config.config'; +import 'rxjs/add/operator/toPromise'; declare var angular: angular.IAngularStatic; @@ -28,28 +35,20 @@ export class ToscaTypesServiceNg2 { protected baseUrl; - constructor(protected http: HttpService, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; } - fetchRelationshipTypes(): Observable<RelationshipTypesMap> { - return this.http.get(this.baseUrl + 'relationshipTypes') - .map((res: Response) => { - return res.json(); - }); + async fetchRelationshipTypes(): Promise<RelationshipTypesMap> { + return this.http.get<RelationshipTypesMap>(this.baseUrl + 'relationshipTypes').toPromise(); } - fetchNodeTypes(): Observable<NodeTypesMap> { - return this.http.get(this.baseUrl + 'nodeTypes') - .map((res: Response) => { - return res.json(); - }); + async fetchNodeTypes(): Promise<NodeTypesMap> { + return this.http.get<NodeTypesMap>(this.baseUrl + 'nodeTypes').toPromise(); } - fetchCapabilityTypes(): Observable<CapabilityTypesMap> { - return this.http.get(this.baseUrl + 'capabilityTypes') - .map((res: Response) => { - return res.json(); - }); + async fetchCapabilityTypes(): Promise<CapabilityTypesMap>{ + return this.http.get<CapabilityTypesMap>(this.baseUrl + 'capabilityTypes').toPromise(); } } + diff --git a/catalog-ui/src/app/ng2/services/user.service.ts b/catalog-ui/src/app/ng2/services/user.service.ts index 87e90432dc..f4186a1087 100644 --- a/catalog-ui/src/app/ng2/services/user.service.ts +++ b/catalog-ui/src/app/ng2/services/user.service.ts @@ -19,80 +19,34 @@ */ import { Injectable, Inject } from "@angular/core"; -import { Headers } from "@angular/http"; import { Observable } from "rxjs/Observable"; -import { HttpService } from "./http.service"; -import { Cookie2Service } from "./cookie.service"; import { IUserProperties } from "../../models/user"; -import {ICookie} from "../../models/app-config"; import {SdcConfigToken, ISdcConfig} from "../config/sdc-config.config"; - +import { HttpClient } from "@angular/common/http"; +import { HttpHelperService } from "./http-hepler.service"; +/** + * User Service provides CRUD for Users. See authentication service for authentication/login. + */ @Injectable() -export class UserService { +export class UserService { private url:string; - private authorizeUrl:string; - private _loggedinUser:IUserProperties; - - constructor(private httpService:HttpService, - private cookieService:Cookie2Service, + constructor(private http: HttpClient, @Inject(SdcConfigToken) private sdcConfig:ISdcConfig) { this.url = this.sdcConfig.api.root + this.sdcConfig.api.GET_user; - this.authorizeUrl = this.sdcConfig.api.root + this.sdcConfig.api.GET_user_authorize; } - public authorize() :Observable<IUserProperties> { - let cookie:ICookie = this.sdcConfig.cookie; - let authorizeHeaders:Headers = new Headers(); - authorizeHeaders.set(cookie.userFirstName, this.cookieService.getFirstName()); - authorizeHeaders.set(cookie.userLastName, this.cookieService.getLastName()); - authorizeHeaders.set(cookie.userEmail, this.cookieService.getEmail()); - authorizeHeaders.set(cookie.userIdSuffix, this.cookieService.getUserId()); - - return this.httpService.get( - this.authorizeUrl, - { headers: authorizeHeaders } - ).map(resp => resp.json()); - } public getAllUsers() :Observable<IUserProperties[]> { - return this.httpService.get( + return this.http.get( this.sdcConfig.api.root + this.sdcConfig.api.GET_all_users - ).map(resp => resp.json()); + ).map(resp => <IUserProperties[]> resp) ; } public getUser(userId:string) :Observable<IUserProperties> { - return this.httpService.get( - HttpService.replaceUrlParams(this.url, { id: userId }) - ).map(resp => resp.json()); - } - - public createUser(userData:{[index:string]: any}) :Observable<IUserProperties> { - return this.httpService.post( - this.sdcConfig.api.root + this.sdcConfig.api.POST_create_user, - userData - ).map(resp => resp.json()); + return this.http.get( + HttpHelperService.replaceUrlParams(this.url, { id: userId }) + ).map(resp => <IUserProperties> resp); } - - public deleteUser(userId:string) :Observable<IUserProperties> { - return this.httpService.delete( - HttpService.replaceUrlParams(this.sdcConfig.api.root + this.sdcConfig.api.DELETE_delete_user, { id: userId }) - ).map(resp => resp.json()); - } - - public editUserRole(userId:string, role:string) :Observable<IUserProperties> { - return this.httpService.post( - HttpService.replaceUrlParams(this.sdcConfig.api.root + this.sdcConfig.api.POST_edit_user_role, { id: userId }), - { role: role } - ).map(resp => resp.json()); - } - - public getLoggedinUser():IUserProperties { - return this._loggedinUser; - } - - public setLoggedinUser(loggedinUser:IUserProperties) { - this._loggedinUser = loggedinUser; - }; } diff --git a/catalog-ui/src/app/ng2/services/workflow.service.ts b/catalog-ui/src/app/ng2/services/workflow.service.ts index 81a2ea3b7f..044ca37266 100644 --- a/catalog-ui/src/app/ng2/services/workflow.service.ts +++ b/catalog-ui/src/app/ng2/services/workflow.service.ts @@ -1,14 +1,13 @@ import { Injectable, Inject } from "@angular/core"; -import { Response } from "@angular/http"; import { Observable } from "rxjs/Observable"; -import { HttpService } from "./http.service"; import { SdcConfigToken, ISdcConfig } from "../config/sdc-config.config"; +import { HttpClient } from "@angular/common/http"; import { Component, OperationModel } from "app/models"; interface WorkflowOutputParameter { - name: string, - type: string, - mandatory: boolean + name: string; + type: string; + mandatory: boolean; } interface WorkflowInputParameter extends WorkflowOutputParameter { @@ -25,40 +24,31 @@ export class WorkflowServiceNg2 { WF_STATE_ARCHIVED = 'ARCHIVED'; VERSION_STATE_CERTIFIED = 'CERTIFIED'; - constructor(private http: HttpService, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + constructor(private http: HttpClient, @Inject(SdcConfigToken) sdcConfig:ISdcConfig) { this.baseUrl = sdcConfig.api.workflow_root; this.catalogBaseUrl = sdcConfig.api.POST_workflow_artifact; } public associateWorkflowArtifact(component: Component, operation: OperationModel): Observable<any> { - return this.http.post(this.baseUrl + '/workflows/' + operation.workflowId + '/versions/' + operation.workflowVersionId + '/artifact-deliveries', { - endpoint: this.catalogBaseUrl + '/' + component.getTypeUrl() + component.uuid + '/interfaces/' + operation.interfaceId + '/operations/' + operation.uniqueId + '/artifacts/' + operation.implementation.artifactUUID, - method: 'POST' - }) - .map((res:Response) => { - return res.json(); + return this.http.post<any>(this.baseUrl + '/workflows/' + operation.workflowId + '/versions/' + operation.workflowVersionId + '/artifact-deliveries', { + endpoint: this.catalogBaseUrl + '/' + component.getTypeUrl() + component.uuid + '/interfaces/' + operation.interfaceId + '/operations/' + + operation.uniqueId + '/artifacts/' + operation.implementation.artifactUUID, method: 'POST' }); } public getWorkflows(filterCertified: boolean = true): Observable<any> { - return this.http.get(this.baseUrl + '/workflows' + (filterCertified ? '?versionState=' + this.VERSION_STATE_CERTIFIED : '')) - .map((res:Response) => { - return res.json().items; - }); + return this.http.get<any>(this.baseUrl + '/workflows' + (filterCertified ? '?versionState=' + this.VERSION_STATE_CERTIFIED : '')); } public getWorkflowVersions(workflowId: string, filterCertified: boolean = true): Observable<any> { - return this.http.get(this.baseUrl + '/workflows/' + workflowId + '/versions' + (filterCertified ? '?state=' + this.VERSION_STATE_CERTIFIED : '')) - .map((res:Response) => { - return _.map(res.json().items, version => version); + return this.http.get<any>(this.baseUrl + '/workflows/' + workflowId + '/versions' + (filterCertified ? '?state=' + this.VERSION_STATE_CERTIFIED : '')) + .map((res) => { + return res.items; }); } public updateWorkflowVersion(workflowId: string, versionId: string, payload: any): Observable<any> { - return this.http.put(this.baseUrl + '/workflows/' + workflowId + '/versions/' + versionId, payload) - .map((res:Response) => { - return res.json(); - }); + return this.http.put<any>(this.baseUrl + '/workflows/' + workflowId + '/versions/' + versionId, payload); } } diff --git a/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.html b/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.html index bee493f2ae..7b9a5ff700 100644 --- a/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.html +++ b/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.html @@ -13,7 +13,6 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> - <div class="multiline-ellipsis-container" [ngClass]="className" [ngStyle]="stylesContainer" #multilineEllipsisContainer> <div class="multiline-ellipsis-content" [ngStyle]="stylesContent" #multilineEllipsisContent> <ng-content></ng-content> diff --git a/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.ts b/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.ts index 68cfedb32b..4cff9de72a 100644 --- a/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.ts +++ b/catalog-ui/src/app/ng2/shared/multiline-ellipsis/multiline-ellipsis.component.ts @@ -1,4 +1,4 @@ -import {Component, OnChanges, AfterViewChecked, ViewChild, ElementRef, Input, Output, SimpleChanges, EventEmitter} from "@angular/core"; +import {Component, OnChanges, AfterContentInit, ViewChild, ElementRef, Input, Output, SimpleChanges, EventEmitter} from "@angular/core"; import {WindowRef} from "../../services/window.service"; @Component({ @@ -6,7 +6,7 @@ import {WindowRef} from "../../services/window.service"; templateUrl: 'multiline-ellipsis.component.html', styleUrls: ['multiline-ellipsis.component.less'] }) -export class MultilineEllipsisComponent implements OnChanges, AfterViewChecked { +export class MultilineEllipsisComponent implements OnChanges, AfterContentInit { @Input() public lines: number; @Input() public lineHeight: string; @@ -30,7 +30,7 @@ export class MultilineEllipsisComponent implements OnChanges, AfterViewChecked { this.prepareStyles() } - public ngAfterViewChecked() { + public ngAfterContentInit() { const hasEllipsis = (this.elmContainer.nativeElement.offsetHeight < this.elmContent.nativeElement.offsetHeight); if (hasEllipsis !== this.hasEllipsis) { this.hasEllipsis = hasEllipsis; diff --git a/catalog-ui/src/app/ng2/shared/translator/translate.pipe.ts b/catalog-ui/src/app/ng2/shared/translator/translate.pipe.ts index 4ec756bcc2..04f6650f38 100644 --- a/catalog-ui/src/app/ng2/shared/translator/translate.pipe.ts +++ b/catalog-ui/src/app/ng2/shared/translator/translate.pipe.ts @@ -19,38 +19,32 @@ */ import { Pipe, PipeTransform } from '@angular/core'; -import { TranslateService, ITranslateArgs } from "./translate.service"; +import { ITranslateArgs, TranslateService } from './translate.service'; +// tslint:disable-next-line:interface-name +interface ITranslateParams { + phrase: string; + args: ITranslateArgs; + language: string; +} @Pipe({ name: 'translate', pure: false }) export class TranslatePipe implements PipeTransform { - private translated:string; - private lastParams: { - phrase: string; - args: {[index: string]: any}; - language: string; - } = { + private translated: string; + private lastParams: ITranslateParams = { phrase: undefined, args: undefined, language: undefined }; - constructor(private translateService:TranslateService) { - } - - private shouldUpdate(curParams:{[index:string]: any}) : boolean { - return ( - curParams.language !== this.lastParams.language || - curParams.args !== this.lastParams.args || - curParams.phrase !== this.lastParams.phrase - ); + constructor(private translateService: TranslateService) { } - public transform(phrase:string, args:ITranslateArgs, language:string=this.translateService.activeLanguage) : string { - const curParams = { phrase, args, language }; + public transform(phrase: string, args: ITranslateArgs, language: string = this.translateService.activeLanguage): string { + const curParams: ITranslateParams = { phrase, args, language }; if (this.shouldUpdate(curParams)) { this.lastParams = curParams; this.translated = this.translateService.translate(phrase, args, language); @@ -58,4 +52,12 @@ export class TranslatePipe implements PipeTransform { return this.translated; } + + private shouldUpdate(curParams: ITranslateParams): boolean { + return ( + curParams.language !== this.lastParams.language || + curParams.args !== this.lastParams.args || + curParams.phrase !== this.lastParams.phrase + ); + } } diff --git a/catalog-ui/src/app/ng2/shared/translator/translate.service.config.ts b/catalog-ui/src/app/ng2/shared/translator/translate.service.config.ts index a1d7833062..6413f6a64e 100644 --- a/catalog-ui/src/app/ng2/shared/translator/translate.service.config.ts +++ b/catalog-ui/src/app/ng2/shared/translator/translate.service.config.ts @@ -18,9 +18,9 @@ * ============LICENSE_END========================================================= */ -import { OpaqueToken } from "@angular/core"; +import { InjectionToken } from "@angular/core"; -export const TranslateServiceConfigToken = new OpaqueToken('TranslateServiceConfigToken'); +export const TranslateServiceConfigToken = new InjectionToken('TranslateServiceConfigToken'); export interface ITranslateServiceConfig { filePrefix:string; diff --git a/catalog-ui/src/app/ng2/shared/translator/translate.service.ts b/catalog-ui/src/app/ng2/shared/translator/translate.service.ts index ff7c643b46..0b5ddae557 100644 --- a/catalog-ui/src/app/ng2/shared/translator/translate.service.ts +++ b/catalog-ui/src/app/ng2/shared/translator/translate.service.ts @@ -19,9 +19,9 @@ */ import { Injectable, Inject } from "@angular/core"; -import { Response, Http } from "@angular/http"; -import { Observable, Observer, ConnectableObservable, Subscription } from "rxjs"; import { ITranslateServiceConfig, TranslateServiceConfigToken } from "./translate.service.config"; +import { Observer, Subscription, Observable, ConnectableObservable } from 'rxjs/Rx'; +import { HttpClient } from "@angular/common/http"; export { ITranslateServiceConfig, TranslateServiceConfigToken }; @@ -145,7 +145,7 @@ export class TranslateService { private _cacheLanguagesJsons:{[index:string]:ITranslateLanguageJson} = {}; private _cacheLanguagesLoaders:{[index:string]:Observable<ITranslateLanguageJson>} = {}; - constructor(@Inject(TranslateServiceConfigToken) private config:ITranslateServiceConfig, private http:Http) { + constructor(@Inject(TranslateServiceConfigToken) private config:ITranslateServiceConfig, private http: HttpClient) { this.initLanguageObservable(); this.loadAndActivateLanguage(this.config.defaultLanguage); } @@ -176,8 +176,7 @@ export class TranslateService { if (!(language in this._cacheLanguagesLoaders)) { const filePath = `${this.config.filePrefix}${language}${this.config.fileSuffix}`; - this._cacheLanguagesLoaders[language] = this.http.get(filePath) - .map<Response, ITranslateLanguageJson>(resp => resp.json()) + this._cacheLanguagesLoaders[language] = this.http.get<ITranslateLanguageJson>(filePath) .catch(() => Observable.throw(`Failed to load language file for "${language}"`)) .publish(); (<ConnectableObservable<ITranslateLanguageJson>>this._cacheLanguagesLoaders[language]).connect(); @@ -204,12 +203,12 @@ export class TranslateService { return false; } - public loadAndActivateLanguage(language:string) : Observable<ITranslateLanguageJson> { + public loadAndActivateLanguage(language:string) : void { + const loadLanguageObservable = this.loadLanguageJsonFile(language, false); loadLanguageObservable.subscribe(() => { this.activateLanguage(language); }, () => {}); - return loadLanguageObservable; } public translate(phraseKey:string, args:ITranslateArgs={}, language:string=this._activeLanguage) : string { diff --git a/catalog-ui/src/app/ng2/store/actions/artifacts.action.ts b/catalog-ui/src/app/ng2/store/actions/artifacts.action.ts new file mode 100644 index 0000000000..a00cc3a9ec --- /dev/null +++ b/catalog-ui/src/app/ng2/store/actions/artifacts.action.ts @@ -0,0 +1,25 @@ +/** + * Created by ob0695 + */ +import {ArtifactModel} from "../../../models/artifacts"; + +export class GetArtifactsByTypeAction { + static readonly type = '[ARTIFACTS] GetArtifactsByType'; + + constructor(public payload: {componentType:string, componentId:string, artifactType: string}) { + } +} + +export class CreateOrUpdateArtifactAction { + static readonly type = '[ARTIFACTS] CreateOrUpdateArtifactAction'; + + constructor(public payload: {componentType:string, componentId:string, artifact:ArtifactModel}) { + } +} + +export class DeleteArtifactAction { + static readonly type = '[ARTIFACTS] DeleteArtifactAction'; + + constructor(public payload: {componentType:string, componentId:string, artifact: ArtifactModel}) { + } +} diff --git a/catalog-ui/src/app/ng2/store/actions/instance-artifacts.actions.ts b/catalog-ui/src/app/ng2/store/actions/instance-artifacts.actions.ts new file mode 100644 index 0000000000..0f1df78352 --- /dev/null +++ b/catalog-ui/src/app/ng2/store/actions/instance-artifacts.actions.ts @@ -0,0 +1,32 @@ +/** + * Created by ob0695 + */ +import {ArtifactModel} from "../../../models/artifacts"; + +export class GetInstanceArtifactsByTypeAction { + static readonly type = '[INSTANCE_ARTIFACTS] GetInstanceArtifactsByTypeAction'; + + constructor(public payload: { componentType: string, componentId: string, artifactType: string, instanceId: string }) { + } +} + +export class CreateInstanceArtifactAction { + static readonly type = '[INSTANCE_ARTIFACTS] CreateInstanceArtifactAction'; + + constructor(public payload: { componentType: string, componentId: string, instanceId: string, artifact: ArtifactModel }) { + } +} + +export class UpdateInstanceArtifactAction { + static readonly type = '[INSTANCE_ARTIFACTS] UpdateInstanceArtifactAction'; + + constructor(public payload: { componentType: string, componentId: string, instanceId: string, artifact: ArtifactModel }) { + } +} + +export class DeleteInstanceArtifactAction { + static readonly type = '[INSTANCE_ARTIFACTS] DeleteInstanceArtifactAction'; + + constructor(public payload: { componentType: string, componentId: string, instanceId: string, artifact: ArtifactModel }) { + } +} diff --git a/catalog-ui/src/app/ng2/store/actions/workspace.action.ts b/catalog-ui/src/app/ng2/store/actions/workspace.action.ts new file mode 100644 index 0000000000..c7f18e0ac6 --- /dev/null +++ b/catalog-ui/src/app/ng2/store/actions/workspace.action.ts @@ -0,0 +1,17 @@ +/** + * Created by ob0695 on 7/17/2018. + */ + +export class UpdateIsViewOnly { + static readonly type = '[WORKSPACE] UpdateIsViewOnly'; + + constructor(public isViewOnly:boolean) { + } +} + +export class UpdateIsDesigner { + static readonly type = '[WORKSPACE] UpdateIsDesigner'; + + constructor(public isDesigner:boolean) { + } +} diff --git a/catalog-ui/src/app/ng2/store/states/artifacts.state.spec.ts b/catalog-ui/src/app/ng2/store/states/artifacts.state.spec.ts new file mode 100644 index 0000000000..c59a4455b6 --- /dev/null +++ b/catalog-ui/src/app/ng2/store/states/artifacts.state.spec.ts @@ -0,0 +1,62 @@ +import { Store } from '@ngxs/store'; +import { Observable } from 'rxjs/Rx'; +import { Mock } from 'ts-mockery'; +import { ArtifactModel } from '../../../models/artifacts'; +import { ArtifactGroupType } from '../../../utils/constants'; +import { ComponentInstanceServiceNg2 } from '../../services/component-instance-services/component-instance.service'; +import { GetInstanceArtifactsByTypeAction, UpdateInstanceArtifactAction } from '../actions/instance-artifacts.actions'; +import { InstanceArtifactsState } from './instance-artifacts.state'; + +describe('Test Artifact State', () => { + + const heat1 = Mock.of<ArtifactModel>({ + uniqueId: '1', artifactName: 'heat1', timeout: 0, artifactDisplayName: 'heat1', artifactGroupType: ArtifactGroupType.DEPLOYMENT + }); + + const heat1env = Mock.of<ArtifactModel>({ + uniqueId: '2', artifactName: 'heat1env', timeout: 0, generatedFromId: '1', artifactDisplayName: 'heat1env', artifactGroupType: ArtifactGroupType.DEPLOYMENT + }); + + const storeMock = Mock.of<Store>( { dispatch : jest.fn() }); + + const artifacts = [ + heat1, + heat1env + ]; + + /** + * NGXS Store state before we run the update + */ + const ngxsState = { + deploymentArtifacts : artifacts + }; + + /** + * The ENV artifact that we wish to update + */ + const updatedArtifact = Mock.of<ArtifactModel>({ + uniqueId: '2', artifactName: 'heat1env', timeout: 33, generatedFromId: '1', artifactDisplayName: 'heat1env-UPDATE', artifactGroupType: ArtifactGroupType.DEPLOYMENT + }); + + const componentInstanceServiceMock: ComponentInstanceServiceNg2 = Mock.of<ComponentInstanceServiceNg2>({ + updateInstanceArtifact: jest.fn().mockImplementation(() => Observable.of(updatedArtifact)), + getComponentInstanceArtifactsByGroupType: jest.fn().mockImplementation(() => Observable.of([heat1, updatedArtifact])) + }); + + const actionMock: UpdateInstanceArtifactAction = Mock.of<UpdateInstanceArtifactAction>({ + payload: { + componentType: '', + componentId: '', + instanceId: '', + artifact: updatedArtifact + } + }); + + it('Test that HEAT timeout is updated', () => { + const state: InstanceArtifactsState = new InstanceArtifactsState(storeMock, componentInstanceServiceMock); + const context = { getState: jest.fn().mockImplementation(() => ngxsState), patchState: jest.fn(), setState: jest.fn(), dispatch: jest.fn() }; + state.updateArtifact(context, actionMock ).subscribe( (v) => console.log('OK')); + expect(storeMock.dispatch).toBeCalled(); + }); + +}); diff --git a/catalog-ui/src/app/ng2/store/states/artifacts.state.ts b/catalog-ui/src/app/ng2/store/states/artifacts.state.ts new file mode 100644 index 0000000000..64efbe96a9 --- /dev/null +++ b/catalog-ui/src/app/ng2/store/states/artifacts.state.ts @@ -0,0 +1,140 @@ +/** + * Created by ob0695 on 7/17/2018. + */ +import { Action, Selector, State, StateContext } from '@ngxs/store'; +import * as _ from 'lodash'; +import { tap } from 'rxjs/operators'; +import { ArtifactModel } from '../../../models/artifacts'; +import { ArtifactGroupType } from '../../../utils/constants'; +import { TopologyTemplateService } from '../../services/component-services/topology-template.service'; +import { ComponentGenericResponse } from '../../services/responses/component-generic-response'; +import { ServiceGenericResponse } from '../../services/responses/service-generic-response'; +import { CreateOrUpdateArtifactAction, DeleteArtifactAction, GetArtifactsByTypeAction } from '../actions/artifacts.action'; + +export interface ArtifactsStateModel { + artifacts: ArtifactModel[]; + deploymentArtifacts: ArtifactModel[]; + toscaArtifacts: ArtifactModel[]; + serviceApiArtifacts: ArtifactModel[]; +} + +@State<ArtifactsStateModel>({ + name: 'artifacts', + defaults: { + artifacts: [], + deploymentArtifacts: [], + toscaArtifacts: [], + serviceApiArtifacts: [] + } +}) + +export class ArtifactsState { + + constructor(protected topologyTemplateService: TopologyTemplateService) { + } + + @Selector() + static getEnvArtifact(state: ArtifactsStateModel, heatEnvArtifact: ArtifactModel) { + return (heatEnvArtifact: ArtifactModel) => { + _.find(state.deploymentArtifacts, (artifact)=> { + return artifact.generatedFromId === heatEnvArtifact.uniqueId + }) + }; + } + + @Selector() + static getArtifactsByType(state: ArtifactsStateModel, type: string) { + return (type: string) => { + switch (type) { + case ArtifactGroupType.TOSCA: + return state.toscaArtifacts; + case ArtifactGroupType.INFORMATION: + return state.artifacts; + case ArtifactGroupType.DEPLOYMENT: + return state.deploymentArtifacts; + case ArtifactGroupType.SERVICE_API: + return state.serviceApiArtifacts; + } + }; + } + + private updateArtifactState = (artifactsState: ArtifactModel[], artifactToUpdate: ArtifactModel, updatedArtifact: ArtifactModel) => { + if (!artifactToUpdate.uniqueId) { // Create Artifact + return [...artifactsState, updatedArtifact] + } else { // Update Artifact + let artifactToUpdateIndex = _.findIndex(artifactsState, (artifact) => { + return artifact.uniqueId === artifactToUpdate.uniqueId + }) + let artifacts = Array.from(artifactsState); + artifacts[artifactToUpdateIndex] = updatedArtifact; + return [...artifacts]; + } + } + + @Action(GetArtifactsByTypeAction) + getArtifactsByType({getState, patchState}: StateContext<ArtifactsStateModel>, action: GetArtifactsByTypeAction) { + const state = getState(); + return this.topologyTemplateService.getArtifactsByType(action.payload.componentType, action.payload.componentId, action.payload.artifactType) + .pipe(tap((resp: ComponentGenericResponse) => { + switch (action.payload.artifactType) { + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: <ArtifactModel[]>_.values(resp.artifacts) + }); + + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: <ArtifactModel[]>_.values(resp.deploymentArtifacts) + }); + + case ArtifactGroupType.TOSCA: + patchState({ + toscaArtifacts: <ArtifactModel[]>_.values(resp.toscaArtifacts) + }); + + case ArtifactGroupType.SERVICE_API: + patchState({ + serviceApiArtifacts: <ArtifactModel[]>_.values((<ServiceGenericResponse>resp).serviceApiArtifacts) + }); + } + })); + } + + @Action(CreateOrUpdateArtifactAction) + createOrUpdateArtifact({getState, patchState}: StateContext<ArtifactsStateModel>, action: CreateOrUpdateArtifactAction) { + const state = getState(); + return this.topologyTemplateService.addOrUpdateArtifact(action.payload.componentType, action.payload.componentId, action.payload.artifact) + .pipe(tap((resp: ArtifactModel) => { + + switch (resp.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: this.updateArtifactState(state.deploymentArtifacts, action.payload.artifact, resp) + }); + + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: this.updateArtifactState(state.artifacts, action.payload.artifact, resp) + }); + } + })); + } + + @Action(DeleteArtifactAction) + deleteArtifact({getState, patchState}: StateContext<ArtifactsStateModel>, action: DeleteArtifactAction) { + const state = getState(); + return this.topologyTemplateService.deleteArtifact(action.payload.componentId, action.payload.componentType, action.payload.artifact.uniqueId, action.payload.artifact.artifactLabel) + .pipe(tap((resp: ArtifactModel) => { + switch (resp.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: state.deploymentArtifacts.filter(({uniqueId}) => uniqueId !== action.payload.artifact.uniqueId) + }); + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: state.artifacts.filter(({uniqueId}) => uniqueId !== action.payload.artifact.uniqueId) + }); + } + })); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/store/states/instance-artifacts.state.ts b/catalog-ui/src/app/ng2/store/states/instance-artifacts.state.ts new file mode 100644 index 0000000000..12ba1ae7ba --- /dev/null +++ b/catalog-ui/src/app/ng2/store/states/instance-artifacts.state.ts @@ -0,0 +1,145 @@ +/** + * Created by ob0695 on 7/17/2018. + */ +import { Action, Selector, State, StateContext, Store } from '@ngxs/store'; +import * as _ from 'lodash'; +import { tap } from 'rxjs/operators'; +import { ArtifactModel } from '../../../models/artifacts'; +import { ArtifactGroupType } from '../../../utils/constants'; +import { ComponentInstanceServiceNg2 } from '../../services/component-instance-services/component-instance.service'; +import { ComponentGenericResponse } from '../../services/responses/component-generic-response'; +import { + CreateInstanceArtifactAction, + DeleteInstanceArtifactAction, + GetInstanceArtifactsByTypeAction, + UpdateInstanceArtifactAction +} from '../actions/instance-artifacts.actions'; +import { ArtifactsStateModel } from './artifacts.state'; + +export interface InstanceArtifactsStateModel { + artifacts: ArtifactModel[]; + deploymentArtifacts: ArtifactModel[]; +} + +@State<InstanceArtifactsStateModel>({ + name: 'instance_artifacts', + defaults: { + artifacts: [], + deploymentArtifacts: [] + } +}) +export class InstanceArtifactsState { + + constructor(private store: Store, protected componentInstanceService: ComponentInstanceServiceNg2) { + } + + @Selector() + static getArtifactsByType(state: InstanceArtifactsStateModel) { + return (type: string) => { + switch (type) { + case ArtifactGroupType.INFORMATION: + return state.artifacts; + case ArtifactGroupType.DEPLOYMENT: + return state.deploymentArtifacts; + } + }; + } + + @Action(GetInstanceArtifactsByTypeAction) + getInstanceArtifactsByType({getState, patchState}: StateContext<InstanceArtifactsStateModel>, action: GetInstanceArtifactsByTypeAction) { + const state = getState(); + return this.componentInstanceService.getComponentInstanceArtifactsByGroupType(action.payload.componentType, action.payload.componentId, action.payload.instanceId, action.payload.artifactType) + .pipe(tap((resp: ComponentGenericResponse) => { + switch (action.payload.artifactType) { + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: _.values(resp) as ArtifactModel[] + }); + break; + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: _.values(resp) as ArtifactModel[] + }); + break; + } + })); + } + + @Action(CreateInstanceArtifactAction) + createArtifact({getState, patchState}: StateContext<ArtifactsStateModel>, action: CreateInstanceArtifactAction) { + const state = getState(); + return this.componentInstanceService.addInstanceArtifact(action.payload.componentType, action.payload.componentId, action.payload.instanceId, action.payload.artifact) + .pipe(tap((resp: ArtifactModel) => { + switch (resp.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: [...state.deploymentArtifacts, resp] + }); + break; + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: [...state.artifacts, resp] + }); + break; + } + })); + } + + @Action(UpdateInstanceArtifactAction) + updateArtifact({getState, patchState}: StateContext<ArtifactsStateModel>, action: UpdateInstanceArtifactAction) { + const state = getState(); + return this.componentInstanceService.updateInstanceArtifact(action.payload.componentType, action.payload.componentId, action.payload.instanceId, action.payload.artifact) + .pipe(tap((resp: ArtifactModel) => { + switch (resp.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + // We cannot simply update the updated artifact state because updating a deployment ENV file may cause an update to his parent HEAT + // file. + // Just dispatch an action to refresh the deployment artifacts list + this.store.dispatch(new GetInstanceArtifactsByTypeAction(({ + componentType: action.payload.componentType, + componentId: action.payload.componentId, + instanceId: action.payload.instanceId, + artifactType: ArtifactGroupType.DEPLOYMENT + }))); + break; + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: this.updateInstanceArtifactState(state.artifacts, action.payload.artifact, resp) + }); + break; + } + })); + } + + @Action(DeleteInstanceArtifactAction) + deleteInstanceArtifact({getState, patchState}: StateContext<ArtifactsStateModel>, action: DeleteInstanceArtifactAction) { + const state = getState(); + return this.componentInstanceService. + deleteInstanceArtifact(action.payload.componentId, action.payload.componentType, action.payload.instanceId, action.payload.artifact.uniqueId, action.payload.artifact.artifactLabel) + .pipe(tap((resp: ArtifactModel) => { + switch (resp.artifactGroupType) { + case ArtifactGroupType.DEPLOYMENT: + patchState({ + deploymentArtifacts: state.deploymentArtifacts.filter(({uniqueId}) => uniqueId !== action.payload.artifact.uniqueId) + }); + break; + case ArtifactGroupType.INFORMATION: + patchState({ + artifacts: state.artifacts.filter(({uniqueId}) => uniqueId !== action.payload.artifact.uniqueId) + }); + break; + } + })); + } + + private updateInstanceArtifactState = (artifactsState: ArtifactModel[], artifactToUpdate: ArtifactModel, updatedArtifact: ArtifactModel) => { + const artifactToUpdateIndex = _.findIndex(artifactsState, (artifact) => { + return artifact.uniqueId === artifactToUpdate.uniqueId; + }); + const artifacts = Array.from(artifactsState); + artifacts[artifactToUpdateIndex] = updatedArtifact; + const ret = [...artifacts]; + return ret; + } + +} diff --git a/catalog-ui/src/app/ng2/store/states/workspace.state.ts b/catalog-ui/src/app/ng2/store/states/workspace.state.ts new file mode 100644 index 0000000000..eb8200f6e0 --- /dev/null +++ b/catalog-ui/src/app/ng2/store/states/workspace.state.ts @@ -0,0 +1,48 @@ +/** + * Created by ob0695 on 7/17/2018. + */ +import {State, Action, StateContext} from '@ngxs/store'; +import {UpdateIsDesigner, UpdateIsViewOnly} from "../actions/workspace.action"; +import {Selector} from "@ngxs/store"; + +export interface WorkspaceStateModel { + isViewOnly: boolean; + isDesigner: boolean; +} + +@State<WorkspaceStateModel>({ + name: 'workspace', + defaults: { + isViewOnly: false, + isDesigner: true + } +}) + +export class WorkspaceState { + + constructor(){} + + @Selector() static isViewOnly(state: WorkspaceStateModel):boolean { + return state.isViewOnly; + } + @Selector() static isDesigner(state: WorkspaceStateModel): boolean { + return state.isDesigner; + } + + @Action(UpdateIsViewOnly) + updateIsViewOnly({getState, setState}: StateContext<WorkspaceStateModel>, action:UpdateIsViewOnly) { + const state = getState(); + setState({ + ...state, + isViewOnly: action.isViewOnly + }); + } + + @Action(UpdateIsDesigner) + updateIsDesigner({getState, patchState}: StateContext<WorkspaceStateModel>, action:UpdateIsDesigner) { + const state = getState(); + patchState({ + isDesigner: action.isDesigner + }); + } +}
\ No newline at end of file diff --git a/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts index fcb21c0c83..aab531d205 100644 --- a/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts +++ b/catalog-ui/src/app/ng2/utils/ng1-upgraded-provider.ts @@ -23,13 +23,12 @@ */ import { DataTypesService } from "../../services/data-types-service"; import ICacheObject = angular.ICacheObject; -import { SharingService } from "../../services/sharing-service"; import { CookieService } from "../../services/cookie-service"; -import { CacheService } from "../../services/cache-service"; import {ComponentFactory} from "../../utils/component-factory" import { EventListenerService } from "app/services/event-listener-service"; import { ModalsHandler } from "app/utils"; import IScope = angular.IScope; +import { SharingService } from "../services/sharing.service"; /** Services we need to upgrade from angular1 to angular2 - in the future we need to rewrite them all to angular2 **/ @@ -61,10 +60,6 @@ export function scopeServiceFactory(cacheObj: ICacheObject) { return cacheObj.get('$scope'); } -export function cacheServiceFactory(cacheObj: ICacheObject) { - return cacheObj.get('Sdc.Services.CacheService'); -} - export function eventListenerServiceServiceFactory(cacheObj: ICacheObject) { return cacheObj.get('EventListenerService'); } @@ -73,6 +68,10 @@ export function notificationServiceFactory(cacheObj: ICacheObject) { return cacheObj.get('Notification'); } +export function ModalsHandlerFactory(cacheObj: ICacheObject) { + return cacheObj.get('ModalsHandler'); +} + export const ComponentFactoryProvider = { provide: ComponentFactory, @@ -80,18 +79,12 @@ export const ComponentFactoryProvider = { deps: ['$injector'] }; - -export function ModalsHandlerFactory(cacheObj: ICacheObject) { - return cacheObj.get('ModalsHandler'); -} - export const DataTypesServiceProvider = { provide: DataTypesService, useFactory: dataTypesServiceFactory, deps: ['$injector'] }; - export const SharingServiceProvider = { provide: SharingService, useFactory: sharingServiceFactory, @@ -122,17 +115,12 @@ export const StateParamsServiceFactory = { useFactory: stateParamsServiceFactory, deps: ['$injector'] }; -export const CacheServiceProvider = { - provide: CacheService, - useFactory: cacheServiceFactory, - deps: ['$injector'] -}; - -export const EventListenerServiceProvider = { - provide: EventListenerService, - useFactory: eventListenerServiceServiceFactory, - deps: ['$injector'] -}; +// +// export const EventListenerServiceProvider = { +// provide: EventListenerService, +// useFactory: eventListenerServiceServiceFactory, +// deps: ['$injector'] +// }; export const NotificationServiceProvider = { provide: 'Notification', @@ -144,4 +132,4 @@ export const ModalsHandlerProvider = { provide: ModalsHandler, useFactory: ModalsHandlerFactory, deps: ['$injector'] -} +}; diff --git a/catalog-ui/src/app/ng2/utils/queue-service-utils.ts b/catalog-ui/src/app/ng2/utils/queue-service-utils.ts new file mode 100644 index 0000000000..8cf7f98383 --- /dev/null +++ b/catalog-ui/src/app/ng2/utils/queue-service-utils.ts @@ -0,0 +1,58 @@ +/** + * Created by ob0695 on 6/3/2018. + */ +/*- + * ============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 {Injectable} from "@angular/core"; + +@Injectable() +export class QueueServiceUtils { + + private executionQueue:any; + + constructor() { + if(!this.executionQueue) { + this.executionQueue = this.getQueue(); + } + } + + private getQueue = () => new Promise((resolve, reject) => { + resolve(true); + }); + + private addMethodToQueue = (runMe:Function):void => { + this.executionQueue = this.executionQueue.then(runMe, runMe); + }; + + addNonBlockingUIAction = (update:Function, releaseUIcallBack?:Function):void => { + // releaseUIcallBack(); + this.addMethodToQueue(update); + }; + + // The Method call is responsible for releasing the UI + addBlockingUIAction = (blockingServerRequest:Function):void => { + this.addMethodToQueue(blockingServerRequest); + }; + + addBlockingUIActionWithReleaseCallback = (blockingServerRequest:Function, releaseUIcallBack:Function):void=> { + this.addMethodToQueue(blockingServerRequest); + // this.addMethodToQueue(releaseUIcallBack); + }; +} |