mumuki-laboratory 9.10.0 → 9.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +15 -9
  4. data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +10 -8
  5. data/app/assets/javascripts/mumuki_laboratory/application/editors.js +5 -3
  6. data/app/assets/javascripts/mumuki_laboratory/application/progress.js +4 -4
  7. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +12 -1
  8. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +8 -2
  9. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_dropdown.scss +11 -0
  10. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +1 -1
  11. data/app/controllers/application_controller.rb +8 -4
  12. data/app/controllers/book_controller.rb +6 -0
  13. data/app/controllers/chapters_controller.rb +9 -0
  14. data/app/controllers/discussions_controller.rb +9 -0
  15. data/app/controllers/exercises_controller.rb +5 -0
  16. data/app/controllers/faqs_controller.rb +4 -0
  17. data/app/controllers/lessons_controller.rb +9 -0
  18. data/app/controllers/users_controller.rb +37 -0
  19. data/app/helpers/application_helper.rb +2 -2
  20. data/app/helpers/concerns/with_student_path_navigation.rb +1 -1
  21. data/app/helpers/content_view_helper.rb +8 -0
  22. data/app/helpers/discussions_helper.rb +1 -1
  23. data/app/helpers/icons_helper.rb +1 -1
  24. data/app/helpers/links_helper.rb +5 -1
  25. data/app/helpers/menu_bar_helper.rb +8 -5
  26. data/app/helpers/notifications_helper.rb +13 -0
  27. data/app/helpers/overlapped_buttons_helper.rb +2 -1
  28. data/app/helpers/time_zone_helper.rb +5 -0
  29. data/app/helpers/user_menu_helper.rb +4 -0
  30. data/app/mailers/user_mailer.rb +11 -1
  31. data/app/views/book/show.html.erb +16 -7
  32. data/app/views/chapters/show.html.erb +1 -0
  33. data/app/views/discussions/_new_message.html.erb +1 -1
  34. data/app/views/discussions/show.html.erb +5 -5
  35. data/app/views/exercise_solutions/_results.html.erb +1 -1
  36. data/app/views/guides/_guide.html.erb +2 -2
  37. data/app/views/layouts/_progress_bar.html.erb +1 -0
  38. data/app/views/layouts/_progress_listing.html.erb +1 -0
  39. data/app/views/layouts/_user_menu.html.erb +1 -0
  40. data/app/views/layouts/application.html.erb +4 -0
  41. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  42. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +6 -2
  43. data/app/views/layouts/exercise_inputs/read_only_editors/_code.html.erb +1 -1
  44. data/app/views/layouts/exercise_inputs/read_only_editors/_hidden.html.erb +0 -0
  45. data/app/views/layouts/exercise_inputs/read_only_editors/_multiple_files.html.erb +4 -4
  46. data/app/views/layouts/exercise_inputs/read_only_editors/_upload.html.erb +0 -0
  47. data/app/views/layouts/mailer.html.erb +7 -1
  48. data/app/views/notifications/_custom.html.erb +1 -0
  49. data/app/views/notifications/_dropdown.html.erb +2 -2
  50. data/app/views/notifications/_exam_authorization_request_updated.html.erb +2 -0
  51. data/app/views/notifications/_exam_registration.html.erb +2 -1
  52. data/app/views/notifications/previews/_custom.html.erb +1 -0
  53. data/app/views/notifications/previews/_discussion.html.erb +2 -0
  54. data/app/views/notifications/previews/_exam_authorization_request_updated.html.erb +2 -0
  55. data/app/views/notifications/previews/_exam_registration.html.erb +2 -0
  56. data/app/views/notifications/previews/_message.html.erb +2 -0
  57. data/app/views/user_mailer/1st_reminder.html.erb +7 -349
  58. data/app/views/user_mailer/1st_reminder.text.erb +0 -4
  59. data/app/views/user_mailer/2nd_reminder.html.erb +7 -349
  60. data/app/views/user_mailer/2nd_reminder.text.erb +0 -4
  61. data/app/views/user_mailer/3rd_reminder.html.erb +7 -349
  62. data/app/views/user_mailer/3rd_reminder.text.erb +0 -4
  63. data/app/views/user_mailer/_mail_template.erb +336 -0
  64. data/app/views/user_mailer/certificate.html.erb +7 -335
  65. data/app/views/user_mailer/certificate.text.erb +0 -3
  66. data/app/views/user_mailer/no_submissions_reminder.html.erb +7 -349
  67. data/app/views/user_mailer/no_submissions_reminder.text.erb +0 -4
  68. data/app/views/user_mailer/notification.html.erb +1 -0
  69. data/app/views/user_mailer/notification.text.erb +6 -0
  70. data/app/views/user_mailer/notifications/_custom.html.erb +11 -0
  71. data/app/views/user_mailer/notifications/_exam_authorization_request_updated.html.erb +1 -0
  72. data/app/views/user_mailer/notifications/_exam_registration.html.erb +7 -0
  73. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_approved.html.erb +7 -0
  74. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_rejected.html.erb +7 -0
  75. data/app/views/users/manage_notifications.html.erb +26 -0
  76. data/app/views/users/notifications.html.erb +54 -0
  77. data/config/routes.rb +4 -0
  78. data/lib/mumuki/laboratory.rb +1 -0
  79. data/lib/mumuki/laboratory/controllers.rb +1 -0
  80. data/lib/mumuki/laboratory/controllers/authorization.rb +5 -1
  81. data/lib/mumuki/laboratory/controllers/notifications.rb +2 -2
  82. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +15 -0
  83. data/lib/mumuki/laboratory/locales/en.yml +35 -1
  84. data/lib/mumuki/laboratory/locales/es-CL.yml +35 -1
  85. data/lib/mumuki/laboratory/locales/es.yml +36 -2
  86. data/lib/mumuki/laboratory/locales/pt.yml +35 -1
  87. data/lib/mumuki/laboratory/mailers/message_delivery.rb +15 -0
  88. data/lib/mumuki/laboratory/version.rb +1 -1
  89. data/spec/controllers/users_controller_spec.rb +48 -0
  90. data/spec/dummy/db/schema.rb +7 -1
  91. data/spec/features/menu_bar_spec.rb +3 -2
  92. data/spec/features/not_found_private_flow_spec.rb +1 -1
  93. data/spec/features/notifications_flow_spec.rb +2 -1
  94. data/spec/features/read_only_flow_spec.rb +920 -0
  95. data/spec/javascripts/editors-spec.js +21 -3
  96. data/spec/javascripts/submissions-store-spec.js +11 -0
  97. data/spec/mailers/previews/user_mailer_preview.rb +87 -1
  98. metadata +35 -19
  99. data/app/views/notifications/_discussion.html.erb +0 -1
  100. data/app/views/notifications/_exam_authorization_request.html.erb +0 -1
  101. data/app/views/notifications/_message.html.erb +0 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: fb8cf028976b13658a28a48a6bf235276d1732ce1256a463cd348b8d915fcba7
