From 5e89a4596a9d8984e2409dd3af1f9689ec1b5854 Mon Sep 17 00:00:00 2001 From: Shibani Medhekar Date: Wed, 10 Oct 2018 12:58:32 -0700 Subject: [PATCH 1/5] Show warning for potential IP address changes when scaling to and from Pv2 (https://msazure.visualstudio.com/Antares/_workitems/edit/3182667) --- client/src/app/shared/models/constants.ts | 1 + .../src/app/shared/models/portal-resources.ts | 1 + client/src/app/shared/resourceDescriptors.ts | 4 +++ .../basic-plan-price-spec.ts | 3 ++- .../elastic-premium-plan-price-spec.ts | 1 + .../free-plan-price-spec.ts | 3 ++- .../isolated-plan-price-spec.ts | 3 ++- .../premium-container-plan-price-spec.ts | 3 ++- .../premium-plan-price-spec.ts | 3 ++- .../premiumv2-plan-price-spec.ts | 1 + .../price-spec-manager/price-spec.ts | 1 + .../shared-plan-price-spec.ts | 3 ++- .../standard-plan-price-spec.ts | 3 ++- .../spec-picker/spec-picker.component.html | 13 +++++----- .../spec-picker/spec-picker.component.scss | 23 ++++++++++------- .../site/spec-picker/spec-picker.component.ts | 25 ++++++++++++++++++- server/Resources/Resources.resx | 3 +++ 17 files changed, 71 insertions(+), 23 deletions(-) diff --git a/client/src/app/shared/models/constants.ts b/client/src/app/shared/models/constants.ts index 9f602c9b3c..8d4c2813ce 100644 --- a/client/src/app/shared/models/constants.ts +++ b/client/src/app/shared/models/constants.ts @@ -142,6 +142,7 @@ export class Links { public static linuxContainersLearnMore = 'https://go.microsoft.com/fwlink/?linkid=861969'; public static premiumV2NotAvailableLearnMore = 'https://go.microsoft.com/fwlink/?linkid=2009376'; public static azureComputeUnitLearnMore = 'https://go.microsoft.com/fwlink/?linkid=2027465'; + public static pv2UpsellInfoLearnMore = 'https://go.microsoft.com/fwlink/?linkid=2028474'; } export class Kinds { diff --git a/client/src/app/shared/models/portal-resources.ts b/client/src/app/shared/models/portal-resources.ts index eeae3e99a9..2880c25949 100644 --- a/client/src/app/shared/models/portal-resources.ts +++ b/client/src/app/shared/models/portal-resources.ts @@ -1009,6 +1009,7 @@ public static pricing_pv2NotAvailable = 'pricing_pv2NotAvailable'; public static pricing_epNotAvailable = 'pricing_epNotAvailable'; public static pricing_scaleUp = 'pricing_scaleUp'; + public static pricing_pv2UpsellInfoMessage = 'pricing_pv2UpsellInfoMessage'; public static free = 'free'; public static pricing_pricePerMonth = 'pricing_pricePerMonth'; public static pricing_pricePerHour = 'pricing_pricePerHour'; diff --git a/client/src/app/shared/resourceDescriptors.ts b/client/src/app/shared/resourceDescriptors.ts index 04f179fc4d..5f26d1fd9d 100644 --- a/client/src/app/shared/resourceDescriptors.ts +++ b/client/src/app/shared/resourceDescriptors.ts @@ -26,6 +26,10 @@ export class ArmSubcriptionDescriptor extends Descriptor { constructor(resourceId: string) { super(resourceId); + if (this.parts[0].toLowerCase() !== 'subscriptions') { + throw Error(`Expected subscriptions segment in resourceId: ${resourceId}`); + } + this.subscriptionId = this.parts[1]; } diff --git a/client/src/app/site/spec-picker/price-spec-manager/basic-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/basic-plan-price-spec.ts index 024ac907bb..976f0e123e 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/basic-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/basic-plan-price-spec.ts @@ -1,9 +1,10 @@ -import { Kinds, Links } from './../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from './../../../shared/models/constants'; import { PortalResources } from './../../../shared/models/portal-resources'; import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export abstract class BasicPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.basic; featureItems = [{ iconUrl: 'image/ssl.svg', diff --git a/client/src/app/site/spec-picker/price-spec-manager/elastic-premium-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/elastic-premium-plan-price-spec.ts index b18208578e..a29ea5b231 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/elastic-premium-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/elastic-premium-plan-price-spec.ts @@ -8,6 +8,7 @@ import { DV2SeriesPriceSpec } from './dV2series-price-spec'; import { NewPlanSpecPickerData } from './plan-price-spec-manager'; export abstract class ElasticPremiumPlanPriceSpec extends DV2SeriesPriceSpec { + sku = ServerFarmSku.elasticPremium; featureItems = [ { diff --git a/client/src/app/site/spec-picker/price-spec-manager/free-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/free-plan-price-spec.ts index 4db96b7cb6..547be69492 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/free-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/free-plan-price-spec.ts @@ -1,10 +1,11 @@ import { Observable } from 'rxjs/Observable'; -import { Kinds, Links } from './../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from './../../../shared/models/constants'; import { PortalResources } from './../../../shared/models/portal-resources'; import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export class FreePlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.free; skuCode = 'F1'; legacySkuName = 'free'; topLevelFeatures = [ diff --git a/client/src/app/site/spec-picker/price-spec-manager/isolated-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/isolated-plan-price-spec.ts index dfd224f972..fcaa222118 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/isolated-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/isolated-plan-price-spec.ts @@ -1,5 +1,5 @@ import { Injector } from '@angular/core'; -import { Kinds, Links } from './../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from './../../../shared/models/constants'; import { PortalResources } from '../../../shared/models/portal-resources'; import { AseService } from '../../../shared/services/ase.service'; import { NationalCloudEnvironment } from './../../../shared/services/scenario/national-cloud.environment'; @@ -7,6 +7,7 @@ import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export abstract class IsolatedPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.isolated; featureItems = [{ iconUrl: 'image/app-service-environment.svg', diff --git a/client/src/app/site/spec-picker/price-spec-manager/premium-container-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/premium-container-plan-price-spec.ts index 99f97b371a..23134e6e46 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/premium-container-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/premium-container-plan-price-spec.ts @@ -1,9 +1,10 @@ import { PriceSpec, PriceSpecInput } from './price-spec'; import { Injector } from '@angular/core'; import { PortalResources } from '../../../shared/models/portal-resources'; -import { Links } from '../../../shared/models/constants'; +import { Links, ServerFarmSku } from '../../../shared/models/constants'; export abstract class PremiumContainerPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.premiumContainer; featureItems = [{ iconUrl: 'image/ssl.svg', diff --git a/client/src/app/site/spec-picker/price-spec-manager/premium-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/premium-plan-price-spec.ts index 5549c951f2..891b343925 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/premium-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/premium-plan-price-spec.ts @@ -1,10 +1,11 @@ import { Injector } from '@angular/core'; -import { Kinds, Links } from './../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from './../../../shared/models/constants'; import { PortalResources } from '../../../shared/models/portal-resources'; import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export abstract class PremiumPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.premium; featureItems = [{ iconUrl: 'image/ssl.svg', diff --git a/client/src/app/site/spec-picker/price-spec-manager/premiumv2-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/premiumv2-plan-price-spec.ts index 50a0f7079b..cdb71625be 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/premiumv2-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/premiumv2-plan-price-spec.ts @@ -8,6 +8,7 @@ import { DV2SeriesPriceSpec } from './dV2series-price-spec'; import { NewPlanSpecPickerData } from './plan-price-spec-manager'; export abstract class PremiumV2PlanPriceSpec extends DV2SeriesPriceSpec { + sku = ServerFarmSku.premiumV2; featureItems = [{ iconUrl: 'image/ssl.svg', diff --git a/client/src/app/site/spec-picker/price-spec-manager/price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/price-spec.ts index 81500b605f..ed9cbb79af 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/price-spec.ts @@ -19,6 +19,7 @@ export interface PriceSpecInput { export abstract class PriceSpec { abstract skuCode: string; // SKU code name, like S1 or P1v2 + abstract sku: string; // Used ONLY for returning legacy PCV3 SKU names to Ibiza create scenario's since it currently // relies on this format. There's no reason why we couldn't remove it going forward but I've diff --git a/client/src/app/site/spec-picker/price-spec-manager/shared-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/shared-plan-price-spec.ts index 484b959db4..af469a7402 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/shared-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/shared-plan-price-spec.ts @@ -1,9 +1,10 @@ -import { Kinds, Links } from '../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from '../../../shared/models/constants'; import { PortalResources } from './../../../shared/models/portal-resources'; import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export class SharedPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.shared; skuCode = 'D1'; legacySkuName = 'shared'; topLevelFeatures = [ diff --git a/client/src/app/site/spec-picker/price-spec-manager/standard-plan-price-spec.ts b/client/src/app/site/spec-picker/price-spec-manager/standard-plan-price-spec.ts index 91445fbed7..e199475397 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/standard-plan-price-spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/standard-plan-price-spec.ts @@ -1,10 +1,11 @@ import { Injector } from '@angular/core/src/core'; -import { Kinds, Links } from './../../../shared/models/constants'; +import { Kinds, Links, ServerFarmSku } from './../../../shared/models/constants'; import { PortalResources } from './../../../shared/models/portal-resources'; import { AppKind } from './../../../shared/Utilities/app-kind'; import { PriceSpec, PriceSpecInput } from './price-spec'; export abstract class StandardPlanPriceSpec extends PriceSpec { + sku = ServerFarmSku.standard; featureItems = [{ iconUrl: 'image/ssl.svg', diff --git a/client/src/app/site/spec-picker/spec-picker.component.html b/client/src/app/site/spec-picker/spec-picker.component.html index 6fb0b91202..f56a330f4f 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.html +++ b/client/src/app/site/spec-picker/spec-picker.component.html @@ -102,14 +102,15 @@

{{'pricing_additionalTiers' | translate}}< + [load-image]="statusMessage?.level === 'error' ? 'image/error.svg' : statusMessage?.level === 'info' ? 'image/info.svg' : 'image/success.svg'"> - + - {{statusMessage?.message}} - + {{statusMessage?.message}} + + Learn more \ No newline at end of file diff --git a/client/src/app/site/spec-picker/spec-picker.component.scss b/client/src/app/site/spec-picker/spec-picker.component.scss index 1d9788e07a..dd2664194d 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.scss +++ b/client/src/app/site/spec-picker/spec-picker.component.scss @@ -241,25 +241,30 @@ $center-column-max-width: calc(#{$spec-card-max-width}*4 - 40px); #spec-picker-footer{ position: relative; height: 100%; + margin-top: auto; + margin-bottom: auto; + top: 10px; - button, .message-icon, .message-text{ - position: absolute; - top: 50%; - transform: translateY(-50%); + button, .message-icon, .message-text { + height: 27px; } - .icon-medium{ + .icon-medium { height: 18px; width: 18px; vertical-align: middle; } - .message-icon{ - left: 160px; + .message-icon { + margin: 0 0 5px 15px; + height: 24px; + left: 20px; } - .message-text{ - left: 185px; + .message-text { + margin-left: 5px; + height: 27px; + left: 45px; font-size: 14px; } } diff --git a/client/src/app/site/spec-picker/spec-picker.component.ts b/client/src/app/site/spec-picker/spec-picker.component.ts index be00e5fa1c..e7c0d5751b 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.ts @@ -1,7 +1,7 @@ import { OnDestroy } from '@angular/core/src/metadata/lifecycle_hooks'; import { PortalService } from 'app/shared/services/portal.service'; import { TranslateService } from '@ngx-translate/core'; -import { ArmResourceDescriptor } from './../../shared/resourceDescriptors'; +import { ArmResourceDescriptor, ArmSubcriptionDescriptor } from './../../shared/resourceDescriptors'; import { AuthzService } from 'app/shared/services/authz.service'; import { PlanPriceSpecManager, NewPlanSpecPickerData, SpecPickerInput } from './price-spec-manager/plan-price-spec-manager'; import { Component, Input, Injector, ViewEncapsulation, ViewChild, ElementRef } from '@angular/core'; @@ -14,6 +14,7 @@ import { ResourceId } from '../../shared/models/arm/arm-obj'; import { PortalResources } from '../../shared/models/portal-resources'; import { SiteTabIds, KeyCodes } from '../../shared/models/constants'; import { Dom } from '../../shared/Utilities/dom'; +import { Links, ServerFarmSku } from './../../shared/models/constants'; export interface StatusMessage { message: string; @@ -44,6 +45,8 @@ export class SpecPickerComponent extends FeatureComponent; + private _isUpdateScenario: boolean; + private _originalSku: string; get applyButtonEnabled(): boolean { if (this.statusMessage && this.statusMessage.level === 'error') { @@ -99,16 +102,25 @@ export class SpecPickerComponent extends FeatureComponent { this.specManager.cleanUpGroups(); + this._originalSku = this.specManager.selectedSpecGroup.selectedSpec.sku; // Clearing isInitializing here because if getSpeccosts fails for some reason, we still want to allow the user to // be able to scale @@ -156,6 +168,17 @@ export class SpecPickerComponent extends FeatureComponent Scale up + + Outgoing IP addresses for your app might change. + Free From a03eb22b24a97c3766643d67fe6e2525b503e1f9 Mon Sep 17 00:00:00 2001 From: Shibani Medhekar Date: Wed, 10 Oct 2018 16:32:48 -0700 Subject: [PATCH 2/5] Resolving comments --- .../plan-price-spec-manager.ts | 50 +++++++++++++- .../spec-picker/spec-picker.component.html | 6 +- .../site/spec-picker/spec-picker.component.ts | 69 +++++-------------- 3 files changed, 71 insertions(+), 54 deletions(-) diff --git a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts index 0fcf7d4fd7..cddc21fbc2 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts @@ -15,7 +15,8 @@ import { SpecCostQueryInput } from './billing-models'; import { PriceSpecInput, PriceSpec } from './price-spec'; import { Subject } from 'rxjs/Subject'; import { BillingMeter } from '../../../shared/models/arm/billingMeter'; -import { LogCategories } from '../../../shared/models/constants'; +import { LogCategories, ServerFarmSku, Links } from '../../../shared/models/constants'; +import { AuthzService } from 'app/shared/services/authz.service'; export interface SpecPickerInput { id: ResourceId; @@ -277,6 +278,53 @@ export class PlanPriceSpecManager { this._ngUnsubscribe$.next(); } + setSelectedSpec(spec: PriceSpec) { + this.selectedSpecGroup.selectedSpec = spec; + + // plan is null for new plans + if (this._plan) { + const tier = this._plan.sku.tier; + if ((tier === ServerFarmSku.premiumV2 && spec.sku !== ServerFarmSku.premiumV2) + || (tier !== ServerFarmSku.premiumV2 && spec.sku === ServerFarmSku.premiumV2)) { + + // show message when upgrading to PV2 or downgrading from PV2. + this._specPicker.statusMessage = { + message: this._ts.instant(PortalResources.pricing_pv2UpsellInfoMessage), + level: 'info', + infoLink: Links.pv2UpsellInfoLearnMore + }; + } + } + } + + checkAccess(input: SpecPickerInput, resourceId: string, authZService: AuthzService) { + return Observable.zip( + !input.data ? authZService.hasPermission(resourceId, [AuthzService.writeScope]) : Observable.of(true), + !input.data ? authZService.hasReadOnlyLock(resourceId) : Observable.of(false) + ).do(r => { + if (!input.data) { + const planDescriptor = new ArmResourceDescriptor(resourceId); + const name = planDescriptor.parts[planDescriptor.parts.length - 1]; + + if (!r[0]) { + this._specPicker.statusMessage = { + message: this._ts.instant(PortalResources.pricing_noWritePermissionsOnPlanFormat).format(name), + level: 'error' + }; + + this._specPicker.shieldEnabled = true; + } else if (r[1]) { + this._specPicker.statusMessage = { + message: this._ts.instant(PortalResources.pricing_planReadonlyLockFormat).format(name), + level: 'error' + }; + + this._specPicker.shieldEnabled = true; + } + } + }); + } + private _getPlan(inputs: SpecPickerInput) { if (!inputs.data) { return this._planService.getPlan(inputs.id, true) diff --git a/client/src/app/site/spec-picker/spec-picker.component.html b/client/src/app/site/spec-picker/spec-picker.component.html index f56a330f4f..ea19d6c99b 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.html +++ b/client/src/app/site/spec-picker/spec-picker.component.html @@ -101,8 +101,8 @@

{{'pricing_additionalTiers' | translate}}< + *ngIf="statusMessage && statusMessage.message" + [load-image]="statusMessageImage"> {{'pricing_additionalTiers' | translate}}< {{statusMessage?.message}} - Learn more + {{'topBar_learnMore' | translate}} \ No newline at end of file diff --git a/client/src/app/site/spec-picker/spec-picker.component.ts b/client/src/app/site/spec-picker/spec-picker.component.ts index e7c0d5751b..f72a16b385 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.ts @@ -1,7 +1,6 @@ import { OnDestroy } from '@angular/core/src/metadata/lifecycle_hooks'; import { PortalService } from 'app/shared/services/portal.service'; import { TranslateService } from '@ngx-translate/core'; -import { ArmResourceDescriptor, ArmSubcriptionDescriptor } from './../../shared/resourceDescriptors'; import { AuthzService } from 'app/shared/services/authz.service'; import { PlanPriceSpecManager, NewPlanSpecPickerData, SpecPickerInput } from './price-spec-manager/plan-price-spec-manager'; import { Component, Input, Injector, ViewEncapsulation, ViewChild, ElementRef } from '@angular/core'; @@ -14,7 +13,6 @@ import { ResourceId } from '../../shared/models/arm/arm-obj'; import { PortalResources } from '../../shared/models/portal-resources'; import { SiteTabIds, KeyCodes } from '../../shared/models/constants'; import { Dom } from '../../shared/Utilities/dom'; -import { Links, ServerFarmSku } from './../../shared/models/constants'; export interface StatusMessage { message: string; @@ -43,10 +41,8 @@ export class SpecPickerComponent extends FeatureComponent; - private _isUpdateScenario: boolean; - private _originalSku: string; get applyButtonEnabled(): boolean { if (this.statusMessage && this.statusMessage.level === 'error') { @@ -65,6 +61,21 @@ export class SpecPickerComponent extends FeatureComponent { this.specManager.cleanUpGroups(); - this._originalSku = this.specManager.selectedSpecGroup.selectedSpec.sku; // Clearing isInitializing here because if getSpeccosts fails for some reason, we still want to allow the user to // be able to scale @@ -129,31 +131,9 @@ export class SpecPickerComponent extends FeatureComponent { - - if (!this._input.data) { - const planDescriptor = new ArmResourceDescriptor(this._planOrSubResourceId); - const name = planDescriptor.parts[planDescriptor.parts.length - 1]; - - if (!r[1]) { - this.statusMessage = { - message: this._ts.instant(PortalResources.pricing_noWritePermissionsOnPlanFormat).format(name), - level: 'error' - }; - - this.shieldEnabled = true; - } else if (r[2]) { - this.statusMessage = { - message: this._ts.instant(PortalResources.pricing_planReadonlyLockFormat).format(name), - level: 'error' - }; - - this.shieldEnabled = true; - } - } }); } @@ -168,18 +148,7 @@ export class SpecPickerComponent extends FeatureComponent Date: Wed, 10 Oct 2018 18:45:09 -0700 Subject: [PATCH 3/5] Fixing tests --- .../plan-price-spec-manager.ts | 5 +- .../price-spec-manager.spec.ts | 16 ++- .../spec-picker/spec-picker.component.spec.ts | 131 ++++++++++++++++++ .../site/spec-picker/spec-picker.component.ts | 8 +- 4 files changed, 144 insertions(+), 16 deletions(-) diff --git a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts index cddc21fbc2..588c03c559 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts @@ -101,7 +101,7 @@ export class PlanPriceSpecManager { specInitCalls = specInitCalls.concat(g.additionalSpecs.map(s => s.initialize(priceSpecInput))); }); - return Observable.zip(...specInitCalls); + return specInitCalls.length > 0 ? Observable.zip(...specInitCalls) : Observable.of(null); }); } @@ -297,7 +297,8 @@ export class PlanPriceSpecManager { } } - checkAccess(input: SpecPickerInput, resourceId: string, authZService: AuthzService) { + checkAccess(input: SpecPickerInput, authZService: AuthzService) { + const resourceId = input.id; return Observable.zip( !input.data ? authZService.hasPermission(resourceId, [AuthzService.writeScope]) : Observable.of(true), !input.data ? authZService.hasReadOnlyLock(resourceId) : Observable.of(false) diff --git a/client/src/app/site/spec-picker/price-spec-manager/price-spec-manager.spec.ts b/client/src/app/site/spec-picker/price-spec-manager/price-spec-manager.spec.ts index 6f2090fd68..a6f59922e9 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/price-spec-manager.spec.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/price-spec-manager.spec.ts @@ -190,14 +190,14 @@ describe('Price Spec Manager', () => { return new MockSpecGroup( injector, [ - new MockPriceSpec(injector, `Group${groupNumber}-Recommended1`), - new MockPriceSpec(injector, `Group${groupNumber}-Recommended2`), - new MockPriceSpec(injector, `Group${groupNumber}-Recommended3`) + new MockPriceSpec(injector, `Group${groupNumber}-Recommended1`, 'Recommended1'), + new MockPriceSpec(injector, `Group${groupNumber}-Recommended2`, 'Recommended2'), + new MockPriceSpec(injector, `Group${groupNumber}-Recommended3`, 'Recommended3') ], [ - new MockPriceSpec(injector, `Group${groupNumber}-Additional1`), - new MockPriceSpec(injector, `Group${groupNumber}-Additional2`), - new MockPriceSpec(injector, `Group${groupNumber}-Additional3`) + new MockPriceSpec(injector, `Group${groupNumber}-Additional1`, 'Additional1'), + new MockPriceSpec(injector, `Group${groupNumber}-Additional2`, 'Additional2'), + new MockPriceSpec(injector, `Group${groupNumber}-Additional3`, 'Additional3') ]); } @@ -211,6 +211,7 @@ describe('Price Spec Manager', () => { }); class MockPriceSpec extends PriceSpec { + sku = null; skuCode = null; legacySkuName = null; topLevelFeatures = [ @@ -239,8 +240,9 @@ class MockPriceSpec extends PriceSpec { }] }; - constructor(injector: Injector, skuCode: string) { + constructor(injector: Injector, skuCode: string, sku: string) { super(injector); + this.sku = sku; this.skuCode = skuCode; this.legacySkuName = `Legacy-${skuCode}`; this.meterFriendlyName = `${skuCode} App Service`; diff --git a/client/src/app/site/spec-picker/spec-picker.component.spec.ts b/client/src/app/site/spec-picker/spec-picker.component.spec.ts index d280909b6a..0191d82024 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.spec.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.spec.ts @@ -20,6 +20,9 @@ import { MockLogService } from '../../test/mocks/log.service.mock'; import { TelemetryService } from '../../shared/services/telemetry.service'; import { SpecPickerInput, NewPlanSpecPickerData, PlanPriceSpecManager } from './price-spec-manager/plan-price-spec-manager'; import { PortalResources } from '../../shared/models/portal-resources'; +import { PriceSpec } from './price-spec-manager/price-spec'; +import { MockPlanService } from '../../test/mocks/plan.service.mock'; +import { PlanService } from '../../shared/services/plan.service'; describe('SpecPickerComponent', () => { let component: SpecPickerComponent; @@ -154,6 +157,128 @@ describe('SpecPickerComponent', () => { }); }); +fdescribe('SpecPickerComponentAccessTest', () => { + let component: SpecPickerComponent; + let fixture: ComponentFixture; + const subscriptionId = 'mysub'; + const planName = 'myplan'; + const planResourceId = `/subscriptions/${subscriptionId}/resourcegroups/myrg/providers/microsoft.web/serverfarms/${planName}`; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + declarations: [ + SpecPickerComponent, + InfoBoxComponent, + SpecListComponent, + SpecFeatureListComponent, + RemoveSpacesPipe, + MockDirective(LoadImageDirective)], + imports: [TranslateModule.forRoot()], + providers: [ + { provide: AuthzService, useClass: MockAuthzService }, + { provide: PortalService, useClass: MockPortalService }, + { provide: PlanService, useClass: MockPlanService }, + { provide: BroadcastService, useClass: MockBroadcastService }, + { provide: LogService, useClass: MockLogService }, + { provide: TelemetryService, useClass: MockTelemetryService }, + PlanPriceSpecManager + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(SpecPickerComponent); + component = fixture.componentInstance; + + fixture.detectChanges(); + }); + + it('should show plan write permission error', () => { + const input = { + resourceId: planResourceId, + dashboardType: null, + node: null, + data: { + id: planResourceId, + data: null, + specPicker: component + } + }; + + const authZService: MockAuthzService = TestBed.get(AuthzService); + const priceSpecManager: PlanPriceSpecManager = TestBed.get(PlanPriceSpecManager); + spyOn(authZService, 'hasPermission').and.callFake(() => { + return Observable.of(false); + }); + + spyOn(authZService, 'hasReadOnlyLock').and.callFake(() => { + return Observable.of(false); + }); + + spyOn(priceSpecManager, 'resetGroups').and.callFake(() => { + return; + }); + + spyOn(priceSpecManager, 'cleanUpGroups').and.callFake(() => { + return; + }); + + spyOn(priceSpecManager, 'getSpecCosts').and.callFake(() => { + return Observable.of(true); + }); + + component.viewInfoInput = input; + + expect(component.shieldEnabled).toBeTruthy(); + expect(component.statusMessage).not.toBeNull(); + expect(component.statusMessage.level).toEqual('error'); + expect(component.statusMessage.message).toEqual(PortalResources.pricing_noWritePermissionsOnPlanFormat); + }); + + it('should show plan read lock error', () => { + const input = { + resourceId: planResourceId, + dashboardType: null, + node: null, + data: { + id: planResourceId, + data: null, + specPicker: component + } + }; + + const authZService: MockAuthzService = TestBed.get(AuthzService); + const priceSpecManager: PlanPriceSpecManager = TestBed.get(PlanPriceSpecManager); + spyOn(authZService, 'hasPermission').and.callFake(() => { + return Observable.of(true); + }); + + spyOn(authZService, 'hasReadOnlyLock').and.callFake(() => { + return Observable.of(true); + }); + + spyOn(priceSpecManager, 'resetGroups').and.callFake(() => { + return; + }); + + spyOn(priceSpecManager, 'cleanUpGroups').and.callFake(() => { + return; + }); + + spyOn(priceSpecManager, 'getSpecCosts').and.callFake(() => { + return Observable.of(true); + }); + + component.viewInfoInput = input; + + expect(component.shieldEnabled).toBeTruthy(); + expect(component.statusMessage).not.toBeNull(); + expect(component.statusMessage.level).toEqual('error'); + expect(component.statusMessage.message).toEqual(PortalResources.pricing_planReadonlyLockFormat); + }); +}); + class MockSpecManager { resetGroups() { }; @@ -168,4 +293,10 @@ class MockSpecManager { cleanUpGroups() { } dispose() { } + + checkAccess(input: SpecPickerInput, resourceId: string, authZService: AuthzService) { + return Observable.of(null); + } + + setSelectedSpec(spec: PriceSpec) { } } diff --git a/client/src/app/site/spec-picker/spec-picker.component.ts b/client/src/app/site/spec-picker/spec-picker.component.ts index f72a16b385..ed2124925a 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.ts @@ -9,7 +9,6 @@ import { TreeViewInfo } from '../../tree-view/models/tree-view-info'; import { Observable } from 'rxjs/Observable'; import { PriceSpec } from './price-spec-manager/price-spec'; import { PriceSpecGroup } from './price-spec-manager/price-spec-group'; -import { ResourceId } from '../../shared/models/arm/arm-obj'; import { PortalResources } from '../../shared/models/portal-resources'; import { SiteTabIds, KeyCodes } from '../../shared/models/constants'; import { Dom } from '../../shared/Utilities/dom'; @@ -41,7 +40,6 @@ export class SpecPickerComponent extends FeatureComponent; get applyButtonEnabled(): boolean { @@ -102,10 +100,6 @@ export class SpecPickerComponent extends FeatureComponent { }); From 3c92089788f608231f5ed1772a38a7a951fb9671 Mon Sep 17 00:00:00 2001 From: Shibani Medhekar Date: Wed, 10 Oct 2018 19:58:49 -0700 Subject: [PATCH 4/5] Fixing missing trailing comma comments --- .../price-spec-manager/plan-price-spec-manager.ts | 6 +++--- .../src/app/site/spec-picker/spec-picker.component.spec.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts index 588c03c559..0dfc169944 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts @@ -301,7 +301,7 @@ export class PlanPriceSpecManager { const resourceId = input.id; return Observable.zip( !input.data ? authZService.hasPermission(resourceId, [AuthzService.writeScope]) : Observable.of(true), - !input.data ? authZService.hasReadOnlyLock(resourceId) : Observable.of(false) + !input.data ? authZService.hasReadOnlyLock(resourceId) : Observable.of(false), ).do(r => { if (!input.data) { const planDescriptor = new ArmResourceDescriptor(resourceId); @@ -310,14 +310,14 @@ export class PlanPriceSpecManager { if (!r[0]) { this._specPicker.statusMessage = { message: this._ts.instant(PortalResources.pricing_noWritePermissionsOnPlanFormat).format(name), - level: 'error' + level: 'error', }; this._specPicker.shieldEnabled = true; } else if (r[1]) { this._specPicker.statusMessage = { message: this._ts.instant(PortalResources.pricing_planReadonlyLockFormat).format(name), - level: 'error' + level: 'error', }; this._specPicker.shieldEnabled = true; diff --git a/client/src/app/site/spec-picker/spec-picker.component.spec.ts b/client/src/app/site/spec-picker/spec-picker.component.spec.ts index 0191d82024..b5a7632fe2 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.spec.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.spec.ts @@ -181,7 +181,7 @@ fdescribe('SpecPickerComponentAccessTest', () => { { provide: BroadcastService, useClass: MockBroadcastService }, { provide: LogService, useClass: MockLogService }, { provide: TelemetryService, useClass: MockTelemetryService }, - PlanPriceSpecManager + PlanPriceSpecManager, ] }) .compileComponents(); @@ -202,7 +202,7 @@ fdescribe('SpecPickerComponentAccessTest', () => { data: { id: planResourceId, data: null, - specPicker: component + specPicker: component, } }; @@ -244,7 +244,7 @@ fdescribe('SpecPickerComponentAccessTest', () => { data: { id: planResourceId, data: null, - specPicker: component + specPicker: component, } }; From 48e0600d152933d0df1a9b0b18e980cdd67145e6 Mon Sep 17 00:00:00 2001 From: Shibani Medhekar Date: Wed, 10 Oct 2018 20:33:17 -0700 Subject: [PATCH 5/5] Fixed some more trailing commas --- .../price-spec-manager/plan-price-spec-manager.ts | 6 +++--- .../src/app/site/spec-picker/spec-picker.component.spec.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts index 0dfc169944..5601036807 100644 --- a/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts +++ b/client/src/app/site/spec-picker/price-spec-manager/plan-price-spec-manager.ts @@ -291,7 +291,7 @@ export class PlanPriceSpecManager { this._specPicker.statusMessage = { message: this._ts.instant(PortalResources.pricing_pv2UpsellInfoMessage), level: 'info', - infoLink: Links.pv2UpsellInfoLearnMore + infoLink: Links.pv2UpsellInfoLearnMore, }; } } @@ -337,7 +337,7 @@ export class PlanPriceSpecManager { if (this._plan.sku.name === this.DynamicSku) { this._specPicker.statusMessage = { message: this._ts.instant(PortalResources.pricing_notAvailableConsumption), - level: 'error' + level: 'error', }; this._specPicker.shieldEnabled = true; @@ -347,7 +347,7 @@ export class PlanPriceSpecManager { } else { this._specPicker.statusMessage = { message: this._ts.instant(PortalResources.pricing_noWritePermissionsOnPlan), - level: 'error' + level: 'error', }; this._specPicker.shieldEnabled = true; diff --git a/client/src/app/site/spec-picker/spec-picker.component.spec.ts b/client/src/app/site/spec-picker/spec-picker.component.spec.ts index b5a7632fe2..cfc6140f8d 100644 --- a/client/src/app/site/spec-picker/spec-picker.component.spec.ts +++ b/client/src/app/site/spec-picker/spec-picker.component.spec.ts @@ -297,6 +297,6 @@ class MockSpecManager { checkAccess(input: SpecPickerInput, resourceId: string, authZService: AuthzService) { return Observable.of(null); } - + setSelectedSpec(spec: PriceSpec) { } }