',
- '
',
- ' ',
- '
',
+ '',
+ '
',
+ ' ',
+ ' ',
+ '
',
__('No RSS Feed data found'),
- '
',
- ' ',
+ ' ',
'
',
- ' ',
+ '',
].join("\n"),
diff --git a/app/assets/javascripts/components/widget-spinner.js b/app/assets/javascripts/components/widget-spinner.js
new file mode 100644
index 00000000000..f21cd7d228f
--- /dev/null
+++ b/app/assets/javascripts/components/widget-spinner.js
@@ -0,0 +1,9 @@
+ManageIQ.angular.app.component('widgetSpinner', {
+ template: [
+ '',
+ '
',
+ ' ',
+ '
',
+ '
',
+ ].join("\n"),
+});
diff --git a/app/assets/javascripts/components/widget-wrapper.js b/app/assets/javascripts/components/widget-wrapper.js
new file mode 100644
index 00000000000..322406f10b0
--- /dev/null
+++ b/app/assets/javascripts/components/widget-wrapper.js
@@ -0,0 +1,85 @@
+ManageIQ.angular.app.component('widgetWrapper', {
+ bindings: {
+ widgetId: '@',
+ widgetType: '@',
+ widgetButtons: '@',
+ widgetBlank: '@',
+ widgetTitle: '@',
+ widgetLastRun: '@',
+ widgetNextRun: '@',
+ },
+ controllerAs: 'vm',
+ controller: ['$http', 'miqService', '$sce', function($http, miqService, $sce) {
+ var vm = this;
+
+ var widgetTypeUrl = {
+ menu: '/dashboard/widget_menu_data/',
+ report: '/dashboard/widget_report_data/',
+ chart: '/dashboard/widget_chart_data/',
+ rss: '/dashboard/widget_rss_data/',
+ };
+
+ var deferred = miqDeferred();
+ vm.promise = deferred.promise;
+
+ this.$onInit = function() {
+ vm.divId = "w_" + vm.widgetId;
+ vm.innerDivId = 'dd_w' + vm.widgetId + '_box';
+ if (vm.widgetBlank === 'false') {
+ $http.get(vm.widgetUrl())
+ .then(function(response) {
+ vm.widgetModel = response.data;
+ // if there's html make it passable
+ if (vm.widgetModel.content) {
+ vm.widgetModel.content = $sce.trustAsHtml(vm.widgetModel.content);
+ }
+ deferred.resolve();
+ })
+ .catch(function(e) {
+ vm.error = true;
+ miqService.handleFailure(e);
+ deferred.reject();
+ });
+ }
+ };
+
+ vm.widgetUrl = function() {
+ if (widgetTypeUrl.hasOwnProperty(vm.widgetType)) {
+ return [widgetTypeUrl[vm.widgetType], vm.widgetId].join('/');
+ } else {
+ console.log('Something went wrong. There is no support for widget type of ', vm.widgetType);
+ }
+ };
+ }],
+ template: [
+ '',
+ '
',
+ '
',
+ '
',
+ ' ',
+ ' ',
+ '
',
+ "{{vm.widgetTitle}}",
+ '
',
+ ' ',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ ' ',
+ '
',
+ '
',
+ '
',
+ '
',
+ '
',
+ ].join("\n"),
+});
diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb
index 149f145ccc9..7fb04ae3373 100644
--- a/app/helpers/dashboard_helper.rb
+++ b/app/helpers/dashboard_helper.rb
@@ -3,4 +3,12 @@ def ext_auth?(auth_option = nil)
return false unless ::Settings.authentication.mode == 'httpd'
auth_option ? ::Settings.authentication[auth_option] : true
end
+
+ def last_next_run(widget)
+ last_run_on = widget.last_run_on_for_user(current_user)
+ next_run_on = widget.next_run_on
+ last_run = last_run_on ? format_timezone(last_run_on, session[:user_tz], "widget_footer") : _('Never')
+ next_run = next_run_on ? format_timezone(next_run_on, session[:user_tz], "widget_footer") : _('Unknown')
+ [last_run, next_run]
+ end
end
diff --git a/app/javascript/spec/dashboards/mocks.js b/app/javascript/spec/dashboards/mocks.js
new file mode 100644
index 00000000000..33a0f26d328
--- /dev/null
+++ b/app/javascript/spec/dashboards/mocks.js
@@ -0,0 +1,43 @@
+require('angular');
+require('angular-mocks');
+const module = window.module;
+const inject = window.inject;
+
+window.ManageIQ = {
+ angular: {
+ app: angular.module('ManageIQ', []),
+ },
+};
+window.__ = (x) => x;
+
+// FIXME: app/assets/javascripts/services/
+ManageIQ.angular.app.service('miqService', function () {
+ this.handleFailure = () => null;
+});
+
+// FIXME: miq_application.js
+window.miqDeferred = () => {
+ var deferred = {};
+
+ deferred.promise = new Promise(function (resolve, reject) {
+ deferred.resolve = resolve;
+ deferred.reject = reject;
+ });
+
+ return deferred;
+};
+
+// FIXME: don't mock PF functions
+$.fn.setupVerticalNavigation = function() {};
+
+require('../../../assets/javascripts/components/widget-chart');
+require('../../../assets/javascripts/components/widget-empty');
+require('../../../assets/javascripts/components/widget-error');
+require('../../../assets/javascripts/components/widget-footer');
+require('../../../assets/javascripts/components/widget-menu');
+require('../../../assets/javascripts/components/widget-report');
+require('../../../assets/javascripts/components/widget-rss');
+require('../../../assets/javascripts/components/widget-spinner');
+require('../../../assets/javascripts/components/widget-wrapper');
+
+export { module, inject };
diff --git a/app/javascript/spec/dashboards/widget-empty.test.js b/app/javascript/spec/dashboards/widget-empty.test.js
new file mode 100644
index 00000000000..ef0e372e670
--- /dev/null
+++ b/app/javascript/spec/dashboards/widget-empty.test.js
@@ -0,0 +1,36 @@
+import { module, inject } from './mocks';
+
+describe('widget-empty', function () {
+ let $scope;
+ let element;
+ let $compile;
+
+ beforeEach(module('ManageIQ'));
+ beforeEach(inject(function (_$compile_, $rootScope, $templateCache) {
+ // FIXME: templateRequest is using $http to get the template, but angular-mocks prevents it
+ $templateCache.put('/static/dropdown-menu.html.haml', '
');
+ $scope = $rootScope;
+ $scope.miqButtonClicked = function () {};
+ $scope.validForm = true;
+ $compile = _$compile_;
+ }));
+
+ it('is rendered in widget-wrapper if widget-blank is set to true', function (done) {
+ element = angular.element(
+ ''
+ );
+ element = $compile(element)($scope);
+
+ $scope.$digest();
+
+ setTimeout(function () {
+ const widget = element.find("widget-empty");
+ expect(widget.length).toBe(1);
+
+ done();
+ });
+ });
+}
+);
diff --git a/app/javascript/spec/dashboards/widget-wrapper.test.js b/app/javascript/spec/dashboards/widget-wrapper.test.js
new file mode 100644
index 00000000000..86982f24940
--- /dev/null
+++ b/app/javascript/spec/dashboards/widget-wrapper.test.js
@@ -0,0 +1,59 @@
+import { module, inject } from './mocks';
+
+describe('widget-wrapper', function () {
+ let $scope;
+ let element;
+ let $compile;
+ const widgetTypes = ['chart', 'menu', 'report', 'rss'];
+
+ beforeEach(module('ManageIQ'));
+
+ beforeEach(inject(function (_$compile_, $rootScope, $templateCache, $http) {
+ // FIXME: templateRequest is using $http to get the template, but angular-mocks prevents it
+ $templateCache.put('/static/dropdown-menu.html.haml', '
');
+
+ $scope = $rootScope;
+
+ $compile = _$compile_;
+ spyOn($http, 'get').and.callFake(function (url) {
+ if (url === '/static/dropdown-menu.html.haml') {
+ return Promise.resolve({
+ data: "
",
+ status: 200,
+ statusText: 'OK',
+ });
+ } else {
+ return Promise.resolve({
+ data: {
+ content: "
",
+ minimized: false,
+ shortcuts: [],
+ },
+ status: 200,
+ statusText: 'OK',
+ });
+ }
+ });
+ }));
+
+ widgetTypes.forEach(function (widget) {
+ it(`renders widget-${widget} when widget-type is ${widget}`, function (done) {
+ element = angular.element(
+ '
'
+ );
+ element = $compile(element)($scope);
+ $scope.$digest();
+
+ const $ctrl = element.find('widget-wrapper').find('div').scope().vm;
+ $ctrl.promise.catch(function () {}).then(function () {
+ $scope.$digest();
+
+ const widgetElement = element.find("widget-".concat(widget));
+ expect(widgetElement.length).toBe(1);
+ done();
+ });
+ });
+ });
+});
diff --git a/app/views/dashboard/_widget.html.haml b/app/views/dashboard/_widget.html.haml
index 2dc16d3f8c1..0921e5d80ac 100644
--- a/app/views/dashboard/_widget.html.haml
+++ b/app/views/dashboard/_widget.html.haml
@@ -1,25 +1,13 @@
--# Parameters:
--# widget MiqWidget object
-%div{:id => "w_#{presenter.widget.id}"}
- .card-pf.card-pf-view
- .card-pf-body
- .card-pf-heading-kebab
- %dropdown-menu{"widget-id" => presenter.widget.id, "buttons-data" => presenter.widget_buttons}
- %h2.card-pf-title.sortable-handle{:style => "cursor:move"}
- = h(presenter.widget.title)
-
- - if presenter.widget.content_type == "menu"
- %widget-menu{:id => presenter.widget.id, "widget-id" => presenter.widget.id}
- - elsif presenter.widget.contents_for_user(current_user).blank?
- = render :partial => 'widget_blank', :locals => {:widget => presenter.widget}
- - elsif presenter.widget.content_type == "report"
- %widget-report{:id => presenter.widget.id, "widget-id" => presenter.widget.id}
- - elsif presenter.widget.content_type == "chart"
- %widget-chart{:id => presenter.widget.id, "widget-id" => presenter.widget.id}
- - elsif presenter.widget.content_type == "rss"
- %widget-rss{:id => presenter.widget.id, "widget-id" => presenter.widget.id}
- - unless presenter.widget.content_type == "menu"
- = render :partial => 'widget_footer', :locals => {:widget => presenter.widget}
+%div{:id => "ww_#{presenter.widget.id}"}
+ - last_run, next_run = last_next_run(presenter.widget)
+ - widget_blank = presenter.widget.content_type == 'menu' ? false : presenter.widget.contents_for_user(current_user).blank?
+ %widget-wrapper{"widget-id" => presenter.widget.id,
+ "widget-type" => presenter.widget.content_type,
+ "widget-buttons" => presenter.widget_buttons,
+ "widget-blank" => widget_blank,
+ "widget-last-run" => last_run,
+ "widget-next-run" => next_run,
+ "widget-title" => presenter.widget.title}
:javascript
- miq_bootstrap("#w_#{presenter.widget.id}");
+ miq_bootstrap("#ww_#{presenter.widget.id}");
diff --git a/app/views/dashboard/_widget_blank.html.haml b/app/views/dashboard/_widget_blank.html.haml
deleted file mode 100644
index 2ef84b0ea97..00000000000
--- a/app/views/dashboard/_widget_blank.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
--#
- Parameters:
- widget -- MiqWidget object
-.mc{:id => "dd_w#{widget.id}_box",
-:style => "#{@sb[:dashboards][@sb[:active_db]][:minimized].include?(widget.id) ? 'display: none;' : ''}"}
- .blank-slate-pf{:style => "padding: 10px"}
- .blank-slate-pf-icon
- %i.fa.fa-cog
- %h1= _('No data found.')
- %p= _('If this widget is new or has just been added to your dashboard, the data is being generated and should be available soon.')
diff --git a/app/views/dashboard/_widget_footer.html.haml b/app/views/dashboard/_widget_footer.html.haml
deleted file mode 100644
index df198451858..00000000000
--- a/app/views/dashboard/_widget_footer.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
--#
- Parameters:
- widget -- MiqWidget object
-.card-pf-footer
- = _('Updated')
- - last_run_on = widget.last_run_on_for_user(current_user)
- - if last_run_on
- = format_timezone(last_run_on, session[:user_tz], "widget_footer")
- - else
- = _('Never')
- |
- = _('Next')
- - next_run_on = widget.next_run_on
- - if next_run_on
- = format_timezone(next_run_on, session[:user_tz], "widget_footer")
- - else
- = _('Unknown')
diff --git a/app/views/dashboard/_zoomed_chart.html.haml b/app/views/dashboard/_zoomed_chart.html.haml
index 7c3c657e8f7..ea4e8a92df2 100644
--- a/app/views/dashboard/_zoomed_chart.html.haml
+++ b/app/views/dashboard/_zoomed_chart.html.haml
@@ -1,6 +1,7 @@
-#
Parameters:
widget -- MiqWidget object
+- last_run, next_run = last_next_run(widget)
#zoomed_chart_div
.card-pf
.card-pf-heading
@@ -11,4 +12,6 @@
%i.fa.fa-close.pull-right
.card-pf-body
= chart_remote('dashboard', :id => 'my_chart', :zoomed => true)
- = render :partial => 'widget_footer', :locals => {:widget => widget}
+ %widget-footer{:id => "zoomed_chart_footer_#{widget.id}", 'widget-last-run' => last_run, 'widget-next-run' => next_run}
+:javascript
+ miq_bootstrap("#zoomed_chart_footer_#{widget.id}");
diff --git a/app/views/layouts/_header.html.haml b/app/views/layouts/_header.html.haml
index ac4878edc09..c89ff7de2b8 100644
--- a/app/views/layouts/_header.html.haml
+++ b/app/views/layouts/_header.html.haml
@@ -31,12 +31,11 @@
= _(menu_item.name)
= render :partial => "layouts/user_options"
-
- = render :partial => "layouts/spinner"
- = render :partial => "layouts/lightbox_panel"
= render :partial => "layouts/notifications_drawer"
= render :partial => "layouts/toast_list"
+= render :partial => "layouts/spinner"
+= render :partial => "layouts/lightbox_panel"
:javascript
miq_bootstrap('#notification-app', 'miq.notifications');