diff --git a/app/controllers/my_controller.rb b/app/controllers/my_controller.rb index 0547ab32894..8f5a4d33e00 100644 --- a/app/controllers/my_controller.rb +++ b/app/controllers/my_controller.rb @@ -46,6 +46,7 @@ class MyController < ApplicationController menu_item :password, only: [:password] menu_item :access_token, only: [:access_token] menu_item :notifications, only: [:notifications] + menu_item :reminders, only: [:reminders] def account; end @@ -81,23 +82,30 @@ class MyController < ApplicationController # Administer access tokens def access_token; end - # Configure user's mail notifications + # Configure user's in app notifications def notifications render html: '', layout: 'angular', locals: { menu_name: :my_menu } end + # Configure user's mail reminders + def reminders + render html: '', + layout: 'angular', + locals: { menu_name: :my_menu } + end + # Create a new feeds key def generate_rss_key - if request.post? - token = Token::RSS.create!(user: current_user) - flash[:info] = [ - t('my.access_token.notice_reset_token', type: 'RSS').html_safe, - content_tag(:strong, token.plain_value), - t('my.access_token.token_value_warning') - ] - end + token = Token::RSS.create!(user: current_user) + flash[:info] = [ + # rubocop:disable Rails/OutputSafety + t('my.access_token.notice_reset_token', type: 'RSS').html_safe, + # rubocop:enable Rails/OutputSafety + content_tag(:strong, token.plain_value), + t('my.access_token.token_value_warning') + ] rescue StandardError => e Rails.logger.error "Failed to reset user ##{current_user.id} RSS key: #{e}" flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message) @@ -107,14 +115,14 @@ class MyController < ApplicationController # Create a new API key def generate_api_key - if request.post? - token = Token::API.create!(user: current_user) - flash[:info] = [ - t('my.access_token.notice_reset_token', type: 'API').html_safe, - content_tag(:strong, token.plain_value), - t('my.access_token.token_value_warning') - ] - end + token = Token::API.create!(user: current_user) + flash[:info] = [ + # rubocop:disable Rails/OutputSafety + t('my.access_token.notice_reset_token', type: 'API').html_safe, + # rubocop:enable Rails/OutputSafety + content_tag(:strong, token.plain_value), + t('my.access_token.token_value_warning') + ] rescue StandardError => e Rails.logger.error "Failed to reset user ##{current_user.id} API key: #{e}" flash[:error] = t('my.access_token.failed_to_reset_token', error: e.message) diff --git a/config/initializers/menus.rb b/config/initializers/menus.rb index 46dd2450b18..b5e76a346af 100644 --- a/config/initializers/menus.rb +++ b/config/initializers/menus.rb @@ -137,6 +137,10 @@ Redmine::MenuManager.map :my_menu do |menu| { controller: '/my', action: 'notifications' }, caption: I18n.t('js.notifications.settings.title'), icon: 'icon2 icon-bell' + menu.push :reminders, + { controller: '/my', action: 'reminders' }, + caption: I18n.t('js.reminders.settings.title'), + icon: 'icon2 icon-email-alert' menu.push :delete_account, :delete_my_account_info_path, caption: I18n.t('account.delete'), diff --git a/config/locales/js-en.yml b/config/locales/js-en.yml index cef93364866..65a9478fa1a 100644 --- a/config/locales/js-en.yml +++ b/config/locales/js-en.yml @@ -627,6 +627,15 @@ en: autocompleter: label: 'Project autocompletion' + reminders: + settings: + daily: + title: 'Send me daily email reminders for unread notifications' + explanation: 'You will receive these reminders only for unread notifications and only at hours you specify.' + label: 'Time %{counter}' + title: 'Email reminders' + + text_are_you_sure: "Are you sure?" text_data_lost: "All entered data will be lost." diff --git a/config/routes.rb b/config/routes.rb index 42fb85a6fb3..ca4ea7e6a44 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -552,6 +552,7 @@ OpenProject::Application.routes.draw do get '/my/account', action: 'account' get '/my/settings', action: 'settings' get '/my/notifications', action: 'notifications' + get '/my/reminders', action: 'reminders' patch '/my/account', action: 'update_account' patch '/my/settings', action: 'update_settings' diff --git a/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html new file mode 100644 index 00000000000..5cb11bff6ba --- /dev/null +++ b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.html @@ -0,0 +1,19 @@ +
+

+
+ +
+
+

