Merge pull request #5055 from opf/feature/autohide-notifications

[24152 ] Auto hide success notifications
pull/5015/head
Oliver Günther 8 years ago committed by GitHub
commit 924438aabc
  1. 4
      app/assets/javascripts/flash_messages.js
  2. 10
      app/helpers/application_helper.rb
  3. 3
      app/models/permitted_params.rb
  4. 9
      app/models/user_preference.rb
  5. 1
      app/views/users/_preferences.html.erb
  6. 1
      config/locales/en.yml
  7. 5
      frontend/app/angular-modules.ts
  8. 6
      frontend/app/components/common/config/configuration.service.ts
  9. 8
      frontend/app/components/common/notifications/notifications.service.test.ts
  10. 25
      frontend/app/components/common/notifications/notifications.service.ts
  11. 1
      lib/api/v3/user_preferences/user_preferences_representer.rb
  12. 25
      spec/controllers/my_controller_spec.rb
  13. 1
      spec/lib/api/v3/user_preferences/user_preferences_representer_spec.rb
  14. 19
      spec/models/user_preference_spec.rb

@ -32,4 +32,8 @@ jQuery(document).ready(function($) {
$(this).parent('.flash, .errorExplanation').remove();
}
});
setTimeout(function() {
$('.flash.autohide-notification').remove();
}, 5000);
});

@ -203,8 +203,14 @@ module ApplicationHelper
end
def render_flash_message(type, message, html_options = {})
css_classes = ["flash #{type} icon icon-#{type}", html_options.delete(:class)].join(' ')
html_options = { class: css_classes, role: 'alert' }.merge(html_options)
css_classes = ["flash #{type} icon icon-#{type}", html_options.delete(:class)]
# Add autohide class to notice flashes if configured
if type.to_s == 'notice' && User.current.pref.auto_hide_popups?
css_classes << 'autohide-notification'
end
html_options = { class: css_classes.join(' '), role: 'alert' }.merge(html_options)
content_tag :div, html_options do
if User.current.impaired?

@ -275,7 +275,8 @@ class PermittedParams
def pref
params.require(:pref).permit(:hide_mail, :time_zone, :impaired, :theme,
:comments_sorting, :warn_on_leaving_unsaved)
:comments_sorting, :warn_on_leaving_unsaved,
:auto_hide_popups)
end
def project

@ -74,9 +74,18 @@ class UserPreference < ActiveRecord::Base
others[:warn_on_leaving_unsaved] = to_boolean(value)
end
def auto_hide_popups=(value)
others[:auto_hide_popups] = to_boolean(value)
end
def auto_hide_popups?
others[:auto_hide_popups] || false
end
# Provide an alias to form builders
alias :comments_in_reverse_order :comments_in_reverse_order?
alias :warn_on_leaving_unsaved :warn_on_leaving_unsaved?
alias :auto_hide_popups :auto_hide_popups?
def comments_in_reverse_order=(value)
others[:comments_sorting] = to_boolean(value) ? 'desc' : 'asc'

@ -50,4 +50,5 @@ See doc/COPYRIGHT.rdoc for more details.
<% end %>
<div class="form--field"><%= pref_fields.check_box :warn_on_leaving_unsaved %></div>
<div class="form--field"><%= pref_fields.check_box :impaired %></div>
<div class="form--field"><%= pref_fields.check_box :auto_hide_popups %></div>
<% end %>

@ -317,6 +317,7 @@ en:
hide_mail: "Hide my email address"
impaired: "Accessibility mode"
time_zone: "Time zone"
auto_hide_popups: "Auto-hide success notifications"
warn_on_leaving_unsaved: "Warn me when leaving a page with unsaved text"
version:
effective_date: "Due date"

