Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PT#142772825 - snapshottimeline #848

Merged
merged 16 commits into from
Jul 27, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ window._ = require('lodash');
window.moment = require('moment');
window.sprintf = require('sprintf-js').sprintf;
window.c3 = require('c3/c3.js');
window.d3 = require('d3/d3.js');

// Vendor libraries, order matters
require('d3/build/d3.min.js');
require('jquery-ui-bundle');
require('moment-timezone');
require('es6-shim');
Expand Down Expand Up @@ -49,6 +49,7 @@ require('datatables.net-select/js/dataTables.select');
require('angular-datatables');
require('angular-datatables/dist/plugins/select/angular-datatables.select');
require('ui-select');
require('patternfly-timeline/dist/timeline');

// Needs imports loader because it expects `this` to be `window`
require('imports-loader?this=>window!actioncable');
Expand All @@ -66,6 +67,8 @@ require('@manageiq/ui-components/dist/css/ui-components.css');
require('ngprogress/ngProgress.css');
require('datatables.net-dt/css/jquery.dataTables.css');
require('ui-select/dist/select.css');
require('patternfly-timeline/dist/timeline.css');


// Application styles
require('./assets/sass/styles.sass');
Expand Down
24 changes: 14 additions & 10 deletions client/app/services/vm-details/vm-details.component.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,11 @@ function ComponentController($stateParams, VmsService, ServicesState, sprintf, l
vm.pollVM = pollVM;
vm.retireVM = retireVM;
vm.getData = resolveData;
vm.storageChartConfigOptions = {'units': __('GB'), 'chartId': 'storageChart', 'label': __('used')};
vm.memoryChartConfigOptions = {'units': __('GB'), 'chartId': 'memoryChart', 'label': __('used')};
vm.storageChartConfigOptions = {'units': __('GB'), 'chartId': 'storageChart', 'label': __('used')};
vm.memoryChartConfigOptions = {'units': __('GB'), 'chartId': 'memoryChart', 'label': __('used')};
vm.cpuChartConfigOptions = {'units': __('MHz'), 'chartId': 'cpuChart', 'label': __('used')};
vm.processInstanceVariables = processInstanceVariables;
// vm.resolveData = resolveData;

function onDestroy() {
Polling.stop('vmPolling');
}
Expand Down Expand Up @@ -55,7 +55,7 @@ function ComponentController($stateParams, VmsService, ServicesState, sprintf, l
},
listActions: [],
});

