From 4de8b3511cbd662b30f09b269d5fbd70f67cd380 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 11:04:57 -0800 Subject: [PATCH 01/13] Quota service wip --- app/app.module.ts | 2 + .../account-quotas-card.component.ts | 189 ++---------------- .../account-quotas-card.html | 8 +- app/models/batch-quotas.ts | 16 ++ app/models/index.ts | 1 + app/services/index.ts | 3 +- .../{pool-service.ts => pool.service.ts} | 4 + app/services/quota.service.ts | 95 +++++++++ 8 files changed, 145 insertions(+), 173 deletions(-) create mode 100644 app/models/batch-quotas.ts rename app/services/{pool-service.ts => pool.service.ts} (98%) create mode 100644 app/services/quota.service.ts diff --git a/app/app.module.ts b/app/app.module.ts index 7bbac40d5f..86c8abc0e8 100644 --- a/app/app.module.ts +++ b/app/app.module.ts @@ -76,6 +76,7 @@ import { PredefinedFormulaService, PricingService, PythonRpcService, + QuotaService, ResourceAccessService, SSHKeyService, SettingsService, @@ -158,6 +159,7 @@ const graphApiServices = [AADApplicationService, AADGraphHttpService, MsGraphHtt PollService, PoolService, PricingService, + QuotaService, PythonRpcService, ResourceAccessService, SettingsService, diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts index 264afca91b..7a42f1978b 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts +++ b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts @@ -1,9 +1,9 @@ -import { Component, Input, OnChanges, OnDestroy } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; import { Subscription } from "rxjs"; import { LoadingStatus } from "app/components/base/loading"; -import { AccountResource, Pool } from "app/models"; -import { ComputeService, PoolListParams, PoolService, VmSizeService } from "app/services"; +import { AccountResource, BatchQuotas, Pool } from "app/models"; +import { ComputeService, PoolListParams, PoolService, QuotaService, VmSizeService } from "app/services"; import { ListView } from "app/services/core"; import { ComponentUtils } from "app/utils"; @@ -16,150 +16,55 @@ type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; @Component({ selector: "bl-account-quotas-card", templateUrl: "account-quotas-card.html", + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class AccountQuotasCardComponent implements OnChanges, OnDestroy { +export class AccountQuotasCardComponent implements OnDestroy, OnInit { @Input() public account: AccountResource; + public get bufferValue(): number { return 100; } - public poolData: ListView; - public vmSizeCores: StringMap; - - private _usedPool: number = null; - private _totalPoolQuota: number = null; - - private _dedicatedCoreLoadingStatus = LoadingStatus.Loading; - private _usedDedicatedCore: number = null; - private _dedicatedCoreQuota: number = null; + public quotas: BatchQuotas = new BatchQuotas(); + public use: BatchQuotas = new BatchQuotas(); - private _lowPriorityCoreStatus = LoadingStatus.Loading; - private _usedLowPrioirtyCore: number = null; - private _lowPriorityCoreQuota: number = null; + private _quotaSub: Subscription; - private _poolListViewSub: Subscription; - private _vmSizeSub: Subscription; - private _computeServiceSub: Subscription; - - constructor(private computeService: ComputeService, - private poolService: PoolService, - private vmSizeService: VmSizeService) { - this.vmSizeCores = {...vmSizeService.additionalVmSizeCores}; - const vmSizeObs = Observable.merge( - this.vmSizeService.virtualMachineSizes, this.vmSizeService.cloudServiceSizes); - this._vmSizeSub = vmSizeObs.subscribe(vmSizes => { - if (vmSizes) { - vmSizes.forEach(vmSize => this.vmSizeCores[vmSize.id] = vmSize.numberOfCores); - } + constructor(private quotaService: QuotaService, private changeDetector: ChangeDetectorRef) { + this._quotaSub = this.quotaService.quotas.subscribe((quotas) => { + this.quotas = quotas; + this.changeDetector.markForCheck(); }); } - public ngOnChanges(changes) { - if (changes.account) { - if (this.account.isBatchManaged) { - this._dedicatedCoreQuota = this.account.properties.dedicatedCoreQuota; - this._lowPriorityCoreQuota = this.account.properties.lowPriorityCoreQuota; - this._listUsage(); - } - - if (this.isByosAccount(changes)) { - if (this._computeServiceSub) { - this._computeServiceSub.unsubscribe(); - } - this._computeServiceSub = this.computeService.getCoreQuota().subscribe((dedicatedCoreQuota) => { - if (this.isByosAccount(changes)) { - this._dedicatedCoreQuota = dedicatedCoreQuota; - this._lowPriorityCoreQuota = null; - this._listUsage(true); - } - }); - } - } + public ngOnInit() { + this.quotaService.getUsage().subscribe((quota) => { + this.use = quota; + }); } public ngOnDestroy(): void { - if (this._poolListViewSub) { - this._poolListViewSub.unsubscribe(); - } - if (this._vmSizeSub) { - this._vmSizeSub.unsubscribe(); - } - if (this._computeServiceSub) { - this._computeServiceSub.unsubscribe(); - } + this._quotaSub.unsubscribe(); } /** * Get pool usage progress bar percent */ public get poolUsagePercent() { - return this._calculatePercentage(this._usedPool, this._totalPoolQuota); - } - - /** - * Get friendly message displayed for pools - * Format: {{used}}/{{total}} ({{Percent}}) - */ - public get poolUsageStatus(): string { - const used = this._usedPool; - const total = this._totalPoolQuota; - if (used !== null && total !== null) { - return `${used}/${total} (${Math.floor(this.poolUsagePercent)}%)`; - } - return "N/A"; + return this._calculatePercentage(this.use.pools, this.quotas.pools); } /** * Get dedicated cores usage progress bar percent */ public get dedicatedCoresPercent() { - return this._calculatePercentage(this._usedDedicatedCore, this._dedicatedCoreQuota); - } - - /** - * Get friendly message displayed for dedicated cores - * Format: {{used}}/{{total}} ({{Percent}}) - */ - public get dedicatedCoreStatus(): string { - switch (this._dedicatedCoreLoadingStatus) { - case LoadingStatus.Loading: - return "Loading."; - case LoadingStatus.Ready: - const used = this._usedDedicatedCore; - const total = this._dedicatedCoreQuota; - if (used !== null && total !== null) { - return `${used}/${total} (${Math.floor(this.dedicatedCoresPercent)}%)`; - } - case LoadingStatus.Error: - default: - return "N/A"; - } + return this._calculatePercentage(this.use.jobs, this.quotas.jobs); } /** * Get low priority cores usage progress bar percent */ public get lowPriorityCoresPercent() { - return this._calculatePercentage(this._usedLowPrioirtyCore, this._lowPriorityCoreQuota); - } - - /** - * Get friendly message displayed for low priority cores - * Format: {{used}}/{{total}} ({{Percent}}) - */ - public get lowPriorityCoreStatus(): string { - switch (this._lowPriorityCoreStatus) { - case LoadingStatus.Loading: - return "Loading."; - case LoadingStatus.Ready: - const used = this._usedLowPrioirtyCore; - const total = this._lowPriorityCoreQuota; - if (used !== null && total !== null) { - return `${used}/${total} (${Math.floor(this.lowPriorityCoresPercent)}%)`; - } - case LoadingStatus.Error: - default: - return "N/A"; - } + return this._calculatePercentage(this.use.lowpriCores, this.quotas.lowpriCores); } /** @@ -176,54 +81,6 @@ export class AccountQuotasCardComponent implements OnChanges, OnDestroy { return "low-usage"; } - /** - * Fetch latest pool list and core usages - */ - private _listUsage(isByos?: boolean) { - this._dedicatedCoreLoadingStatus = LoadingStatus.Loading; - this._lowPriorityCoreStatus = LoadingStatus.Loading; - this.poolData = this.poolService.listView(); - this.poolData.fetchNext(); - if (this._poolListViewSub) { - this._poolListViewSub.unsubscribe(); - } - this._poolListViewSub = this.poolData.items.subscribe((pools) => { - this._usedPool = pools.size; - this._loadCoreUsages(pools); - this._dedicatedCoreLoadingStatus = LoadingStatus.Ready; - if (isByos) { - this._lowPriorityCoreStatus = LoadingStatus.Error; - } else { - this._lowPriorityCoreStatus = LoadingStatus.Ready; - } - }, (error) => { - this._dedicatedCoreLoadingStatus = LoadingStatus.Error; - this._lowPriorityCoreStatus = LoadingStatus.Error; - }); - this._totalPoolQuota = this.account.properties.poolQuota; - } - - /** - * Calculate pool dedicated cores and low priority core usage - * @param pools - */ - private _loadCoreUsages(pools: List) { - this._usedDedicatedCore = 0; - this._usedLowPrioirtyCore = 0; - if (!pools || pools.size === 0) { - return; - } - pools.forEach(pool => { - if (pool && pool.vmSize) { - const key = pool.vmSize.toLowerCase(); - if (this.vmSizeCores[key]) { - this._usedDedicatedCore += (this.vmSizeCores[key] * pool.currentDedicatedNodes); - this._usedLowPrioirtyCore += (this.vmSizeCores[key] * pool.currentLowPriorityNodes); - } - } - }); - } - /** * Calculate percentage of used pools, dedicated/lowPriority cores * @param used @@ -235,8 +92,4 @@ export class AccountQuotasCardComponent implements OnChanges, OnDestroy { } return 0; } - - private isByosAccount(changes) { - return ComponentUtils.recordChangedId(changes.account) && !this.account.isBatchManaged; - } } diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.html b/app/components/account/details/account-quotas-card/account-quotas-card.html index 2998fa5a34..43c4cfcb3e 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.html +++ b/app/components/account/details/account-quotas-card/account-quotas-card.html @@ -2,7 +2,7 @@
Pool usage
- {{ poolUsageStatus }} + {{ quotas?.pools }}
Job quota
-
{{account.properties.activeJobAndJobScheduleQuota}}
+
{{quotas?.jobs}}
Dedicated core usage
- {{ dedicatedCoreStatus }} + {{ quotas?.dedicatedCores }}
Low-pri core usage
- {{ lowPriorityCoreStatus }} + {{ quotas?.lowpriCores }}
{ + @Prop() public pools: number; + @Prop() public jobs: number; + @Prop() public dedicatedCores: number; + @Prop() public lowpriCores: number; +} diff --git a/app/models/index.ts b/app/models/index.ts index 9f8a4790c3..c5754d51b7 100644 --- a/app/models/index.ts +++ b/app/models/index.ts @@ -13,6 +13,7 @@ export * from "./application-package-reference"; export * from "./auto-user"; export * from "./autoscale-formula"; export * from "./batch-error"; +export * from "./batch-quotas"; export * from "./blob-container"; export * from "./certificate-reference"; export * from "./cloud-service-configuration"; diff --git a/app/services/index.ts b/app/services/index.ts index aff5f5d7b7..c6ec35dcdc 100644 --- a/app/services/index.ts +++ b/app/services/index.ts @@ -20,13 +20,14 @@ export * from "./monaco-loader.service"; export * from "./monitor-http"; export * from "./navigator.service"; export * from "./pinned-entity.service"; -export * from "./pool-service"; +export * from "./pool.service"; export * from "./ncj-file-group.service"; export * from "./ncj-submit.service"; export * from "./ncj-template.service"; export * from "./node-service"; export * from "./node-user.service"; export * from "./pricing.service"; +export * from "./quota.service"; export * from "./resource-access"; export * from "./settings.service"; export * from "./ssh-key.service"; diff --git a/app/services/pool-service.ts b/app/services/pool.service.ts similarity index 98% rename from app/services/pool-service.ts rename to app/services/pool.service.ts index 6c156c251f..85d970b019 100644 --- a/app/services/pool-service.ts +++ b/app/services/pool.service.ts @@ -65,6 +65,10 @@ export class PoolService extends ServiceBase { } } + public listAll(options?: ListOptionsAttributes) { + return this._listGetter.fetchAll(options); + } + public listView(options: ListOptionsAttributes = {}): ListView { return new ListView({ cache: () => this._cache, diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts new file mode 100644 index 0000000000..b464fef631 --- /dev/null +++ b/app/services/quota.service.ts @@ -0,0 +1,95 @@ +import { Injectable, OnDestroy } from "@angular/core"; +import { Observable, Subscription } from "rxjs"; + +import { AccountResource, BatchQuotas, Pool } from "app/models"; +import { AccountService } from "app/services"; +import { List } from "immutable"; +import { ComputeService } from "./compute.service"; +import { PoolService } from "./pool.service"; +import { VmSizeService } from "./vm-size.service"; + +/** + * Service grouping all quotas needed + */ +@Injectable() +export class QuotaService implements OnDestroy { + public quotas: Observable; + + private vmSizeCores: StringMap; + private _subs: Subscription[] = []; + + constructor( + private accountService: AccountService, + private computeService: ComputeService, + private poolService: PoolService, + private vmSizeService: VmSizeService) { + + this.vmSizeCores = { ...vmSizeService.additionalVmSizeCores }; + const vmSizeObs = Observable.merge( + this.vmSizeService.virtualMachineSizes, this.vmSizeService.cloudServiceSizes); + this._subs.push(vmSizeObs.subscribe(vmSizes => { + if (vmSizes) { + vmSizes.forEach(vmSize => this.vmSizeCores[vmSize.id] = vmSize.numberOfCores); + } + })); + + this.quotas = this.accountService.currentAccount.flatMap((account) => { + return this._computeQuotas(account); + }).shareReplay(1); + } + + public ngOnDestroy() { + this._subs.forEach(x => x.unsubscribe()); + } + + public getUsage(): Observable { + return this.poolService.listAll().map((pools) => { + const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); + return new BatchQuotas({ + pools: pools.size, + dedicatedCores, + lowpriCores, + jobs: 0, + }); + }).share(); + } + + private _computeQuotas(account: AccountResource): Observable { + if (account.isBatchManaged) { + return Observable.of(new BatchQuotas({ + dedicatedCores: account.properties.dedicatedCoreQuota, + lowpriCores: account.properties.lowPriorityCoreQuota, + pools: account.properties.poolQuota, + jobs: account.properties.activeJobAndJobScheduleQuota, + })); + } else { + this.computeService.getCoreQuota().map((dedicatedCoreQuota) => { + return Observable.of(new BatchQuotas({ + dedicatedCores: dedicatedCoreQuota, + lowpriCores: null, + pools: account.properties.poolQuota, + jobs: account.properties.activeJobAndJobScheduleQuota, + })); + }); + } + } + + private _getCoreUsages(pools: List) { + let dedicatedCores = 0; + let lowpriCores = 0; + if (!pools || pools.size === 0) { + return; + } + pools.forEach(pool => { + if (pool && pool.vmSize) { + const key = pool.vmSize.toLowerCase(); + if (this.vmSizeCores[key]) { + dedicatedCores += (this.vmSizeCores[key] * pool.currentDedicatedNodes); + lowpriCores += (this.vmSizeCores[key] * pool.currentLowPriorityNodes); + } + } + }); + + return { dedicatedCores, lowpriCores }; + } +} From 4b805132acae80a6119162e356a741b544634b79 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 11:23:52 -0800 Subject: [PATCH 02/13] Works --- .../account-quotas-card.component.ts | 47 +++++++++++++++---- .../account-quotas-card.html | 6 +-- app/services/quota.service.ts | 3 +- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts index 7a42f1978b..a5af247adf 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts +++ b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts @@ -1,14 +1,9 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; import { Subscription } from "rxjs"; -import { LoadingStatus } from "app/components/base/loading"; -import { AccountResource, BatchQuotas, Pool } from "app/models"; -import { ComputeService, PoolListParams, PoolService, QuotaService, VmSizeService } from "app/services"; -import { ListView } from "app/services/core"; -import { ComponentUtils } from "app/utils"; +import { AccountResource, BatchQuotas, BatchQuotasAttributes } from "app/models"; +import { QuotaService } from "app/services"; -import { List } from "immutable"; -import { Observable } from "rxjs/Observable"; import "./account-quotas-card.scss"; type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; @@ -25,7 +20,8 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { return 100; } public quotas: BatchQuotas = new BatchQuotas(); - public use: BatchQuotas = new BatchQuotas(); + public use: BatchQuotas = new BatchQuotas(); + public loadingUse = true; private _quotaSub: Subscription; @@ -38,7 +34,9 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { public ngOnInit() { this.quotaService.getUsage().subscribe((quota) => { + this.loadingUse = false; this.use = quota; + this.changeDetector.markForCheck(); }); } @@ -53,11 +51,31 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { return this._calculatePercentage(this.use.pools, this.quotas.pools); } + /** + * Get friendly message displayed for pools + * Format: {{used}}/{{total}} ({{Percent}}) + */ + public get poolStatus(): string { + return this._prettyUsage("pools"); + } + + public get jobStatus(): string { + return this._prettyUsage("jobs"); + } + + public get dedicatedCoresStatus(): string { + return this._prettyUsage("dedicatedCores"); + } + + public get lowPriCoresStatus(): string { + return this._prettyUsage("lowpriCores"); + } + /** * Get dedicated cores usage progress bar percent */ public get dedicatedCoresPercent() { - return this._calculatePercentage(this.use.jobs, this.quotas.jobs); + return this._calculatePercentage(this.use.dedicatedCores, this.quotas.dedicatedCores); } /** @@ -92,4 +110,13 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { } return 0; } + + private _prettyUsage(name: keyof (BatchQuotasAttributes)) { + const used = this.use && this.use[name]; + const total = this.quotas && this.quotas[name]; + if (used !== null && total !== null) { + return `${used}/${total} (${Math.floor(this._calculatePercentage(used, total))}%)`; + } + return "N/A"; + } } diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.html b/app/components/account/details/account-quotas-card/account-quotas-card.html index 43c4cfcb3e..6050a8f3e3 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.html +++ b/app/components/account/details/account-quotas-card/account-quotas-card.html @@ -2,7 +2,7 @@
Pool usage
- {{ quotas?.pools }} + {{ poolStatus }}
Dedicated core usage
- {{ quotas?.dedicatedCores }} + {{ dedicatedCoresStatus }}
Low-pri core usage
- {{ quotas?.lowpriCores }} + {{ lowPriCoresStatus }}
{ return this.poolService.listAll().map((pools) => { + console.log("pools", pools && pools.toJS()); const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); return new BatchQuotas({ pools: pools.size, @@ -78,7 +79,7 @@ export class QuotaService implements OnDestroy { let dedicatedCores = 0; let lowpriCores = 0; if (!pools || pools.size === 0) { - return; + return { dedicatedCores: 0, lowpriCores: 0 }; } pools.forEach(pool => { if (pool && pool.vmSize) { From a5b493ff9fd3c7397dd5f8a96ddaab678c62fb81 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 11:32:14 -0800 Subject: [PATCH 03/13] Fix account change detection --- app/components/account/details/account-details.component.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/components/account/details/account-details.component.ts b/app/components/account/details/account-details.component.ts index 83a9b09890..8bde95023d 100644 --- a/app/components/account/details/account-details.component.ts +++ b/app/components/account/details/account-details.component.ts @@ -1,4 +1,4 @@ -import { Component, NgZone, OnDestroy, OnInit, ViewContainerRef } from "@angular/core"; +import { ChangeDetectorRef, Component, NgZone, OnDestroy, OnInit, ViewContainerRef } from "@angular/core"; import { ActivatedRoute, Router } from "@angular/router"; import { autobind } from "app/core"; import { Subscription } from "rxjs"; @@ -44,6 +44,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy { constructor( router: Router, + private changeDetector: ChangeDetectorRef, private activatedRoute: ActivatedRoute, private accountService: AccountService, private dialogService: DialogService, @@ -56,6 +57,7 @@ export class AccountDetailsComponent implements OnInit, OnDestroy { this.data = this.accountService.view(); this.data.item.subscribe((account) => { this.account = account; + this.changeDetector.markForCheck(); if (account) { this._loadQuickAccessLists(); } From fa986d0a03112b2e04f110b2ad676d531a422328 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 13:07:27 -0800 Subject: [PATCH 04/13] QUota display component --- app/components/account/account.module.ts | 3 +- .../account/base/inline-quota/index.ts | 1 + .../inline-quota/inline-quota.component.ts | 97 +++++++++++++++++++ .../base/inline-quota/inline-quota.html | 9 ++ .../base/inline-quota/inline-quota.scss | 0 .../account-quotas-card.component.ts | 5 +- .../account-quotas-card.html | 39 +------- .../account-quotas-card.scss | 36 ------- app/components/base/base.module.ts | 2 + .../base/browse-layout/browse-layout.html | 4 + app/components/base/quotas/index.ts | 2 + .../base/quotas/quota-display/index.ts | 1 + .../quota-display/quota-display.component.ts | 66 +++++++++++++ .../quotas/quota-display/quota-display.html | 8 ++ .../quotas/quota-display/quota-display.scss | 44 +++++++++ app/components/base/quotas/quotas.module.ts | 17 ++++ app/components/pool/home/pool-home.html | 1 + app/components/pool/pool.module.ts | 4 +- app/services/quota.service.ts | 1 - src/client/startup.ts | 2 +- 20 files changed, 264 insertions(+), 78 deletions(-) create mode 100644 app/components/account/base/inline-quota/index.ts create mode 100644 app/components/account/base/inline-quota/inline-quota.component.ts create mode 100644 app/components/account/base/inline-quota/inline-quota.html create mode 100644 app/components/account/base/inline-quota/inline-quota.scss create mode 100644 app/components/base/quotas/index.ts create mode 100644 app/components/base/quotas/quota-display/index.ts create mode 100644 app/components/base/quotas/quota-display/quota-display.component.ts create mode 100644 app/components/base/quotas/quota-display/quota-display.html create mode 100644 app/components/base/quotas/quota-display/quota-display.scss create mode 100644 app/components/base/quotas/quotas.module.ts diff --git a/app/components/account/account.module.ts b/app/components/account/account.module.ts index 18d4563e22..69874c1b01 100644 --- a/app/components/account/account.module.ts +++ b/app/components/account/account.module.ts @@ -5,6 +5,7 @@ import { EditStorageAccountFormComponent } from "app/components/account/action/e import { StorageAccountPickerComponent } from "app/components/account/base/storage-account-picker"; import { PoolBaseModule } from "app/components/pool/base"; import { DeleteAccountDialogComponent } from "./action/delete/delete-account-dialog.component"; +import { InlineQuotaComponent } from "./base/inline-quota"; import { AccountBrowseModule } from "./browse"; import { AccountDefaultComponent, AccountDetailsComponent } from "./details"; import { AccountQuotasCardComponent } from "./details/account-quotas-card"; @@ -17,7 +18,7 @@ const components = [ AccountDefaultComponent, AccountDetailsComponent, AccountHomeComponent, DeleteAccountDialogComponent, StorageAccountCardComponent, EditStorageAccountFormComponent, StorageAccountPickerComponent, AccountQuotasCardComponent, - MonitorChartComponent, + MonitorChartComponent, InlineQuotaComponent, ]; const modules = [ diff --git a/app/components/account/base/inline-quota/index.ts b/app/components/account/base/inline-quota/index.ts new file mode 100644 index 0000000000..83017ca9a2 --- /dev/null +++ b/app/components/account/base/inline-quota/index.ts @@ -0,0 +1 @@ +export * from "./inline-quota.component"; diff --git a/app/components/account/base/inline-quota/inline-quota.component.ts b/app/components/account/base/inline-quota/inline-quota.component.ts new file mode 100644 index 0000000000..df2bb8509b --- /dev/null +++ b/app/components/account/base/inline-quota/inline-quota.component.ts @@ -0,0 +1,97 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { Subscription } from "rxjs"; + +import { BatchQuotas, BatchQuotasAttributes } from "app/models"; +import { QuotaService } from "app/services"; + +import "./inline-quota.scss"; + +type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; + +@Component({ + selector: "bl-inline-quota", + templateUrl: "inline-quota.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class InlineQuotaComponent implements OnDestroy, OnInit { + @Input() public set include(quotas: string | string[]) { + this._include = Array.isArray(quotas) ? quotas : quotas.split(",") as any; + } + + public bufferValue = 100; + + public quotas: BatchQuotas = new BatchQuotas(); + public use: BatchQuotas = new BatchQuotas(); + public loadingUse = true; + public statues = []; + + private _include: Array; + private _quotaSub: Subscription; + + constructor(private quotaService: QuotaService, private changeDetector: ChangeDetectorRef) { + this._quotaSub = this.quotaService.quotas.subscribe((quotas) => { + this.quotas = quotas; + this._update(); + }); + } + + public ngOnInit() { + this.quotaService.getUsage().subscribe((quota) => { + this.loadingUse = false; + this.use = quota; + this._update(); + + }); + } + + public ngOnDestroy(): void { + this._quotaSub.unsubscribe(); + } + + public get mainStatus() { + return this.statues.first(); + } + + /** + * Defines usage progress bar color for pool usage, dedicated/lowPriority cores usage. + * Use 3 different states (error, warn and success) to represent high usage, medium usage and low usage + * @param percent + */ + public getColorClass(percent: number): ProgressColorClass { + if (percent <= 100 && percent >= 90) { + return "high-usage"; + } else if (percent >= 50) { + return "medium-usage"; + } + return "low-usage"; + } + + private _update() { + this.statues = this._include.map((name) => { + const usage = this.use && this.use[name]; + const total = this.quotas && this.quotas[name]; + const percent = this._calculatePercentage(usage, total); + + return { + name, + usage, + total, + percent, + }; + }).sort((a, b) => b.percent - a.percent); + this.changeDetector.markForCheck(); + } + + /** + * Calculate percentage of used pools, dedicated/lowPriority cores + * @param used + * @param total + */ + private _calculatePercentage(used: number, total: number): number { + if (used !== null && total !== null && total > 0) { + return (used / total) * 100; + } + return 0; + } + +} diff --git a/app/components/account/base/inline-quota/inline-quota.html b/app/components/account/base/inline-quota/inline-quota.html new file mode 100644 index 0000000000..90bbc4b470 --- /dev/null +++ b/app/components/account/base/inline-quota/inline-quota.html @@ -0,0 +1,9 @@ +
+
+ {{mainStatus.name}} ({{mainStatus.usage}}/{{mainStatus.total}}) +
+
+ + +
+
diff --git a/app/components/account/base/inline-quota/inline-quota.scss b/app/components/account/base/inline-quota/inline-quota.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts index a5af247adf..b4fe8b76ce 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts +++ b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts @@ -16,9 +16,8 @@ type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; export class AccountQuotasCardComponent implements OnDestroy, OnInit { @Input() public account: AccountResource; - public get bufferValue(): number { - return 100; - } + public bufferValue = 100; + public quotas: BatchQuotas = new BatchQuotas(); public use: BatchQuotas = new BatchQuotas(); public loadingUse = true; diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.html b/app/components/account/details/account-quotas-card/account-quotas-card.html index 6050a8f3e3..3d52afc3f1 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.html +++ b/app/components/account/details/account-quotas-card/account-quotas-card.html @@ -1,47 +1,16 @@
-
Pool usage
-
- {{ poolStatus }} -
-
- - -
+
-
Job quota
-
{{quotas?.jobs}}
+
-
Dedicated core usage
-
- {{ dedicatedCoresStatus }} -
-
- - -
+
-
Low-pri core usage
-
- {{ lowPriCoresStatus }} -
-
- - -
+
diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.scss b/app/components/account/details/account-quotas-card/account-quotas-card.scss index 00d01d3e63..6c5955588a 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.scss +++ b/app/components/account/details/account-quotas-card/account-quotas-card.scss @@ -15,42 +15,6 @@ bl-account-quotas-card { padding: 0; vertical-align: top; - > .label { - font-size: 11px; - color: $dusty-grey; - height: 35%; - margin: 3px 5px 0; - } - - > .value { - font-size: 15px; - color: map-get($primary, 500); - margin: 0 5px; - - > .statistic { - margin-bottom: 2px; - } - } - - > .progress { - mat-progress-bar { - height: 4px; - margin-top: 13px; - .mat-progress-bar-buffer { - background-color: transparent; - } - &.low-usage > .mat-progress-bar-fill:after { - background-color: $successText; - } - &.medium-usage > .mat-progress-bar-fill:after { - background-color: $warningText; - } - &.high-usage > .mat-progress-bar-fill:after { - background-color: $errorText; - } - } - } - &:not(:last-child) { border-right: 1px solid $border-color; } diff --git a/app/components/base/base.module.ts b/app/components/base/base.module.ts index 40f77c11bd..55238f9e9f 100644 --- a/app/components/base/base.module.ts +++ b/app/components/base/base.module.ts @@ -33,6 +33,7 @@ import { NotificationModule } from "./notifications"; import { PinnedDropDownComponent } from "./pinned-entity-dropdown"; import { PropertyListModule } from "./property-list"; import { QuickListModule } from "./quick-list"; +import { QuotasModule } from "./quotas"; import { ScrollableModule } from "./scrollable"; import { SidebarModule } from "./sidebar"; import { SimpleDialogComponent } from "./simple-dialog"; @@ -69,6 +70,7 @@ const modules = [ TabsModule, TagsModule, FormModule, + QuotasModule, ScrollableModule, SplitPaneModule, SummaryCardModule, diff --git a/app/components/base/browse-layout/browse-layout.html b/app/components/base/browse-layout/browse-layout.html index d3af81ebdd..bafba11655 100644 --- a/app/components/base/browse-layout/browse-layout.html +++ b/app/components/base/browse-layout/browse-layout.html @@ -7,6 +7,10 @@
+
+ +
+
diff --git a/app/components/base/quotas/index.ts b/app/components/base/quotas/index.ts new file mode 100644 index 0000000000..0947f70eb4 --- /dev/null +++ b/app/components/base/quotas/index.ts @@ -0,0 +1,2 @@ +export * from "./quota-display"; +export * from "./quotas.module"; diff --git a/app/components/base/quotas/quota-display/index.ts b/app/components/base/quotas/quota-display/index.ts new file mode 100644 index 0000000000..c2dc8ebe0b --- /dev/null +++ b/app/components/base/quotas/quota-display/index.ts @@ -0,0 +1 @@ +export * from "./quota-display.component"; diff --git a/app/components/base/quotas/quota-display/quota-display.component.ts b/app/components/base/quotas/quota-display/quota-display.component.ts new file mode 100644 index 0000000000..21b0b26bd2 --- /dev/null +++ b/app/components/base/quotas/quota-display/quota-display.component.ts @@ -0,0 +1,66 @@ +import { ChangeDetectionStrategy, Component, Input, OnChanges } from "@angular/core"; + +import "./quota-display.scss"; + +type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; + +export enum QuotaDisplayType { + normal = "normal", + compact = "compact", +} + +@Component({ + selector: "bl-quota-display", + templateUrl: "quota-display.html", + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class QuotaDisplayComponent implements OnChanges { + @Input() public label: string; + @Input() public use: number = null; + @Input() public quota: number = null; + @Input() public type: QuotaDisplayType = QuotaDisplayType.normal; + + public percent: number; + + public ngOnChanges(changes) { + if (changes.use || changes.quota) { + this.percent = this._calculatePercentage(); + } + } + + /** + * Defines usage progress bar color for pool usage, dedicated/lowPriority cores usage. + * Use 3 different states (error, warn and success) to represent high usage, medium usage and low usage + * @param percent + */ + public get colorClass(): ProgressColorClass { + const percent = this.percent; + if (percent <= 100 && percent >= 90) { + return "high-usage"; + } else if (percent >= 50) { + return "medium-usage"; + } + return "low-usage"; + } + + public get status() { + if (this.use !== null && this.quota !== null) { + return `${this.use}/${this.quota} (${Math.floor(this.percent)}%)`; + } + return "N/A"; + } + /** + * Calculate percentage of used pools, dedicated/lowPriority cores + * @param used + * @param total + */ + private _calculatePercentage(): number { + const use = this.use; + const quota = this.quota; + if (use !== null && quota !== null && quota > 0) { + return (use / quota) * 100; + } + return 0; + } + +} diff --git a/app/components/base/quotas/quota-display/quota-display.html b/app/components/base/quotas/quota-display/quota-display.html new file mode 100644 index 0000000000..173ddd9648 --- /dev/null +++ b/app/components/base/quotas/quota-display/quota-display.html @@ -0,0 +1,8 @@ +
{{label}}
+
+ {{ status }} +
+
+ + +
diff --git a/app/components/base/quotas/quota-display/quota-display.scss b/app/components/base/quotas/quota-display/quota-display.scss new file mode 100644 index 0000000000..4c00c28446 --- /dev/null +++ b/app/components/base/quotas/quota-display/quota-display.scss @@ -0,0 +1,44 @@ +@import "app/styles/variables"; + +bl-quota-display { + width: 140px; + height: 100%; + padding: 0; + vertical-align: top; + + > .label { + font-size: 11px; + color: $dusty-grey; + height: 35%; + margin: 3px 5px 0; + } + + > .value { + font-size: 15px; + color: map-get($primary, 500); + margin: 0 5px; + + > .statistic { + margin-bottom: 2px; + } + } + + > .progress { + mat-progress-bar { + height: 4px; + margin-top: 13px; + .mat-progress-bar-buffer { + background-color: transparent; + } + &.low-usage > .mat-progress-bar-fill:after { + background-color: $successText; + } + &.medium-usage > .mat-progress-bar-fill:after { + background-color: $warningText; + } + &.high-usage > .mat-progress-bar-fill:after { + background-color: $errorText; + } + } + } +} diff --git a/app/components/base/quotas/quotas.module.ts b/app/components/base/quotas/quotas.module.ts new file mode 100644 index 0000000000..a642ae7b53 --- /dev/null +++ b/app/components/base/quotas/quotas.module.ts @@ -0,0 +1,17 @@ +import { NgModule } from "@angular/core"; +import { MatProgressBarModule } from "@angular/material"; +import { BrowserModule } from "@angular/platform-browser"; + +import { QuotaDisplayComponent } from "./quota-display"; + +const privateComponents = []; +const publicComponents = [QuotaDisplayComponent]; + +@NgModule({ + imports: [BrowserModule, MatProgressBarModule], + declarations: [...privateComponents, publicComponents], + exports: publicComponents, +}) +export class QuotasModule { + +} diff --git a/app/components/pool/home/pool-home.html b/app/components/pool/home/pool-home.html index 7e03fc520e..76295255da 100644 --- a/app/components/pool/home/pool-home.html +++ b/app/components/pool/home/pool-home.html @@ -2,6 +2,7 @@
Pools
+
diff --git a/app/components/pool/pool.module.ts b/app/components/pool/pool.module.ts index 471c8e41f8..a3dbdc998f 100644 --- a/app/components/pool/pool.module.ts +++ b/app/components/pool/pool.module.ts @@ -27,6 +27,7 @@ import { PoolScalePickerComponent, VmSizePickerComponent, } from "app/components/pool/action"; +import { AccountModule } from "../account/account.module"; const components = [ AppLicensePickerComponent, AutoscaleFormulaPickerComponent, @@ -37,7 +38,8 @@ const components = [ ]; const modules = [ - PoolBaseModule, PoolDetailsModule, NetworkConfigurationModule, PoolGraphsModule, StartTaskModule, UserAccountModule, + PoolBaseModule, PoolDetailsModule, NetworkConfigurationModule, PoolGraphsModule, + StartTaskModule, UserAccountModule, AccountModule, TaskBaseModule, ...commonModules, ]; diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts index 0706eedca2..94971e3da0 100644 --- a/app/services/quota.service.ts +++ b/app/services/quota.service.ts @@ -44,7 +44,6 @@ export class QuotaService implements OnDestroy { public getUsage(): Observable { return this.poolService.listAll().map((pools) => { - console.log("pools", pools && pools.toJS()); const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); return new BatchQuotas({ pools: pools.size, diff --git a/src/client/startup.ts b/src/client/startup.ts index a4bb62cdc5..15c12eafc0 100644 --- a/src/client/startup.ts +++ b/src/client/startup.ts @@ -46,7 +46,7 @@ async function startApplication(batchLabsApp: BatchLabsApplication) { // Uncomment to view why windows don't show up. batchLabsApp.init().then(() => { batchLabsApp.start(); - // batchLabsApp.debugCrash(); + batchLabsApp.debugCrash(); }); setMenu(batchLabsApp); } From 964d072319038e1e2d62987a447d09a5b12c5669 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 13:30:54 -0800 Subject: [PATCH 05/13] Quota --- .../inline-quota/inline-quota.component.ts | 20 ++++-- .../base/inline-quota/inline-quota.html | 8 +-- .../quota-display/quota-display.component.ts | 3 +- .../quotas/quota-display/quota-display.html | 8 ++- .../quotas/quota-display/quota-display.scss | 71 +++++++++++++------ 5 files changed, 70 insertions(+), 40 deletions(-) diff --git a/app/components/account/base/inline-quota/inline-quota.component.ts b/app/components/account/base/inline-quota/inline-quota.component.ts index df2bb8509b..99790c4788 100644 --- a/app/components/account/base/inline-quota/inline-quota.component.ts +++ b/app/components/account/base/inline-quota/inline-quota.component.ts @@ -8,6 +8,12 @@ import "./inline-quota.scss"; type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; +const labels = { + pools: "Pools quota", + jobs: "Jobs quota", + dedicatedCores: "Dedicated cores quota", + lowpriCores: "LowPri cores quota", +}; @Component({ selector: "bl-inline-quota", templateUrl: "inline-quota.html", @@ -25,7 +31,7 @@ export class InlineQuotaComponent implements OnDestroy, OnInit { public loadingUse = true; public statues = []; - private _include: Array; + private _include: Array = []; private _quotaSub: Subscription; constructor(private quotaService: QuotaService, private changeDetector: ChangeDetectorRef) { @@ -68,14 +74,14 @@ export class InlineQuotaComponent implements OnDestroy, OnInit { private _update() { this.statues = this._include.map((name) => { - const usage = this.use && this.use[name]; - const total = this.quotas && this.quotas[name]; - const percent = this._calculatePercentage(usage, total); + const use = this.use && this.use[name]; + const quota = this.quotas && this.quotas[name]; + const percent = this._calculatePercentage(use, quota); return { - name, - usage, - total, + label: labels[name], + use, + quota, percent, }; }).sort((a, b) => b.percent - a.percent); diff --git a/app/components/account/base/inline-quota/inline-quota.html b/app/components/account/base/inline-quota/inline-quota.html index 90bbc4b470..79661b9010 100644 --- a/app/components/account/base/inline-quota/inline-quota.html +++ b/app/components/account/base/inline-quota/inline-quota.html @@ -1,9 +1,3 @@
-
- {{mainStatus.name}} ({{mainStatus.usage}}/{{mainStatus.total}}) -
-
- - -
+
diff --git a/app/components/base/quotas/quota-display/quota-display.component.ts b/app/components/base/quotas/quota-display/quota-display.component.ts index 21b0b26bd2..61c50c3129 100644 --- a/app/components/base/quotas/quota-display/quota-display.component.ts +++ b/app/components/base/quotas/quota-display/quota-display.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component, Input, OnChanges } from "@angular/core"; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges } from "@angular/core"; import "./quota-display.scss"; @@ -18,6 +18,7 @@ export class QuotaDisplayComponent implements OnChanges { @Input() public label: string; @Input() public use: number = null; @Input() public quota: number = null; + @HostBinding("attr.type") @Input() public type: QuotaDisplayType = QuotaDisplayType.normal; public percent: number; diff --git a/app/components/base/quotas/quota-display/quota-display.html b/app/components/base/quotas/quota-display/quota-display.html index 173ddd9648..4dc12f98c1 100644 --- a/app/components/base/quotas/quota-display/quota-display.html +++ b/app/components/base/quotas/quota-display/quota-display.html @@ -1,6 +1,8 @@ -
{{label}}
-
- {{ status }} +
+
{{label}}
+
+ {{ status }} +
diff --git a/app/components/base/quotas/quota-display/quota-display.scss b/app/components/base/quotas/quota-display/quota-display.scss index 4c00c28446..fc5c1894e9 100644 --- a/app/components/base/quotas/quota-display/quota-display.scss +++ b/app/components/base/quotas/quota-display/quota-display.scss @@ -6,39 +6,66 @@ bl-quota-display { padding: 0; vertical-align: top; - > .label { - font-size: 11px; - color: $dusty-grey; - height: 35%; - margin: 3px 5px 0; + > .details > .label { + color: $secondary-text; } - > .value { - font-size: 15px; - color: map-get($primary, 500); - margin: 0 5px; + > .details > .value { + color: $primary-text; + } - > .statistic { - margin-bottom: 2px; + > .progress mat-progress-bar { + &.low-usage > .mat-progress-bar-fill:after { + background-color: $successText; + } + &.medium-usage > .mat-progress-bar-fill:after { + background-color: $warningText; + } + &.high-usage > .mat-progress-bar-fill:after { + background-color: $errorText; } } - > .progress { - mat-progress-bar { + &[type="normal"] { + > .details > .label { + font-size: 11px; + height: 35%; + margin: 3px 5px 0; + } + + > .details > .value { + font-size: 15px; + color: map-get($primary, 500); + margin: 0 5px; + + > .statistic { + margin-bottom: 2px; + } + } + + > .progress mat-progress-bar { height: 4px; margin-top: 13px; + .mat-progress-bar-buffer { background-color: transparent; } - &.low-usage > .mat-progress-bar-fill:after { - background-color: $successText; - } - &.medium-usage > .mat-progress-bar-fill:after { - background-color: $warningText; - } - &.high-usage > .mat-progress-bar-fill:after { - background-color: $errorText; - } + } + } + + &[type="compact"] { + > .details > .label { + display: inline; + } + + > .details > .value { + display: inline; + color: $secondary-text; + } + + > .progress mat-progress-bar { + height: 3px; + margin-top: 5px; } } } From f3ad7d1b9d2f33c6b60cf7d093e600f55011cb49 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 14:13:25 -0800 Subject: [PATCH 06/13] Inline quota --- .../base/inline-quota/inline-quota.component.ts | 6 ++++++ .../account/base/inline-quota/inline-quota.html | 10 +++++++--- .../account/base/inline-quota/inline-quota.scss | 8 ++++++++ .../application/details/application-details.html | 2 +- .../base/browse-layout/browse-layout.html | 7 +++---- .../base/browse-layout/browse-layout.scss | 4 ++++ .../base/quotas/quota-display/quota-display.scss | 16 +++++++--------- 7 files changed, 36 insertions(+), 17 deletions(-) diff --git a/app/components/account/base/inline-quota/inline-quota.component.ts b/app/components/account/base/inline-quota/inline-quota.component.ts index 99790c4788..4a22590518 100644 --- a/app/components/account/base/inline-quota/inline-quota.component.ts +++ b/app/components/account/base/inline-quota/inline-quota.component.ts @@ -58,6 +58,12 @@ export class InlineQuotaComponent implements OnDestroy, OnInit { return this.statues.first(); } + public get title() { + return this.statues.map(x => { + return `${x.label.padEnd(20)}: ${x.use}/${x.quota}(${x.percent}%)`; + }).join("\n"); + } + /** * Defines usage progress bar color for pool usage, dedicated/lowPriority cores usage. * Use 3 different states (error, warn and success) to represent high usage, medium usage and low usage diff --git a/app/components/account/base/inline-quota/inline-quota.html b/app/components/account/base/inline-quota/inline-quota.html index 79661b9010..59d593c313 100644 --- a/app/components/account/base/inline-quota/inline-quota.html +++ b/app/components/account/base/inline-quota/inline-quota.html @@ -1,3 +1,7 @@ -
- -
+ + diff --git a/app/components/account/base/inline-quota/inline-quota.scss b/app/components/account/base/inline-quota/inline-quota.scss index e69de29bb2..9bea2ec656 100644 --- a/app/components/account/base/inline-quota/inline-quota.scss +++ b/app/components/account/base/inline-quota/inline-quota.scss @@ -0,0 +1,8 @@ +bl-inline-quota { + display: block; + position: relative; + + > bl-quota-display { + margin: 5px; + } +} diff --git a/app/components/application/details/application-details.html b/app/components/application/details/application-details.html index 50e869db1c..6e9b6837cf 100644 --- a/app/components/application/details/application-details.html +++ b/app/components/application/details/application-details.html @@ -1,5 +1,5 @@ -
+
{{decorator.id}}
diff --git a/app/components/base/browse-layout/browse-layout.html b/app/components/base/browse-layout/browse-layout.html index bafba11655..e9567c374a 100644 --- a/app/components/base/browse-layout/browse-layout.html +++ b/app/components/base/browse-layout/browse-layout.html @@ -7,10 +7,6 @@
-
- -
-
@@ -35,6 +31,9 @@
+
+ +
diff --git a/app/components/base/browse-layout/browse-layout.scss b/app/components/base/browse-layout/browse-layout.scss index 8930b0a77a..cc2356034f 100644 --- a/app/components/base/browse-layout/browse-layout.scss +++ b/app/components/base/browse-layout/browse-layout.scss @@ -109,6 +109,10 @@ bl-browse-layout { position: relative; } } + + .quotas { + border-top: 1px solid $border-color; + } } } diff --git a/app/components/base/quotas/quota-display/quota-display.scss b/app/components/base/quotas/quota-display/quota-display.scss index fc5c1894e9..576b4222e7 100644 --- a/app/components/base/quotas/quota-display/quota-display.scss +++ b/app/components/base/quotas/quota-display/quota-display.scss @@ -1,10 +1,10 @@ @import "app/styles/variables"; bl-quota-display { - width: 140px; height: 100%; padding: 0; vertical-align: top; + display: block; > .details > .label { color: $secondary-text; @@ -54,18 +54,16 @@ bl-quota-display { } &[type="compact"] { - > .details > .label { - display: inline; - } - - > .details > .value { - display: inline; - color: $secondary-text; + > .details { + display: flex; + > .label { + flex: 1; + } } > .progress mat-progress-bar { height: 3px; - margin-top: 5px; + margin-top: 3px; } } } From dc1e26df9fe0be39cef69810a2b34b6afabe6ccf Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 14:49:32 -0800 Subject: [PATCH 07/13] Load job quotas --- .../account-quotas-card.component.ts | 79 +------------------ .../account-quotas-card.html | 6 +- .../quota-display/quota-display.component.ts | 9 ++- app/services/quota.service.ts | 44 ++++++++--- 4 files changed, 45 insertions(+), 93 deletions(-) diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts index b4fe8b76ce..6ffe5901ec 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts +++ b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts @@ -1,12 +1,11 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; import { Subscription } from "rxjs"; -import { AccountResource, BatchQuotas, BatchQuotasAttributes } from "app/models"; +import { AccountResource, BatchQuotas } from "app/models"; import { QuotaService } from "app/services"; import "./account-quotas-card.scss"; -type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; @Component({ selector: "bl-account-quotas-card", @@ -42,80 +41,4 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { public ngOnDestroy(): void { this._quotaSub.unsubscribe(); } - - /** - * Get pool usage progress bar percent - */ - public get poolUsagePercent() { - return this._calculatePercentage(this.use.pools, this.quotas.pools); - } - - /** - * Get friendly message displayed for pools - * Format: {{used}}/{{total}} ({{Percent}}) - */ - public get poolStatus(): string { - return this._prettyUsage("pools"); - } - - public get jobStatus(): string { - return this._prettyUsage("jobs"); - } - - public get dedicatedCoresStatus(): string { - return this._prettyUsage("dedicatedCores"); - } - - public get lowPriCoresStatus(): string { - return this._prettyUsage("lowpriCores"); - } - - /** - * Get dedicated cores usage progress bar percent - */ - public get dedicatedCoresPercent() { - return this._calculatePercentage(this.use.dedicatedCores, this.quotas.dedicatedCores); - } - - /** - * Get low priority cores usage progress bar percent - */ - public get lowPriorityCoresPercent() { - return this._calculatePercentage(this.use.lowpriCores, this.quotas.lowpriCores); - } - - /** - * Defines usage progress bar color for pool usage, dedicated/lowPriority cores usage. - * Use 3 different states (error, warn and success) to represent high usage, medium usage and low usage - * @param percent - */ - public getColorClass(percent: number): ProgressColorClass { - if (percent <= 100 && percent >= 90) { - return "high-usage"; - } else if (percent >= 50) { - return "medium-usage"; - } - return "low-usage"; - } - - /** - * Calculate percentage of used pools, dedicated/lowPriority cores - * @param used - * @param total - */ - private _calculatePercentage(used: number, total: number): number { - if (used !== null && total !== null && total > 0) { - return (used / total) * 100; - } - return 0; - } - - private _prettyUsage(name: keyof (BatchQuotasAttributes)) { - const used = this.use && this.use[name]; - const total = this.quotas && this.quotas[name]; - if (used !== null && total !== null) { - return `${used}/${total} (${Math.floor(this._calculatePercentage(used, total))}%)`; - } - return "N/A"; - } } diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.html b/app/components/account/details/account-quotas-card/account-quotas-card.html index 3d52afc3f1..699492805f 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.html +++ b/app/components/account/details/account-quotas-card/account-quotas-card.html @@ -3,14 +3,14 @@
- +
- +
- +
diff --git a/app/components/base/quotas/quota-display/quota-display.component.ts b/app/components/base/quotas/quota-display/quota-display.component.ts index 61c50c3129..f5eacd68f8 100644 --- a/app/components/base/quotas/quota-display/quota-display.component.ts +++ b/app/components/base/quotas/quota-display/quota-display.component.ts @@ -45,10 +45,13 @@ export class QuotaDisplayComponent implements OnChanges { } public get status() { - if (this.use !== null && this.quota !== null) { - return `${this.use}/${this.quota} (${Math.floor(this.percent)}%)`; + if (this.quota === null) { + return "N/A"; } - return "N/A"; + if (this.use === null) { + return this.quota; + } + return `${this.use}/${this.quota} (${Math.floor(this.percent)}%)`; } /** * Calculate percentage of used pools, dedicated/lowPriority cores diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts index 94971e3da0..81441ff5a0 100644 --- a/app/services/quota.service.ts +++ b/app/services/quota.service.ts @@ -1,10 +1,12 @@ import { Injectable, OnDestroy } from "@angular/core"; import { Observable, Subscription } from "rxjs"; -import { AccountResource, BatchQuotas, Pool } from "app/models"; +import { AccountResource, BatchQuotas, Job, JobState, Pool } from "app/models"; import { AccountService } from "app/services"; +import { FilterBuilder } from "app/utils/filter-builder"; import { List } from "immutable"; import { ComputeService } from "./compute.service"; +import { JobService } from "./job-service"; import { PoolService } from "./pool.service"; import { VmSizeService } from "./vm-size.service"; @@ -22,6 +24,7 @@ export class QuotaService implements OnDestroy { private accountService: AccountService, private computeService: ComputeService, private poolService: PoolService, + private jobService: JobService, private vmSizeService: VmSizeService) { this.vmSizeCores = { ...vmSizeService.additionalVmSizeCores }; @@ -42,18 +45,41 @@ export class QuotaService implements OnDestroy { this._subs.forEach(x => x.unsubscribe()); } - public getUsage(): Observable { - return this.poolService.listAll().map((pools) => { - const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); - return new BatchQuotas({ - pools: pools.size, - dedicatedCores, - lowpriCores, - jobs: 0, + public getUsage(include: { job?: boolean, pool?: boolean } = null): Observable { + include = include || { job: true, pool: true }; + let poolObs; + let jobObs; + if (include.pool) { + poolObs = this.poolService.listAll({ + select: "id,vmSize,currentDedicatedNodes,currentLowPriorityNodes", }); + } else { + poolObs = Observable.of(List([])); + } + if (include.job) { + jobObs = this.jobService.listAll({ + filter: FilterBuilder.prop("state").eq(JobState.active).toOData(), + select: "id", + }); + } else { + jobObs = Observable.of(List([])); + } + return Observable.forkJoin(poolObs, jobObs).map(([pools, jobs]: any) => { + return this._extractUsage(pools, jobs); }).share(); } + private _extractUsage(pools: List, jobs: List) { + console.log("oools", this.poolService); + const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); + return new BatchQuotas({ + pools: pools.size, + dedicatedCores, + lowpriCores, + jobs: jobs.size, + }); + } + private _computeQuotas(account: AccountResource): Observable { if (account.isBatchManaged) { return Observable.of(new BatchQuotas({ From 37e188b0861a2bdc19faf54a058bc5492bd98877 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 15:02:03 -0800 Subject: [PATCH 08/13] Commmon module --- app/app.module.ts | 2 ++ app/common.ts | 3 ++- app/components/account/account.module.ts | 3 +-- app/components/common/common.module.ts | 22 +++++++++++++++++++ app/components/common/index.ts | 10 +++++++++ .../base => common}/inline-quota/index.ts | 0 .../inline-quota/inline-quota.component.ts | 0 .../inline-quota/inline-quota.html | 0 .../inline-quota/inline-quota.scss | 0 app/components/job/home/job-home.html | 1 + app/components/pool/pool.module.ts | 4 ++-- app/services/quota.service.ts | 1 - 12 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 app/components/common/common.module.ts create mode 100644 app/components/common/index.ts rename app/components/{account/base => common}/inline-quota/index.ts (100%) rename app/components/{account/base => common}/inline-quota/inline-quota.component.ts (100%) rename app/components/{account/base => common}/inline-quota/inline-quota.html (100%) rename app/components/{account/base => common}/inline-quota/inline-quota.scss (100%) diff --git a/app/app.module.ts b/app/app.module.ts index 86c8abc0e8..7627ebdba2 100644 --- a/app/app.module.ts +++ b/app/app.module.ts @@ -32,6 +32,7 @@ import { BatchLabsErrorHandler } from "app/error-handler"; // services import { HttpModule } from "@angular/http"; +import { CommonModule } from "app/components/common"; import { LayoutModule } from "app/components/layout"; import { MiscModule } from "app/components/misc"; import { MaterialModule } from "app/core"; @@ -135,6 +136,7 @@ const graphApiServices = [AADApplicationService, AADGraphHttpService, MsGraphHtt BatchClientService, CacheDataService, CommandService, + CommonModule, ComputeService, ElectronRemote, ElectronShell, diff --git a/app/common.ts b/app/common.ts index ab1826cbef..fb6904c6e1 100644 --- a/app/common.ts +++ b/app/common.ts @@ -3,10 +3,11 @@ import { BrowserModule } from "@angular/platform-browser"; import { RouterModule } from "@angular/router"; import { BaseModule } from "app/components/base"; +import { CommonModule } from "app/components/common"; import { MaterialModule } from "app/core"; export const commonModules = [ BrowserModule, MaterialModule, RouterModule, FormsModule, ReactiveFormsModule, - BaseModule, + CommonModule, BaseModule, ]; diff --git a/app/components/account/account.module.ts b/app/components/account/account.module.ts index 69874c1b01..18d4563e22 100644 --- a/app/components/account/account.module.ts +++ b/app/components/account/account.module.ts @@ -5,7 +5,6 @@ import { EditStorageAccountFormComponent } from "app/components/account/action/e import { StorageAccountPickerComponent } from "app/components/account/base/storage-account-picker"; import { PoolBaseModule } from "app/components/pool/base"; import { DeleteAccountDialogComponent } from "./action/delete/delete-account-dialog.component"; -import { InlineQuotaComponent } from "./base/inline-quota"; import { AccountBrowseModule } from "./browse"; import { AccountDefaultComponent, AccountDetailsComponent } from "./details"; import { AccountQuotasCardComponent } from "./details/account-quotas-card"; @@ -18,7 +17,7 @@ const components = [ AccountDefaultComponent, AccountDetailsComponent, AccountHomeComponent, DeleteAccountDialogComponent, StorageAccountCardComponent, EditStorageAccountFormComponent, StorageAccountPickerComponent, AccountQuotasCardComponent, - MonitorChartComponent, InlineQuotaComponent, + MonitorChartComponent, ]; const modules = [ diff --git a/app/components/common/common.module.ts b/app/components/common/common.module.ts new file mode 100644 index 0000000000..907f72c142 --- /dev/null +++ b/app/components/common/common.module.ts @@ -0,0 +1,22 @@ +import { NgModule } from "@angular/core"; +import { BrowserModule } from "@angular/platform-browser"; + +import { BaseModule } from "app/components/base"; +import { InlineQuotaComponent } from "./inline-quota"; + +const privateComponents = []; +const publicComponents = [InlineQuotaComponent]; + +/** + * Commons module shouldn't import any module that: + * - are not external dependencies + * - BaseModule and other Common components are the only exceptions. + */ +@NgModule({ + imports: [BrowserModule, BaseModule], + declarations: [...privateComponents, publicComponents], + exports: publicComponents, +}) +export class CommonModule { + +} diff --git a/app/components/common/index.ts b/app/components/common/index.ts new file mode 100644 index 0000000000..58881b6236 --- /dev/null +++ b/app/components/common/index.ts @@ -0,0 +1,10 @@ +/** + * Common module is a grouping of components that are being used in many places + * in different areas of the app(pools, jobs, etc.) but do not belong in the Base Module. + * + * Base module -> SHould be utility components that are not techinically tied to BatchLabs/ + * This could technically be extracted in its own package + * + * Common module -> Batch/BatchLabs specific components + */ +export * from "./common.module"; diff --git a/app/components/account/base/inline-quota/index.ts b/app/components/common/inline-quota/index.ts similarity index 100% rename from app/components/account/base/inline-quota/index.ts rename to app/components/common/inline-quota/index.ts diff --git a/app/components/account/base/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts similarity index 100% rename from app/components/account/base/inline-quota/inline-quota.component.ts rename to app/components/common/inline-quota/inline-quota.component.ts diff --git a/app/components/account/base/inline-quota/inline-quota.html b/app/components/common/inline-quota/inline-quota.html similarity index 100% rename from app/components/account/base/inline-quota/inline-quota.html rename to app/components/common/inline-quota/inline-quota.html diff --git a/app/components/account/base/inline-quota/inline-quota.scss b/app/components/common/inline-quota/inline-quota.scss similarity index 100% rename from app/components/account/base/inline-quota/inline-quota.scss rename to app/components/common/inline-quota/inline-quota.scss diff --git a/app/components/job/home/job-home.html b/app/components/job/home/job-home.html index da1be6582d..8887b8c8c5 100644 --- a/app/components/job/home/job-home.html +++ b/app/components/job/home/job-home.html @@ -2,6 +2,7 @@
Jobs
+
diff --git a/app/components/pool/pool.module.ts b/app/components/pool/pool.module.ts index a3dbdc998f..d829cdb394 100644 --- a/app/components/pool/pool.module.ts +++ b/app/components/pool/pool.module.ts @@ -12,6 +12,7 @@ import { TaskBaseModule } from "app/components/task/base"; import { NetworkConfigurationModule } from "./network-configuration"; import { UserAccountModule } from "./user-account"; +import { CommonModule } from "app/components/common"; import { AppLicensePickerComponent, AutoscaleFormulaPickerComponent, @@ -27,7 +28,6 @@ import { PoolScalePickerComponent, VmSizePickerComponent, } from "app/components/pool/action"; -import { AccountModule } from "../account/account.module"; const components = [ AppLicensePickerComponent, AutoscaleFormulaPickerComponent, @@ -39,7 +39,7 @@ const components = [ const modules = [ PoolBaseModule, PoolDetailsModule, NetworkConfigurationModule, PoolGraphsModule, - StartTaskModule, UserAccountModule, AccountModule, + StartTaskModule, UserAccountModule, TaskBaseModule, ...commonModules, ]; diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts index 81441ff5a0..6440b305ff 100644 --- a/app/services/quota.service.ts +++ b/app/services/quota.service.ts @@ -70,7 +70,6 @@ export class QuotaService implements OnDestroy { } private _extractUsage(pools: List, jobs: List) { - console.log("oools", this.poolService); const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); return new BatchQuotas({ pools: pools.size, From 827ad635a10253db23b544e0225eacb4109ce759 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 15:33:31 -0800 Subject: [PATCH 09/13] More pools --- .../account-quotas-card.component.ts | 36 +++++++-- .../quota-display/quota-display.component.ts | 7 +- .../inline-quota/inline-quota.component.ts | 52 ++++++++----- app/services/quota.service.ts | 78 ++++++++++++------- 4 files changed, 115 insertions(+), 58 deletions(-) diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts index 6ffe5901ec..54a61b7fdc 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.component.ts +++ b/app/components/account/details/account-quotas-card/account-quotas-card.component.ts @@ -1,12 +1,15 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnDestroy, OnInit, +} from "@angular/core"; import { Subscription } from "rxjs"; import { AccountResource, BatchQuotas } from "app/models"; -import { QuotaService } from "app/services"; +import { ElectronShell, QuotaService } from "app/services"; +import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; +import { Constants } from "common"; import "./account-quotas-card.scss"; - @Component({ selector: "bl-account-quotas-card", templateUrl: "account-quotas-card.html", @@ -22,23 +25,42 @@ export class AccountQuotasCardComponent implements OnDestroy, OnInit { public loadingUse = true; private _quotaSub: Subscription; + private _usageSub: Subscription; - constructor(private quotaService: QuotaService, private changeDetector: ChangeDetectorRef) { + constructor( + private quotaService: QuotaService, + private changeDetector: ChangeDetectorRef, + private contextMenuService: ContextMenuService, + private shell: ElectronShell) { this._quotaSub = this.quotaService.quotas.subscribe((quotas) => { this.quotas = quotas; this.changeDetector.markForCheck(); }); - } - public ngOnInit() { - this.quotaService.getUsage().subscribe((quota) => { + this._usageSub = this.quotaService.usage.subscribe((quota) => { this.loadingUse = false; this.use = quota; this.changeDetector.markForCheck(); }); } + public ngOnInit() { + this.quotaService.updateUsages(); + } public ngOnDestroy(): void { this._quotaSub.unsubscribe(); + this._usageSub.unsubscribe(); + } + + @HostListener("contextmenu") + public showContextMenu() { + this.contextMenuService.openMenu(new ContextMenu([ + new ContextMenuItem("Refresh", () => this.quotaService.refresh()), + new ContextMenuItem("Request quota increase", () => this._gotoQuotaRequest()), + ])); + } + + private _gotoQuotaRequest() { + this.shell.openExternal(Constants.ExternalLinks.supportRequest); } } diff --git a/app/components/base/quotas/quota-display/quota-display.component.ts b/app/components/base/quotas/quota-display/quota-display.component.ts index f5eacd68f8..710c76eae9 100644 --- a/app/components/base/quotas/quota-display/quota-display.component.ts +++ b/app/components/base/quotas/quota-display/quota-display.component.ts @@ -1,5 +1,8 @@ -import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges } from "@angular/core"; +import { ChangeDetectionStrategy, Component, HostBinding, HostListener, Input, OnChanges, Output, EventEmitter } from "@angular/core"; +import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; +import { ElectronShell } from "app/services"; +import { Constants } from "common"; import "./quota-display.scss"; type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; @@ -15,6 +18,7 @@ export enum QuotaDisplayType { changeDetection: ChangeDetectionStrategy.OnPush, }) export class QuotaDisplayComponent implements OnChanges { + @Input() public label: string; @Input() public use: number = null; @Input() public quota: number = null; @@ -53,6 +57,7 @@ export class QuotaDisplayComponent implements OnChanges { } return `${this.use}/${this.quota} (${Math.floor(this.percent)}%)`; } + /** * Calculate percentage of used pools, dedicated/lowPriority cores * @param used diff --git a/app/components/common/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts index 4a22590518..09e73465d1 100644 --- a/app/components/common/inline-quota/inline-quota.component.ts +++ b/app/components/common/inline-quota/inline-quota.component.ts @@ -1,9 +1,13 @@ -import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnDestroy, OnInit } from "@angular/core"; +import { + ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnChanges, OnDestroy, OnInit, +} from "@angular/core"; import { Subscription } from "rxjs"; import { BatchQuotas, BatchQuotasAttributes } from "app/models"; -import { QuotaService } from "app/services"; +import { ElectronShell, QuotaService } from "app/services"; +import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; +import { Constants } from "common"; import "./inline-quota.scss"; type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; @@ -19,7 +23,7 @@ const labels = { templateUrl: "inline-quota.html", changeDetection: ChangeDetectionStrategy.OnPush, }) -export class InlineQuotaComponent implements OnDestroy, OnInit { +export class InlineQuotaComponent implements OnChanges, OnDestroy { @Input() public set include(quotas: string | string[]) { this._include = Array.isArray(quotas) ? quotas : quotas.split(",") as any; } @@ -33,25 +37,34 @@ export class InlineQuotaComponent implements OnDestroy, OnInit { private _include: Array = []; private _quotaSub: Subscription; + private _usageSub: Subscription; - constructor(private quotaService: QuotaService, private changeDetector: ChangeDetectorRef) { + constructor( + private quotaService: QuotaService, + private changeDetector: ChangeDetectorRef, + private contextMenuService: ContextMenuService, + private shell: ElectronShell) { this._quotaSub = this.quotaService.quotas.subscribe((quotas) => { this.quotas = quotas; this._update(); }); - } - public ngOnInit() { - this.quotaService.getUsage().subscribe((quota) => { + this._usageSub = this.quotaService.usage.subscribe((quota) => { this.loadingUse = false; this.use = quota; this._update(); - }); } + public ngOnChanges(changes) { + if (changes.include) { + this._update(); + } + } + public ngOnDestroy(): void { this._quotaSub.unsubscribe(); + this._usageSub.unsubscribe(); } public get mainStatus() { @@ -64,21 +77,20 @@ export class InlineQuotaComponent implements OnDestroy, OnInit { }).join("\n"); } - /** - * Defines usage progress bar color for pool usage, dedicated/lowPriority cores usage. - * Use 3 different states (error, warn and success) to represent high usage, medium usage and low usage - * @param percent - */ - public getColorClass(percent: number): ProgressColorClass { - if (percent <= 100 && percent >= 90) { - return "high-usage"; - } else if (percent >= 50) { - return "medium-usage"; - } - return "low-usage"; + @HostListener("contextmenu") + public showContextMenu() { + this.contextMenuService.openMenu(new ContextMenu([ + new ContextMenuItem("Refresh", () => this.quotaService.refresh()), + new ContextMenuItem("Request quota increase", () => this._gotoQuotaRequest()), + ])); + } + + private _gotoQuotaRequest() { + this.shell.openExternal(Constants.ExternalLinks.supportRequest); } private _update() { + console.log("UPDate dist", this._include, this.use.toJS()); this.statues = this._include.map((name) => { const use = this.use && this.use[name]; const quota = this.quotas && this.quotas[name]; diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts index 6440b305ff..6f56499e84 100644 --- a/app/services/quota.service.ts +++ b/app/services/quota.service.ts @@ -1,5 +1,5 @@ import { Injectable, OnDestroy } from "@angular/core"; -import { Observable, Subscription } from "rxjs"; +import { BehaviorSubject, Observable, Subscription } from "rxjs"; import { AccountResource, BatchQuotas, Job, JobState, Pool } from "app/models"; import { AccountService } from "app/services"; @@ -16,6 +16,8 @@ import { VmSizeService } from "./vm-size.service"; @Injectable() export class QuotaService implements OnDestroy { public quotas: Observable; + public usage: Observable; + private readonly _usage = new BehaviorSubject(new BatchQuotas()); private vmSizeCores: StringMap; private _subs: Subscription[] = []; @@ -39,44 +41,60 @@ export class QuotaService implements OnDestroy { this.quotas = this.accountService.currentAccount.flatMap((account) => { return this._computeQuotas(account); }).shareReplay(1); + this.usage = this._usage.asObservable(); + + this.updateUsages(); } public ngOnDestroy() { this._subs.forEach(x => x.unsubscribe()); } - public getUsage(include: { job?: boolean, pool?: boolean } = null): Observable { - include = include || { job: true, pool: true }; - let poolObs; - let jobObs; - if (include.pool) { - poolObs = this.poolService.listAll({ - select: "id,vmSize,currentDedicatedNodes,currentLowPriorityNodes", - }); - } else { - poolObs = Observable.of(List([])); - } - if (include.job) { - jobObs = this.jobService.listAll({ - filter: FilterBuilder.prop("state").eq(JobState.active).toOData(), - select: "id", - }); - } else { - jobObs = Observable.of(List([])); - } - return Observable.forkJoin(poolObs, jobObs).map(([pools, jobs]: any) => { - return this._extractUsage(pools, jobs); - }).share(); + public refresh() { + return Observable.forkJoin(this.accountService.refresh(), this.updateUsages()); + } + + public updateUsages() { + return Observable.forkJoin(this.updatePoolUsage(), this.updateJobUsage()); } - private _extractUsage(pools: List, jobs: List) { - const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); - return new BatchQuotas({ - pools: pools.size, - dedicatedCores, - lowpriCores, - jobs: jobs.size, + public updatePoolUsage() { + const obs = this.poolService.listAll({ + select: "id,vmSize,currentDedicatedNodes,currentLowPriorityNodes", }); + obs.subscribe((pools) => { + const { dedicatedCores, lowpriCores } = this._getCoreUsages(pools); + + this._usage.next(new BatchQuotas({ + ...this._getExistingQuota().toJS(), + pools: pools.size, + dedicatedCores, + lowpriCores, + })); + }); + return obs; + } + + public updateJobUsage() { + const obs = this.jobService.listAll({ + filter: FilterBuilder.prop("state").eq(JobState.active).toOData(), + select: "id", + }); + obs.subscribe((jobs) => { + this._usage.next(new BatchQuotas({ + ...this._getExistingQuota().toJS(), + jobs: jobs.size, + })); + }); + return obs; + } + + public resetUsage() { + this._usage.next(new BatchQuotas()); + } + + private _getExistingQuota() { + return this._usage.value || new BatchQuotas(); } private _computeQuotas(account: AccountResource): Observable { From b7b142160b2fd7fa5086b80f1b628b2c64e6b6a2 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 15:40:22 -0800 Subject: [PATCH 10/13] More --- .../inline-quota/inline-quota.component.ts | 17 ++++++++++++----- .../common/inline-quota/inline-quota.html | 11 ++++++++++- .../common/inline-quota/inline-quota.scss | 9 +++++++++ 3 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/components/common/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts index 09e73465d1..77558cc496 100644 --- a/app/components/common/inline-quota/inline-quota.component.ts +++ b/app/components/common/inline-quota/inline-quota.component.ts @@ -33,7 +33,8 @@ export class InlineQuotaComponent implements OnChanges, OnDestroy { public quotas: BatchQuotas = new BatchQuotas(); public use: BatchQuotas = new BatchQuotas(); public loadingUse = true; - public statues = []; + public statuses = []; + public expanded: boolean = false; private _include: Array = []; private _quotaSub: Subscription; @@ -68,19 +69,25 @@ export class InlineQuotaComponent implements OnChanges, OnDestroy { } public get mainStatus() { - return this.statues.first(); + return this.statuses.first(); } public get title() { - return this.statues.map(x => { + return this.statuses.map(x => { return `${x.label.padEnd(20)}: ${x.use}/${x.quota}(${x.percent}%)`; }).join("\n"); } + @HostListener("click") + public toggleExpanded() { + this.expanded = !this.expanded; + this.changeDetector.markForCheck(); + } + @HostListener("contextmenu") public showContextMenu() { this.contextMenuService.openMenu(new ContextMenu([ - new ContextMenuItem("Refresh", () => this.quotaService.refresh()), + new ContextMenuItem("Refresh quotas", () => this.quotaService.refresh()), new ContextMenuItem("Request quota increase", () => this._gotoQuotaRequest()), ])); } @@ -91,7 +98,7 @@ export class InlineQuotaComponent implements OnChanges, OnDestroy { private _update() { console.log("UPDate dist", this._include, this.use.toJS()); - this.statues = this._include.map((name) => { + this.statuses = this._include.map((name) => { const use = this.use && this.use[name]; const quota = this.quotas && this.quotas[name]; const percent = this._calculatePercentage(use, quota); diff --git a/app/components/common/inline-quota/inline-quota.html b/app/components/common/inline-quota/inline-quota.html index 59d593c313..3d335f34f9 100644 --- a/app/components/common/inline-quota/inline-quota.html +++ b/app/components/common/inline-quota/inline-quota.html @@ -1,7 +1,16 @@ - +
+ + +
+ diff --git a/app/components/common/inline-quota/inline-quota.scss b/app/components/common/inline-quota/inline-quota.scss index 9bea2ec656..4c56636209 100644 --- a/app/components/common/inline-quota/inline-quota.scss +++ b/app/components/common/inline-quota/inline-quota.scss @@ -1,8 +1,17 @@ bl-inline-quota { display: block; position: relative; + cursor: pointer; + user-select: none; > bl-quota-display { margin: 5px; } + + > .expanded { + padding: 5px; + > bl-quota-display { + + } + } } From 77d74318a279721956e7f6c5dc3794fd73f7ce55 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 15:52:08 -0800 Subject: [PATCH 11/13] SHow application quotas --- .../application/home/application-home.html | 1 + .../inline-quota/inline-quota.component.ts | 3 ++- .../common/inline-quota/inline-quota.scss | 3 --- app/models/batch-quotas.ts | 2 ++ app/services/application-service.ts | 4 ++++ app/services/quota.service.ts | 24 +++++++++++++++++-- 6 files changed, 31 insertions(+), 6 deletions(-) diff --git a/app/components/application/home/application-home.html b/app/components/application/home/application-home.html index 3d55c24891..6fb1b0dbde 100644 --- a/app/components/application/home/application-home.html +++ b/app/components/application/home/application-home.html @@ -2,6 +2,7 @@
Application packages
+
diff --git a/app/components/common/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts index 77558cc496..eaa18f737b 100644 --- a/app/components/common/inline-quota/inline-quota.component.ts +++ b/app/components/common/inline-quota/inline-quota.component.ts @@ -10,14 +10,15 @@ import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components import { Constants } from "common"; import "./inline-quota.scss"; -type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; const labels = { pools: "Pools quota", jobs: "Jobs quota", dedicatedCores: "Dedicated cores quota", lowpriCores: "LowPri cores quota", + applications: "Applications quota", }; + @Component({ selector: "bl-inline-quota", templateUrl: "inline-quota.html", diff --git a/app/components/common/inline-quota/inline-quota.scss b/app/components/common/inline-quota/inline-quota.scss index 4c56636209..00e7205cc7 100644 --- a/app/components/common/inline-quota/inline-quota.scss +++ b/app/components/common/inline-quota/inline-quota.scss @@ -10,8 +10,5 @@ bl-inline-quota { > .expanded { padding: 5px; - > bl-quota-display { - - } } } diff --git a/app/models/batch-quotas.ts b/app/models/batch-quotas.ts index 96f719505d..6b87c43e89 100644 --- a/app/models/batch-quotas.ts +++ b/app/models/batch-quotas.ts @@ -5,12 +5,14 @@ export interface BatchQuotasAttributes { jobs: number; dedicatedCores: number; lowpriCores: number; + applications: number; } @Model() export class BatchQuotas extends Record { @Prop() public pools: number; @Prop() public jobs: number; + @Prop() public applications: number; @Prop() public dedicatedCores: number; @Prop() public lowpriCores: number; } diff --git a/app/services/application-service.ts b/app/services/application-service.ts index fc39347080..aaac41c1e2 100644 --- a/app/services/application-service.ts +++ b/app/services/application-service.ts @@ -75,6 +75,10 @@ export class ApplicationService extends ServiceBase { }); } + public listAll(options?: ListOptionsAttributes) { + return this._listGetter.fetchAll(options); + } + public get(applicationId: string, options: any = {}): Observable { return this._getter.fetch({ id: applicationId }); } diff --git a/app/services/quota.service.ts b/app/services/quota.service.ts index 6f56499e84..8440761082 100644 --- a/app/services/quota.service.ts +++ b/app/services/quota.service.ts @@ -1,10 +1,11 @@ import { Injectable, OnDestroy } from "@angular/core"; import { BehaviorSubject, Observable, Subscription } from "rxjs"; -import { AccountResource, BatchQuotas, Job, JobState, Pool } from "app/models"; +import { AccountResource, BatchQuotas, JobState, Pool } from "app/models"; import { AccountService } from "app/services"; import { FilterBuilder } from "app/utils/filter-builder"; import { List } from "immutable"; +import { ApplicationService } from "./application-service"; import { ComputeService } from "./compute.service"; import { JobService } from "./job-service"; import { PoolService } from "./pool.service"; @@ -25,6 +26,7 @@ export class QuotaService implements OnDestroy { constructor( private accountService: AccountService, private computeService: ComputeService, + private applicationService: ApplicationService, private poolService: PoolService, private jobService: JobService, private vmSizeService: VmSizeService) { @@ -55,7 +57,10 @@ export class QuotaService implements OnDestroy { } public updateUsages() { - return Observable.forkJoin(this.updatePoolUsage(), this.updateJobUsage()); + return Observable.forkJoin( + this.updatePoolUsage(), + this.updateJobUsage(), + this.updateApplicationUsage()); } public updatePoolUsage() { @@ -89,6 +94,19 @@ export class QuotaService implements OnDestroy { return obs; } + public updateApplicationUsage() { + const obs = this.applicationService.listAll({ + select: "id", + }); + obs.subscribe((applications) => { + this._usage.next(new BatchQuotas({ + ...this._getExistingQuota().toJS(), + applications: applications.size, + })); + }); + return obs; + } + public resetUsage() { this._usage.next(new BatchQuotas()); } @@ -104,6 +122,7 @@ export class QuotaService implements OnDestroy { lowpriCores: account.properties.lowPriorityCoreQuota, pools: account.properties.poolQuota, jobs: account.properties.activeJobAndJobScheduleQuota, + applications: 20, })); } else { this.computeService.getCoreQuota().map((dedicatedCoreQuota) => { @@ -112,6 +131,7 @@ export class QuotaService implements OnDestroy { lowpriCores: null, pools: account.properties.poolQuota, jobs: account.properties.activeJobAndJobScheduleQuota, + applications: 20, })); }); } From 1e08ffaf5b1852e0374cd1fb116e206fc58dd7e0 Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 15:52:54 -0800 Subject: [PATCH 12/13] Plurar --- .../details/account-quotas-card/account-quotas-card.html | 4 ++-- app/components/common/inline-quota/inline-quota.component.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/components/account/details/account-quotas-card/account-quotas-card.html b/app/components/account/details/account-quotas-card/account-quotas-card.html index 699492805f..e95ed9d584 100644 --- a/app/components/account/details/account-quotas-card/account-quotas-card.html +++ b/app/components/account/details/account-quotas-card/account-quotas-card.html @@ -1,9 +1,9 @@
- +
- +
diff --git a/app/components/common/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts index eaa18f737b..a7e3913dcc 100644 --- a/app/components/common/inline-quota/inline-quota.component.ts +++ b/app/components/common/inline-quota/inline-quota.component.ts @@ -10,7 +10,6 @@ import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components import { Constants } from "common"; import "./inline-quota.scss"; - const labels = { pools: "Pools quota", jobs: "Jobs quota", From 544077c6d639721d48b61d064d178bbbbfb350cc Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 26 Feb 2018 16:45:22 -0800 Subject: [PATCH 13/13] Fix tslint --- .../base/quotas/quota-display/quota-display.component.ts | 5 +---- .../common/inline-quota/inline-quota.component.ts | 7 +++++-- app/components/common/inline-quota/inline-quota.html | 2 +- app/components/pool/pool.module.ts | 1 - src/client/startup.ts | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/app/components/base/quotas/quota-display/quota-display.component.ts b/app/components/base/quotas/quota-display/quota-display.component.ts index 710c76eae9..2648bfd8d2 100644 --- a/app/components/base/quotas/quota-display/quota-display.component.ts +++ b/app/components/base/quotas/quota-display/quota-display.component.ts @@ -1,8 +1,5 @@ -import { ChangeDetectionStrategy, Component, HostBinding, HostListener, Input, OnChanges, Output, EventEmitter } from "@angular/core"; +import { ChangeDetectionStrategy, Component, HostBinding, Input, OnChanges } from "@angular/core"; -import { ContextMenu, ContextMenuItem, ContextMenuService } from "app/components/base/context-menu"; -import { ElectronShell } from "app/services"; -import { Constants } from "common"; import "./quota-display.scss"; type ProgressColorClass = "high-usage" | "medium-usage" | "low-usage"; diff --git a/app/components/common/inline-quota/inline-quota.component.ts b/app/components/common/inline-quota/inline-quota.component.ts index a7e3913dcc..8e1968ba51 100644 --- a/app/components/common/inline-quota/inline-quota.component.ts +++ b/app/components/common/inline-quota/inline-quota.component.ts @@ -1,5 +1,5 @@ import { - ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnChanges, OnDestroy, OnInit, + ChangeDetectionStrategy, ChangeDetectorRef, Component, HostListener, Input, OnChanges, OnDestroy, } from "@angular/core"; import { Subscription } from "rxjs"; @@ -92,12 +92,15 @@ export class InlineQuotaComponent implements OnChanges, OnDestroy { ])); } + public trackStatus(index, status) { + return status.label; + } + private _gotoQuotaRequest() { this.shell.openExternal(Constants.ExternalLinks.supportRequest); } private _update() { - console.log("UPDate dist", this._include, this.use.toJS()); this.statuses = this._include.map((name) => { const use = this.use && this.use[name]; const quota = this.quotas && this.quotas[name]; diff --git a/app/components/common/inline-quota/inline-quota.html b/app/components/common/inline-quota/inline-quota.html index 3d335f34f9..6fd5984e93 100644 --- a/app/components/common/inline-quota/inline-quota.html +++ b/app/components/common/inline-quota/inline-quota.html @@ -6,7 +6,7 @@ type="compact">
- { batchLabsApp.start(); - batchLabsApp.debugCrash(); + // batchLabsApp.debugCrash(); }); setMenu(batchLabsApp); }