mumuki-laboratory 9.12.1 → 9.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +3 -2
  3. data/app/assets/javascripts/mumuki_laboratory/application/codemirror-builder.js +9 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +5 -4
  5. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +6 -5
  6. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_breadcrumb.scss +11 -0
  7. data/app/controllers/application_controller.rb +15 -4
  8. data/app/controllers/book_controller.rb +6 -0
  9. data/app/controllers/chapters_controller.rb +9 -0
  10. data/app/controllers/discussions_controller.rb +20 -9
  11. data/app/controllers/exam_authorization_requests_controller.rb +1 -1
  12. data/app/controllers/exercise_solutions_controller.rb +1 -1
  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 +8 -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/editor_helper.rb +5 -0
  21. data/app/helpers/exam_registration_helper.rb +1 -1
  22. data/app/helpers/links_helper.rb +5 -1
  23. data/app/helpers/menu_bar_helper.rb +2 -3
  24. data/app/helpers/overlapped_buttons_helper.rb +2 -1
  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 +2 -2
  28. data/app/views/discussions/show.html.erb +5 -5
  29. data/app/views/exam_authorization_requests/_pending.html.erb +12 -5
  30. data/app/views/exam_registrations/show.html.erb +11 -9
  31. data/app/views/exercise_solutions/_results.html.erb +1 -1
  32. data/app/views/guides/_guide.html.erb +2 -2
  33. data/app/views/layouts/_progress_bar.html.erb +1 -0
  34. data/app/views/layouts/_progress_listing.html.erb +1 -0
  35. data/app/views/layouts/application.html.erb +11 -0
  36. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  37. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +6 -2
  38. data/app/views/layouts/exercise_inputs/read_only_editors/_hidden.html.erb +0 -0
  39. data/app/views/layouts/exercise_inputs/read_only_editors/_upload.html.erb +0 -0
  40. data/lib/mumuki/laboratory/controllers/authorization.rb +5 -1
  41. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +15 -0
  42. data/lib/mumuki/laboratory/controllers.rb +1 -0
  43. data/lib/mumuki/laboratory/locales/en.yml +1 -0
  44. data/lib/mumuki/laboratory/locales/es-CL.yml +7 -4
  45. data/lib/mumuki/laboratory/locales/es.yml +7 -4
  46. data/lib/mumuki/laboratory/locales/pt.yml +1 -0
  47. data/lib/mumuki/laboratory/version.rb +1 -1
  48. data/spec/features/menu_bar_spec.rb +3 -2
  49. data/spec/features/not_found_private_flow_spec.rb +1 -1
  50. data/spec/features/read_only_flow_spec.rb +933 -0
  51. metadata +127 -128
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 67eb631fe5c878f8151a859f82bcffc8225e087e59669f600e08ff66cc9034c0
4
- data.tar.gz: ab208353d092d920d65d899ad84aecb58f7f89e15ecae80f6869e09e0df85b1e
3
+ metadata.gz: 5ad2aade204189333166666c1e645465a61863e7157e88a3cb7f7c70fbc56531
4
+ data.tar.gz: 3ba1bd40a4cce76c17a0c33ce377939f67c959ac17d09f324157d8b902a07dc7
5
5
  SHA512:
6
- metadata.gz: 9b33f896c2ae44fee775b055f2dfcd965280452a3ef073a19a8650d0ca4c7e66c223247a28e5d126bc7a8dda256ae74683488d2090f796d97aa390a63bc3c00a
7
- data.tar.gz: 21b1a86962e2edfcb17e09a96ae9983cf89828251cf283b3a0dd2004a8b98b09d495cf75b2995793d02d138aba5eaaba9cda6bf4c8f22e722881a0a312f94c87
6
+ metadata.gz: 404d077ba985d112f9a213b2cf548fe0c57dffe9b09d94472bf32a4505fb8cd90c3d5572d74eb2d03babe20644cdd9c7e7626bc2e3c9865fe665cbee80c95b1d
7
+ data.tar.gz: c1250fb109892e4646adf02f6ddc7b86fc867377a194b0846ff0e38722e116b51357e4d4adf2a66eea06b146f18bf3134d0e35e9ef8519d8adf79d563788610d
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
 
@@ -71,6 +71,15 @@
71
71
  return this;
72
72
  }
73
73
 
