mumuki-laboratory 9.11.0 → 9.13.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (60) 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/controllers/application_controller.rb +8 -4
  10. data/app/controllers/book_controller.rb +6 -0
  11. data/app/controllers/chapters_controller.rb +9 -0
  12. data/app/controllers/discussions_controller.rb +9 -0
  13. data/app/controllers/exercises_controller.rb +5 -0
  14. data/app/controllers/faqs_controller.rb +4 -0
  15. data/app/controllers/lessons_controller.rb +9 -0
  16. data/app/controllers/users_controller.rb +5 -1
  17. data/app/helpers/concerns/with_student_path_navigation.rb +1 -1
  18. data/app/helpers/content_view_helper.rb +8 -0
  19. data/app/helpers/discussions_helper.rb +1 -1
  20. data/app/helpers/links_helper.rb +5 -1
  21. data/app/helpers/menu_bar_helper.rb +2 -3
  22. data/app/helpers/overlapped_buttons_helper.rb +2 -1
  23. data/app/helpers/time_zone_helper.rb +5 -0
  24. data/app/mailers/user_mailer.rb +2 -0
  25. data/app/views/book/show.html.erb +16 -7
  26. data/app/views/chapters/show.html.erb +1 -0
  27. data/app/views/discussions/_new_message.html.erb +1 -1
  28. data/app/views/discussions/show.html.erb +5 -5
  29. data/app/views/exercise_solutions/_results.html.erb +1 -1
  30. data/app/views/guides/_guide.html.erb +2 -2
  31. data/app/views/layouts/_progress_bar.html.erb +1 -0
  32. data/app/views/layouts/_progress_listing.html.erb +1 -0
  33. data/app/views/layouts/application.html.erb +4 -0
  34. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  35. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +6 -2
  36. data/app/views/layouts/exercise_inputs/read_only_editors/_hidden.html.erb +0 -0
  37. data/app/views/layouts/exercise_inputs/read_only_editors/_upload.html.erb +0 -0
  38. data/app/views/notifications/previews/_exam_authorization_request_updated.html.erb +1 -1
  39. data/app/views/user_mailer/_mail_template.erb +1 -1
  40. data/app/views/user_mailer/notifications/_exam_registration.html.erb +1 -1
  41. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_approved.html.erb +1 -1
  42. data/app/views/users/manage_notifications.html.erb +1 -1
  43. data/app/views/users/notifications.html.erb +1 -1
  44. data/lib/mumuki/laboratory.rb +1 -0
  45. data/lib/mumuki/laboratory/controllers.rb +1 -0
  46. data/lib/mumuki/laboratory/controllers/authorization.rb +5 -1
  47. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +15 -0
  48. data/lib/mumuki/laboratory/locales/en.yml +1 -0
  49. data/lib/mumuki/laboratory/locales/es-CL.yml +1 -0
  50. data/lib/mumuki/laboratory/locales/es.yml +1 -0
  51. data/lib/mumuki/laboratory/locales/pt.yml +1 -0
  52. data/lib/mumuki/laboratory/mailers/message_delivery.rb +15 -0
  53. data/lib/mumuki/laboratory/version.rb +1 -1
  54. data/spec/features/menu_bar_spec.rb +3 -2
  55. data/spec/features/not_found_private_flow_spec.rb +1 -1
  56. data/spec/features/read_only_flow_spec.rb +920 -0
  57. data/spec/javascripts/editors-spec.js +21 -3
  58. data/spec/javascripts/submissions-store-spec.js +11 -0
  59. data/spec/mailers/previews/user_mailer_preview.rb +48 -6
  60. metadata +15 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b34448fea9940dfe1784874284974ae37cce2df2b56277e2eb1afdff93cab092
4
- data.tar.gz: 14cafb4c1d57a5ecf4569c3de18a8ab35f2b0d7030dcd3f0c925945d3f9f2200
3
+ metadata.gz: b47dd283c616c348e2ce6aab4a61cb1e12ec68fa902fcff158e8dc6a7bf63a64
4
+ data.tar.gz: 44a250723c61c458d582fe87b62dab762ee846a687c74d9c5e738a9eb5b14f92
5
5
  SHA512:
