From 39b533344f0a86401f5c41025cfdcf3139934569 Mon Sep 17 00:00:00 2001 From: "andre.schmid" Date: Thu, 15 Apr 2021 18:47:55 +0100 Subject: Fix VSP update for checked-in resources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Checkout the VF (if checked-in) related to the VSP before loading the VF workspace. Change-Id: I9576fd5b429fdae2ac00de5bfbd38e183b93be59 Issue-ID: SDC-3560 Signed-off-by: André Schmid --- .../app/models/component-lifecycle-state.enum.ts | 30 ++++++++++ .../modals/onboarding-modal/import-vsp.service.ts | 5 +- .../onboarding-modal/onboarding-modal.component.ts | 11 +++- .../home/__snapshots__/home.component.spec.ts.snap | 1 + .../src/app/ng2/pages/home/home.component.spec.ts | 33 +++++------ .../src/app/ng2/pages/home/home.component.ts | 66 +++++++++++++++------- catalog-ui/src/app/ng2/pages/home/home.module.ts | 3 +- .../component-services/resource.service.spec.ts | 48 ++++++++++++++++ .../component-services/resource.service.ts | 29 +++++++--- .../workspace/tabs/general/general-view-model.ts | 39 ++++++------- 10 files changed, 192 insertions(+), 73 deletions(-) create mode 100644 catalog-ui/src/app/models/component-lifecycle-state.enum.ts create mode 100644 catalog-ui/src/app/ng2/services/component-services/resource.service.spec.ts diff --git a/catalog-ui/src/app/models/component-lifecycle-state.enum.ts b/catalog-ui/src/app/models/component-lifecycle-state.enum.ts new file mode 100644 index 0000000000..2861e43c8e --- /dev/null +++ b/catalog-ui/src/app/models/component-lifecycle-state.enum.ts @@ -0,0 +1,30 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +export enum ComponentLifecycleState { + CHECKOUT = 'checkout', + CHECKIN = 'checkin', + CERTIFICATION_REQUEST = 'certificationRequest', + UNDO_CHECKOUT = 'undoCheckout', + CANCEL_CERTIFICATION = 'cancelCertification', + START_CERTIFICATION = 'startCertification', + FAIL_CERTIFICATION = 'failCertification', + CERTIFY = 'certify', + DISTRIBUTE = 'distribute' +} 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 index 8e7364660f..ad80bcda49 100644 --- 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 @@ -1,8 +1,7 @@ import { Injectable, Inject } from "@angular/core"; -import { OnboardingModalComponent } from "./onboarding-modal.component"; +import {ImportVSPdata, 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"; @@ -25,7 +24,7 @@ export class ImportVSPService { } as SdcUiCommon.IModalConfig; const onboardingModalInstance = this.modalService.openCustomModal(onboardingModalConfig, OnboardingModalComponent, {currentCsarUUID: csarUUID, currentCsarVersion: csarVersion}); onboardingModalInstance.innerModalContent.instance.closeModalEvent.subscribe( - (result: any) => { + (result: ImportVSPdata) => { subject.next(result); onboardingModalInstance.closeModal(); }, (err) =>{} 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 index 10d7f67860..1e07c8b2ef 100644 --- 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 @@ -18,6 +18,12 @@ export interface ImportVSPdata { componentCsar: Resource; previousComponent?: Resource; type: string; + actionType: ImportVSPActionType; +} + +export enum ImportVSPActionType { + IMPORT_VSP, + UPDATE_VSP } // tslint:disable-next-line:interface-name @@ -118,13 +124,14 @@ export class OnboardingModalComponent implements OnInit { } importOrUpdateCsar = (): void => { - const selectedComponentConverted = this.onBoardingService.convertMetaDataToComponent(this.selectedComponent); + const selectedComponentConverted: Resource = 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() + type: ComponentType.RESOURCE.toLowerCase(), + actionType: this.isCsarComponentExists ? ImportVSPActionType.UPDATE_VSP : ImportVSPActionType.IMPORT_VSP }; this.closeModalEvent.emit(importVSPdata); } 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 index ae5445e546..42686c1567 100644 --- 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 @@ -13,6 +13,7 @@ exports[`home component should match current snapshot 1`] = ` loaderService={[Function Object]} modalService={[Function Object]} modalsHandler={[Function Object]} + resourceService={[Function Object]} sdcConfig={[Function Object]} sdcMenu={[Function Object]} translateService={[Function Object]} 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 index df854024fa..1c03790e04 100644 --- a/catalog-ui/src/app/ng2/pages/home/home.component.spec.ts +++ b/catalog-ui/src/app/ng2/pages/home/home.component.spec.ts @@ -1,22 +1,17 @@ +import {SdcConfigToken} from "../../config/sdc-config.config"; +import {SdcMenuToken} from "../../config/sdc-menu.config"; -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 {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 {TranslateService} from "../../shared/translator/translate.service"; +import {AuthenticationService, CacheService, HomeService, ImportVSPService, ResourceServiceNg2} 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"; - - +import {HomeFilter} from "../../../models/home-filter"; describe('home component', () => { @@ -33,6 +28,7 @@ describe('home component', () => { let homeFilterMock :Partial; let foldersMock; let loaderServiceMock; + let resourceServiceNg2Mock: Partial; beforeEach( @@ -62,7 +58,11 @@ describe('home component', () => { loaderServiceMock = { activate: jest.fn(), deactivate: jest.fn() - } + }; + + resourceServiceNg2Mock = { + checkout: jest.fn() + }; const configure: ConfigureFn = testBed => { testBed.configureTestingModule({ @@ -80,7 +80,8 @@ describe('home component', () => { {provide: ModalsHandler, useValue: {}}, {provide: SdcUiServices.ModalService, useValue: modalServiceMock}, {provide: SdcUiServices.LoaderService, useValue: loaderServiceMock}, - {provide: ImportVSPService, useValue: {}} + {provide: ImportVSPService, useValue: {}}, + {provide: ResourceServiceNg2, useValue: resourceServiceNg2Mock} ], }); }; diff --git a/catalog-ui/src/app/ng2/pages/home/home.component.ts b/catalog-ui/src/app/ng2/pages/home/home.component.ts index 77fd3b5ae4..f0e8815d93 100644 --- a/catalog-ui/src/app/ng2/pages/home/home.component.ts +++ b/catalog-ui/src/app/ng2/pages/home/home.component.ts @@ -18,19 +18,20 @@ * ============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'; +import {Component as NgComponent, Inject, OnInit} from '@angular/core'; +import {Component, ComponentMetadata, IConfigRoles, IUserProperties, Resource} from 'app/models'; +import {HomeFilter} from 'app/models/home-filter'; +import {AuthenticationService, CacheService, HomeService, ResourceServiceNg2} from 'app/services-ng2'; +import {ComponentState, 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'; +import {ImportVSPdata} from "../../components/modals/onboarding-modal/onboarding-modal.component"; @NgComponent({ selector: 'home-page', @@ -63,8 +64,9 @@ export class HomeComponent implements OnInit { private modalsHandler: ModalsHandler, private modalService: SdcUiServices.ModalService, private loaderService: SdcUiServices.LoaderService, - private importVSPService: ImportVSPService - ) {} + private importVSPService: ImportVSPService, + private resourceService: ResourceServiceNg2 + ) { } ngOnInit(): void { this.initHomeComponentVars(); @@ -90,16 +92,38 @@ export class HomeComponent implements OnInit { // 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.importVSPService.openOnboardingModal().subscribe((importVSPdata: ImportVSPdata) => { + const actualComponent = importVSPdata.previousComponent; + if (!actualComponent || actualComponent.csarVersion !== importVSPdata.componentCsar.csarVersion) { + this.cacheService.set(CHANGE_COMPONENT_CSAR_VERSION_FLAG, importVSPdata.componentCsar.csarVersion); } + const vfExistsAndIsNotCheckedOut: boolean = actualComponent && actualComponent.lifecycleState != ComponentState.NOT_CERTIFIED_CHECKOUT; + if (vfExistsAndIsNotCheckedOut) { + this.checkoutAndRedirectToWorkspace(importVSPdata); + return; + } + this.$state.go('workspace.general', { + id: actualComponent && actualComponent.uniqueId, + componentCsar: importVSPdata.componentCsar, + type: importVSPdata.type + }); + }); + } + + private checkoutAndRedirectToWorkspace(importVSPdata: ImportVSPdata) { + this.loaderService.activate(); + this.resourceService.checkout(importVSPdata.previousComponent.uniqueId) + .subscribe((componentMetadata: ComponentMetadata) => { this.$state.go('workspace.general', { - id: result.previousComponent && result.previousComponent.uniqueId, - componentCsar: result.componentCsar, - type: result.type + id: componentMetadata.uniqueId, + componentCsar: importVSPdata.componentCsar, + type: importVSPdata.type }); + this.loaderService.deactivate(); + }, () => { + this.loaderService.deactivate(); }); + return; } public onImportVf(file: any): void { diff --git a/catalog-ui/src/app/ng2/pages/home/home.module.ts b/catalog-ui/src/app/ng2/pages/home/home.module.ts index 3e7c0cd312..1a397b44be 100644 --- a/catalog-ui/src/app/ng2/pages/home/home.module.ts +++ b/catalog-ui/src/app/ng2/pages/home/home.module.ts @@ -6,6 +6,7 @@ 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 { ResourceServiceNg2 } from "../../services/component-services/resource.service"; @NgModule({ declarations: [ @@ -25,7 +26,7 @@ import { SdcUiComponentsModule } from "onap-ui-angular"; entryComponents: [ HomeComponent ], - providers: [] + providers: [ResourceServiceNg2] }) export class HomeModule { } diff --git a/catalog-ui/src/app/ng2/services/component-services/resource.service.spec.ts b/catalog-ui/src/app/ng2/services/component-services/resource.service.spec.ts new file mode 100644 index 0000000000..062797bea6 --- /dev/null +++ b/catalog-ui/src/app/ng2/services/component-services/resource.service.spec.ts @@ -0,0 +1,48 @@ +/* + * ============LICENSE_START======================================================= + * Copyright (C) 2021 Nordix Foundation + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +import {inject, TestBed} from '@angular/core/testing'; + +import {ResourceServiceNg2} from "./resource.service"; +import {HttpClient} from "@angular/common/http"; +import {ISdcConfig, SdcConfigToken} from "../../config/sdc-config.config"; + +describe('ResourceServiceNg2', () => { + beforeEach(() => { + const sdcConfigToken: Partial = { + 'api': { + 'root': 'testRoot', + 'component_api_root': 'testApiRoot', + } + }; + let httpServiceMock: Partial = { + get: jest.fn() + }; + TestBed.configureTestingModule({ + providers: [ResourceServiceNg2, + {provide: SdcConfigToken, useValue: sdcConfigToken}, + {provide: HttpClient, useValue: httpServiceMock} + ] + }); + }); + + it('should be created', inject([ResourceServiceNg2], (service: ResourceServiceNg2) => { + expect(service).toBeTruthy(); + })); +}); 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 d20f5415bc..2f84418e9e 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 @@ -18,21 +18,32 @@ * ============LICENSE_END========================================================= */ -import { Injectable } from '@angular/core'; -import 'rxjs/add/operator/map'; -import 'rxjs/add/operator/toPromise'; -import { HttpClient } from '@angular/common/http'; +import {Inject, Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {ISdcConfig, SdcConfigToken} from "../../config/sdc-config.config"; +import {Observable} from "rxjs/Observable"; +import {ComponentMetadata} from "../../../models/component-metadata"; +import {ComponentLifecycleState} from "../../../models/component-lifecycle-state.enum"; @Injectable() export class ResourceServiceNg2 { - protected baseUrl = ""; - - constructor(private http: HttpClient) { - - } + private readonly baseUrl: string; + constructor(protected http: HttpClient, @Inject(SdcConfigToken) sdcConfig: ISdcConfig) { + this.baseUrl = sdcConfig.api.root + sdcConfig.api.component_api_root; + } + public checkout(componentUniqueId: string): Observable { + return this.changeLifecycleState(componentUniqueId, ComponentLifecycleState.CHECKOUT); + } + private changeLifecycleState(componentUniqueId: string, state: ComponentLifecycleState): Observable { + const url: string = this.baseUrl + 'resources/' + componentUniqueId + '/lifecycleState/' + state; + return this.http.post(url, {}).map(value => { + return value; + } + ); + } } diff --git a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts index 28765444b6..b456bece19 100644 --- a/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts +++ b/catalog-ui/src/app/view-models/workspace/tabs/general/general-view-model.ts @@ -20,17 +20,24 @@ 'use strict'; import * as _ from "lodash"; -import {ModalsHandler, ValidationUtils, EVENTS, CHANGE_COMPONENT_CSAR_VERSION_FLAG, ComponentType, DEFAULT_ICON, - ResourceType, ComponentState, instantiationType, ComponentFactory} from "app/utils"; -import { EventListenerService, ProgressService} from "app/services"; -import {CacheService, OnboardingService, ImportVSPService, ElementService} from "app/services-ng2"; -import {IAppConfigurtaion, IValidate, IMainCategory, Resource, ISubCategory,Service, ICsarComponent, Component, IMetadataKey} from "app/models"; -import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; import {Dictionary} from "lodash"; -import { PREVIOUS_CSAR_COMPONENT, CATEGORY_SERVICE_METADATA_KEYS } from "../../../../utils/constants"; -import { Observable, Subject } from "rxjs"; -import { MetadataEntry } from "app/models/metadataEntry"; -import { Metadata } from "app/models/metadata"; +import { + ComponentFactory, + ComponentState, + ComponentType, + DEFAULT_ICON, + EVENTS, + instantiationType, + ModalsHandler, + ResourceType, + ValidationUtils +} from "app/utils"; +import {EventListenerService, ProgressService} from "app/services"; +import {CacheService, ElementService, ImportVSPService, OnboardingService} from "app/services-ng2"; +import {Component, IAppConfigurtaion, ICsarComponent, IMainCategory, IMetadataKey, ISubCategory, IValidate, Resource, Service} from "app/models"; +import {IWorkspaceViewModelScope} from "app/view-models/workspace/workspace-view-model"; +import {CATEGORY_SERVICE_METADATA_KEYS, PREVIOUS_CSAR_COMPONENT} from "../../../../utils/constants"; +import {Observable} from "rxjs"; export class Validation { componentNameValidationPattern:RegExp; @@ -272,19 +279,9 @@ export class GeneralViewModel { if (this.$stateParams.componentCsar && !this.$scope.isCreateMode()) { this.$scope.updateUnsavedFileFlag(true); - // We are coming from update VSP modal we need to automatically checkout (if needed) and save the VF - if (this.$scope.component.lifecycleState !== ComponentState.NOT_CERTIFIED_CHECKOUT) { - // Checkout is needed after that a save will be invoked in workspace-view.handleLifeCycleStateChange - this.EventListenerService.notifyObservers(EVENTS.ON_LIFECYCLE_CHANGE_WITH_SAVE, 'checkOut'); - // if(this.$scope.component.lifecycleState !== 'NOT_CERTIFIED_CHECKIN') { - // (this.$scope.component).csarVersion = this.$stateParams.componentCsar.csarVersion; - // } - } else { - this.$scope.save(); - } + this.$scope.save(); } - if (this.$scope.component.isResource() && (this.$scope.component as Resource).resourceType === ResourceType.VF || (this.$scope.component as Resource).resourceType === ResourceType.PNF && (this.$scope.component as Resource).csarUUID) { -- cgit 1.2.3-korg