+ + + + +
+ +
diff --git a/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts new file mode 100644 index 00000000000..eacbe2f035c --- /dev/null +++ b/frontend/src/app/features/user-preferences/reminder-settings/page/reminder-settings-page.component.ts @@ -0,0 +1,55 @@ +import { + ChangeDetectionStrategy, Component, Input, OnInit, +} from '@angular/core'; +import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { CurrentUserService } from 'core-app/core/current-user/current-user.service'; +import { take } from 'rxjs/internal/operators/take'; +import { UIRouterGlobals } from '@uirouter/core'; +import { UserPreferencesService } from 'core-app/features/user-preferences/state/user-preferences.service'; +import { UserPreferencesQuery } from 'core-app/features/user-preferences/state/user-preferences.query'; + +export const myReminderPageComponentSelector = 'op-reminders-page'; + +@Component({ + selector: myReminderPageComponentSelector, + templateUrl: './reminder-settings-page.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReminderSettingsPageComponent implements OnInit { + @Input() userId:string; + + text = { + title: this.I18n.t('js.reminders.settings.title'), + save: this.I18n.t('js.button_save'), + daily: { + title: this.I18n.t('js.reminders.settings.daily.title'), + explanation: this.I18n.t('js.reminders.settings.daily.explanation'), + }, + }; + + constructor( + private I18n:I18nService, + private stateService:UserPreferencesService, + private query:UserPreferencesQuery, + private currentUserService:CurrentUserService, + private uiRouterGlobals:UIRouterGlobals, + ) { + } + + ngOnInit():void { + this.userId = this.userId || this.uiRouterGlobals.params.userId; + this + .currentUserService + .user$ + .pipe(take(1)) + .subscribe((user) => { + this.userId = this.userId || user.id!; + this.stateService.get(this.userId); + }); + } + + public saveChanges():void { + const prefs = this.query.getValue(); + this.stateService.update(this.userId, prefs); + } +} diff --git a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html new file mode 100644 index 00000000000..5b3e178fa6a --- /dev/null +++ b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.html @@ -0,0 +1,20 @@ + diff --git a/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts new file mode 100644 index 00000000000..72944ba93d5 --- /dev/null +++ b/frontend/src/app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component.ts @@ -0,0 +1,30 @@ +import { Component, Input, OnInit } from "@angular/core"; +import { ChangeDetectionStrategy } from "@angular/core"; +import { I18nService } from 'core-app/core/i18n/i18n.service'; +import { UserPreferencesStore } from 'core-app/features/user-preferences/state/user-preferences.store'; + +@Component({ + selector: 'op-reminder-settings-daily-time', + templateUrl: './reminder-settings-daily-time.component.html', + changeDetection: ChangeDetectionStrategy.OnPush, +}) +export class ReminderSettingsDailyTimeComponent implements OnInit { + public dailyReminderTimes = ["08:00", "12:00", "16:00"] + + text = { + label: (counter:number):string => this.I18n.t('js.reminders.settings.daily.label', { counter: counter }), + }; + + constructor( + private I18n:I18nService, + private store:UserPreferencesStore, + ) { + } + + ngOnInit():void { + + } + + public saveChanges():void { + } +} diff --git a/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts b/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts index c5c41633822..51a75249c0f 100644 --- a/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts +++ b/frontend/src/app/features/user-preferences/user-preferences.lazy-routes.ts @@ -39,4 +39,9 @@ export const MY_ACCOUNT_LAZY_ROUTES:Ng2StateDeclaration[] = [ url: '/users/:userId/edit/notifications', loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule), }, + { + name: 'my_reminders.**', + url: '/my/reminders', + loadChildren: () => import('./user-preferences.module').then((m) => m.OpenProjectMyAccountModule), + }, ]; diff --git a/frontend/src/app/features/user-preferences/user-preferences.module.ts b/frontend/src/app/features/user-preferences/user-preferences.module.ts index 3b31d787a96..b65caa0559e 100644 --- a/frontend/src/app/features/user-preferences/user-preferences.module.ts +++ b/frontend/src/app/features/user-preferences/user-preferences.module.ts @@ -13,6 +13,8 @@ import { NotificationSettingInlineCreateComponent } from 'core-app/features/user import { MY_ACCOUNT_ROUTES } from 'core-app/features/user-preferences/user-preferences.routes'; import { NotificationsSettingsToolbarComponent } from './notifications-settings/toolbar/notifications-settings-toolbar.component'; import { NotificationSettingsTableComponent } from './notifications-settings/table/notification-settings-table.component'; +import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component'; +import { ReminderSettingsDailyTimeComponent } from 'core-app/features/user-preferences/reminder-settings/reminder-time/reminder-settings-daily-time.component'; @NgModule({ providers: [ @@ -26,6 +28,8 @@ import { NotificationSettingsTableComponent } from './notifications-settings/tab NotificationSettingInlineCreateComponent, NotificationsSettingsToolbarComponent, NotificationSettingsTableComponent, + ReminderSettingsPageComponent, + ReminderSettingsDailyTimeComponent ], imports: [ CommonModule, diff --git a/frontend/src/app/features/user-preferences/user-preferences.routes.ts b/frontend/src/app/features/user-preferences/user-preferences.routes.ts index 19cd25e5776..9b6ec82df99 100644 --- a/frontend/src/app/features/user-preferences/user-preferences.routes.ts +++ b/frontend/src/app/features/user-preferences/user-preferences.routes.ts @@ -28,6 +28,7 @@ import { Ng2StateDeclaration } from '@uirouter/angular'; import { NotificationsSettingsPageComponent } from 'core-app/features/user-preferences/notifications-settings/page/notifications-settings-page.component'; +import { ReminderSettingsPageComponent } from './reminder-settings/page/reminder-settings-page.component'; export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [ { @@ -40,4 +41,9 @@ export const MY_ACCOUNT_ROUTES:Ng2StateDeclaration[] = [ url: '/users/:userId/edit/notifications', component: NotificationsSettingsPageComponent, }, + { + name: 'my_reminders', + url: '/my/reminders', + component: ReminderSettingsPageComponent, + }, ]; diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg index b21c0578ef4..8336ce11080 100644 --- a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg +++ b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.svg @@ -232,563 +232,566 @@ - + - - + - - + diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf index 5807107f7ed..9cb3ca62c0e 100644 Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.ttf differ diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff index bf4bfe152e8..23c0bf27a1f 100644 Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff differ diff --git a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 index b8ce478fb70..2a32699dc84 100644 Binary files a/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 and b/frontend/src/assets/fonts/openproject_icon/openproject-icon-font.woff2 differ diff --git a/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss b/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss index 840e9d15126..ed1ebced572 100644 --- a/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss +++ b/frontend/src/global_styles/fonts/_openproject_icon_definitions.scss @@ -448,1119 +448,1125 @@ .icon-edit:before { content: "\f14b"; } -@mixin icon-mixin-enterprise { +@mixin icon-mixin-email-alert { content: "\f14c"; } -.icon-enterprise:before { +.icon-email-alert:before { content: "\f14c"; } -@mixin icon-mixin-enumerations { +@mixin icon-mixin-enterprise { content: "\f14d"; } -.icon-enumerations:before { +.icon-enterprise:before { content: "\f14d"; } -@mixin icon-mixin-error { +@mixin icon-mixin-enumerations { content: "\f14e"; } -.icon-error:before { +.icon-enumerations:before { content: "\f14e"; } -@mixin icon-mixin-export-atom { +@mixin icon-mixin-error { content: "\f14f"; } -.icon-export-atom:before { +.icon-error:before { content: "\f14f"; } -@mixin icon-mixin-export-bcf { +@mixin icon-mixin-export-atom { content: "\f150"; } -.icon-export-bcf:before { +.icon-export-atom:before { content: "\f150"; } -@mixin icon-mixin-export-csv { +@mixin icon-mixin-export-bcf { content: "\f151"; } -.icon-export-csv:before { +.icon-export-bcf:before { content: "\f151"; } -@mixin icon-mixin-export-pdf-descr { +@mixin icon-mixin-export-csv { content: "\f152"; } -.icon-export-pdf-descr:before { +.icon-export-csv:before { content: "\f152"; } -@mixin icon-mixin-export-pdf-with-descriptions { +@mixin icon-mixin-export-pdf-descr { content: "\f153"; } -.icon-export-pdf-with-descriptions:before { +.icon-export-pdf-descr:before { content: "\f153"; } -@mixin icon-mixin-export-pdf { +@mixin icon-mixin-export-pdf-with-descriptions { content: "\f154"; } -.icon-export-pdf:before { +.icon-export-pdf-with-descriptions:before { content: "\f154"; } -@mixin icon-mixin-export-xls-descr { +@mixin icon-mixin-export-pdf { content: "\f155"; } -.icon-export-xls-descr:before { +.icon-export-pdf:before { content: "\f155"; } -@mixin icon-mixin-export-xls-with-descriptions { +@mixin icon-mixin-export-xls-descr { content: "\f156"; } -.icon-export-xls-with-descriptions:before { +.icon-export-xls-descr:before { content: "\f156"; } -@mixin icon-mixin-export-xls-with-relations { +@mixin icon-mixin-export-xls-with-descriptions { content: "\f157"; } -.icon-export-xls-with-relations:before { +.icon-export-xls-with-descriptions:before { content: "\f157"; } -@mixin icon-mixin-export-xls { +@mixin icon-mixin-export-xls-with-relations { content: "\f158"; } -.icon-export-xls:before { +.icon-export-xls-with-relations:before { content: "\f158"; } -@mixin icon-mixin-export { +@mixin icon-mixin-export-xls { content: "\f159"; } -.icon-export:before { +.icon-export-xls:before { content: "\f159"; } -@mixin icon-mixin-external-link { +@mixin icon-mixin-export { content: "\f15a"; } -.icon-external-link:before { +.icon-export:before { content: "\f15a"; } -@mixin icon-mixin-faq { +@mixin icon-mixin-external-link { content: "\f15b"; } -.icon-faq:before { +.icon-external-link:before { content: "\f15b"; } -@mixin icon-mixin-filter { +@mixin icon-mixin-faq { content: "\f15c"; } -.icon-filter:before { +.icon-faq:before { content: "\f15c"; } -@mixin icon-mixin-flag { +@mixin icon-mixin-filter { content: "\f15d"; } -.icon-flag:before { +.icon-filter:before { content: "\f15d"; } -@mixin icon-mixin-folder-add { +@mixin icon-mixin-flag { content: "\f15e"; } -.icon-folder-add:before { +.icon-flag:before { content: "\f15e"; } -@mixin icon-mixin-folder-locked { +@mixin icon-mixin-folder-add { content: "\f15f"; } -.icon-folder-locked:before { +.icon-folder-add:before { content: "\f15f"; } -@mixin icon-mixin-folder-open { +@mixin icon-mixin-folder-locked { content: "\f160"; } -.icon-folder-open:before { +.icon-folder-locked:before { content: "\f160"; } -@mixin icon-mixin-folder-remove { +@mixin icon-mixin-folder-open { content: "\f161"; } -.icon-folder-remove:before { +.icon-folder-open:before { content: "\f161"; } -@mixin icon-mixin-folder { +@mixin icon-mixin-folder-remove { content: "\f162"; } -.icon-folder:before { +.icon-folder-remove:before { content: "\f162"; } -@mixin icon-mixin-forums { +@mixin icon-mixin-folder { content: "\f163"; } -.icon-forums:before { +.icon-folder:before { content: "\f163"; } -@mixin icon-mixin-from-fullscreen { +@mixin icon-mixin-forums { content: "\f164"; } -.icon-from-fullscreen:before { +.icon-forums:before { content: "\f164"; } -@mixin icon-mixin-getting-started { +@mixin icon-mixin-from-fullscreen { content: "\f165"; } -.icon-getting-started:before { +.icon-from-fullscreen:before { content: "\f165"; } -@mixin icon-mixin-glossar { +@mixin icon-mixin-getting-started { content: "\f166"; } -.icon-glossar:before { +.icon-getting-started:before { content: "\f166"; } -@mixin icon-mixin-google-plus { +@mixin icon-mixin-glossar { content: "\f167"; } -.icon-google-plus:before { +.icon-glossar:before { content: "\f167"; } -@mixin icon-mixin-group-by { +@mixin icon-mixin-google-plus { content: "\f168"; } -.icon-group-by:before { +.icon-google-plus:before { content: "\f168"; } -@mixin icon-mixin-group { +@mixin icon-mixin-group-by { content: "\f169"; } -.icon-group:before { +.icon-group-by:before { content: "\f169"; } -@mixin icon-mixin-hamburger { +@mixin icon-mixin-group { content: "\f16a"; } -.icon-hamburger:before { +.icon-group:before { content: "\f16a"; } -@mixin icon-mixin-headline1 { +@mixin icon-mixin-hamburger { content: "\f16b"; } -.icon-headline1:before { +.icon-hamburger:before { content: "\f16b"; } -@mixin icon-mixin-headline2 { +@mixin icon-mixin-headline1 { content: "\f16c"; } -.icon-headline2:before { +.icon-headline1:before { content: "\f16c"; } -@mixin icon-mixin-headline3 { +@mixin icon-mixin-headline2 { content: "\f16d"; } -.icon-headline3:before { +.icon-headline2:before { content: "\f16d"; } -@mixin icon-mixin-headset { +@mixin icon-mixin-headline3 { content: "\f16e"; } -.icon-headset:before { +.icon-headline3:before { content: "\f16e"; } -@mixin icon-mixin-help { +@mixin icon-mixin-headset { content: "\f16f"; } -.icon-help:before { +.icon-headset:before { content: "\f16f"; } -@mixin icon-mixin-help1 { +@mixin icon-mixin-help { content: "\f170"; } -.icon-help1:before { +.icon-help:before { content: "\f170"; } -@mixin icon-mixin-help2 { +@mixin icon-mixin-help1 { content: "\f171"; } -.icon-help2:before { +.icon-help1:before { content: "\f171"; } -@mixin icon-mixin-hierarchy { +@mixin icon-mixin-help2 { content: "\f172"; } -.icon-hierarchy:before { +.icon-help2:before { content: "\f172"; } -@mixin icon-mixin-home { +@mixin icon-mixin-hierarchy { content: "\f173"; } -.icon-home:before { +.icon-hierarchy:before { content: "\f173"; } -@mixin icon-mixin-hosting { +@mixin icon-mixin-home { content: "\f174"; } -.icon-hosting:before { +.icon-home:before { content: "\f174"; } -@mixin icon-mixin-ifc { +@mixin icon-mixin-hosting { content: "\f175"; } -.icon-ifc:before { +.icon-hosting:before { content: "\f175"; } -@mixin icon-mixin-image1 { +@mixin icon-mixin-ifc { content: "\f176"; } -.icon-image1:before { +.icon-ifc:before { content: "\f176"; } -@mixin icon-mixin-image2 { +@mixin icon-mixin-image1 { content: "\f177"; } -.icon-image2:before { +.icon-image1:before { content: "\f177"; } -@mixin icon-mixin-import { +@mixin icon-mixin-image2 { content: "\f178"; } -.icon-import:before { +.icon-image2:before { content: "\f178"; } -@mixin icon-mixin-info1 { +@mixin icon-mixin-import { content: "\f179"; } -.icon-info1:before { +.icon-import:before { content: "\f179"; } -@mixin icon-mixin-info2 { +@mixin icon-mixin-info1 { content: "\f17a"; } -.icon-info2:before { +.icon-info1:before { content: "\f17a"; } -@mixin icon-mixin-input-disabled { +@mixin icon-mixin-info2 { content: "\f17b"; } -.icon-input-disabled:before { +.icon-info2:before { content: "\f17b"; } -@mixin icon-mixin-installation-services { +@mixin icon-mixin-input-disabled { content: "\f17c"; } -.icon-installation-services:before { +.icon-input-disabled:before { content: "\f17c"; } -@mixin icon-mixin-italic { +@mixin icon-mixin-installation-services { content: "\f17d"; } -.icon-italic:before { +.icon-installation-services:before { content: "\f17d"; } -@mixin icon-mixin-key { +@mixin icon-mixin-italic { content: "\f17e"; } -.icon-key:before { +.icon-italic:before { content: "\f17e"; } -@mixin icon-mixin-link { +@mixin icon-mixin-key { content: "\f17f"; } -.icon-link:before { +.icon-key:before { content: "\f17f"; } -@mixin icon-mixin-loading1 { +@mixin icon-mixin-link { content: "\f180"; } -.icon-loading1:before { +.icon-link:before { content: "\f180"; } -@mixin icon-mixin-loading2 { +@mixin icon-mixin-loading1 { content: "\f181"; } -.icon-loading2:before { +.icon-loading1:before { content: "\f181"; } -@mixin icon-mixin-location { +@mixin icon-mixin-loading2 { content: "\f182"; } -.icon-location:before { +.icon-loading2:before { content: "\f182"; } -@mixin icon-mixin-locked { +@mixin icon-mixin-location { content: "\f183"; } -.icon-locked:before { +.icon-location:before { content: "\f183"; } -@mixin icon-mixin-logout { +@mixin icon-mixin-locked { content: "\f184"; } -.icon-logout:before { +.icon-locked:before { content: "\f184"; } -@mixin icon-mixin-mail1 { +@mixin icon-mixin-logout { content: "\f185"; } -.icon-mail1:before { +.icon-logout:before { content: "\f185"; } -@mixin icon-mixin-mail2 { +@mixin icon-mixin-mail1 { content: "\f186"; } -.icon-mail2:before { +.icon-mail1:before { content: "\f186"; } -@mixin icon-mixin-maintenance-support { +@mixin icon-mixin-mail2 { content: "\f187"; } -.icon-maintenance-support:before { +.icon-mail2:before { content: "\f187"; } -@mixin icon-mixin-mark-all-read { +@mixin icon-mixin-maintenance-support { content: "\f188"; } -.icon-mark-all-read:before { +.icon-maintenance-support:before { content: "\f188"; } -@mixin icon-mixin-mark-read { +@mixin icon-mixin-mark-all-read { content: "\f189"; } -.icon-mark-read:before { +.icon-mark-all-read:before { content: "\f189"; } -@mixin icon-mixin-meetings { +@mixin icon-mixin-mark-read { content: "\f18a"; } -.icon-meetings:before { +.icon-mark-read:before { content: "\f18a"; } -@mixin icon-mixin-menu { +@mixin icon-mixin-meetings { content: "\f18b"; } -.icon-menu:before { +.icon-meetings:before { content: "\f18b"; } -@mixin icon-mixin-merge-branch { +@mixin icon-mixin-menu { content: "\f18c"; } -.icon-merge-branch:before { +.icon-menu:before { content: "\f18c"; } -@mixin icon-mixin-microphone { +@mixin icon-mixin-merge-branch { content: "\f18d"; } -.icon-microphone:before { +.icon-merge-branch:before { content: "\f18d"; } -@mixin icon-mixin-milestone { +@mixin icon-mixin-microphone { content: "\f18e"; } -.icon-milestone:before { +.icon-microphone:before { content: "\f18e"; } -@mixin icon-mixin-minus1 { +@mixin icon-mixin-milestone { content: "\f18f"; } -.icon-minus1:before { +.icon-milestone:before { content: "\f18f"; } -@mixin icon-mixin-minus2 { +@mixin icon-mixin-minus1 { content: "\f190"; } -.icon-minus2:before { +.icon-minus1:before { content: "\f190"; } -@mixin icon-mixin-mobile { +@mixin icon-mixin-minus2 { content: "\f191"; } -.icon-mobile:before { +.icon-minus2:before { content: "\f191"; } -@mixin icon-mixin-modules { +@mixin icon-mixin-mobile { content: "\f192"; } -.icon-modules:before { +.icon-mobile:before { content: "\f192"; } -@mixin icon-mixin-more { +@mixin icon-mixin-modules { content: "\f193"; } -.icon-more:before { +.icon-modules:before { content: "\f193"; } -@mixin icon-mixin-move { +@mixin icon-mixin-more { content: "\f194"; } -.icon-move:before { +.icon-more:before { content: "\f194"; } -@mixin icon-mixin-movie { +@mixin icon-mixin-move { content: "\f195"; } -.icon-movie:before { +.icon-move:before { content: "\f195"; } -@mixin icon-mixin-music { +@mixin icon-mixin-movie { content: "\f196"; } -.icon-music:before { +.icon-movie:before { content: "\f196"; } -@mixin icon-mixin-new-planning-element { +@mixin icon-mixin-music { content: "\f197"; } -.icon-new-planning-element:before { +.icon-music:before { content: "\f197"; } -@mixin icon-mixin-news { +@mixin icon-mixin-new-planning-element { content: "\f198"; } -.icon-news:before { +.icon-new-planning-element:before { content: "\f198"; } -@mixin icon-mixin-no-hierarchy { +@mixin icon-mixin-news { content: "\f199"; } -.icon-no-hierarchy:before { +.icon-news:before { content: "\f199"; } -@mixin icon-mixin-no-zen-mode { +@mixin icon-mixin-no-hierarchy { content: "\f19a"; } -.icon-no-zen-mode:before { +.icon-no-hierarchy:before { content: "\f19a"; } -@mixin icon-mixin-not-supported { +@mixin icon-mixin-no-zen-mode { content: "\f19b"; } -.icon-not-supported:before { +.icon-no-zen-mode:before { content: "\f19b"; } -@mixin icon-mixin-notes { +@mixin icon-mixin-not-supported { content: "\f19c"; } -.icon-notes:before { +.icon-not-supported:before { content: "\f19c"; } -@mixin icon-mixin-openid { +@mixin icon-mixin-notes { content: "\f19d"; } -.icon-openid:before { +.icon-notes:before { content: "\f19d"; } -@mixin icon-mixin-openproject { +@mixin icon-mixin-openid { content: "\f19e"; } -.icon-openproject:before { +.icon-openid:before { content: "\f19e"; } -@mixin icon-mixin-ordered-list { +@mixin icon-mixin-openproject { content: "\f19f"; } -.icon-ordered-list:before { +.icon-openproject:before { content: "\f19f"; } -@mixin icon-mixin-outline { +@mixin icon-mixin-ordered-list { content: "\f1a0"; } -.icon-outline:before { +.icon-ordered-list:before { content: "\f1a0"; } -@mixin icon-mixin-paragraph-left { +@mixin icon-mixin-outline { content: "\f1a1"; } -.icon-paragraph-left:before { +.icon-outline:before { content: "\f1a1"; } -@mixin icon-mixin-paragraph-right { +@mixin icon-mixin-paragraph-left { content: "\f1a2"; } -.icon-paragraph-right:before { +.icon-paragraph-left:before { content: "\f1a2"; } -@mixin icon-mixin-paragraph { +@mixin icon-mixin-paragraph-right { content: "\f1a3"; } -.icon-paragraph:before { +.icon-paragraph-right:before { content: "\f1a3"; } -@mixin icon-mixin-payment-history { +@mixin icon-mixin-paragraph { content: "\f1a4"; } -.icon-payment-history:before { +.icon-paragraph:before { content: "\f1a4"; } -@mixin icon-mixin-phone { +@mixin icon-mixin-payment-history { content: "\f1a5"; } -.icon-phone:before { +.icon-payment-history:before { content: "\f1a5"; } -@mixin icon-mixin-pin { +@mixin icon-mixin-phone { content: "\f1a6"; } -.icon-pin:before { +.icon-phone:before { content: "\f1a6"; } -@mixin icon-mixin-play { +@mixin icon-mixin-pin { content: "\f1a7"; } -.icon-play:before { +.icon-pin:before { content: "\f1a7"; } -@mixin icon-mixin-plugins { +@mixin icon-mixin-play { content: "\f1a8"; } -.icon-plugins:before { +.icon-play:before { content: "\f1a8"; } -@mixin icon-mixin-plus { +@mixin icon-mixin-plugins { content: "\f1a9"; } -.icon-plus:before { +.icon-plugins:before { content: "\f1a9"; } -@mixin icon-mixin-pre { +@mixin icon-mixin-plus { content: "\f1aa"; } -.icon-pre:before { +.icon-plus:before { content: "\f1aa"; } -@mixin icon-mixin-presentation { +@mixin icon-mixin-pre { content: "\f1ab"; } -.icon-presentation:before { +.icon-pre:before { content: "\f1ab"; } -@mixin icon-mixin-preview { +@mixin icon-mixin-presentation { content: "\f1ac"; } -.icon-preview:before { +.icon-presentation:before { content: "\f1ac"; } -@mixin icon-mixin-print { +@mixin icon-mixin-preview { content: "\f1ad"; } -.icon-print:before { +.icon-preview:before { content: "\f1ad"; } -@mixin icon-mixin-priority { +@mixin icon-mixin-print { content: "\f1ae"; } -.icon-priority:before { +.icon-print:before { content: "\f1ae"; } -@mixin icon-mixin-project-types { +@mixin icon-mixin-priority { content: "\f1af"; } -.icon-project-types:before { +.icon-priority:before { content: "\f1af"; } -@mixin icon-mixin-projects { +@mixin icon-mixin-project-types { content: "\f1b0"; } -.icon-projects:before { +.icon-project-types:before { content: "\f1b0"; } -@mixin icon-mixin-publish { +@mixin icon-mixin-projects { content: "\f1b1"; } -.icon-publish:before { +.icon-projects:before { content: "\f1b1"; } -@mixin icon-mixin-pulldown-up { +@mixin icon-mixin-publish { content: "\f1b2"; } -.icon-pulldown-up:before { +.icon-publish:before { content: "\f1b2"; } -@mixin icon-mixin-pulldown { +@mixin icon-mixin-pulldown-up { content: "\f1b3"; } -.icon-pulldown:before { +.icon-pulldown-up:before { content: "\f1b3"; } -@mixin icon-mixin-quote { +@mixin icon-mixin-pulldown { content: "\f1b4"; } -.icon-quote:before { +.icon-pulldown:before { content: "\f1b4"; } -@mixin icon-mixin-quote2 { +@mixin icon-mixin-quote { content: "\f1b5"; } -.icon-quote2:before { +.icon-quote:before { content: "\f1b5"; } -@mixin icon-mixin-redo { +@mixin icon-mixin-quote2 { content: "\f1b6"; } -.icon-redo:before { +.icon-quote2:before { content: "\f1b6"; } -@mixin icon-mixin-relation-follows { +@mixin icon-mixin-redo { content: "\f1b7"; } -.icon-relation-follows:before { +.icon-redo:before { content: "\f1b7"; } -@mixin icon-mixin-relation-new-child { +@mixin icon-mixin-relation-follows { content: "\f1b8"; } -.icon-relation-new-child:before { +.icon-relation-follows:before { content: "\f1b8"; } -@mixin icon-mixin-relation-precedes { +@mixin icon-mixin-relation-new-child { content: "\f1b9"; } -.icon-relation-precedes:before { +.icon-relation-new-child:before { content: "\f1b9"; } -@mixin icon-mixin-relations { +@mixin icon-mixin-relation-precedes { content: "\f1ba"; } -.icon-relations:before { +.icon-relation-precedes:before { content: "\f1ba"; } -@mixin icon-mixin-reload { +@mixin icon-mixin-relations { content: "\f1bb"; } -.icon-reload:before { +.icon-relations:before { content: "\f1bb"; } -@mixin icon-mixin-reminder { +@mixin icon-mixin-reload { content: "\f1bc"; } -.icon-reminder:before { +.icon-reload:before { content: "\f1bc"; } -@mixin icon-mixin-remove { +@mixin icon-mixin-reminder { content: "\f1bd"; } -.icon-remove:before { +.icon-reminder:before { content: "\f1bd"; } -@mixin icon-mixin-rename { +@mixin icon-mixin-remove { content: "\f1be"; } -.icon-rename:before { +.icon-remove:before { content: "\f1be"; } -@mixin icon-mixin-reported-by-me { +@mixin icon-mixin-rename { content: "\f1bf"; } -.icon-reported-by-me:before { +.icon-rename:before { content: "\f1bf"; } -@mixin icon-mixin-resizer-bottom-right { +@mixin icon-mixin-reported-by-me { content: "\f1c0"; } -.icon-resizer-bottom-right:before { +.icon-reported-by-me:before { content: "\f1c0"; } -@mixin icon-mixin-resizer-vertical-lines { +@mixin icon-mixin-resizer-bottom-right { content: "\f1c1"; } -.icon-resizer-vertical-lines:before { +.icon-resizer-bottom-right:before { content: "\f1c1"; } -@mixin icon-mixin-roadmap { +@mixin icon-mixin-resizer-vertical-lines { content: "\f1c2"; } -.icon-roadmap:before { +.icon-resizer-vertical-lines:before { content: "\f1c2"; } -@mixin icon-mixin-rss { +@mixin icon-mixin-roadmap { content: "\f1c3"; } -.icon-rss:before { +.icon-roadmap:before { content: "\f1c3"; } -@mixin icon-mixin-rubber { +@mixin icon-mixin-rss { content: "\f1c4"; } -.icon-rubber:before { +.icon-rss:before { content: "\f1c4"; } -@mixin icon-mixin-save { +@mixin icon-mixin-rubber { content: "\f1c5"; } -.icon-save:before { +.icon-rubber:before { content: "\f1c5"; } -@mixin icon-mixin-search { +@mixin icon-mixin-save { content: "\f1c6"; } -.icon-search:before { +.icon-save:before { content: "\f1c6"; } -@mixin icon-mixin-send-mail { +@mixin icon-mixin-search { content: "\f1c7"; } -.icon-send-mail:before { +.icon-search:before { content: "\f1c7"; } -@mixin icon-mixin-server-key { +@mixin icon-mixin-send-mail { content: "\f1c8"; } -.icon-server-key:before { +.icon-send-mail:before { content: "\f1c8"; } -@mixin icon-mixin-settings { +@mixin icon-mixin-server-key { content: "\f1c9"; } -.icon-settings:before { +.icon-server-key:before { content: "\f1c9"; } -@mixin icon-mixin-settings2 { +@mixin icon-mixin-settings { content: "\f1ca"; } -.icon-settings2:before { +.icon-settings:before { content: "\f1ca"; } -@mixin icon-mixin-settings3 { +@mixin icon-mixin-settings2 { content: "\f1cb"; } -.icon-settings3:before { +.icon-settings2:before { content: "\f1cb"; } -@mixin icon-mixin-settings4 { +@mixin icon-mixin-settings3 { content: "\f1cc"; } -.icon-settings4:before { +.icon-settings3:before { content: "\f1cc"; } -@mixin icon-mixin-shortcuts { +@mixin icon-mixin-settings4 { content: "\f1cd"; } -.icon-shortcuts:before { +.icon-settings4:before { content: "\f1cd"; } -@mixin icon-mixin-show-all-projects { +@mixin icon-mixin-shortcuts { content: "\f1ce"; } -.icon-show-all-projects:before { +.icon-shortcuts:before { content: "\f1ce"; } -@mixin icon-mixin-show-more-horizontal { +@mixin icon-mixin-show-all-projects { content: "\f1cf"; } -.icon-show-more-horizontal:before { +.icon-show-all-projects:before { content: "\f1cf"; } -@mixin icon-mixin-show-more { +@mixin icon-mixin-show-more-horizontal { content: "\f1d0"; } -.icon-show-more:before { +.icon-show-more-horizontal:before { content: "\f1d0"; } -@mixin icon-mixin-slack { +@mixin icon-mixin-show-more { content: "\f1d1"; } -.icon-slack:before { +.icon-show-more:before { content: "\f1d1"; } -@mixin icon-mixin-sort-ascending { +@mixin icon-mixin-slack { content: "\f1d2"; } -.icon-sort-ascending:before { +.icon-slack:before { content: "\f1d2"; } -@mixin icon-mixin-sort-by { +@mixin icon-mixin-sort-ascending { content: "\f1d3"; } -.icon-sort-by:before { +.icon-sort-ascending:before { content: "\f1d3"; } -@mixin icon-mixin-sort-descending { +@mixin icon-mixin-sort-by { content: "\f1d4"; } -.icon-sort-descending:before { +.icon-sort-by:before { content: "\f1d4"; } -@mixin icon-mixin-sort-down { +@mixin icon-mixin-sort-descending { content: "\f1d5"; } -.icon-sort-down:before { +.icon-sort-descending:before { content: "\f1d5"; } -@mixin icon-mixin-sort-up { +@mixin icon-mixin-sort-down { content: "\f1d6"; } -.icon-sort-up:before { +.icon-sort-down:before { content: "\f1d6"; } -@mixin icon-mixin-square { +@mixin icon-mixin-sort-up { content: "\f1d7"; } -.icon-square:before { +.icon-sort-up:before { content: "\f1d7"; } -@mixin icon-mixin-star { +@mixin icon-mixin-square { content: "\f1d8"; } -.icon-star:before { +.icon-square:before { content: "\f1d8"; } -@mixin icon-mixin-status-reporting { +@mixin icon-mixin-star { content: "\f1d9"; } -.icon-status-reporting:before { +.icon-star:before { content: "\f1d9"; } -@mixin icon-mixin-status { +@mixin icon-mixin-status-reporting { content: "\f1da"; } -.icon-status:before { +.icon-status-reporting:before { content: "\f1da"; } -@mixin icon-mixin-strike-through { +@mixin icon-mixin-status { content: "\f1db"; } -.icon-strike-through:before { +.icon-status:before { content: "\f1db"; } -@mixin icon-mixin-text { +@mixin icon-mixin-strike-through { content: "\f1dc"; } -.icon-text:before { +.icon-strike-through:before { content: "\f1dc"; } -@mixin icon-mixin-ticket-checked { +@mixin icon-mixin-text { content: "\f1dd"; } -.icon-ticket-checked:before { +.icon-text:before { content: "\f1dd"; } -@mixin icon-mixin-ticket-down { +@mixin icon-mixin-ticket-checked { content: "\f1de"; } -.icon-ticket-down:before { +.icon-ticket-checked:before { content: "\f1de"; } -@mixin icon-mixin-ticket-edit { +@mixin icon-mixin-ticket-down { content: "\f1df"; } -.icon-ticket-edit:before { +.icon-ticket-down:before { content: "\f1df"; } -@mixin icon-mixin-ticket-minus { +@mixin icon-mixin-ticket-edit { content: "\f1e0"; } -.icon-ticket-minus:before { +.icon-ticket-edit:before { content: "\f1e0"; } -@mixin icon-mixin-ticket-note { +@mixin icon-mixin-ticket-minus { content: "\f1e1"; } -.icon-ticket-note:before { +.icon-ticket-minus:before { content: "\f1e1"; } -@mixin icon-mixin-ticket { +@mixin icon-mixin-ticket-note { content: "\f1e2"; } -.icon-ticket:before { +.icon-ticket-note:before { content: "\f1e2"; } -@mixin icon-mixin-time { +@mixin icon-mixin-ticket { content: "\f1e3"; } -.icon-time:before { +.icon-ticket:before { content: "\f1e3"; } -@mixin icon-mixin-to-fullscreen { +@mixin icon-mixin-time { content: "\f1e4"; } -.icon-to-fullscreen:before { +.icon-time:before { content: "\f1e4"; } -@mixin icon-mixin-training-consulting { +@mixin icon-mixin-to-fullscreen { content: "\f1e5"; } -.icon-training-consulting:before { +.icon-to-fullscreen:before { content: "\f1e5"; } -@mixin icon-mixin-two-factor-authentication { +@mixin icon-mixin-training-consulting { content: "\f1e6"; } -.icon-two-factor-authentication:before { +.icon-training-consulting:before { content: "\f1e6"; } -@mixin icon-mixin-types { +@mixin icon-mixin-two-factor-authentication { content: "\f1e7"; } -.icon-types:before { +.icon-two-factor-authentication:before { content: "\f1e7"; } -@mixin icon-mixin-underline { +@mixin icon-mixin-types { content: "\f1e8"; } -.icon-underline:before { +.icon-types:before { content: "\f1e8"; } -@mixin icon-mixin-undo { +@mixin icon-mixin-underline { content: "\f1e9"; } -.icon-undo:before { +.icon-underline:before { content: "\f1e9"; } -@mixin icon-mixin-unit { +@mixin icon-mixin-undo { content: "\f1ea"; } -.icon-unit:before { +.icon-undo:before { content: "\f1ea"; } -@mixin icon-mixin-unlocked { +@mixin icon-mixin-unit { content: "\f1eb"; } -.icon-unlocked:before { +.icon-unit:before { content: "\f1eb"; } -@mixin icon-mixin-unordered-list { +@mixin icon-mixin-unlocked { content: "\f1ec"; } -.icon-unordered-list:before { +.icon-unlocked:before { content: "\f1ec"; } -@mixin icon-mixin-unwatched { +@mixin icon-mixin-unordered-list { content: "\f1ed"; } -.icon-unwatched:before { +.icon-unordered-list:before { content: "\f1ed"; } -@mixin icon-mixin-upload { +@mixin icon-mixin-unwatched { content: "\f1ee"; } -.icon-upload:before { +.icon-unwatched:before { content: "\f1ee"; } -@mixin icon-mixin-user-minus { +@mixin icon-mixin-upload { content: "\f1ef"; } -.icon-user-minus:before { +.icon-upload:before { content: "\f1ef"; } -@mixin icon-mixin-user-plus { +@mixin icon-mixin-user-minus { content: "\f1f0"; } -.icon-user-plus:before { +.icon-user-minus:before { content: "\f1f0"; } -@mixin icon-mixin-user { +@mixin icon-mixin-user-plus { content: "\f1f1"; } -.icon-user:before { +.icon-user-plus:before { content: "\f1f1"; } -@mixin icon-mixin-view-card { +@mixin icon-mixin-user { content: "\f1f2"; } -.icon-view-card:before { +.icon-user:before { content: "\f1f2"; } -@mixin icon-mixin-view-fullscreen { +@mixin icon-mixin-view-card { content: "\f1f3"; } -.icon-view-fullscreen:before { +.icon-view-card:before { content: "\f1f3"; } -@mixin icon-mixin-view-list { +@mixin icon-mixin-view-fullscreen { content: "\f1f4"; } -.icon-view-list:before { +.icon-view-fullscreen:before { content: "\f1f4"; } -@mixin icon-mixin-view-model { +@mixin icon-mixin-view-list { content: "\f1f5"; } -.icon-view-model:before { +.icon-view-list:before { content: "\f1f5"; } -@mixin icon-mixin-view-split-viewer-table { +@mixin icon-mixin-view-model { content: "\f1f6"; } -.icon-view-split-viewer-table:before { +.icon-view-model:before { content: "\f1f6"; } -@mixin icon-mixin-view-split { +@mixin icon-mixin-view-split-viewer-table { content: "\f1f7"; } -.icon-view-split:before { +.icon-view-split-viewer-table:before { content: "\f1f7"; } -@mixin icon-mixin-view-split2 { +@mixin icon-mixin-view-split { content: "\f1f8"; } -.icon-view-split2:before { +.icon-view-split:before { content: "\f1f8"; } -@mixin icon-mixin-view-timeline { +@mixin icon-mixin-view-split2 { content: "\f1f9"; } -.icon-view-timeline:before { +.icon-view-split2:before { content: "\f1f9"; } -@mixin icon-mixin-warning { +@mixin icon-mixin-view-timeline { content: "\f1fa"; } -.icon-warning:before { +.icon-view-timeline:before { content: "\f1fa"; } -@mixin icon-mixin-watched { +@mixin icon-mixin-warning { content: "\f1fb"; } -.icon-watched:before { +.icon-warning:before { content: "\f1fb"; } -@mixin icon-mixin-wiki-edit { +@mixin icon-mixin-watched { content: "\f1fc"; } -.icon-wiki-edit:before { +.icon-watched:before { content: "\f1fc"; } -@mixin icon-mixin-wiki { +@mixin icon-mixin-wiki-edit { content: "\f1fd"; } -.icon-wiki:before { +.icon-wiki-edit:before { content: "\f1fd"; } -@mixin icon-mixin-wiki2 { +@mixin icon-mixin-wiki { content: "\f1fe"; } -.icon-wiki2:before { +.icon-wiki:before { content: "\f1fe"; } -@mixin icon-mixin-work-packages { +@mixin icon-mixin-wiki2 { content: "\f1ff"; } -.icon-work-packages:before { +.icon-wiki2:before { content: "\f1ff"; } -@mixin icon-mixin-workflow { +@mixin icon-mixin-work-packages { content: "\f200"; } -.icon-workflow:before { +.icon-work-packages:before { content: "\f200"; } -@mixin icon-mixin-yes { +@mixin icon-mixin-workflow { content: "\f201"; } -.icon-yes:before { +.icon-workflow:before { content: "\f201"; } -@mixin icon-mixin-zen-mode { +@mixin icon-mixin-yes { content: "\f202"; } -.icon-zen-mode:before { +.icon-yes:before { content: "\f202"; } -@mixin icon-mixin-zoom-auto { +@mixin icon-mixin-zen-mode { content: "\f203"; } -.icon-zoom-auto:before { +.icon-zen-mode:before { content: "\f203"; } -@mixin icon-mixin-zoom-in { +@mixin icon-mixin-zoom-auto { content: "\f204"; } -.icon-zoom-in:before { +.icon-zoom-auto:before { content: "\f204"; } -@mixin icon-mixin-zoom-out { +@mixin icon-mixin-zoom-in { content: "\f205"; } -.icon-zoom-out:before { +.icon-zoom-in:before { content: "\f205"; } +@mixin icon-mixin-zoom-out { + content: "\f206"; +} +.icon-zoom-out:before { + content: "\f206"; +} diff --git a/frontend/src/global_styles/fonts/_openproject_icon_font.lsg b/frontend/src/global_styles/fonts/_openproject_icon_font.lsg index 85800e8208b..699aa85cc49 100644 --- a/frontend/src/global_styles/fonts/_openproject_icon_font.lsg +++ b/frontend/src/global_styles/fonts/_openproject_icon_font.lsg @@ -78,6 +78,7 @@
  • drag-handle
  • duplicate
  • edit
  • +
  • email-alert
  • enterprise
  • enumerations
  • error
  • diff --git a/spec/features/notifications/digest_mail_spec.rb b/spec/features/notifications/digest_mail_spec.rb index 4672736f649..50ac533594a 100644 --- a/spec/features/notifications/digest_mail_spec.rb +++ b/spec/features/notifications/digest_mail_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' require 'support/pages/my/notifications' +# TODO: This feature spec is to be replaced by the reminder_mail_spec.rb in the same directory. describe "Digest email", type: :feature, js: true do let!(:project) { FactoryBot.create :project, members: { current_user => role } } let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } } diff --git a/spec/features/notifications/reminder_mail_spec.rb b/spec/features/notifications/reminder_mail_spec.rb new file mode 100644 index 00000000000..e33935e933c --- /dev/null +++ b/spec/features/notifications/reminder_mail_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' +require 'support/pages/my/notifications' + +describe "Reminder email", type: :feature, js: true do + let!(:project) { FactoryBot.create :project, members: { current_user => role } } + let!(:mute_project) { FactoryBot.create :project, members: { current_user => role } } + let(:reminders_settings_page) { Pages::My::Reminders.new(current_user) } + let(:role) { FactoryBot.create(:role, permissions: %i[view_work_packages]) } + let(:other_user) { FactoryBot.create(:user) } + let(:work_package) { FactoryBot.create(:work_package, project: project) } + let(:watched_work_package) { FactoryBot.create(:work_package, project: project, watcher_users: [current_user]) } + let(:involved_work_package) { FactoryBot.create(:work_package, project: project, assigned_to: current_user) } + + current_user do + FactoryBot.create :user, + notification_settings: [ + FactoryBot.build(:mail_notification_setting, + involved: false, + watched: false, + mentioned: false, + work_package_commented: false, + work_package_created: false, + work_package_processed: false, + work_package_prioritized: false, + work_package_scheduled: false, + all: false), + FactoryBot.build(:in_app_notification_setting, + involved: false, + watched: false, + mentioned: false, + work_package_commented: false, + work_package_created: false, + work_package_processed: false, + work_package_prioritized: false, + work_package_scheduled: false, + all: false), + FactoryBot.build(:mail_digest_notification_setting, + involved: true, + watched: true, + mentioned: true, + work_package_commented: true, + work_package_created: true, + work_package_processed: true, + work_package_prioritized: true, + work_package_scheduled: true, + all: false) + ] + end + + before do + watched_work_package + work_package + involved_work_package + + ActiveJob::Base.queue_adapter.enqueued_jobs.clear + end + + it 'sends a reminder mail based on the configuration', with_settings: { journal_aggregation_time_minutes: 0 } do + # Configure the digest + reminders_settings_page.visit! + + # By default a reminder timed for 8:00 should be configured + reminders_settings_page.expect_active_daily_times("08:00") + end +end diff --git a/spec/routing/my_spec.rb b/spec/routing/my_spec.rb index 110c13ed16e..be6c3526897 100644 --- a/spec/routing/my_spec.rb +++ b/spec/routing/my_spec.rb @@ -45,6 +45,14 @@ describe 'my routes', type: :routing do expect(patch('/my/settings')).to route_to('my#update_settings') end + it '/my/notifications GET routes to my#notifications' do + expect(get('/my/notifications')).to route_to('my#notifications') + end + + it '/my/reminders GET routes to my#notifications' do + expect(get('/my/reminders')).to route_to('my#reminders') + end + it '/my/generate_rss_key POST routes to my#generate_rss_key' do expect(post('/my/generate_rss_key')).to route_to('my#generate_rss_key') end @@ -53,8 +61,8 @@ describe 'my routes', type: :routing do expect(post('/my/generate_api_key')).to route_to('my#generate_api_key') end - it { + it '/my/deletion_info GET routes to users#deletion_info' do expect(get('/my/deletion_info')).to route_to(controller: 'users', action: 'deletion_info') - } + end end diff --git a/spec/support/pages/my/reminders.rb b/spec/support/pages/my/reminders.rb new file mode 100644 index 00000000000..13daca47d69 --- /dev/null +++ b/spec/support/pages/my/reminders.rb @@ -0,0 +1,39 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'support/pages/reminders/settings' + +module Pages + module My + class Reminders < ::Pages::Reminders::Settings + def path + my_reminders_path + end + end + end +end diff --git a/spec/support/pages/reminders/settings.rb b/spec/support/pages/reminders/settings.rb new file mode 100644 index 00000000000..747993ae8f0 --- /dev/null +++ b/spec/support/pages/reminders/settings.rb @@ -0,0 +1,64 @@ +#-- copyright +# OpenProject is an open source project management software. +# Copyright (C) 2012-2021 the OpenProject GmbH +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License version 3. +# +# OpenProject is a fork of ChiliProject, which is a fork of Redmine. The copyright follows: +# Copyright (C) 2006-2013 Jean-Philippe Lang +# Copyright (C) 2010-2013 the ChiliProject Team +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation; either version 2 +# of the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# +# See docs/COPYRIGHT.rdoc for more details. +#++ + +require 'support/pages/page' + +module Pages + module Reminders + class Settings < ::Pages::Page + attr_reader :user + + def initialize(user) + super() + @user = user + end + + def path + edit_user_path(user, tab: :reminders) + end + + def expect_active_daily_times(*times) + times.each_with_index do |time, index| + expect(page) + .to have_checked_field "Time #{index + 1}" + + expect(page) + .to have_css("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']") + + expect(page.find("input[data-qa-selector='op-settings-daily-time--time-#{index + 1}']").value) + .to eql(time) + end + end + + def save + click_button 'Save' + end + + end + end +end diff --git a/vendor/openproject-icon-font/src/email-alert.svg b/vendor/openproject-icon-font/src/email-alert.svg new file mode 100644 index 00000000000..92bb4f1d80e --- /dev/null +++ b/vendor/openproject-icon-font/src/email-alert.svg @@ -0,0 +1,4 @@ + + + +