6
- metadata.gz: f8796a932c0f10ffd127de92b75d21cae746aae645e57a1a8c40e714c139ef9cd5b833cacd7e1c2259ca298ea8a076d1248c7891aacb49f69066a85bd37cbfce
7
- data.tar.gz: 77ebd8d9a64a7eb2767e35a67befe8cf9f96346e4df97a92b23ad805c71c275252d1965744193d8c9b16d7f7b555323fd7d38b6176bacf32511944dd3296b3a0
6
+ metadata.gz: 2f39b477c86a46e4eaa33535adfd2adea92fe78f2fdc858c893b6eff24ff16c943b73a3a846045fe6af5ce0b80b6251614c17af13ef29d551ce7a198d2636d94
7
+ data.tar.gz: 78e3cf4b67a33b11a3dccb90551ea2382714e5f1ab3b3bdfecdc3cbeabeb45af53178fbd6843fd78859f4e5844afc56656191eba00cffa6431460d8f98858ee7
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) {
@@ -19,12 +19,11 @@ class ApplicationController < ActionController::Base
19
19
  before_action :set_time_zone!
20
20
 
21
21
  before_action :ensure_user_enabled!, if: :current_user?
22
- before_action :validate_active_organization!
23
22
 
24
23
  before_action :redirect_to_proper_context!, if: :immersive_context_wrong?
24
+ before_action :validate_active_organization!
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
 
@@ -43,7 +42,8 @@ class ApplicationController < ActionController::Base
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
@@ -56,7 +56,7 @@ class UsersController < ApplicationController
56
56
  end
57
57
 
58
58
  def notifications
59
- @notifications = @user.notifications.order(created_at: :desc).page(params[:page])
59
+ @notifications = @user.notifications_in_organization.order(created_at: :desc).page(params[:page])
60
60
  end
61
61
 
62
62
  def toggle_read
@@ -108,4 +108,8 @@ class UsersController < ApplicationController
108
108
  def discussion_filter_params
109
109
  @filter_params ||= params.permit([:page])
110
110
  end
111
+
112
+ def authorization_minimum_role
113
+ :ex_student
114
+ end
111
115
  end
@@ -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
@@ -7,6 +7,14 @@ module ContentViewHelper
7
7
  content.name
8
8
  end
9
9
 
10
+ def show_content?(content)
11
+ current_access_mode.show_content?(content)
12
+ end
13
+
14
+ def show_content_element?
15
+ current_access_mode.show_content_element?
16
+ end
17
+
10
18
  private
11
19
 
12
20
  def content_type_number(content)
@@ -8,7 +8,7 @@ module DiscussionsHelper
8
8
  end
9
9
 
10
10
  def solve_discussions_link
11
- discussions_link others_discussions_icon(t(:solve_doubts)), discussions_path(solve_discussion_params_for(current_user)), class: 'dropdown-item'
11
+ discussions_link others_discussions_icon(t(:solve_doubts)), discussions_path(solve_discussion_params_for(current_user)), class: 'dropdown-item' if current_access_mode.resolve_discussions_here?
12
12
  end
13
13
 
14
14
  def user_discussions_link
@@ -71,7 +71,11 @@ module LinksHelper
71
71
  end
72
72
 
73
73
  def faqs_enabled_here?
74
- Organization.current.faqs.present?
74
+ current_access_mode.faqs_here?
75
+ end
76
+
77
+ def profile_enabled_here?
78
+ current_access_mode.profile_here?
75
79
  end
76
80
 
77
81
  private
@@ -1,7 +1,6 @@
1
1
  module MenuBarHelper
2
2
  def menu_bar_links
3
3
  [
4
- menu_link_to_profile,
5
4
  menu_link_to_classroom,
6
5
  menu_link_to_bibliotheca,
7
6
  solve_discussions_link,
@@ -18,7 +17,7 @@ module MenuBarHelper
18
17
  end
19
18
 
20
19
  def menu_link_to_profile
21
- menu_item('user', :my_account, user_path)
20
+ li_tag menu_item('user', :my_account, user_path)
22
21
  end
23
22
 
24
23
  def menu_link_to_classroom
@@ -36,7 +35,7 @@ module MenuBarHelper
36
35
  end
37
36
 
38
37
  def logout_menu_link
39
- li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for))
38
+ li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for, organization: Organization.current))
40
39
  end
41
40
 
42
41
  def menu_item(icon, name, url, css_class = nil, **translation_params)
@@ -16,7 +16,8 @@ module OverlappedButtonsHelper
16
16
  {class: 'mu-content-toolbar-item mu-restart-guide',
17
17
  data: {confirm: t(:confirm_restart)}, method: :delete, 'data-bs-placement': 'top'})
18
18
 
19
- link_to overlapped_button_icon(:undo), guide_progress_path(guide), all_options
19
+ link_to overlapped_button_icon(:undo), guide_progress_path(guide), all_options if show_content_element?
20
+
20
21
  end
21
22
 
22
23
  private