74
+ setupSpellCheckedEditor() {
75
+ this.editor = this.createEditor({
76
+ inputStyle: 'contenteditable',
77
+ spellcheck: true
78
+ });
79
+
80
+ return this;
81
+ }
82
+
74
83
  setupLanguage(language) {
75
84
  var highlightMode = language || this.$textarea.data('editor-language');
76
85
  if (highlightMode === 'dynamic') {
@@ -9,12 +9,13 @@ mumuki.load(() => {
9
9
 
10
10
  function createNewMessageEditor() {
11
11
  var $textarea = $("#discussion-new-message");
12
- var textarea = $textarea[0];
13
- if (!textarea) return;
12
+ var editorContainer = $(".mu-spell-checked-editor")[0];
13
+ if (!editorContainer) return;
14
14
 
15
- return new mumuki.editor.CodeMirrorBuilder(textarea)
16
- .setupSimpleEditor()
15
+ return new mumuki.editor.CodeMirrorBuilder(editorContainer)
16
+ .setupSpellCheckedEditor()
17
17
  .setupMinLines($textarea.data('lines'))
18
+ .setupLanguage()
18
19
  .build();
19
20
  }
20
21
 
@@ -27,11 +27,12 @@ mumuki.renderers.results = (() => {
27
27
  */
28
28
  function classForStatus(status) {
29
29
  switch (status) {
30
- case "errored": return "broken";
31
- case "failed": return "danger";
32
- case "passed_with_warnings": return "warning";
33
- case "passed": return "success";
34
- case "pending": return "muted";
30
+ case "errored": return "broken";
31
+ case "failed": return "danger";
32
+ case "manual_evaluation_pending": return "info";
33
+ case "passed_with_warnings": return "warning";
34
+ case "passed": return "success";
35
+ case "pending": return "muted";
35
36
  }
36
37
  }
37
38
 
@@ -38,3 +38,14 @@
38
38
  max-width: 70vw;
39
39
  }
40
40
  }
41
+
42
+ .mu-read-only {
43
+ display: flex;
44
+ flex-direction: column;
45
+ justify-content: center;
46
+ align-content: center;
47
+ background-color: $mu-color-complementary;
48
+ color: white;
49
+ text-align: center;
50
+ padding: 15px;
51
+ }
@@ -19,14 +19,14 @@ 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?
29
+ before_action :ensure_restore_progress!, if: :current_user?
30
30
 
31
31
  before_action :visit_organization!, if: :current_user?
32
32
 
@@ -43,7 +43,8 @@ class ApplicationController < ActionController::Base
43
43
  :current_immersive_organizations,
44
44
  :theme_stylesheet_url,
45
45
  :extension_javascript_url,
46
- :current_immersive_path
46
+ :current_immersive_path,
47
+ :current_access_mode
47
48
 
48
49
  add_flash_types :info
49
50
 
@@ -72,7 +73,7 @@ class ApplicationController < ActionController::Base
72
73
 
73
74
  def validate_active_organization!
74
75
  return if current_user&.teacher_here?
75
- Organization.current.validate_active!
76
+ Organization.current.validate_active_for! current_user
76
77
  end
77
78
 
78
79
  # required by Mumukit::Login
@@ -160,4 +161,14 @@ class ApplicationController < ActionController::Base
160
161
  def leave_organization!
161
162
  Mumukit::Platform::Organization.leave!
162
163
  end
164
+
165
+ def current_access_mode
166
+ Organization.current.access_mode(current_user)
167
+ end
168
+
169
+ def ensure_restore_progress!
170
+ if current_access_mode.restore_indicators?(Organization.current.book)
171
+ current_user.restore_organization_progress!(Organization.current)
172
+ end
173
+ end
163
174
  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
 
@@ -38,17 +39,19 @@ class DiscussionsController < ApplicationController
38
39
  end
39
40
 
40
41
  def responsible
41
- if subject.can_toggle_responsible? current_user
42
- subject.toggle_responsible! current_user
42
+ subject.with_pg_lock proc {
43
+ if subject.can_toggle_responsible? current_user
44
+ subject.toggle_responsible! current_user
43
45
 
44
- set_flash_responsible_confirmation!
45
- status = :ok
46
- else
47
- set_flash_responsible_alert!
48
- status = :conflict
49
- end
46
+ set_flash_responsible_confirmation!
47
+ status = :ok
48
+ else
49
+ set_flash_responsible_alert!
50
+ status = :conflict
51
+ end
50
52
 
51
- render :partial => 'layouts/toast', status: status
53
+ render :partial => 'layouts/toast', status: status
54
+ }
52
55
  end
53
56
 
54
57
  def create
@@ -99,4 +102,12 @@ class DiscussionsController < ApplicationController
99
102
  def discussion_filter_params
100
103
  @filter_params ||= params.permit(Discussion.permitted_query_params)
101
104
  end
105
+
106
+ def authorization_minimum_role
107
+ :ex_student
108
+ end
109
+
110
+ def validate_access_mode!
111
+ current_access_mode.validate_discuss_here! subject
112
+ end
102
113
  end
@@ -31,6 +31,6 @@ class ExamAuthorizationRequestsController < ApplicationController
31
31
 
32
32
  def verify_registration_opened!
33
33
  exam_registration = ExamRegistration.find(authorization_request_params[:exam_registration_id])
34
- raise Mumuki::Domain::GoneError if exam_registration.end_time.past?
34
+ raise Mumuki::Domain::GoneError if exam_registration.ended?
35
35
  end
36
36
  end
@@ -8,7 +8,7 @@ class ExerciseSolutionsController < AjaxController
8
8
 
9
9
  def create
10
10
  assignment = @exercise.try_submit_solution!(current_user, solution_params)
11
- render_results_json assignment, status: assignment.status
11
+ render_results_json assignment, status: assignment.visible_status
12
12
  end
13
13
 
14
14
  private
@@ -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
@@ -29,7 +29,10 @@ class UsersController < ApplicationController
29
29
  end
30
30
 
31
31
  def discussions
32
- @watched_discussions = current_user.watched_discussions_in_organization.scoped_query_by(discussion_filter_params).unread_first
32
+ @watched_discussions = current_user.watched_discussions_in_organization
33
+ .where(exercise: Organization.current.exercises)
34
+ .scoped_query_by(discussion_filter_params)
35
+ .unread_first
33
36
  end
34
37
 
35
38
  def activity
@@ -108,4 +111,8 @@ class UsersController < ApplicationController
108
111
  def discussion_filter_params
109
112
  @filter_params ||= params.permit([:page])
110
113
  end
114
+
115
+ def authorization_minimum_role
116
+ :ex_student
117
+ end
111
118
  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
@@ -10,4 +10,9 @@ module EditorHelper
10
10
  editor_options = editor_defaults(language, options, 'read-only-editor')
11
11
  text_area_tag :solution_content, content, editor_options
12
12
  end
13
+
14
+ def spell_checked_editor(name, options = {})
15
+ editor_options = editor_defaults('markdown', options, 'form-control mu-spell-checked-editor')
16
+ text_area_tag name, '', editor_options
17
+ end
13
18
  end
@@ -1,6 +1,6 @@
1
1
  module ExamRegistrationHelper
2
2
  def exam_registration_view
3
- if @registration.end_time.past?
3
+ if @registration.ended?
4
4
  { icon: :times_circle, class: :danger, t: :exam_registration_finished_html }
5
5
  else
6
6
  { icon: :info_circle, class: :info, t: :exam_registration_explanation_html }
@@ -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
@@ -6,16 +6,25 @@
6
6
  <%= render partial: 'book/header' %>
7
7
 
8
8
  <% @book.next_lesson_for(current_user).try do |lesson| %>
9
- <div class="actions">
10
- <a href="<%= lesson_path(lesson) %>" class="btn btn-complementary">
11
- <%= t book_practice_key_for(current_user) %>
12
- </a>
13
- </div>
9
+ <% if show_content_element? %>
10
+ <div class="actions">
11
+ <a href="<%= lesson_path(lesson) %>" class="btn btn-complementary">
12
+ <%= t book_practice_key_for(current_user) %>
13
+ </a>
14
+ </div>
15
+ <% else %>
16
+ <br>
17
+ <% end %>
14
18
  <% end %>
15
19
 
16
- <h2><%= t(:chapters) %></h2>
20
+ <% if show_content?(@book) %>
21
+ <h2><%= t(:chapters) %></h2>
22
+ <% end %>
17
23
 
18
24
  <% @book.chapter_visibilities_in(current_workspace).each do |it, enabled| %>
25
+
26
+ <% next unless show_content?(it.topic) %>
27
+
19
28
  <div class="chapter-container">
20
29
  <div class="chapter <%= enabled ? '' : 'mu-locked' %>">
21
30
  <h3><%= it.number %>. <%= link_to_path_element it, mode: :plain %></h3>
@@ -24,7 +33,7 @@
24
33
  </div>
25
34
  </div>
26
35
 
27
- <% unless enabled %>
36
+ <% unless enabled %>
28
37
  <div class="text-center mu-lock">
29
38
  <i class="fas fa-lock fa-5x"></i>
30
39
  <p><%= t :locked_content %></p>