mumuki-laboratory 9.12.0 → 9.13.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) 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/stylesheets/mumuki_laboratory/application/modules/_breadcrumb.scss +11 -0
  6. data/app/controllers/application_controller.rb +15 -4
  7. data/app/controllers/book_controller.rb +6 -0
  8. data/app/controllers/chapters_controller.rb +9 -0
  9. data/app/controllers/discussions_controller.rb +20 -9
  10. data/app/controllers/exercises_controller.rb +5 -0
  11. data/app/controllers/faqs_controller.rb +4 -0
  12. data/app/controllers/lessons_controller.rb +9 -0
  13. data/app/controllers/users_controller.rb +8 -1
  14. data/app/helpers/concerns/with_student_path_navigation.rb +1 -1
  15. data/app/helpers/content_view_helper.rb +8 -0
  16. data/app/helpers/discussions_helper.rb +1 -1
  17. data/app/helpers/editor_helper.rb +5 -0
  18. data/app/helpers/links_helper.rb +5 -1
  19. data/app/helpers/menu_bar_helper.rb +2 -3
  20. data/app/helpers/overlapped_buttons_helper.rb +2 -1
  21. data/app/views/book/show.html.erb +16 -7
  22. data/app/views/chapters/show.html.erb +1 -0
  23. data/app/views/discussions/_new_message.html.erb +2 -2
  24. data/app/views/discussions/show.html.erb +5 -5
  25. data/app/views/exercise_solutions/_results.html.erb +1 -1
  26. data/app/views/guides/_guide.html.erb +2 -2
  27. data/app/views/layouts/_progress_bar.html.erb +1 -0
  28. data/app/views/layouts/_progress_listing.html.erb +1 -0
  29. data/app/views/layouts/application.html.erb +11 -0
  30. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  31. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +6 -2
  32. data/app/views/layouts/exercise_inputs/read_only_editors/_hidden.html.erb +0 -0
  33. data/app/views/layouts/exercise_inputs/read_only_editors/_upload.html.erb +0 -0
  34. data/lib/mumuki/laboratory/controllers/authorization.rb +5 -1
  35. data/lib/mumuki/laboratory/controllers/validate_access_mode.rb +15 -0
  36. data/lib/mumuki/laboratory/controllers.rb +1 -0
  37. data/lib/mumuki/laboratory/locales/en.yml +1 -0
  38. data/lib/mumuki/laboratory/locales/es-CL.yml +1 -0
  39. data/lib/mumuki/laboratory/locales/es.yml +1 -0
  40. data/lib/mumuki/laboratory/locales/pt.yml +1 -0
  41. data/lib/mumuki/laboratory/mailers/message_delivery.rb +1 -1
  42. data/lib/mumuki/laboratory/version.rb +1 -1
  43. data/spec/features/menu_bar_spec.rb +3 -2
  44. data/spec/features/not_found_private_flow_spec.rb +1 -1
  45. data/spec/features/read_only_flow_spec.rb +933 -0
  46. metadata +13 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 81fd1285c7db44af533a878ee10b6388976451e1d9b493c5aabf5db6516855bd
4
- data.tar.gz: b76dcfb86b425f80fa31e2683561d897c7e047fb7ff538319317c1cee40a327c
3
+ metadata.gz: 19427492957bc679a64b679d1b50e33e38a750a032e9ca916fa8ba543eef8c03
4
+ data.tar.gz: bab7ef25dbc9e0970471fca58b4d8e3a1efd592d9f34d092e6f0eeed4c8eeb5d
5
5
  SHA512:
6
- metadata.gz: 9d583004159cc6bc0d88e318b3b0da5c9363b61a2b57f228f25f9ee19a016acb518f420b70015254367b2dd930b1887ae1ef61332cd349982251a1ecce995199
7
- data.tar.gz: fec344b55bec29c42ca6b8017b7500ec08e0209aaf7649391890268adcc6f626077cb91040bfb2f391a38fe0a67e7af266b93fa0ccb28b6e01c2f9a5ebe9076b
6
+ metadata.gz: bb031814c4d6bc24bcfe00e988923cbdaf0e9cdd6e033d37fd09542736be3a6d9489bc41b5b9cb85bafd52b461ef48a2483082e441a2e92a02370135ba77b3be
7
+ data.tar.gz: eb0b4b0e606f74d5e679b77cb05088eebcbd651e733730acfee0f28fb15d5aecaa490d011d5bc4319cda746951419c5a85f9398472a9aa4cf40b5f5408dc5f3a
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
 
@@ -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
@@ -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
@@ -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>
@@ -28,6 +28,7 @@
28
28
  <h3><%= t(:lessons) %></h3>
29
29
 
30
30
  <% @chapter.lessons.includes(guide: :exercises).each do |lesson| %>
31
+ <% next unless show_content?(lesson.guide) %>
31
32
  <h4><%= lesson.number %>. <%= link_to_path_element lesson, mode: :plain %></h4>
32
33
  <%= render partial: 'layouts/progress_listing', locals: { guide: lesson.guide } %>
33
34
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= form_for [@discussion, Message.new] do |f| %>
2
- <%= render layout: 'discussions/message_container', locals: {user: user} do %>
2
+ <%= render layout: 'discussions/message_container', locals: { user: user } do %>
3
3
  <div class="discussion-message-bubble">
4
4
  <div class="discussion-message-bubble-header">
5
5
  <div class="discussion-message-bubble-title">
@@ -10,7 +10,7 @@
10
10
  <div class="container-fluid">
11
11
  <div class="row">
12
12
  <div class="discussion-new-message-content">
13
- <%= f.editor :content, '', { id: 'discussion-new-message', class: 'form-control', placeholder: t(:message) } %>
13
+ <%= spell_checked_editor 'message[content]', { id: 'discussion-new-message', placeholder: t(:message) } %>
14
14
  </div>
15
15
  <div class="discussion-message-content d-none" id="discussion-new-message-preview"></div>
16
16
  </div>