EventNotifications.info(__("The contents of this page is a function of the current users's group."));
resolveData();
Polling.start('vmPolling', pollVM, LONG_POLLING_INTERVAL);
Expand All @@ -77,12 +77,15 @@ function ComponentController($stateParams, VmsService, ServicesState, sprintf, l
function retireVM() {
PowerOperations.retireVM(vm.vmDetails);
}

function viewSelected(view) {
vm.viewType = view;
}

function pollVM() {
resolveData(true);
}

function resolveData(refresh) {
return VmsService.getVm($stateParams.vmId, refresh).then(handleSuccess, handleFailure);

Expand All @@ -91,7 +94,7 @@ function ComponentController($stateParams, VmsService, ServicesState, sprintf, l
const allocatedStorage = UsageGraphsService.convertBytestoGb(vm.vmDetails.allocated_disk_storage); // convert bytes to gb
const usedStorage = UsageGraphsService.convertBytestoGb(vm.vmDetails.used_storage);
const totalMemory = vm.vmDetails.ram_size / 1024;
const usedMemory = UsageGraphsService.convertBytestoGb(vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period);
const usedMemory = UsageGraphsService.convertBytestoGb(vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period);
const usedCPU = vm.vmDetails.cpu_usagemhz_rate_average_avg_over_time_period;
const totalCPU = (angular.isDefined(vm.vmDetails.hardware.aggregate_cpu_speed) ? vm.vmDetails.hardware.aggregate_cpu_speed : 0);
if (response.cloud) {
Expand Down Expand Up @@ -136,19 +139,20 @@ function ComponentController($stateParams, VmsService, ServicesState, sprintf, l

return lodash.compact(buttons).length > 0;
}

function hasUsageGraphs() {
if (angular.isUndefined(vm.vmDetails.allocated_disk_storage) || vm.vmDetails.allocated_disk_storage === 0) {
vm.usageGraphs = false;
}
if (angular.isUndefined(vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period)
|| vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period === 0) {
if (angular.isUndefined(vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period)
|| vm.vmDetails.max_mem_usage_absolute_average_avg_over_time_period === 0) {
vm.usageGraphs = false;
}
if (angular.isUndefined(vm.vmDetails.hardware.aggregate_cpu_speed)
|| vm.vmDetails.hardware.aggregate_cpu_speed === 0) {
if (angular.isUndefined(vm.vmDetails.hardware.aggregate_cpu_speed)
|| vm.vmDetails.hardware.aggregate_cpu_speed === 0) {
vm.usageGraphs = false;
}

return vm.usageGraphs;
}

Expand Down
5 changes: 3 additions & 2 deletions client/app/services/vm-details/vm-details.html
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
</div>
</div>
<pf-toolbar ng-if="vm.listActions.length" class="section-toolbar section-toolbar-right-actions"
config="vm.headerConfig">
config="vm.headerConfig">
<actions>
<div class="ss-details-header__actions">
<div uib-dropdown class="ss-details-header__actions__inner dropdown-kebab-pf">
Expand Down Expand Up @@ -72,7 +72,8 @@
display-field="vm.vmDetails.cloud===false"></detail-reveal>
<detail-reveal detail-title="CPU Affinity" detail="{{ vm.vmDetails.cpu_affinity }}"
display-field="vm.vmDetails.cloud===false"></detail-reveal>
<detail-reveal display-field="vm.permissions.vm_snapshot_show_list" detail-title="Snapshots" icon="fa fa-camera fa-lg"
<detail-reveal display-field="vm.permissions.vm_snapshot_show_list" detail-title="Snapshots"
icon="fa fa-camera fa-lg"
detail="{{ vm.vmDetails.v_total_snapshots }}"></detail-reveal>
<detail-reveal detail-title="Advanced Settings" icon="fa fa-wrench fa-lg"
detail="{{ vm.vmDetails.advanced_settings.length }}"></detail-reveal>
Expand Down
12 changes: 12 additions & 0 deletions client/app/services/vms/_snapshots.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@import 'app_colors'

div
.timeline
border-bottom: 1px solid $app-color-light-gray-5
margin-left: -120px

.timeline-pf-label
display: none

.well-panel
background-color: $app-color-white
58 changes: 56 additions & 2 deletions client/app/services/vms/snapshots.component.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint camelcase: "off" */
/* eslint camelcase: "off", no-undef: "off" */
import './_snapshots.sass';
import templateUrl from './snapshots.html';

export const VmSnapshotsComponent = {
Expand All @@ -12,7 +13,7 @@ export const VmSnapshotsComponent = {
};

/** @ngInject */
function ComponentController(VmsService, sprintf, EventNotifications, ListView, ModalService) {
function ComponentController(VmsService, sprintf, EventNotifications, ListView, ModalService, lodash) {
const vm = this;

vm.$onInit = function() {
Expand Down Expand Up @@ -166,6 +167,25 @@ function ComponentController(VmsService, sprintf, EventNotifications, ListView,
vm.loading = false;
vm.toolbarConfig.filterConfig.resultsCount = response.subcount;
vm.snapshots = response.resources;

const start = lodash.minBy(vm.snapshots, 'create_time');
const end = lodash.maxBy(vm.snapshots, 'create_time');
const tlSnapshots = vm.snapshots.map((item) => ({
"date": new Date(item.create_time),
"details": {"event": item.name, "object": item.name, item},
}));

vm.tlData = [{
"data": tlSnapshots,
"display": true,
}];

vm.tlOptions = {
start: new Date(start.create_time),
end: new Date(end.create_time),
eventShape: '\uf030',
eventHover: showTooltip,
};
}

function failure(_error) {
Expand Down Expand Up @@ -237,4 +257,38 @@ function ComponentController(VmsService, sprintf, EventNotifications, ListView,
};
ModalService.open(modalOptions);
}

const showTooltip = (item) => {
d3.select('body').selectAll('.popover').remove();

const fontSize = 12; // in pixels
const tooltipWidth = 9; // in rem
const tooltip = d3
.select('body')
.append('div')
.attr('class', 'popover fade bottom in')
.attr('role', 'tooltip')
.on('mouseout', () => {
d3.select('body').selectAll('.popover').remove();
});
const rightOrLeftLimit = fontSize * tooltipWidth;
const direction = d3.event.pageX > rightOrLeftLimit ? 'right' : 'left';
const left = direction === 'right' ? d3.event.pageX - rightOrLeftLimit : d3.event.pageX;
const options = {weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric'};

tooltip.html(
`
<div class="arrow"></div>
<div class="popover-content">
<div>Name: ${item.details.event}</div>
<div>Date: ${item.date.toLocaleDateString("en-US", options)}</div>
</div>
`
);

tooltip
.style('left', `${left}px`)
.style('top', `${d3.event.pageY + 8}px`)
.style('display', 'block');
};
}
117 changes: 63 additions & 54 deletions client/app/services/vms/snapshots.component.spec.js
Original file line number Diff line number Diff line change
@@ -1,58 +1,67 @@
describe('Component: snapshots', function () {
beforeEach(function () {
module('app.services');
});
describe('Component: snapshots', function() {
beforeEach(function() {
module('app.services');
});

describe('with $componentController', function() {
let $componentController, ctrl, notificationsSpy, collectionsApiSpy;
let bindings = {vmId: "1"};
let successResponse = {
message: "Success"
};
let errorResponse = 'error';

describe('with $componentController', function () {
let $componentController, ctrl, notificationsSpy, collectionsApiSpy;
let bindings = {vmId: "1"};
let successResponse = {
message: "Success"
beforeEach(
inject(function(_$componentController_) {
$componentController = _$componentController_;
let transclude = function() {
let returnObj = {};
returnObj.length = 0;
return returnObj
};
let errorResponse = 'error';

beforeEach(
inject(function (_$componentController_) {
$componentController = _$componentController_;
let transclude = function () {
let returnObj = {};
returnObj.length = 0;
return returnObj
};
ctrl = $componentController('vmSnapshots', {$transclude: transclude}, bindings);
})
);

it('is defined, accepts bindings', function () {
expect(ctrl).to.be.defined;
expect(ctrl.vmId).to.equal("1");
});

it('sets correct title', function () {
expect(ctrl.title).to.equal("Snapshots");
});

it('has an onInit()', function () {
ctrl.$onInit();
expect(ctrl.$onInit).to.be.defined;
});

beforeEach(function () {
bard.inject('CollectionsApi', 'EventNotifications');
notificationsSpy = sinon.stub(EventNotifications, 'result').returns(null);
ctrl.$onInit();
});

it('should query the API to resolve snapshots', function () {
collectionsApiSpy = sinon.stub(CollectionsApi, 'query').returns(Promise.resolve(successResponse));
ctrl.resolveSnapshots();
expect(collectionsApiSpy).to.have.been.called;
});

it('should call CollectionsApi post to delete snapshots', function () {
collectionsApiSpy = sinon.stub(CollectionsApi, 'post').returns(Promise.resolve(successResponse));
ctrl.deleteSnapshots();
expect(collectionsApiSpy).to.have.been.called;
});
ctrl = $componentController('vmSnapshots', {$transclude: transclude}, bindings);
})
);

it('is defined, accepts bindings', function() {
expect(ctrl).to.be.defined;
expect(ctrl.vmId).to.equal("1");
});

it('sets correct title', function() {
expect(ctrl.title).to.equal("Snapshots");
});

it('has an onInit()', function() {
expect(ctrl.$onInit).to.be.defined;
});

it('has a deleteSnapshots()', function() {
ctrl.deleteSnapshots()
ctrl.deleteSnapshots("action", {item: "item"})
expect(ctrl.deleteSnapshots()).to.be.defined;
});

it('has a cancelDelete()', function() {
expect(ctrl.cancelDelete()).to.be.defined;
});

beforeEach(function() {
bard.inject('CollectionsApi', 'EventNotifications');
notificationsSpy = sinon.stub(EventNotifications, 'result').returns(null);
ctrl.$onInit();
});

it('should query the API to resolve snapshots', function() {
collectionsApiSpy = sinon.stub(CollectionsApi, 'query').returns(Promise.resolve(successResponse));
ctrl.resolveSnapshots();
expect(collectionsApiSpy).to.have.been.called;
});

it('should call CollectionsApi post to delete snapshots', function() {
collectionsApiSpy = sinon.stub(CollectionsApi, 'post').returns(Promise.resolve(successResponse));
ctrl.deleteSnapshots();
expect(collectionsApiSpy).to.have.been.called;
});
});
});
13 changes: 9 additions & 4 deletions client/app/services/vms/snapshots.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@
</ol>
</span>
</div>

<div class="section-container well-panel">
<timeline data="vm.tlData" options="vm.tlOptions"></timeline>
</div>

<pf-toolbar class="section-toolbar section-toolbar-right-actions" config="vm.toolbarConfig">
<actions>
<custom-dropdown config="vm.listActions" menu-right="true"></custom-dropdown>
Expand All @@ -38,10 +43,10 @@
confirmation-items="vm.catalogsToDelete"
confirmation-item-name-field="name">
<pf-list-view
config="vm.listConfig"
items="vm.snapshots"
menu-actions="vm.menuActions"
update-menu-action-for-item-fn="vm.updateMenuActionForItemFn">
config="vm.listConfig"
items="vm.snapshots"
menu-actions="vm.menuActions"
update-menu-action-for-item-fn="vm.updateMenuActionForItemFn">
<div class="row">
<div class="col-lg-2 col-md-2 col-sm-2 col-xs-2">
<span class="no-wrap">
Expand Down
2 changes: 2 additions & 0 deletions client/app/shared/shared.module.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {LoadingComponent} from "./loading.component.js";
import {PaginationComponent} from "./pagination/pagination.component.js";
import {SSCardComponent} from "./ss-card/ss-card.component.js";
import {TaggingComponent} from "./tagging/tagging.component.js";
import {TimelineComponent} from "./timeline/timeline.component.js";
import {substitute} from "./substitute.filter.js";

export const SharedModule = angular
Expand All @@ -35,6 +36,7 @@ export const SharedModule = angular
.component('loading', LoadingComponent)
.component('ssCard', SSCardComponent)
.component('taggingWidget', TaggingComponent)
.component('timeline', TimelineComponent)
.directive('autofocus', AutofocusDirective)
.component('aceEditor', AceEditorComponent)
.directive('confirmation', ConfirmationDirective)
Expand Down
2 changes: 2 additions & 0 deletions client/app/shared/timeline/_timeline.sass
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.timeline
position: relative
Loading