@ -40,6 +40,7 @@ export const animationsModule = angular.module('openproject.animations', [
export const opConfigModule = angular.module('openproject.config', []);
export const opServicesModule = angular.module('openproject.services', [
'openproject.uiComponents',
'openproject.config',
'openproject.helpers',
'openproject.workPackages.config',
'openproject.workPackages.helpers',
@ -148,6 +149,10 @@ export const opApiModule = angular.module('openproject.api', [
export const opTemplatesModule = angular.module('openproject.templates', []);
export const opNotificationsModule = angular.module('openproject.notifications', [
'openproject.config'
]);
// refactoring
angular.module('openproject.inplace-edit', []);
angular.module('openproject.responsive', []);

@ -65,7 +65,8 @@ function ConfigurationService($q, $http, $window, PathHelper, I18n) {
time_zone: '',
others: {
comments_sorting: 'asc',
warn_on_leaving_unsaved: true
warn_on_leaving_unsaved: true,
auto_hide_popups: false
}
}
};
@ -93,6 +94,9 @@ function ConfigurationService($q, $http, $window, PathHelper, I18n) {
warnOnLeavingUnsaved: function () {
return this.settings.user_preferences.others.warn_on_leaving_unsaved === true;
},
autoHidePopups: function () {
return this.settings.user_preferences.others.auto_hide_popups === true;
},
isTimezoneSet: function () {
return this.settings.user_preferences.time_zone !== '';
},

@ -75,9 +75,15 @@ describe('NotificationsService', function () {
});
});
it('should throw an Error if trying to create an upload with uploads = null', function () {
expect(function () {
NotificationsService.addWorkPackageUpload('themUploads', null);
}).to.throw(Error);
});
it('should throw an Error if trying to create an upload without uploads', function () {
expect(function () {
NotificationsService.addWorkPackageUpload('themUploads');
NotificationsService.addWorkPackageUpload('themUploads', []);
}).to.throw(Error);
});

@ -26,7 +26,9 @@
// See doc/COPYRIGHT.rdoc for more details.
// ++
function notifications($rootScope) {
import {opServicesModule} from '../../../angular-modules';
function NotificationsService($rootScope, $timeout, ConfigurationService) {
var createNotification = function (message) {
if (typeof message === 'string') {
return {message: message};
@ -39,17 +41,17 @@ function notifications($rootScope) {
createWarningNotification = function (message) {
return _.extend(createNotification(message), {type: 'warning'});
},
createErrorNotification = function (message, errors) {
createErrorNotification = function (message, errors : Array<any>) {
return _.extend(createNotification(message), {
type: 'error',
errors: errors || []
errors: errors
});
},
createNoticeNotification = function (message) {
return _.extend(createNotification(message), {type: ''});
},
createWorkPackageUploadNotification = function (message, uploads) {
if (!uploads) {
createWorkPackageUploadNotification = function (message, uploads : Array<any>) {
if (!uploads.length) {
throw new Error('Cannot create an upload notification without uploads!');
}
return _.extend(createNotification(message), {
@ -91,13 +93,16 @@ function notifications($rootScope) {
});
// public
var add = function (message) {
var add = function (message, timeoutAfter = 5000) {
var notification = createNotification(message);
broadcast('notification.add', notification);
notificationAdded(notification);
if (message.type === 'success' && ConfigurationService.autoHidePopups()) {
$timeout(() => remove(notification), timeoutAfter);
}
return notification;
},
addError = function (message, errors) {
addError = function (message, errors : Array<any> = []) {
return add(createErrorNotification(message, errors));
},
addWarning = function (message) {
@ -109,7 +114,7 @@ function notifications($rootScope) {
addNotice = function (message) {
return add(createNoticeNotification(message));
},
addWorkPackageUpload = function (message, uploads) {
addWorkPackageUpload = function (message, uploads : Array<any>) {
return add(createWorkPackageUploadNotification(message, uploads));
},
remove = function (notification) {
@ -127,6 +132,4 @@ function notifications($rootScope) {
};
}
angular
.module('openproject.services')
.factory('NotificationsService', notifications);
opServicesModule.factory('NotificationsService', NotificationsService);

@ -63,6 +63,7 @@ module API
property :warn_on_leaving_unsaved
property :comments_in_reverse_order,
as: :commentSortDescending
property :auto_hide_popups
property :impaired?,
as: :accessibilityMode

@ -174,6 +174,31 @@ describe MyController, type: :controller do
end
end
describe 'settings:auto_hide_popups' do
context 'with render_views' do
before do
as_logged_in_user user do
get :settings
end
end
render_views
it 'renders auto hide popups checkbox' do
expect(response.body).to have_selector('#my_account_form #pref_auto_hide_popups')
end
end
context 'PATCH' do
before do
as_logged_in_user user do
user.pref.auto_hide_popups = false
patch :settings, params: { user: { language: 'en' } }
end
end
end
end
describe 'account with disabled password login' do
before do
allow(OpenProject::Configuration).to receive(:disable_password_login?).and_return(true)

@ -48,6 +48,7 @@ describe ::API::V3::UserPreferences::UserPreferencesRepresenter do
it { is_expected.to have_json_path('theme') }
it { is_expected.to have_json_path('commentSortDescending') }
it { is_expected.to have_json_path('warnOnLeavingUnsaved') }
it { is_expected.to have_json_path('autoHidePopups') }
it { is_expected.to have_json_path('accessibilityMode') }
describe 'timeZone' do

@ -44,6 +44,10 @@ describe UserPreference do
it 'activates no self notification' do
expect(subject.others[:no_self_notified]).to be_truthy
end
it 'disables auto hide popups' do
expect(subject.auto_hide_popups).to eql(false)
end
end
shared_examples 'accepts real and false booleans' do |setter, getter|
@ -88,6 +92,12 @@ describe UserPreference do
:warn_on_leaving_unsaved?
end
describe 'auto hide popups' do
it_behaves_like 'accepts real and false booleans',
:auto_hide_popups=,
:auto_hide_popups?
end
describe 'time_zone' do
it 'allows to save short time zones' do
subject.time_zone = 'Berlin'
@ -160,14 +170,17 @@ describe UserPreference do
it 'will save the values on sending "save"' do
subject.save
value = !subject[:no_self_notified]
value_no_self_notified = !subject[:no_self_notified]
value_auto_hide_popups = !subject[:auto_hide_popups]
subject[:no_self_notified] = value
subject[:no_self_notified] = value_no_self_notified
subject[:auto_hide_popups] = value_auto_hide_popups
subject.save
subject.reload
expect(subject[:no_self_notified]).to eql(value)
expect(subject[:no_self_notified]).to eql(value_no_self_notified)
expect(subject[:auto_hide_popups]).to eql(value_auto_hide_popups)
end
end
end

Loading…
Cancel
Save