4
- data.tar.gz: 990a6400b5f4fa76f7f17cd5dc97575c9cb475bc7588a15d4ae8102d434583b7
3
+ metadata.gz: 331d677d1f93dd36a842d9b39bd78a364e3cfdfe6b87711a52b8dbd0fdd3c678
4
+ data.tar.gz: 159c5b4bf7abb35721da25fea0f11c861fe2df4243c505a4bc8b3c6f523f6207
5
5
  SHA512:
6
- metadata.gz: 371681f22a60126bad63d241eee30f52f5a8c674077648a2bad413f15375fb704276af6149fc04046f0fc6d72d1562f76ccfcb1ec4d013a7bdcd4480dd817766
7
- data.tar.gz: 40875a8558cb07951d09ca67bfe2c10405b9fb75d225ff1afd9f4ede4803ba87b9a1460a8aae18268027c3e4380b9a00d20eb9d03d8c2d5561d4ee023453d4a6
6
+ metadata.gz: b575ed92e419931d69aae5b043f7e29a792c9d9180746e1be3c6bd1261b29394d656a5d97231a6f419181c97daf0dd01b40637c62ab7cc35ee52fd31f84de2b6
7
+ data.tar.gz: 84a684078c62aa1f15c070a3af71f82c6afce2ea657c0b8de0017954c2d020e7a3eb9c5014031746440bfd44b363386cbae0d8f1d6a9e58ee16cb2cb7368257e
data/README.md CHANGED
@@ -559,15 +559,16 @@ Before using the API, you must create an `ApiClient` using `rails c`, which will
559
559
 
560
560
  Before using the API, take a look to the roles hierarchy:
561
561
 
