diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts new file mode 100644 index 0000000000..c5f16c883b --- /dev/null +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-available.ts @@ -0,0 +1,30 @@ +import { Observable } from 'rxjs'; +import { filter, map, publishReplay, refCount, startWith } from 'rxjs/operators'; + +import { EntityServiceFactory } from '../../../../core/src/core/entity-service-factory.service'; +import { APIResource, EntityInfo } from '../../../../store/src/types/api.types'; +import { GetAppAutoscalerInfoAction } from '../../store/app-autoscaler.actions'; +import { AutoscalerInfo } from '../../store/app-autoscaler.types'; + +export const fetchAutoscalerInfo = ( + endpointGuid: string, + esf: EntityServiceFactory): Observable>> => { + const action = new GetAppAutoscalerInfoAction(endpointGuid); + const entityService = esf.create>(action.entityKey, action.entity, endpointGuid, action); + return entityService.entityObs$.pipe( + filter(entityInfo => + !!entityInfo && + !!entityInfo.entityRequestInfo && + !entityInfo.entityRequestInfo.fetching + ), + publishReplay(1), + refCount() + ); +}; + +export const isAutoscalerEnabled = (endpointGuid: string, esf: EntityServiceFactory): Observable => { + return fetchAutoscalerInfo(endpointGuid, esf).pipe( + map(entityInfo => !entityInfo.entityRequestInfo.error), + startWith(false) + ); +}; diff --git a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts index 46fd4b6d21..a1b84d299c 100644 --- a/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts +++ b/src/frontend/packages/cf-autoscaler/src/core/autoscaler-helpers/autoscaler-util.ts @@ -106,28 +106,28 @@ export class AutoscalerConstants { } export const PolicyAlert = { - alertInvalidPolicyMinimumRange: 'The Minimum Instance Count must be a integer less than the Maximum Instance Count.', - alertInvalidPolicyMaximumRange: 'The Maximum Instance Count must be a integer greater than the Minimum Instance Count.', + alertInvalidPolicyMinimumRange: 'The Minimum Instance Count must be an integer less than the Maximum Instance Count.', + alertInvalidPolicyMaximumRange: 'The Maximum Instance Count must be an integer greater than the Minimum Instance Count.', alertInvalidPolicyInitialMaximumRange: - 'The Initial Minimum Instance Count must be a integer in the range of Minimum Instance Count to Maximum Instance Count.', + 'The Initial Minimum Instance Count must be an integer between Minimum Instance Count and Maximum Instance Count.', alertInvalidPolicyTriggerUpperThresholdRange: 'The Upper Threshold value must be an integer greater than the Lower Threshold value.', - alertInvalidPolicyTriggerLowerThresholdRange: 'The Lower Threshold value must be an integer in the range of 1 to (Upper Threshold-1).', + alertInvalidPolicyTriggerLowerThresholdRange: 'The Lower Threshold value must be an integer between 1 and (Upper Threshold-1).', alertInvalidPolicyTriggerThreshold100: 'The Lower/Upper Threshold value of memoryutil must be an integer below or equal to 100.', - alertInvalidPolicyTriggerStepPercentageRange: 'The Instance Step Up/Down percentage must be a integer greater than 1.', - alertInvalidPolicyTriggerStepRange: 'The Instance Step Up/Down value must be a integer in the range of 1 to (Maximum Instance-1).', + alertInvalidPolicyTriggerStepPercentageRange: 'The Instance Step Up/Down percentage must be an integer greater than 1.', + alertInvalidPolicyTriggerStepRange: 'The Instance Step Up/Down value must be an integer between 1 and (Maximum Instance-1).', alertInvalidPolicyTriggerBreachDurationRange: - `The breach duration value must be an integer in the range of ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_min} to + `The breach duration value must be an integer between ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_min} and ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_max} seconds.`, alertInvalidPolicyTriggerCooldownRange: - `The cooldown period value must be an integer in the range of ${AutoscalerConstants.PolicyDefaultSetting.cool_down_secs_min} to + `The cooldown period value must be an integer between ${AutoscalerConstants.PolicyDefaultSetting.cool_down_secs_min} and ${AutoscalerConstants.PolicyDefaultSetting.breach_duration_secs_max} seconds.`, - alertInvalidPolicyScheduleDateBeforeNow: 'Start/End date should be after or equal to current date.', + alertInvalidPolicyScheduleDateBeforeNow: 'Start/End date should be after or equal to the current date.', alertInvalidPolicyScheduleEndDateBeforeStartDate: 'Start date must be earlier than the end date.', alertInvalidPolicyScheduleEndTimeBeforeStartTime: 'Start time must be earlier than the end time.', alertInvalidPolicyScheduleRepeatOn: 'Please select at least one "Repeat On" day.', alertInvalidPolicyScheduleEndDateTimeBeforeStartDateTime: 'Start date and time must be earlier than the end date and time.', - alertInvalidPolicyScheduleStartDateTimeBeforeNow: 'Start date and time must be after or equal to current date time.', - alertInvalidPolicyScheduleEndDateTimeBeforeNow: 'End date and time must be after or equal the current date and time.', + alertInvalidPolicyScheduleStartDateTimeBeforeNow: 'Start date and time must be after or equal to the current date time.', + alertInvalidPolicyScheduleEndDateTimeBeforeNow: 'End date and time must be after or equal to the current date and time.', alertInvalidPolicyScheduleRecurringConflict: 'Recurring schedule configuration conflict occurs.', alertInvalidPolicyScheduleSpecificConflict: 'Specific date configuration conflict occurs.', alertInvalidPolicyTriggerScheduleEmpty: 'At least one Scaling Rule or Schedule should be defined.', diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html index 1ea6e15f7a..413228ea3d 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.html @@ -1,52 +1,29 @@ + + + + + + + + +
- - - - - Status - - - - - - - - - - - - - - - Status - - - - - - - - - - + @@ -59,7 +36,6 @@ - - - @@ -204,17 +175,15 @@ No recurring schedule.
- - - - - + + + {{noPolicyMessageFirstLine}}. {{noPolicyMessageSecondLine.text}} + + Latest Events @@ -259,5 +228,8 @@ + + \ No newline at end of file diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss index 2388c6fd13..34a7885005 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.scss @@ -17,15 +17,17 @@ } &__latest-metrics { + flex: 2; min-height: 200px; } } + .app-metadata { display: flex; flex-direction: row; &__two-cols { - flex: 1; + padding-right: 40px; app-metadata-item:first-child { margin-top: 0; } @@ -52,6 +54,15 @@ table { } .autoscaler-tile-events { + flex-direction: column; + + &__no-policy { + align-items: center; + display: flex; + margin-bottom: 12px; + padding: 14px; + } + &__header { display: flex; flex: 1; @@ -69,3 +80,16 @@ table { } } } +.nav-button-with-text { + margin: 0 10px 0 0; + padding: 0 5px; + + &__span { + align-items: center; + display: flex; + } + + &__icon { + margin-right: 5px; + } +} diff --git a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts index b011679585..7f739c0c47 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/autoscaler-tab-extension/autoscaler-tab-extension.component.ts @@ -2,8 +2,8 @@ import { Component, OnDestroy, OnInit } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, SimpleSnackBar } from '@angular/material'; import { ActivatedRoute } from '@angular/router'; import { Store } from '@ngrx/store'; -import { Observable, Subscription } from 'rxjs'; -import { distinctUntilChanged, filter, first, map, publishReplay, refCount, startWith } from 'rxjs/operators'; +import { combineLatest, Observable, Subscription } from 'rxjs'; +import { distinctUntilChanged, filter, first, map, pairwise, publishReplay, refCount } from 'rxjs/operators'; import { EntityService } from '../../../../core/src/core/entity-service'; import { EntityServiceFactory } from '../../../../core/src/core/entity-service-factory.service'; @@ -21,8 +21,9 @@ import { applicationSchemaKey, entityFactory } from '../../../../store/src/helpe import { createEntityRelationPaginationKey } from '../../../../store/src/helpers/entity-relations/entity-relations.types'; import { ActionState } from '../../../../store/src/reducers/api-request-reducer/types'; import { getPaginationObservables } from '../../../../store/src/reducers/pagination-reducer/pagination-reducer.helper'; -import { selectUpdateInfo } from '../../../../store/src/selectors/api.selectors'; +import { selectDeletionInfo } from '../../../../store/src/selectors/api.selectors'; import { APIResource } from '../../../../store/src/types/api.types'; +import { isAutoscalerEnabled } from '../../core/autoscaler-helpers/autoscaler-available'; import { AutoscalerConstants } from '../../core/autoscaler-helpers/autoscaler-util'; import { AutoscalerPaginationParams, @@ -30,12 +31,9 @@ import { GetAppAutoscalerAppMetricAction, GetAppAutoscalerPolicyAction, GetAppAutoscalerScalingHistoryAction, - UpdateAppAutoscalerPolicyAction, } from '../../store/app-autoscaler.actions'; import { - AppAutoscalerFetchPolicyFailedResponse, AppAutoscalerMetricData, - AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppAutoscalerScalingHistory, AppScalingTrigger, @@ -46,25 +44,6 @@ import { appAutoscalerScalingHistorySchemaKey, } from '../../store/autoscaler.store.module'; -const enableAutoscaler = (appGuid: string, endpointGuid: string, esf: EntityServiceFactory): Observable => { - // This will eventual be moved out into a service and made generic to the cf (one call per cf, rather than one call per app - See #3583) - const action = new GetAppAutoscalerPolicyAction(appGuid, endpointGuid); - const entityService = esf.create(action.entityKey, action.entity, action.guid, action); - return entityService.entityObs$.pipe( - filter(entityInfo => - !!entityInfo && !!entityInfo.entityRequestInfo && !!entityInfo.entityRequestInfo.response && - !entityInfo.entityRequestInfo.fetching), - map(entityInfo => { - // Autoscaler feature should be enabled if either .. - // 1) There's an autoscaler policy - // 2) There's a 404 no policy error (as opposed to a 404 url not found error) - const noPolicySet = (entityInfo.entityRequestInfo.response as AppAutoscalerFetchPolicyFailedResponse).noPolicy; - return !!entityInfo.entity || noPolicySet; - }), - startWith(true) - ); -}; - @StratosTab({ type: StratosTabType.Application, label: 'Autoscale', @@ -73,8 +52,7 @@ const enableAutoscaler = (appGuid: string, endpointGuid: string, esf: EntityServ iconFont: 'stratos-icons', hidden: (store: Store, esf: EntityServiceFactory, activatedRoute: ActivatedRoute) => { const endpointGuid = getGuids('cf')(activatedRoute) || window.location.pathname.split('/')[2]; - const appGuid = getGuids()(activatedRoute) || window.location.pathname.split('/')[3]; - return enableAutoscaler(appGuid, endpointGuid, esf).pipe(map(enabled => !enabled)); + return isAutoscalerEnabled(endpointGuid, esf).pipe(map(enabled => !enabled)); } }) @Component({ @@ -100,14 +78,20 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { appAutoscalerScalingHistory$: Observable; appAutoscalerAppMetricNames$: Observable; + public showNoPolicyMessage$: Observable; + public showAutoscalerHistory$: Observable; + + public noPolicyMessageFirstLine = 'This application has no Autoscaler Policy'; + public noPolicyMessageSecondLine = { + text: 'To create a policy click the + icon above' + }; + private appAutoscalerPolicyErrorSub: Subscription; private appAutoscalerScalingHistoryErrorSub: Subscription; private appAutoscalerPolicySnackBarRef: MatSnackBarRef; private appAutoscalerScalingHistorySnackBarRef: MatSnackBarRef; private scalingHistoryAction: GetAppAutoscalerScalingHistoryAction; - private detachConfirmOk = 0; - appAutoscalerAppMetrics = {}; paramsMetrics: AutoscalerPaginationParams = { @@ -190,6 +174,24 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { refCount() ); this.initErrorSub(); + + this.showAutoscalerHistory$ = combineLatest([ + this.appAutoscalerPolicy$, + this.appAutoscalerScalingHistory$ + ]).pipe( + map(([policy, history]) => !!policy || (!!history && history.total_results > 0)), + publishReplay(1), + refCount() + ); + + this.showNoPolicyMessage$ = combineLatest([ + this.appAutoscalerPolicy$, + this.appAutoscalerScalingHistory$ + ]).pipe( + map(([policy, history]) => !policy && (!history || history.total_results === 0)), + publishReplay(1), + refCount() + ); } getAppMetric(metricName: string, trigger: AppScalingTrigger, params: AutoscalerPaginationParams) { @@ -227,13 +229,8 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { } this.appAutoscalerPolicyErrorSub = this.appAutoscalerPolicyService.entityMonitor.entityRequest$.pipe( - filter(request => !!request.error), - map(request => { - const msg = request.message; - request.error = false; - request.message = ''; - return msg; - }), + filter(response => !!response.error && (!response.response || !response.response.noPolicy)), + map(response => response.message), distinctUntilChanged(), ).subscribe(errorMessage => { if (this.appAutoscalerPolicySnackBarRef) { @@ -259,16 +256,13 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { disableAutoscaler() { const confirmation = new ConfirmationDialogConfig( - 'Detach And Delete Policy', - 'Are you sure you want to detach and delete the policy?', - 'Detach and Delete', + 'Delete Policy', + 'Are you sure you want to delete the policy?', + 'Delete', true ); - this.detachConfirmOk = this.detachConfirmOk === 1 ? 0 : 1; this.confirmDialog.open(confirmation, () => { - this.detachConfirmOk = 2; - const doUpdate = () => this.detachPolicy(); - doUpdate().pipe( + this.detachPolicy().pipe( first(), ).subscribe(actionState => { if (actionState.error) { @@ -283,20 +277,25 @@ export class AutoscalerTabExtensionComponent implements OnInit, OnDestroy { this.store.dispatch( new DetachAppAutoscalerPolicyAction(this.applicationService.appGuid, this.applicationService.cfGuid) ); - const actionState = selectUpdateInfo(appAutoscalerPolicySchemaKey, - this.applicationService.appGuid, - UpdateAppAutoscalerPolicyAction.updateKey); - return this.store.select(actionState).pipe(filter(item => !!item)); + return this.store.select(selectDeletionInfo(appAutoscalerPolicySchemaKey, this.applicationService.appGuid)).pipe( + pairwise(), + filter(([oldV, newV]) => oldV.busy && !newV.busy), + map(([, newV]) => newV) + ); } - updatePolicyPage = () => { + updatePolicyPage = (isCreate = false) => { + const query = isCreate ? { + create: isCreate + } : {}; this.store.dispatch(new RouterNav({ path: [ 'autoscaler', this.applicationService.cfGuid, this.applicationService.appGuid, 'edit-autoscaler-policy' - ] + ], + query })); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts index 4d5752f7b2..c7a8dcad17 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-service.ts @@ -8,7 +8,7 @@ import { entityFactory } from '../../../../store/src/helpers/entity-factory'; import { EntityInfo } from '../../../../store/src/types/api.types'; import { autoscalerTransformArrayToMap } from '../../core/autoscaler-helpers/autoscaler-transform-policy'; import { GetAppAutoscalerPolicyAction } from '../../store/app-autoscaler.actions'; -import { AppAutoscalerPolicy, AppAutoscalerPolicyLocal } from '../../store/app-autoscaler.types'; +import { AppAutoscalerPolicyLocal } from '../../store/app-autoscaler.types'; import { appAutoscalerPolicySchemaKey } from '../../store/autoscaler.store.module'; @Injectable() @@ -47,13 +47,13 @@ export class EditAutoscalerPolicyService { first(), ).subscribe((({ entity }) => { if (entity && entity.entity) { - this.stateSubject.next(entity.entity); + this.setState(entity.entity); } })); } setState(state: AppAutoscalerPolicyLocal) { - const {...newState} = state; + const newState = JSON.parse(JSON.stringify(state)); this.stateSubject.next(newState); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html index eab22c9e5a..a05d996466 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step1/edit-autoscaler-policy-step1.component.html @@ -1,4 +1,6 @@
+ Use minimum and maximum instance counts to provide default values for your policy. +
diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html index 5f15c5934d..aa6cb4f718 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.html @@ -1,4 +1,6 @@
+

Add scaling rules that work with built-in metrics to scale your application. Metrics are averaged over all the + instances of your application.

@@ -34,11 +36,14 @@ - {{getMetricUnit(rule.metric_type)}} for + + {{metricUnit$ | async}} + + for - seconsds, then {{editScaleType=='upper'?'add':'remove'}} + seconds, then {{editScaleType=='upper'?'add':'remove'}} diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss index 2e060b8936..3ed560932a 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.scss @@ -19,6 +19,10 @@ .autoscaler-policy-edit-trigger-item-desc { margin-bottom: .5em; } + .autoscaler-policy-edit-trigger-item__unit { + display: inline-block; + width: 23px; + } mat-error { font-size: 85%; } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts index 29165b0c00..ff2306b454 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step2/edit-autoscaler-policy-step2.component.ts @@ -1,8 +1,10 @@ -import { Component, OnInit } from '@angular/core'; +import { Component, OnDestroy, OnInit } from '@angular/core'; import { AbstractControl, FormBuilder, FormGroup, ValidatorFn, Validators } from '@angular/forms'; import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/material'; -import { Observable } from 'rxjs'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; +import { map } from 'rxjs/operators'; +import { safeUnsubscribe } from '../../../../../core/src/core/utils.service'; import { ApplicationService } from '../../../../../core/src/features/applications/application.service'; import { AutoscalerConstants, @@ -15,7 +17,11 @@ import { getThresholdMin, numberWithFractionOrExceedRange, } from '../../../core/autoscaler-helpers/autoscaler-validation'; -import { AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppAutoscalerInvalidPolicyError } from '../../../store/app-autoscaler.types'; +import { + AppAutoscalerInvalidPolicyError, + AppAutoscalerPolicy, + AppAutoscalerPolicyLocal, +} from '../../../store/app-autoscaler.types'; import { EditAutoscalerPolicy } from '../edit-autoscaler-policy-base-step'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; @@ -27,10 +33,12 @@ import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; { provide: ErrorStateMatcher, useClass: ShowOnDirtyErrorStateMatcher } ] }) -export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy implements OnInit { +export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy implements OnInit, OnDestroy { policyAlert = PolicyAlert; metricTypes = AutoscalerConstants.MetricTypes; + private metricUnitSubject = new BehaviorSubject(this.metricTypes[0]); + metricUnit$: Observable; operatorTypes = AutoscalerConstants.UpperOperators.concat(AutoscalerConstants.LowerOperators); editTriggerForm: FormGroup; appAutoscalerPolicy$: Observable; @@ -41,6 +49,7 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp private editMetricType = ''; private editScaleType = 'upper'; private editAdjustmentType = 'value'; + private subs: Subscription[] = []; constructor( public applicationService: ApplicationService, @@ -63,6 +72,12 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp ]], adjustment_type: [0, this.validateTriggerAdjustmentType()] }); + + this.metricUnit$ = this.metricUnitSubject.asObservable(); + + this.subs.push(this.editTriggerForm.get('metric_type').valueChanges.pipe( + map(value => this.getMetricUnit(value)), + ).subscribe(unit => this.metricUnitSubject.next(unit))); } addTrigger = () => { @@ -92,6 +107,7 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp cool_down_secs: this.currentPolicy.scaling_rules_form[index].cool_down_secs, adjustment_type: this.editAdjustmentType }); + this.metricUnitSubject.next(this.getMetricUnit(this.editMetricType)); } finishTrigger() { @@ -185,4 +201,8 @@ export class EditAutoscalerPolicyStep2Component extends EditAutoscalerPolicy imp getMetricUnit(metricType: string) { return AutoscalerConstants.getMetricUnit(metricType); } + + ngOnDestroy() { + safeUnsubscribe(...this.subs); + } } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html index 4ed111d0e6..f5c4e8f174 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step3/edit-autoscaler-policy-step3.component.html @@ -1,4 +1,6 @@
+

Add schedules that reoccur to overwrite the default instance limits for specific time periods. During these time + periods, all dynamic scaling rules are still effective.

diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html index 948cf3574a..91654b3744 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.html @@ -1,4 +1,6 @@
+

Add schedules for specific dates and times that overwrite the default instance limits. Like reoccuring schedules, + during these time periods all dynamic scaling rules are still effective.

diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts index f43e297500..930cc00ac5 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy-step4/edit-autoscaler-policy-step4.component.ts @@ -4,7 +4,7 @@ import { ErrorStateMatcher, ShowOnDirtyErrorStateMatcher } from '@angular/materi import { Store } from '@ngrx/store'; import * as moment from 'moment-timezone'; import { Observable, of as observableOf } from 'rxjs'; -import { distinctUntilChanged, filter, map, take } from 'rxjs/operators'; +import { filter, first, map, pairwise } from 'rxjs/operators'; import { EntityService } from '../../../../../core/src/core/entity-service'; import { EntityServiceFactory } from '../../../../../core/src/core/entity-service-factory.service'; @@ -20,10 +20,11 @@ import { } from '../../../core/autoscaler-helpers/autoscaler-validation'; import { UpdateAppAutoscalerPolicyAction } from '../../../store/app-autoscaler.actions'; import { + AppAutoscalerInvalidPolicyError, AppAutoscalerPolicy, AppAutoscalerPolicyLocal, AppSpecificDate, - AppAutoscalerInvalidPolicyError } from '../../../store/app-autoscaler.types'; +} from '../../../store/app-autoscaler.types'; import { appAutoscalerPolicySchemaKey } from '../../../store/autoscaler.store.module'; import { EditAutoscalerPolicy } from '../edit-autoscaler-policy-base-step'; import { EditAutoscalerPolicyService } from '../edit-autoscaler-policy-service'; @@ -88,46 +89,26 @@ export class EditAutoscalerPolicyStep4Component extends EditAutoscalerPolicy imp this.store.dispatch( new UpdateAppAutoscalerPolicyAction(this.applicationService.appGuid, this.applicationService.cfGuid, this.currentPolicy) ); - const waitForAppAutoscalerUpdateStatus$ = this.updateAppAutoscalerPolicyService.entityMonitor.entityRequest$.pipe( - filter(request => { - if (request.message && request.message.indexOf('fetch policy') >= 0) { - request.message = ''; - return false; - } else { - return !!request.error || !!request.response; - } - }), - map(request => { - const msg = request.message; - request.error = false; - request.response = null; - request.message = ''; - return msg; - }), - distinctUntilChanged(), - ).pipe(map( - errorMessage => { - if (errorMessage) { - return { - success: false, - message: `Could not update policy: ${errorMessage}`, - }; - } else { - return { - success: true, - redirect: true - }; - } - })); - return waitForAppAutoscalerUpdateStatus$.pipe(take(1), map(res => { - return { - ...res, - }; - })); + return this.updateAppAutoscalerPolicyService.entityMonitor.entityRequest$.pipe( + pairwise(), + filter(([oldV, newV]) => ( + oldV.updating[UpdateAppAutoscalerPolicyAction.updateKey] && oldV.updating[UpdateAppAutoscalerPolicyAction.updateKey].busy + ) && ( + newV.updating[UpdateAppAutoscalerPolicyAction.updateKey] && !newV.updating[UpdateAppAutoscalerPolicyAction.updateKey].busy + ) + ), + map(([, newV]) => newV.updating[UpdateAppAutoscalerPolicyAction.updateKey]), + map(request => ({ + success: !request.error, + redirect: !request.error, + message: request.error ? `Could not update policy${request.message ? `: ${request.message}` : ''}` : null + })), + first(), + ); } addSpecificDate = () => { - const {...newSchedule} = AutoscalerConstants.PolicyDefaultSpecificDate; + const { ...newSchedule } = AutoscalerConstants.PolicyDefaultSpecificDate; this.currentPolicy.schedules.specific_date.push(newSchedule); this.editSpecificDate(this.currentPolicy.schedules.specific_date.length - 1); } diff --git a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html index adc4db13ee..09913641f6 100644 --- a/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html +++ b/src/frontend/packages/cf-autoscaler/src/features/edit-autoscaler-policy/edit-autoscaler-policy.component.html @@ -1,6 +1,7 @@
-

Edit AutoScaler Policy: {{ (applicationService.application$ | async)?.app.entity.name }}

+

{{isCreate ? 'Create' : 'Edit' }} AutoScaler Policy: + {{ (applicationService.application$ | async)?.app.entity.name }}

- +
-
+ +
+ + \ No newline at end of file diff --git a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss index 03cff2642b..96e3d7bc25 100644 --- a/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss +++ b/src/frontend/packages/cf-autoscaler/src/shared/card-autoscaler-default/card-autoscaler-default.component.scss @@ -1,52 +1,10 @@ .card-autoscaler-default { - &__left { - border-right: 1px solid #808080; - display: inline-block; - padding-right: 1em; - } - - &__right { - display: inline-block; - padding-left: 1em; - } - - &__actions { - bottom: 24px; - padding-top: 0; - position: absolute; - right: 24px; - text-align: right; - } - - app-metadata-item { - .metadata-item__content { - flex-direction: row; - } - } - - .metadata-item__content { - display: inline; - } - - .metadata-item__value { - display: inline; - margin-right: 10px; - } - - .metadata-item__label { - display: inline; - margin-right: 10px; - } -} + &__min-max { + display: flex; + flex-direction: row; -.app-metadata { - display: flex; - flex-direction: row; - &__two-cols { - flex: 1; - margin-right: 1em; - app-metadata-item:first-child { - margin-top: 0; + app-metadata-item { + padding-right: 10px; } } } diff --git a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts index 0fa992e75b..ba27b3cbc3 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.actions.ts @@ -9,6 +9,7 @@ import { AppAutoscalerPolicyLocal, AppScalingTrigger } from './app-autoscaler.ty import { appAutoscalerAppMetricSchemaKey, appAutoscalerHealthSchemaKey, + appAutoscalerInfoSchemaKey, appAutoscalerPolicySchemaKey, appAutoscalerPolicyTriggerSchemaKey, appAutoscalerScalingHistorySchemaKey, @@ -45,14 +46,28 @@ export const DETACH_APP_AUTOSCALER_POLICY = '[New App Autoscaler] Detach policy' export const APP_AUTOSCALER_HEALTH = '[New App Autoscaler] Fetch Health'; export const APP_AUTOSCALER_SCALING_HISTORY = '[New App Autoscaler] Fetch Scaling History'; export const FETCH_APP_AUTOSCALER_METRIC = '[New App Autoscaler] Fetch Metric'; +export const AUTOSCALER_INFO = '[Autoscaler] Fetch Info'; export const UPDATE_APP_AUTOSCALER_POLICY_STEP = '[Edit Autoscaler Policy] Step'; +export class GetAppAutoscalerInfoAction implements IRequestAction { + public guid: string; + constructor( + public endpointGuid: string, + ) { + this.guid = endpointGuid; + } + type = AUTOSCALER_INFO; + entity = entityFactory(appAutoscalerInfoSchemaKey); + entityKey = appAutoscalerInfoSchemaKey; +} + export class GetAppAutoscalerHealthAction implements IRequestAction { + public guid: string; constructor( - public guid: string, public endpointGuid: string, ) { + this.guid = endpointGuid; } type = APP_AUTOSCALER_HEALTH; entity = entityFactory(appAutoscalerHealthSchemaKey); @@ -78,6 +93,7 @@ export class UpdateAppAutoscalerPolicyAction implements IRequestAction { ) { } type = UPDATE_APP_AUTOSCALER_POLICY; entityKey = appAutoscalerPolicySchemaKey; + updatingKey = UpdateAppAutoscalerPolicyAction.updateKey; } export class DetachAppAutoscalerPolicyAction implements IRequestAction { diff --git a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts index d7cb19c63d..541d124d38 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/app-autoscaler.types.ts @@ -1,5 +1,12 @@ import { AutoscalerQuery } from './app-autoscaler.actions'; +export interface AutoscalerInfo { + name: string; + build: string; + support: string; + description: string; +} + export interface AppAutoscalerPolicy { instance_min_count: number; instance_max_count: number; diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts index 03e76301f8..218ec8c126 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.effects.ts @@ -12,7 +12,7 @@ import { resultPerPageParamDefault, } from '../../../store/src/reducers/pagination-reducer/pagination-reducer.types'; import { selectPaginationState } from '../../../store/src/selectors/pagination.selectors'; -import { APIResource, NormalizedResponse } from '../../../store/src/types/api.types'; +import { APIResource, NormalizedResponse, PaginationResponse } from '../../../store/src/types/api.types'; import { PaginatedAction, PaginationEntityState, PaginationParam } from '../../../store/src/types/pagination.types'; import { StartRequestAction, @@ -30,28 +30,29 @@ import { APP_AUTOSCALER_POLICY, APP_AUTOSCALER_POLICY_TRIGGER, APP_AUTOSCALER_SCALING_HISTORY, + AUTOSCALER_INFO, + AutoscalerPaginationParams, AutoscalerQuery, DETACH_APP_AUTOSCALER_POLICY, DetachAppAutoscalerPolicyAction, FETCH_APP_AUTOSCALER_METRIC, GetAppAutoscalerHealthAction, + GetAppAutoscalerInfoAction, GetAppAutoscalerMetricAction, GetAppAutoscalerPolicyAction, GetAppAutoscalerPolicyTriggerAction, GetAppAutoscalerScalingHistoryAction, UPDATE_APP_AUTOSCALER_POLICY, UpdateAppAutoscalerPolicyAction, - AutoscalerPaginationParams, } from './app-autoscaler.actions'; import { + AppAutoscalerEvent, AppAutoscalerFetchPolicyFailedResponse, - AppAutoscalerMetricDataLocal, - AppScalingTrigger, AppAutoscalerMetricData, + AppAutoscalerMetricDataLocal, AppAutoscalerPolicyLocal, - AppAutoscalerEvent, + AppScalingTrigger, } from './app-autoscaler.types'; -import { PaginationResponse } from '../../../store/src/types/api.types'; const { proxyAPIVersion } = environment; const commonPrefix = `/pp/${proxyAPIVersion}/autoscaler`; @@ -68,6 +69,34 @@ export class AutoscalerEffects { private store: Store, ) { } + @Effect() + fetchAutoscalerInfo$ = this.actions$.pipe( + ofType(AUTOSCALER_INFO), + mergeMap(action => { + const actionType = 'fetch'; + this.store.dispatch(new StartRequestAction(action, actionType)); + const options = new RequestOptions(); + options.url = `${commonPrefix}/info`; + options.method = 'get'; + options.headers = this.addHeaders(action.endpointGuid); + return this.http + .request(new Request(options)).pipe( + mergeMap(response => { + const autoscalerInfo = response.json(); + const mappedData = { + entities: { [action.entityKey]: {} }, + result: [] + } as NormalizedResponse; + this.transformData(action.entityKey, mappedData, action.endpointGuid, autoscalerInfo); + return [ + new WrapperRequestActionSuccess(mappedData, action, actionType) + ]; + }), + catchError(err => [ + new WrapperRequestActionFailed(createAutoscalerRequestMessage('fetch autoscaler info', err), action, actionType) + ])); + })); + @Effect() fetchAppAutoscalerHealth$ = this.actions$.pipe( ofType(APP_AUTOSCALER_HEALTH), @@ -86,7 +115,7 @@ export class AutoscalerEffects { entities: { [action.entityKey]: {} }, result: [] } as NormalizedResponse; - this.transformData(action.entityKey, mappedData, action.guid, healthInfo); + this.transformData(action.entityKey, mappedData, action.endpointGuid, healthInfo); return [ new WrapperRequestActionSuccess(mappedData, action, actionType) ]; @@ -348,14 +377,14 @@ export class AutoscalerEffects { mappedData.result.push(id); } - transformData(key: string, mappedData: NormalizedResponse, appGuid: string, data: any) { - mappedData.entities[key][appGuid] = { + transformData(key: string, mappedData: NormalizedResponse, guid: string, data: any) { + mappedData.entities[key][guid] = { entity: data, metadata: { - guid: appGuid + guid } }; - mappedData.result.push(appGuid); + mappedData.result.push(guid); } transformEventData(key: string, mappedData: NormalizedResponse, appGuid: string, data: PaginationResponse) { diff --git a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts index 702c816075..725050dc18 100644 --- a/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts +++ b/src/frontend/packages/cf-autoscaler/src/store/autoscaler.store.module.ts @@ -6,12 +6,18 @@ import { ExtensionEntitySchema } from '../../../core/src/core/extension/extensio import { getAPIResourceGuid } from '../../../store/src/selectors/api.selectors'; export const appAutoscalerHealthSchemaKey = 'autoscalerHealth'; +export const appAutoscalerInfoSchemaKey = 'autoscalerInfo'; export const appAutoscalerPolicySchemaKey = 'autoscalerPolicy'; export const appAutoscalerPolicyTriggerSchemaKey = 'autoscalerPolicyTrigger'; export const appAutoscalerScalingHistorySchemaKey = 'autoscalerScalingHistory'; export const appAutoscalerAppMetricSchemaKey = 'autoscalerAppMetric'; export const autoscalerEntities: ExtensionEntitySchema[] = [ + { + entityKey: appAutoscalerInfoSchemaKey, + definition: {}, + options: { idAttribute: getAPIResourceGuid } + }, { entityKey: appAutoscalerPolicySchemaKey, definition: {}, diff --git a/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html b/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html index 4a9d17d0d3..9ce13dc103 100644 --- a/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html +++ b/src/frontend/packages/core/src/shared/components/cards/card-cf-info/card-cf-info.component.html @@ -8,21 +8,29 @@