mumuki-laboratory 9.8.1 → 9.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/messages.js +1 -30
  3. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +32 -34
  4. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_dropdown.scss +11 -0
  5. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_editor.scss +3 -0
  6. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +1 -1
  7. data/app/controllers/application_controller.rb +1 -1
  8. data/app/controllers/messages_controller.rb +2 -5
  9. data/app/controllers/users_controller.rb +38 -1
  10. data/app/helpers/application_helper.rb +6 -2
  11. data/app/helpers/discussions_helper.rb +11 -4
  12. data/app/helpers/exam_registration_helper.rb +0 -4
  13. data/app/helpers/icons_helper.rb +1 -1
  14. data/app/helpers/menu_bar_helper.rb +7 -3
  15. data/app/helpers/messages_helper.rb +4 -8
  16. data/app/helpers/notifications_helper.rb +13 -0
  17. data/app/helpers/profile_helper.rb +4 -0
  18. data/app/helpers/user_discussions_helper.rb +38 -0
  19. data/app/helpers/user_menu_helper.rb +4 -0
  20. data/app/mailers/user_mailer.rb +9 -1
  21. data/app/views/discussions/new.html.erb +1 -1
  22. data/app/views/layouts/_copyright.html.erb +1 -1
  23. data/app/views/layouts/_discussions.html.erb +1 -37
  24. data/app/views/layouts/_discussions_list.html.erb +38 -0
  25. data/app/views/layouts/_messages.html.erb +1 -7
  26. data/app/views/layouts/_user_menu.html.erb +1 -0
  27. data/app/views/layouts/application.html.erb +1 -1
  28. data/app/views/layouts/exercise_inputs/forms/_interactive_form.html.erb +1 -1
  29. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +1 -1
  30. data/app/views/layouts/exercise_inputs/read_only_editors/_code.html.erb +1 -1
  31. data/app/views/layouts/exercise_inputs/read_only_editors/_multiple_files.html.erb +4 -4
  32. data/app/views/layouts/mailer.html.erb +7 -1
  33. data/app/views/notifications/_custom.html.erb +1 -0
  34. data/app/views/notifications/_dropdown.html.erb +2 -2
  35. data/app/views/notifications/_exam_authorization_request_updated.html.erb +2 -0
  36. data/app/views/notifications/_exam_registration.html.erb +2 -1
  37. data/app/views/notifications/previews/_custom.html.erb +1 -0
  38. data/app/views/notifications/previews/_discussion.html.erb +2 -0
  39. data/app/views/notifications/previews/_exam_authorization_request_updated.html.erb +2 -0
  40. data/app/views/notifications/previews/_exam_registration.html.erb +2 -0
  41. data/app/views/notifications/previews/_message.html.erb +2 -0
  42. data/app/views/user_mailer/1st_reminder.html.erb +7 -349
  43. data/app/views/user_mailer/1st_reminder.text.erb +1 -5
  44. data/app/views/user_mailer/2nd_reminder.html.erb +7 -349
  45. data/app/views/user_mailer/2nd_reminder.text.erb +1 -5
  46. data/app/views/user_mailer/3rd_reminder.html.erb +7 -349
  47. data/app/views/user_mailer/3rd_reminder.text.erb +1 -5
  48. data/app/views/user_mailer/_mail_template.erb +336 -0
  49. data/app/views/user_mailer/certificate.html.erb +7 -335
  50. data/app/views/user_mailer/certificate.text.erb +1 -4
  51. data/app/views/user_mailer/no_submissions_reminder.html.erb +7 -349
  52. data/app/views/user_mailer/no_submissions_reminder.text.erb +1 -5
  53. data/app/views/user_mailer/notification.html.erb +1 -0
  54. data/app/views/user_mailer/notification.text.erb +6 -0
  55. data/app/views/user_mailer/notifications/_custom.html.erb +11 -0
  56. data/app/views/user_mailer/notifications/_exam_authorization_request_updated.html.erb +1 -0
  57. data/app/views/user_mailer/notifications/_exam_registration.html.erb +7 -0
  58. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_approved.html.erb +7 -0
  59. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_rejected.html.erb +7 -0
  60. data/app/views/users/_basic_profile_fields.html.erb +30 -0
  61. data/app/views/users/_profile_fields.html.erb +1 -20
  62. data/app/views/users/discussions.html.erb +19 -11
  63. data/app/views/users/manage_notifications.html.erb +26 -0
  64. data/app/views/users/messages.html.erb +1 -1
  65. data/app/views/users/notifications.html.erb +54 -0
  66. data/config/routes.rb +4 -1
  67. data/lib/mumuki/laboratory/controllers/notifications.rb +2 -2
  68. data/lib/mumuki/laboratory/locales/en.yml +46 -7
  69. data/lib/mumuki/laboratory/locales/es-CL.yml +47 -7
  70. data/lib/mumuki/laboratory/locales/es.yml +48 -9
  71. data/lib/mumuki/laboratory/locales/pt.yml +45 -6
  72. data/lib/mumuki/laboratory/version.rb +1 -1
  73. data/spec/controllers/organizations_api_controller_spec.rb +6 -1
  74. data/spec/controllers/students_api_controller_spec.rb +1 -1
  75. data/spec/controllers/users_controller_spec.rb +48 -0
  76. data/spec/dummy/db/schema.rb +9 -1
  77. data/spec/features/not_found_public_flow_spec.rb +1 -1
  78. data/spec/features/notifications_flow_spec.rb +2 -1
  79. data/spec/features/profile_flow_spec.rb +1 -1
  80. data/spec/mailers/previews/user_mailer_preview.rb +45 -1
  81. metadata +136 -119
  82. data/app/views/messages/errors.html.erb +0 -1
  83. data/app/views/notifications/_discussion.html.erb +0 -1
  84. data/app/views/notifications/_exam_authorization_request.html.erb +0 -1
  85. data/app/views/notifications/_message.html.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '010359ac9c5634aef6562e79e3bf7bcbda5636811940d65b8b4cea33bf925343'