562
- ![roles hierarchy](https://yuml.me/diagram/plain/class/[Admin]%5E-[Janitor],[Admin]%5E-[Moderator],%20[Janitor]%5E-[Headmaster],%20[Headmaster]%5E-[Teacher],%20[Teacher]%5E-[Student],%20,%20[Admin]%5E-[Editor],%20[Editor]%5E-[Writer],%20[Owner]%5E-[Admin]).
562
+ ![roles hierarchy](https://yuml.me/diagram/plain/class/[Admin]%5E-[Janitor],[Admin]%5E-[Forum%20Supervisor],[Forum%20Supervisor]%5E-[Moderator],[Janitor]%5E-[Headmaster],[Headmaster]%5E-[Teacher],[Teacher]%5E-[Student],[Student]%5E-[Ex%20Student],[Admin]%5E-[Editor],[Editor]%5E-[Writer],[Owner]%5E-[Admin]).
563
563
 
564
564
  Permissions are bound to a scope, that states in which context the operation can be performed. Scopes are simply two-level contexts, expressed as slugss `<first>/<second>`, without any explicit semantic. They exact meaning depends on the role:
565
565
 
566
+ * ex_student: `organization/course`
566
567
  * student: `organization/course`
567
568
  * teacher and headmaster: `organization/course`
568
569
  * writer and editor: `organization/content`
569
570
  * janitor: `organization/_`
570
- * moderator: `organization/_`
571
+ * moderator and forum_supervisor: `organization/_`
571
572
  * admin: `_/_`
572
573
  * owner: `_/_`
573
574
 
@@ -13,7 +13,9 @@
13
13
  * @typedef {{
14
14
  * status: SubmissionStatus,
15
15
  * class_for_progress_list_item?: string,
16
- * guide_finished_by_solution?: boolean
16
+ * guide_finished_by_solution?: boolean,
17
+ * title_html?: string,
18
+ * in_gamified_context?: boolean
17
19
  * }} SubmissionResult
18
20
  */
19
21
 
@@ -47,7 +49,13 @@
47
49
  */
48
50
 
49
51
  /**
50
- * @typedef {Contents & {client_result?: SubmissionClientResult}} Submission
52
+ * A submission, composed by:
53
+ *
54
+ * * its actual contents
55
+ * * the client results
56
+ * * the _pristine flag, that tells whether the submission has been sent without previous results
57
+ *
58
+ * @typedef {Contents & {client_result?: SubmissionClientResult, _pristine: boolean}} Submission
51
59
  */
52
60
 
53
61
  /**
@@ -83,12 +91,12 @@ mumuki.bridge = (() => {
83
91
  */
84
92
  _submitSolution(submission) {
85
93
  const lastSubmission = mumuki.SubmissionsStore.getSubmissionResultFor(mumuki.exercise.id, submission);
86
- if (lastSubmission) {
87
- return $.Deferred().resolve(lastSubmission);
88
- } else {
94
+ if (submission._pristine || !lastSubmission) {
89
95
  return this._sendNewSolution(submission).done((result) => {
90
96
  mumuki.SubmissionsStore.setSubmissionResultFor(mumuki.exercise.id, {submission, result});
91
97
  });
98
+ } else {
99
+ return $.Deferred().resolve(lastSubmission);
92
100
  }
93
101
  }
94
102
 
@@ -107,14 +115,12 @@ mumuki.bridge = (() => {
107
115
  }
108
116
 
109
117
  /**
110
- * Pre-renders some html parts of submission UI, adding them to the given result
111
- *
112
118
  * @param {SubmissionResult} result
113
119
  * @returns {SubmissionResult}
120
+ * @see mumuki.renderers.results.preRenderResult
114
121
  */
115
122
  _preRenderResult(result) {
116
- result.class_for_progress_list_item = mumuki.renderers.results.progressListItemClassForStatus(result.status, true);
117
- result.title_html = mumuki.renderers.results.translatedTitleHtml(result.status, result.in_gamified_context);
123
+ mumuki.renderers.results.preRenderResult(result);
118
124
  return result;
119
125
  }
120
126
  }
@@ -1,17 +1,19 @@
1
1
  mumuki.load(() => {
2
- $('.btn-confirmation').on('click change', function (_evt) {
3
- var token = new mumuki.CsrfToken();
2
+ $('.btn-confirmation').on('click change', function (_event) {
3
+ const token = new mumuki.CsrfToken();
4
+ const $element = $(this);
4
5
 
5
6
  $.ajax(token.newRequest({
6
7
  method: 'POST',
7
- url: $(this).data('confirmation-url'),
8
+ url: $element.data('confirmation-url'),
8
9
  xhrFields: {withCredentials: true},
9
- success: function(data){
10
- mumuki.updateProgressBarAndShowModal(data);
10
+ success: function(result) {
11
+ result.status = "passed";
12
+ mumuki.renderers.results.preRenderResult(result)
13
+ mumuki.updateProgressBarAndShowModal(result);
14
+ window.location = $element.attr("href");
11
15
  }
12
16
  }));
13
-
14
- return true;
17
+ return false;
15
18
  });
16
19
  });
17
-
@@ -55,12 +55,14 @@ mumuki.editors = {
55
55
  * @returns {Submission}
56
56
  */
57
57
  getSubmission() {
58
- let content = {};
58
+ let submission = {
59
+ _pristine: $('.submission-results').children().length === 0
60
+ };
59
61
  let contents = this.getContents();
60
62
  contents.forEach((it) => {
61
- content[it.name] = it.value;
63
+ submission[it.name] = it.value;
62
64
  });
63
- return content;
65
+ return submission;
64
66
  },
65
67
 
66
68
  /**
@@ -2,11 +2,11 @@ mumuki.progress = (() => {
2
2
  /**
3
3
  * Updates the current exercise progress indicator
4
4
  *
5
- * @param {SubmissionResult} data
5
+ * @param {SubmissionResult} result
6
6
  * */
7
- function updateProgressBarAndShowModal(data) {
8
- $('.progress-list-item.active').attr('class', data.class_for_progress_list_item);
9
- if(data.guide_finished_by_solution) new bootstrap.Modal('#guide-done').show();
7
+ function updateProgressBarAndShowModal(result) {
8
+ $('.progress-list-item.active').attr('class', result.class_for_progress_list_item);
9
+ if(result.guide_finished_by_solution) new bootstrap.Modal('#guide-done').show();
10
10
  }
11
11
 
12
12
  /**
@@ -71,11 +71,22 @@ mumuki.renderers.results = (() => {
71
71
  return `progress-list-item text-center ${classForStatus(status)} ${active ? 'active' : ''}`;
72
72
  }
73
73
 
74
+ /**
75
+ * Pre-renders some html parts of submission UI, adding them to the given result
76
+ *
77
+ * @param {SubmissionResult} result
78
+ */
79
+ function preRenderResult(result) {
80
+ result.class_for_progress_list_item = progressListItemClassForStatus(result.status, true);
81
+ result.title_html = translatedTitleHtml(result.status, result.in_gamified_context);
82
+ }
83
+
74
84
  return {
75
85
  classForStatus,
76
86
  iconForStatus,
77
87
  progressListItemClassForStatus,
78
- translatedTitleHtml
88
+ translatedTitleHtml,
89
+ preRenderResult
79
90
  };
80
91
  })();
81
92
 
@@ -89,8 +89,14 @@ mumuki.SubmissionsStore = (() => {
89
89
 
90
90
  // private API
91
91
 
92
- _asString(object) {
93
- return JSON.stringify(object);
92
+ /**
93
+ * Serializes the submission and result.
94
+ * Private attributes are ignored
95
+ */
96
+ _asString(submissionAndResult) {
97
+ return JSON.stringify(submissionAndResult, (key, value) => {
98
+ if (!key.startsWith("_")) return value
99
+ });
94
100
  }
95
101
 
96
102
  _keyFor(exerciseId) {
@@ -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
+ }
@@ -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 {
@@ -24,7 +24,6 @@ class ApplicationController < ActionController::Base
24
24
  before_action :redirect_to_proper_context!, if: :immersive_context_wrong?
25
25
 
26
26
  before_action :authorize_if_private!
27
- before_action :validate_active_organization!
28
27
  before_action :validate_user_profile!, if: :current_user?
29
28
  before_action :validate_accepted_role_terms!, if: :current_user?
30
29
 
@@ -37,13 +36,14 @@ class ApplicationController < ActionController::Base
37
36
  :login_button,
38
37
  :notifications_count,
39
38
  :has_notifications?,
40
- :notifications,
39
+ :user_notifications,
41
40
  :subject,
42
41
  :should_choose_organization?,
43
42
  :current_immersive_organizations,
44
43
  :theme_stylesheet_url,
45
44
  :extension_javascript_url,
46
- :current_immersive_path
45
+ :current_immersive_path,
46
+ :current_access_mode
47
47
 
48
48
  add_flash_types :info
49
49
 
@@ -72,7 +72,7 @@ class ApplicationController < ActionController::Base
72
72
 
73
73
  def validate_active_organization!
74
74
  return if current_user&.teacher_here?
75
- Organization.current.validate_active!
75
+ Organization.current.validate_active_for! current_user
76
76
  end
77
77
 
78
78
  # required by Mumukit::Login
@@ -160,4 +160,8 @@ class ApplicationController < ActionController::Base
160
160
  def leave_organization!
161
161
  Mumukit::Platform::Organization.leave!
162
162
  end
163
+
164
+ def current_access_mode
165
+ Organization.current.access_mode(current_user)
166
+ end
163
167
  end
@@ -4,4 +4,10 @@ class BookController < ApplicationController
4
4
  @book = Organization.current.book
5
5
  @exams = Organization.current.accessible_exams_for current_user
6
6
  end
7
+
8
+ private
9
+
10
+ def authorization_minimum_role
11
+ :ex_student
12
+ end
7
13
  end
@@ -3,6 +3,7 @@ require 'addressable/uri'
3
3
  # It acts as a guide container in monolesson contexts
4
4
  class ChaptersController < GuideContainerController
5
5
  include Mumuki::Laboratory::Controllers::ImmersiveNavigation
6
+ include Mumuki::Laboratory::Controllers::ValidateAccessMode
6
7
 
7
8
  def subject
8
9
  @chapter ||= Chapter.find_by(id: params[:id])
@@ -14,4 +15,12 @@ class ChaptersController < GuideContainerController
14
15
  @monolesson = subject.monolesson
15
16
  @guide = @monolesson&.guide
16
17
  end
18
+
19
+ def authorization_minimum_role
20
+ :ex_student
21
+ end
22
+
23
+ def subject_container
24
+ subject.topic
25
+ end
17
26
  end
@@ -7,6 +7,7 @@ class DiscussionsController < ApplicationController
7
7
  before_action :discussion_filter_params, only: :index
8
8
  before_action :read_discussion, only: :show
9
9
  before_action :authorize_moderator!, only: [:responsible]
10
+ before_action :validate_access_mode!
10
11
 
11
12
  helper_method :discussion_filter_params
12
13
 
@@ -99,4 +100,12 @@ class DiscussionsController < ApplicationController
99
100
  def discussion_filter_params
100
101
  @filter_params ||= params.permit(Discussion.permitted_query_params)
101
102
  end
103
+
104
+ def authorization_minimum_role
105
+ :ex_student
106
+ end
107
+
108
+ def validate_access_mode!
109
+ current_access_mode.validate_discuss_here! subject
110
+ end
102
111
  end
@@ -2,6 +2,7 @@ class ExercisesController < ApplicationController
2
2
  include Mumuki::Laboratory::Controllers::Content
3
3
  include Mumuki::Laboratory::Controllers::ExerciseSeed
4
4
  include Mumuki::Laboratory::Controllers::ImmersiveNavigation
5
+ include Mumuki::Laboratory::Controllers::ValidateAccessMode
5
6
 
6
7
  before_action :set_guide!, only: :show
7
8
  before_action :set_assignment!, only: :show, if: :current_user?
@@ -50,4 +51,8 @@ class ExercisesController < ApplicationController
50
51
  :guide_id, :number,
51
52
  :layout, :expectations_yaml)
52
53
  end
54
+
55
+ def authorization_minimum_role
56
+ :ex_student
57
+ end
53
58
  end
@@ -1,4 +1,8 @@
1
1
  class FAQsController < ApplicationController
2
+ def authorization_minimum_role
3
+ :ex_student
4
+ end
5
+
2
6
  def index
3
7
  @faqs = Organization.current.faqs_html
4
8
  raise Mumuki::Domain::NotFoundError unless @faqs.present?
@@ -1,9 +1,18 @@
1
1
  class LessonsController < GuideContainerController
2
2
  include Mumuki::Laboratory::Controllers::ImmersiveNavigation
3
+ include Mumuki::Laboratory::Controllers::ValidateAccessMode
3
4
 
4
5
  private
5
6
 
6
7
  def subject
7
8
  @lesson ||= Lesson.find_by(id: params[:id])
8
9
  end
10
+
11
+ def authorization_minimum_role
12
+ :ex_student
13
+ end
14
+
15
+ def subject_container
16
+ subject.guide
17
+ end
9
18
  end
@@ -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
@@ -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_in_organization.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
 
@@ -75,4 +108,8 @@ class UsersController < ApplicationController
75
108
  def discussion_filter_params
76
109
  @filter_params ||= params.permit([:page])
77
110
  end
111
+
112
+ def authorization_minimum_role
113
+ :ex_student
114
+ end
78
115
  end
@@ -45,8 +45,8 @@ 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
50
  end
51
51
 
52
52
  def current_time_zone_html
@@ -9,7 +9,7 @@ module WithStudentPathNavigation
9
9
  end
10
10
 
11
11
  def next_exercise_button(exercise)
12
- next_button(exercise) || next_button(exercise.guide.lesson)
12
+ next_button(exercise) || next_button(exercise.guide.lesson) if show_content_element?
13
13
  end
14
14
 
15
15
  def close_modal_button