diff --git a/aio/closure/webpack.config.js b/aio/closure/webpack.config.js
index 8bc924a40df7..65f048109a4f 100644
--- a/aio/closure/webpack.config.js
+++ b/aio/closure/webpack.config.js
@@ -69,6 +69,7 @@ module.exports = {
'styles': [
'../node_modules/material-design-icons/iconfont/material-icons.css',
'../node_modules/roboto-fontface/css/roboto/roboto-fontface.css',
+ '../node_modules/nvd3/build/nv.d3.css',
'../src/app/frontend/styles.scss'
],
},
@@ -93,6 +94,8 @@ module.exports = {
process.cwd(), '../node_modules/material-design-icons/iconfont/material-icons.css'),
path.join(
process.cwd(), '../node_modules/roboto-fontface/css/roboto/roboto-fontface.css'),
+ path.join(
+ process.cwd(), '../node_modules/nvd3/build/nv.d3.css'),
path.join(process.cwd(), '../src/app/frontend/styles.scss')
],
'test': /\.css$/,
@@ -110,6 +113,8 @@ module.exports = {
process.cwd(), '../node_modules/material-design-icons/iconfont/material-icons.css'),
path.join(
process.cwd(), '../node_modules/roboto-fontface/css/roboto/roboto-fontface.css'),
+ path.join(
+ process.cwd(), '../node_modules/nvd3/build/nv.d3.css'),
path.join(process.cwd(), '../src/app/frontend/styles.scss')
],
'test': /\.css$/,
@@ -127,6 +132,8 @@ module.exports = {
process.cwd(), '../node_modules/material-design-icons/iconfont/material-icons.css'),
path.join(
process.cwd(), '../node_modules/roboto-fontface/css/roboto/roboto-fontface.css'),
+ path.join(
+ process.cwd(), '../node_modules/nvd3/build/nv.d3.css'),
path.join(process.cwd(), '../src/app/frontend/styles.scss')
],
'test': /\.scss$|\.sass$/,
@@ -148,6 +155,8 @@ module.exports = {
process.cwd(), '../node_modules/material-design-icons/iconfont/material-icons.css'),
path.join(
process.cwd(), '../node_modules/roboto-fontface/css/roboto/roboto-fontface.css'),
+ path.join(
+ process.cwd(), '../node_modules/nvd3/build/nv.d3.css'),
path.join(process.cwd(), '../src/app/frontend/styles.scss')
],
'test': /\.scss$|\.sass$/,
diff --git a/angular.json b/angular.json
index 879a408ae69b..c612bb6aee0d 100644
--- a/angular.json
+++ b/angular.json
@@ -27,6 +27,7 @@
"node_modules/material-design-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
"node_modules/xterm/dist/xterm.css",
+ "node_modules/nvd3/build/nv.d3.css",
"src/app/frontend/index.scss"
],
"scripts": ["node_modules/sockjs-client/dist/sockjs.min.js"]
@@ -80,6 +81,7 @@
"styles": [
"node_modules/material-design-icons/iconfont/material-icons.css",
"node_modules/roboto-fontface/css/roboto/roboto-fontface.css",
+ "node_modules/nvd3/build/nv.d3.css",
"src/app/frontend/index.scss"
],
"assets": [
diff --git a/src/app/frontend/common/components/allocationchart/component.ts b/src/app/frontend/common/components/allocationchart/component.ts
index 3bc09cf5c6de..5ee9689e7478 100644
--- a/src/app/frontend/common/components/allocationchart/component.ts
+++ b/src/app/frontend/common/components/allocationchart/component.ts
@@ -62,12 +62,8 @@ export class AllocationChartComponent implements OnInit {
const chart = nv.models.pieChart()
.showLegend(false)
.showLabels(true)
- .x((d) => {
- return d.value;
- })
- .y((d) => {
- return d.value;
- })
+ .x(d => d.key)
+ .y(d => d.value)
.donut(true)
.donutRatio(ratio)
.color(colors)
@@ -78,7 +74,11 @@ export class AllocationChartComponent implements OnInit {
.labelType(labelFunc);
chart.tooltip.enabled(this.enableTooltips);
- chart.tooltip.contentGenerator((obj) => {
+ chart.tooltip.contentGenerator(obj => {
+ if (obj.data.key.includes(':')) {
+ const values = obj.data.key.split(':');
+ return `
${values[0]}
${values[1]}
`;
+ }
return obj.data.key;
});
diff --git a/src/app/frontend/common/resources/list.ts b/src/app/frontend/common/resources/list.ts
index ee1c8d714ea3..135d243f6bc1 100644
--- a/src/app/frontend/common/resources/list.ts
+++ b/src/app/frontend/common/resources/list.ts
@@ -91,7 +91,7 @@ export abstract class ResourceListBase {
+ let httpMock: HttpTestingController;
+ let configService: ConfigService;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed
+ .configureTestingModule({
+ declarations: [
+ CardComponent, OverviewComponent, MockDaemonSetListComponent, AllocationChartComponent,
+ WorkloadStatusComponent
+ ],
+ imports: [
+ MatIconModule, MatCardModule, MatDividerModule, MatTooltipModule, NoopAnimationsModule,
+ HttpClientTestingModule, FlexLayoutModule
+ ],
+ providers: [ConfigService, NotificationsService],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ })
+ .compileComponents();
+ httpMock = TestBed.get(HttpTestingController);
+ configService = TestBed.get(ConfigService);
+ }));
+
+ beforeEach(() => {
+ configService.init();
+ const configRequest = httpMock.expectOne('config');
+ const config: AppConfig = {serverTime: new Date().getTime()};
+ configRequest.flush(config);
+
+ testHostFixture = TestBed.createComponent(OverviewComponent);
+ });
+
+ it('should mount with empty resourcesRatio', () => {
+ const instance = testHostFixture.componentInstance;
+ expect(instance.resourcesRatio).toEqual(emptyResourcesRatio);
+ });
+
+ it('should update resourcesRatio', () => {
+ const instance = testHostFixture.componentInstance;
+
+ instance.updateResourcesRatio({
+ id: ListIdentifiers.daemonSet,
+ groupId: ListGroupIdentifiers.workloads,
+ items: mockDaemonSetData.listMeta.totalItems,
+ filtered: false,
+ resourceList: mockDaemonSetData
+ });
+
+ expect(instance.resourcesRatio).toEqual({
+ ...emptyResourcesRatio,
+ daemonSetRatio:
+ Helper.getResourceRatio(mockDaemonSetData.status, mockDaemonSetData.listMeta.totalItems)
+ });
+
+ expect(instance.showWorkloadStatuses()).toEqual(true);
+ });
+
+ it('should update resourcesRatio on pod identifier', () => {
+ // This checks the ResourceRatioModes.Completable
+ const instance = testHostFixture.componentInstance;
+
+ instance.updateResourcesRatio({
+ id: ListIdentifiers.pod,
+ groupId: ListGroupIdentifiers.workloads,
+ items: mockPodsData.listMeta.totalItems,
+ filtered: false,
+ resourceList: mockPodsData
+ });
+
+ expect(instance.resourcesRatio).toEqual({
+ ...emptyResourcesRatio,
+ podRatio: Helper.getResourceRatio(
+ mockPodsData.status, mockPodsData.listMeta.totalItems, ResourceRatioModes.Completable)
+ });
+
+ expect(instance.showWorkloadStatuses()).toEqual(true);
+ });
+
+ it('should update resourcesRatio on cron job(suspendable) identifier', () => {
+ // This checks the ResourceRatioModes.Suspendable
+ const instance = testHostFixture.componentInstance;
+
+ instance.updateResourcesRatio({
+ id: ListIdentifiers.cronJob,
+ groupId: ListGroupIdentifiers.workloads,
+ items: mockCronJobsData.listMeta.totalItems,
+ filtered: false,
+ resourceList: mockCronJobsData
+ });
+
+ expect(instance.resourcesRatio).toEqual({
+ ...emptyResourcesRatio,
+ cronJobRatio: Helper.getResourceRatio(
+ mockCronJobsData.status, mockCronJobsData.listMeta.totalItems,
+ ResourceRatioModes.Suspendable)
+ });
+
+ expect(instance.showWorkloadStatuses()).toEqual(true);
+ });
+});
diff --git a/src/app/frontend/overview/component.ts b/src/app/frontend/overview/component.ts
index 4c843872c697..3fbbd68c739f 100644
--- a/src/app/frontend/overview/component.ts
+++ b/src/app/frontend/overview/component.ts
@@ -13,14 +13,22 @@
// limitations under the License.
import {Component} from '@angular/core';
-import {ListGroupIdentifiers} from '../common/components/resourcelist/groupids';
+import {CronJobList, DaemonSetList, DeploymentList, JobList, PodList, ReplicaSetList, ReplicationControllerList, StatefulSetList} from '@api/backendapi';
+import {OnListChangeEvent, ResourcesRatio} from '@api/frontendapi';
+
+import {ListGroupIdentifiers, ListIdentifiers} from '../common/components/resourcelist/groupids';
import {GroupedResourceList} from '../common/resources/groupedlist';
+import {Helper, ResourceRatioModes} from './helper';
+import {emptyResourcesRatio} from './workloadstatus/component';
+
@Component({
selector: 'kd-overview',
templateUrl: './template.html',
})
export class OverviewComponent extends GroupedResourceList {
+ resourcesRatio: ResourcesRatio = emptyResourcesRatio;
+
hasWorkloads(): boolean {
return this.isGroupVisible(ListGroupIdentifiers.workloads);
}
@@ -32,4 +40,64 @@ export class OverviewComponent extends GroupedResourceList {
hasConfig(): boolean {
return this.isGroupVisible(ListGroupIdentifiers.config);
}
+
+ updateResourcesRatio(event: OnListChangeEvent) {
+ switch (event.id) {
+ case ListIdentifiers.cronJob: {
+ const cronJobs = event.resourceList as CronJobList;
+ this.resourcesRatio.cronJobRatio = Helper.getResourceRatio(
+ cronJobs.status, cronJobs.listMeta.totalItems, ResourceRatioModes.Suspendable);
+ break;
+ }
+ case ListIdentifiers.daemonSet: {
+ const daemonSets = event.resourceList as DaemonSetList;
+ this.resourcesRatio.daemonSetRatio =
+ Helper.getResourceRatio(daemonSets.status, daemonSets.listMeta.totalItems);
+ break;
+ }
+ case ListIdentifiers.deployment: {
+ const deployments = event.resourceList as DeploymentList;
+ this.resourcesRatio.deploymentRatio =
+ Helper.getResourceRatio(deployments.status, deployments.listMeta.totalItems);
+ break;
+ }
+ case ListIdentifiers.job: {
+ const jobs = event.resourceList as JobList;
+ this.resourcesRatio.jobRatio = Helper.getResourceRatio(
+ jobs.status, jobs.listMeta.totalItems, ResourceRatioModes.Completable);
+ break;
+ }
+ case ListIdentifiers.pod: {
+ const pods = event.resourceList as PodList;
+ this.resourcesRatio.podRatio = Helper.getResourceRatio(
+ pods.status, pods.listMeta.totalItems, ResourceRatioModes.Completable);
+ break;
+ }
+ case ListIdentifiers.replicaSet: {
+ const replicaSets = event.resourceList as ReplicaSetList;
+ this.resourcesRatio.replicaSetRatio =
+ Helper.getResourceRatio(replicaSets.status, replicaSets.listMeta.totalItems);
+ break;
+ }
+ case ListIdentifiers.replicationController: {
+ const replicationControllers = event.resourceList as ReplicationControllerList;
+ this.resourcesRatio.replicationControllerRatio = Helper.getResourceRatio(
+ replicationControllers.status, replicationControllers.listMeta.totalItems);
+ break;
+ }
+ case ListIdentifiers.statefulSet: {
+ const statefulSets = event.resourceList as StatefulSetList;
+ this.resourcesRatio.statefulSetRatio =
+ Helper.getResourceRatio(statefulSets.status, statefulSets.listMeta.totalItems);
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ showWorkloadStatuses(): boolean {
+ return Object.values(this.resourcesRatio)
+ .reduce((sum, ratioItems) => sum + ratioItems.length, 0) !== 0;
+ }
}
diff --git a/src/app/frontend/overview/helper.ts b/src/app/frontend/overview/helper.ts
new file mode 100644
index 000000000000..6b1bbab59baa
--- /dev/null
+++ b/src/app/frontend/overview/helper.ts
@@ -0,0 +1,75 @@
+// Copyright 2017 The Kubernetes Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {Status} from '@api/backendapi';
+import {RatioItem} from '@api/frontendapi';
+
+export enum ResourceRatioModes {
+ Default = 'default',
+ Suspendable = 'suspendable',
+ Completable = 'completable'
+}
+
+export class Helper {
+ static getResourceRatio(status: Status, totalItems: number, mode = ResourceRatioModes.Default):
+ RatioItem[] {
+ if (totalItems === 0) {
+ return [];
+ }
+
+ let items = [{
+ key: `Running: ${status.running}`,
+ value: status.running / totalItems * 100,
+ }];
+
+ switch (mode) {
+ case ResourceRatioModes.Suspendable:
+ items.push({
+ key: `Suspended: ${status.failed}`,
+ value: status.failed / totalItems * 100,
+ });
+ break;
+ case ResourceRatioModes.Completable:
+ items = items.concat([
+ {
+ key: `Failed: ${status.failed}`,
+ value: status.failed / totalItems * 100,
+ },
+ {
+ key: `Pending: ${status.pending}`,
+ value: status.pending / totalItems * 100,
+ },
+ {
+ key: `Succeeded: ${status.succeeded}`,
+ value: status.succeeded / totalItems * 100,
+ }
+ ]);
+ break;
+ default:
+ items = items.concat([
+ {
+ key: `Failed: ${status.failed}`,
+ value: status.failed / totalItems * 100,
+ },
+ {
+ key: `Pending: ${status.pending}`,
+ value: status.pending / totalItems * 100,
+ }
+ ]);
+ break;
+ }
+
+ return items;
+ }
+}
diff --git a/src/app/frontend/overview/template.html b/src/app/frontend/overview/template.html
index abffe31da31b..dcda85ea3522 100644
--- a/src/app/frontend/overview/template.html
+++ b/src/app/frontend/overview/template.html
@@ -18,24 +18,25 @@
-
-
-
+
+
-
-
-
-
-
-
-
diff --git a/src/app/frontend/overview/workloadstatus/component.spec.ts b/src/app/frontend/overview/workloadstatus/component.spec.ts
new file mode 100644
index 000000000000..ae306aa5cfc1
--- /dev/null
+++ b/src/app/frontend/overview/workloadstatus/component.spec.ts
@@ -0,0 +1,118 @@
+// Copyright 2017 The Kubernetes Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
+import {CUSTOM_ELEMENTS_SCHEMA} from '@angular/core';
+import {async, ComponentFixture, TestBed} from '@angular/core/testing';
+import {FlexLayoutModule} from '@angular/flex-layout';
+import {MatCardModule, MatDividerModule, MatIconModule, MatTooltipModule} from '@angular/material';
+import {By} from '@angular/platform-browser';
+import {NoopAnimationsModule} from '@angular/platform-browser/animations';
+import {AppConfig} from '@api/backendapi';
+import {ResourcesRatio} from '@api/frontendapi';
+
+import {AllocationChartComponent} from '../../common/components/allocationchart/component';
+import {CardComponent} from '../../common/components/card/component';
+import {ConfigService} from '../../common/services/global/config';
+
+import {WorkloadStatusComponent} from './component';
+
+const testResourcesRatio: ResourcesRatio = {
+ cronJobRatio: [],
+ daemonSetRatio: [
+ {key: 'Running: 1', value: 100}, {key: 'Failed: 0', value: 0}, {key: 'Pending: 0', value: 0}
+ ],
+ deploymentRatio: [
+ {key: 'Running: 1', value: 50}, {key: 'Failed: 1', value: 50}, {key: 'Pending: 0', value: 0}
+ ],
+ jobRatio: [],
+ podRatio: [
+ {key: 'Running: 10', value: 83.33333333333334}, {key: 'Failed: 2', value: 16.666666666666664},
+ {key: 'Pending: 0', value: 0}, {key: 'Succeeded: 0', value: 0}
+ ],
+ replicaSetRatio: [
+ {key: 'Running: 1', value: 50}, {key: 'Failed: 1', value: 50}, {key: 'Pending: 0', value: 0}
+ ],
+ replicationControllerRatio: [
+ {key: 'Running: 2', value: 100}, {key: 'Failed: 0', value: 0}, {key: 'Pending: 0', value: 0}
+ ],
+ statefulSetRatio: []
+};
+
+describe('WorkloadStatusComponent', () => {
+ let httpMock: HttpTestingController;
+ let configService: ConfigService;
+ let testHostFixture: ComponentFixture;
+
+ beforeEach(async(() => {
+ TestBed
+ .configureTestingModule({
+ declarations: [CardComponent, AllocationChartComponent, WorkloadStatusComponent],
+ imports: [
+ MatIconModule, MatCardModule, MatDividerModule, MatTooltipModule, NoopAnimationsModule,
+ HttpClientTestingModule, FlexLayoutModule
+ ],
+ providers: [ConfigService],
+ schemas: [CUSTOM_ELEMENTS_SCHEMA]
+ })
+ .compileComponents();
+ httpMock = TestBed.get(HttpTestingController);
+ configService = TestBed.get(ConfigService);
+ }));
+
+ beforeEach(() => {
+ configService.init();
+ const configRequest = httpMock.expectOne('config');
+ const config: AppConfig = {serverTime: new Date().getTime()};
+ configRequest.flush(config);
+
+ testHostFixture = TestBed.createComponent(WorkloadStatusComponent);
+ });
+
+ it('shows component heading', () => {
+ testHostFixture.detectChanges();
+ const debugElement =
+ testHostFixture.debugElement.query(By.css('kd-card mat-card mat-card-title div'));
+ expect(debugElement).toBeTruthy();
+
+ const htmlElement = debugElement.nativeElement;
+ expect(htmlElement.innerText).toContain('Workload Status');
+ });
+
+ it('does not show cron jobs status', () => {
+ const component = testHostFixture.componentInstance;
+ component.resourcesRatio = testResourcesRatio;
+
+ testHostFixture.detectChanges();
+ const debugElements = testHostFixture.debugElement.queryAll(
+ By.css('kd-card mat-card div mat-card-content div.kd-graph-title'));
+
+ debugElements.forEach(debugElement => {
+ const htmlElement = debugElement.nativeElement;
+ expect(htmlElement.innerText === 'Cron Jobs').toBeFalsy();
+ });
+ });
+
+ it('shows pod status', () => {
+ const component = testHostFixture.componentInstance;
+ component.resourcesRatio = testResourcesRatio;
+
+ testHostFixture.detectChanges();
+ const debugElement = testHostFixture.debugElement.query(
+ By.css('kd-card mat-card div mat-card-content kd-allocation-chart #pods'));
+ expect(debugElement).toBeTruthy();
+
+ expect(debugElement.context.data === testResourcesRatio.podRatio).toBeTruthy();
+ });
+});
diff --git a/src/app/frontend/overview/workloadstatus/component.ts b/src/app/frontend/overview/workloadstatus/component.ts
index 3a245e13524f..77ae73d24684 100644
--- a/src/app/frontend/overview/workloadstatus/component.ts
+++ b/src/app/frontend/overview/workloadstatus/component.ts
@@ -12,96 +12,29 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import {Component} from '@angular/core';
+import {Component, Input} from '@angular/core';
+import {ResourcesRatio} from '@api/frontendapi';
-@Component({
- selector: 'kd-workload-statuses',
- templateUrl: './template.html',
-})
+export const emptyResourcesRatio: ResourcesRatio = {
+ cronJobRatio: [],
+ daemonSetRatio: [],
+ deploymentRatio: [],
+ jobRatio: [],
+ podRatio: [],
+ replicaSetRatio: [],
+ replicationControllerRatio: [],
+ statefulSetRatio: []
+};
+
+@Component(
+ {selector: 'kd-workload-statuses', templateUrl: './template.html', styleUrls: ['./style.scss']})
export class WorkloadStatusComponent {
- // @Input() ratio: any;
+ @Input() resourcesRatio: ResourcesRatio;
colors: string[] = ['#00c752', '#f00', '#ffad20', '#006028'];
- // /** @export {Array