4
- data.tar.gz: 985a05cba4d9d05a240b214e8c1fc26b7f1be78f2798945a20cd4d101609920a
3
+ metadata.gz: b34448fea9940dfe1784874284974ae37cce2df2b56277e2eb1afdff93cab092
4
+ data.tar.gz: 14cafb4c1d57a5ecf4569c3de18a8ab35f2b0d7030dcd3f0c925945d3f9f2200
5
5
  SHA512:
6
- metadata.gz: 075a1767df05f3dbeb70bef5f3d8d4276379df342bc4ea63d7a370bbc94c2d3a92f4357f86ef456c0f33f8b347250869162959823e534138f943abfcb0f6ed9d
7
- data.tar.gz: 73b01a26563a8af9eed8210fa8c1e1553ef7a444d64ea39b2f97c3e41e5faa33f2cfb9cb37c857602f05947bf9d5d7ae4254db71e3df6219e2b49cd9096aaa6c
6
+ metadata.gz: f8796a932c0f10ffd127de92b75d21cae746aae645e57a1a8c40e714c139ef9cd5b833cacd7e1c2259ca298ea8a076d1248c7891aacb49f69066a85bd37cbfce
7
+ data.tar.gz: 77ebd8d9a64a7eb2767e35a67befe8cf9f96346e4df97a92b23ad805c71c275252d1965744193d8c9b16d7f7b555323fd7d38b6176bacf32511944dd3296b3a0
@@ -19,6 +19,7 @@ mumuki.load(() => {
19
19
  $('.pending-messages-filter').removeClass('pending-messages-filter');
20
20
  $('button.btn-submit').removeClass('disabled');
21
21
  $('.pending-messages-text').remove();
22
+ $("a[data-bs-target='#messages']")[0].click();
22
23
  },
23
24
  readMessages: function (url) {
24
25
  Chat.tokenRequest({
@@ -47,35 +48,6 @@ mumuki.load(() => {
47
48
  setMessagesInterval: function () {
48
49
  mumuki.setInterval(Chat.getMessages, 60000);
49
50
  },
50
- submitMessagesForm: function (url, readUrl, errorUrl) {
51
- var $container = $('.mu-view-messages');
52
-
53
- function renderHTML(data) {
54
- $container.empty();
55
- $container.html(data);
56
- $("a[data-bs-target='#messages']").click();
57
- }
58
-
59
- function success(data) {
60
- renderHTML(data);
61
- Chat.readMessages(readUrl);
62
- }
63
-
64
- function error(_xhr) {
65
- Chat.tokenRequest({
66
- url: errorUrl,
67
- success: renderHTML,
68
- xhrFields: {withCredentials: true}
69
- });
70
- }
71
-
72
- $.ajax({
73
- url: url,
74
- success: success,
75
- error: error,
76
- xhrFields: {withCredentials: true}
77
- });
78
- },
79
51
  openNewMessageModal: function () {
80
52
  Chat.$newMessageModal().modal({backdrop: false, keyboard: false});
81
53
  Chat.$body().addClass("new-message-modal-open");
@@ -90,4 +62,3 @@ mumuki.load(() => {
90
62
  mumuki.Chat = Chat;
91
63
 
92
64
  });
93
-
@@ -50,36 +50,28 @@ $moderator-badge-color: #dd9900;
50
50
  }
51
51
  }
52
52
 
53
- .discussion-pagination {
54
- display: flex;
55
- flex-direction: row;
56
- align-items: center;
57
- justify-content: center;
58
- }
59
-
60
53
  .discussion-language-icon {
61
- float: right;
62
- margin-right: 10px;
63
- margin-top: 6px;
64
- -ms-transform: scale(1.4, 1.4);
65
- -webkit-transform: scale(1.4, 1.4);
66
- transform: scale(1.4, 1.4);
54
+ display: inline;
55
+ padding-right: 5px;
67
56
  }
68
57
 
69
- .discussion-icon {
70
- -ms-transform: scale(0.8, 0.8);
71
- -webkit-transform: scale(0.8, 0.8);
72
- transform: scale(0.8, 0.8);
73
- margin-left: 5px;
74
- float: right;
75
- .fa-stack-1x {
76
- -ms-transform: scale(0.9, 0.9);
77
- -webkit-transform: scale(0.9, 0.9);
78
- transform: scale(0.9, 0.9);
58
+ .discussion-validated-messages-count {
59
+ .fa-check {
60
+ font-size: calc(#{$font-size-base} * 0.5);
61
+ position: absolute;
62
+ margin-left: -13px;
63
+ margin-top: 10px;
79
64
  }
80
65
  }
81
66
  }
82
67
 
68
+ .discussion-pagination {
69
+ display: flex;
70
+ flex-direction: row;
71
+ align-items: center;
72
+ justify-content: center;
73
+ }
74
+
83
75
  .discussions-no-filtered-results {
84
76
  padding: 20px;
85
77
  border: 1px solid $discussion-toolbar-border-color;
@@ -96,7 +88,7 @@ $moderator-badge-color: #dd9900;
96
88
  }
97
89
 
98
90
  .discussion-status-icon {
99
- padding-right: 10px;
91
+ padding-left: 2px;
100
92
  }
101
93
 
102
94
  .discussion-row {
@@ -106,11 +98,26 @@ $moderator-badge-color: #dd9900;
106
98
  font-size: 18px;
107
99
  font-weight: bold;
108
100
  }
101
+ .discussion-title-icons {
102
+ display: flex;
103
+ flex-shrink: 0;
104
+ align-items: center;
105
+ font-size: calc(#{$font-size-base} * 0.95);
106
+
107
+ > * {
108
+ padding-left: 20px;
109
+ }
110
+ }
109
111
  .discussion-description {
110
112
  overflow: hidden;
111
113
  display: block;
112
114
  text-overflow: ellipsis;
113
115
  white-space: nowrap;
116
+ margin-bottom: 20px;
117
+ font-size: calc(#{$font-size-base} * 0.95);
118
+ }
119
+ .discussion-initiator {
120
+ font-size: calc(#{$font-size-base} * 0.85);
114
121
  }
115
122
  }
116
123
 
@@ -308,6 +315,7 @@ $moderator-badge-color: #dd9900;
308
315
  border: 1px solid $discussion-message-border-color;
309
316
  border-radius: 3px;
310
317
  position: relative;
318
+ margin-bottom: 20px;
311
319
  .discussion-message-bubble-header {
312
320
  background-color: $mu-color-highlight-background;
313
321
  height: 40px;
@@ -400,13 +408,3 @@ $moderator-badge-color: #dd9900;
400
408
  font-weight: normal;
401
409
  }
402
410
  }
403
-
404
- .discussion-moderator-access {
405
- margin-right: 20px;
406
- display: flex;
407
- flex-direction: column;
408
- align-items: center;
409
- .moderator-initials {
410
- font-size: 14px;
411
- }
412
- }
@@ -1,3 +1,14 @@
1
1
  .fixed-icon {
2
2
  margin-right: 5px;
3
3
  }
4
+
5
+ .mu-notification-preview {
6
+ max-width: 350px;
7
+ @extend .d-inline-block;
8
+ @extend .text-truncate;
9
+ }
10
+
11
+ .mu-stealth-link {
12
+ text-decoration: none;
13
+ color: unset;
14
+ }
@@ -115,3 +115,6 @@ body.fullscreen {
115
115
  }
116
116
  }
117
117
 
118
+ .pending-messages-text {
119
+ top: auto;
120
+ }
@@ -21,7 +21,7 @@
21
21
 
22
22
  @include media-breakpoint-down(md) {
23
23
  &:not(.active) {
24
- h3, p {
24
+ h3, p, ul {
25
25
  display: none
26
26
  }
27
27
  h2 {
@@ -37,7 +37,7 @@ class ApplicationController < ActionController::Base
37
37
  :login_button,
38
38
  :notifications_count,
39
39
  :has_notifications?,
40
- :notifications,
40
+ :user_notifications,
41
41
  :subject,
42
42
  :should_choose_organization?,
43
43
  :current_immersive_organizations,
@@ -2,7 +2,8 @@ class MessagesController < AjaxController
2
2
  before_action :set_exercise!, only: [:create, :read_messages]
3
3
 
4
4
  def index
5
- render json: {has_messages: has_messages?, messages_count: messages_count}
5
+ pending_messages = current_user.unread_messages
6
+ render json: {has_messages: pending_messages.present?, messages_count: pending_messages.count}
6
7
  end
7
8
 
8
9
  def read_messages
@@ -15,10 +16,6 @@ class MessagesController < AjaxController
15
16
  redirect_to @exercise
16
17
  end
17
18
 
18
- def errors
19
- render 'messages/errors', layout: false
20
- end
21
-
22
19
  private
23
20
 
24
21
  def set_exercise!
@@ -3,6 +3,8 @@ class UsersController < ApplicationController
3
3
 
4
4
  before_action :authenticate!, except: :terms
5
5
  before_action :set_user!
6
+ before_action :set_notification!, only: :toggle_read
7
+ before_action :verify_owns_notification!, only: :toggle_read
6
8
  skip_before_action :validate_accepted_role_terms!
7
9
 
8
10
  def update
@@ -27,7 +29,7 @@ class UsersController < ApplicationController
27
29
  end
28
30
 
29
31
  def discussions
30
- @watched_discussions = current_user.watched_discussions_in_organization
32
+ @watched_discussions = current_user.watched_discussions_in_organization.scoped_query_by(discussion_filter_params).unread_first
31
33
  end
32
34
 
33
35
  def activity
@@ -53,8 +55,39 @@ class UsersController < ApplicationController
53
55
  super << [:avatar_id, :avatar_type]
54
56
  end
55
57
 
58
+ def notifications
59
+ @notifications = @user.notifications.order(created_at: :desc).page(params[:page])
60
+ end
61
+
62
+ def toggle_read
63
+ @notification.toggle! :read
64
+ redirect_to notifications_user_path
65
+ end
66
+
67
+ def show_manage_notifications
68
+ render 'manage_notifications'
69
+ end
70
+
71
+ def manage_notifications
72
+ @user.update! ignored_notifications: manage_notifications_params.reject { |_, allowed| allowed.to_boolean }.keys
73
+
74
+ redirect_to notifications_user_path, notice: I18n.t(:preferences_updated_successfully)
75
+ end
76
+
56
77
  private
57
78
 
79
+ def manage_notifications_params
80
+ params.require(:notifications)
81
+ end
82
+
83
+ def set_notification!
84
+ @notification = Notification.find(params[:id])
85
+ end
86
+
87
+ def verify_owns_notification!
88
+ raise Mumuki::Domain::NotFoundError unless @notification.user == @user
89
+ end
90
+
58
91
  def validate_user_profile!
59
92
  end
60
93
 
@@ -71,4 +104,8 @@ class UsersController < ApplicationController
71
104
  nil
72
105
  end
73
106
  end
107
+
108
+ def discussion_filter_params
109
+ @filter_params ||= params.permit([:page])
110
+ end
74
111
  end
@@ -45,7 +45,11 @@ module ApplicationHelper
45
45
  }.html_safe
46
46
  end
47
47
 
48
- def notification_preview_for(target)
49
- render "notifications/#{target.class.name.underscore}", { target: target }
48
+ def notification_preview_for(notification)
49
+ render "notifications/previews/#{notification.subject}", { notification: notification }
50
+ end
51
+
52
+ def current_time_zone_html
53
+ %Q{(<span class="select-date-timezone">#{Organization.current.time_zone}</span>)}.html_safe
50
54
  end
51
55
  end
@@ -60,11 +60,18 @@ module DiscussionsHelper
60
60
  }.html_safe
61
61
  end
62
62
 
63
- def discussion_messages_icon(discussion)
63
+ def discussion_messages_count(discussion)
64
64
  %Q{
65
- <span class="discussion-icon fa-stack fa-xs">
66
- <i class="far fa-comment fa-stack-2x"></i>
67
- <i class="fas fa-stack-1x">#{discussion.validated_messages_count}</i>
65
+ <span class="discussion-messages-count">
66
+ #{fa_icon :comments, type: :regular, text: discussion.messages_count}
67
+ </span>
68
+ }.html_safe
69
+ end
70
+
71
+ def discussion_validated_messages_count(discussion)
72
+ %Q{
73
+ <span class="discussion-validated-messages-count">
74
+ #{fa_icon :comment, type: :regular}#{fa_icon :check, text: discussion.validated_messages_count}
68
75
  </span>
69
76
  }.html_safe
70
77
  end
@@ -6,8 +6,4 @@ module ExamRegistrationHelper
6
6
  { icon: :info_circle, class: :info, t: :exam_registration_explanation_html }
7
7
  end
8
8
  end
9
-
10
- def current_time_zone_html
11
- %Q{(<span class="select-date-timezone">#{Organization.current&.time_zone}</span>)}.html_safe
12
- end
13
9
  end
@@ -50,6 +50,6 @@ module IconsHelper
50
50
  end
51
51
 
52
52
  def icon_for_read(read)
53
- tag('i', class: "fa#{read ? 'r' : 's'} fa-envelope")
53
+ tag.i(class: "fa#{read ? 'r' : 's'} fa-envelope#{read ? '-open' : ''}")
54
54
  end
55
55
  end
@@ -39,8 +39,12 @@ module MenuBarHelper
39
39
  li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for))
40
40
  end
41
41
 
42
- def menu_item(icon, name, url, translation_params = {})
43
- link_to fixed_fa_icon(icon, text: t(name, translation_params)), url, role: 'menuitem', tabindex: '-1', class: 'dropdown-item'
42
+ def menu_item(icon, name, url, css_class = nil, **translation_params)
43
+ menu_text_item(icon, t(name, translation_params), url, css_class)
44
+ end
45
+
46
+ def menu_text_item(icon, text, url, css_class = nil)
47
+ link_to fixed_fa_icon(icon, text: text), url, role: 'menuitem', tabindex: '-1', class: "dropdown-item #{css_class}"
44
48
  end
45
49
 
46
50
  def any_menu_bar_links?
@@ -48,6 +52,6 @@ module MenuBarHelper
48
52
  end
49
53
 
50
54
  def menu_link_to_faqs
51
- li_tag menu_item('question', :faqs, faqs_path)
55
+ li_tag menu_item(:info, :faqs_abbreviated, faqs_path)
52
56
  end
53
57
  end
@@ -1,8 +1,4 @@
1
1
  module MessagesHelper
2
- def messages_url(exercise)
3
- exercise.messages_url_for(current_user) if current_user?
4
- end
5
-
6
2
  def hidden_pending(assignment)
7
3
  assignment.pending_messages? ? '' : 'd-none'
8
4
  end
@@ -15,11 +11,11 @@ module MessagesHelper
15
11
  'pending-messages-filter' if assignment.pending_messages?
16
12
  end
17
13
 
18
- def read_messages_caption(assignment)
19
- assignment.pending_messages? ? :read_messages : :exit
20
- end
21
-
22
14
  def sender_class(message)
23
15
  message.blank? || message.from_user?(current_user) ? 'self' : 'other'
24
16
  end
17
+
18
+ def staleness_class(message)
19
+ 'mu-stale' if message.stale?
20
+ end
25
21
  end
@@ -0,0 +1,13 @@
1
+ module NotificationsHelper
2
+ def background_for_notification(notification)
3
+ notification.read? ? '' : 'bg-light'
4
+ end
5
+
6
+ def notification_preview_item(icon, name, url, **translation_params)
7
+ menu_item icon, name, url, 'mu-notification-preview', **translation_params
8
+ end
9
+
10
+ def notification_text_preview_item(icon, text, url)
11
+ menu_text_item(icon, text, url, 'mu-notification-preview')
12
+ end
13
+ end
@@ -10,4 +10,8 @@ module ProfileHelper
10
10
  def save_edit_profile_button(form)
11
11
  form.submit t(:save), class: 'btn btn-complementary mu-edit-profile-btn'
12
12
  end
13
+
14
+ def show_verified_full_name_notice?(user, organization)
15
+ user.has_verified_full_name? && organization.private?
16
+ end
13
17
  end
@@ -0,0 +1,38 @@
1
+ module UserDiscussionsHelper
2
+ def user_discussions_table_title(discussion, user, last_read)
3
+ %Q{
4
+ <tr></tr>
5
+ <thead>
6
+ <tr>
7
+ <td class="#{last_read.nil? ? '' : 'pt-5'}" colspan="4">
8
+ <strong>#{discussion.read_by?(user) ? t(:discussions_read) : t(:discussions_unread)}</strong>
9
+ </td>
10
+ </tr>
11
+ </thead>
12
+ }.html_safe
13
+ end
14
+
15
+ def user_discussions_table_header
16
+ %Q{
17
+ <tr class="fw-bold">
18
+ <td></td>
19
+ <td>#{t(:exercise)}</td>
20
+ <td>#{t(:discussion_created_by)}</td>
21
+ <td>#{t(:last_message)}</td>
22
+ </tr>
23
+ }.html_safe
24
+ end
25
+
26
+ def user_discussions_table_item(discussion, user)
27
+ %Q{
28
+ <tr>
29
+ <td class="text-center">
30
+ #{icon_for_read(discussion.read_by?(user))}
31
+ </td>
32
+ <td>#{link_to discussion.item.name, item_discussion_path(discussion)}</td>
33
+ <td>#{discussion_user_name discussion.initiator}</td>
34
+ <td>#{t(:time_since, time: time_ago_in_words(discussion.last_message_date))}</td>
35
+ </tr>
36
+ }.html_safe
37
+ end
38
+ end