mumuki-laboratory 9.4.1 → 9.5.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 57bb3a9beabe4c776c88430fb3d6b0f2cc00328da5657f8f4c72bf568cf0d5df
4
- data.tar.gz: 2375d1c0b8eeda4ac72f71d4b36ed83fc759fc3db1be5db69bb48d331d386aa0
3
+ metadata.gz: 598ef4d5c4426c6ea43779dc40f73a06e9ed5711d4e5888d093449095c05d5e9
4
+ data.tar.gz: eb8d8d5c9348f0408b299d1597c940961dd822787726f646f62e4a3072af8b93
5
5
  SHA512:
6
- metadata.gz: 81d9b2bc3d633cc66105cd4f9052fb13c170bde9aeffb25878ea5e7e3d62a40c0db3824f6da132c82491d1d914ee869aee35a580ce323aa648845ff3b263929c
7
- data.tar.gz: ec9fc1503a59f7caccaf5e0fac8f92bc93556ee08538904b8d92f30e8d965868fdf01c30df91594cdd2a2853e2d85229c97845d37e3e9a9b0344948ef84ce947
6
+ metadata.gz: 0f330db4125a3b789d48a458f3de9c2106f2af89343a381d8cd2a89530ce185d8645cbaab42063ecdd2afb0c75ad40d1a52fb085bd29b656fb7e2953e6a0c92e
7
+ data.tar.gz: 7fe289dcbf8ec42865983f42cf4c27a37fb64dcafbe875588dba9c9cef85f74cfa75584da4d4a0a3cde6b598902876928e261ee5c2d01e460a9489f80b50335e
@@ -1,6 +1,7 @@
1
1
  mumuki.load(() => {
2
2
  var $subscriptionButtons = $('.discussion-subscription > button');
3
3
  var $upvoteButtons = $('.discussion-upvote > button');
4
+ var $responsibleButton = $('.discussion-responsible > button');
4
5
  let $messagePreviewButton = $('.discussion-new-message-preview-button.preview');
5
6
  let $messageEditButton = $('.discussion-new-message-preview-button.edit');
6
7
  let $newMessageContent = $('.discussion-new-message-content');
@@ -53,9 +54,23 @@ mumuki.load(() => {
53
54
  discussionUpvote: function (url) {
54
55
  Forum.discussionPostAndToggle(url, $upvoteButtons);
55
56
  },
57
+ discussionResponsible: function (url) {
58
+ Forum.discussionPostToggleAndRenderToast(url, $responsibleButton);
59
+ $('.responsible-moderator-badge').toggleClass('d-none');
60
+ },
56
61
  discussionPostAndToggle: function (url, elem) {
57
62
  Forum.discussionPost(url).done(Forum.toggleButton(elem));
58
63
  },
64
+ discussionPostToggleAndRenderToast: function (url, elem) {
65
+ Forum.discussionPost(url)
66
+ .done(function (response) {
67
+ Forum.toggleButton(elem);
68
+ mumuki.toast.addToast(response);
69
+ })
70
+ .fail(function (response) {
71
+ mumuki.toast.addToast(response.responseText);
72
+ });
73
+ },
59
74
  discussionMessageToggleApprove: function (url, elem) {
60
75
  Forum.discussionPost(url).done(function () {
61
76
  elem.toggleClass("selected");
@@ -1,3 +1,14 @@
1
+ mumuki.toast = {
2
+ load() {
3
+ document.querySelectorAll('.toast').forEach((toast) => new bootstrap.Toast(toast).show());
4
+ },
5
+
6
+ addToast(content) {
7
+ $('.toast-container').html(content);
8
+ mumuki.toast.load();
9
+ }
10
+ };
11
+
1
12
  mumuki.load(() => {
2
- document.querySelectorAll('.toast').forEach((toast) => new bootstrap.Toast(toast).show());
13
+ mumuki.toast.load();
3
14
  });
@@ -2,10 +2,11 @@ class DiscussionsController < ApplicationController
2
2
  include Mumuki::Laboratory::Controllers::Content
3
3
  include WithUserDiscussionValidation
4
4
 
5
- before_action :set_debatable, except: [:subscription]
5
+ before_action :set_debatable, except: [:subscription, :responsible]
6
6
  before_action :authenticate!, only: [:update, :create]
7
7
  before_action :discussion_filter_params, only: :index
8
8
  before_action :read_discussion, only: :show
9
+ before_action :authorize_moderator!, only: [:responsible]
9
10
 
10
11
  helper_method :discussion_filter_params
11
12
 
@@ -19,7 +20,6 @@ class DiscussionsController < ApplicationController
19
20
  end
20
21
 
21
22
  def show
22
- @discussion.update_last_moderator_access! current_user
23
23
  end
24
24
 
25
25
  def update
@@ -37,6 +37,20 @@ class DiscussionsController < ApplicationController
37
37
  head :ok
38
38
  end
39
39
 
40
+ def responsible
41
+ if subject.can_toggle_responsible? current_user
42
+ subject.toggle_responsible! current_user
43
+
44
+ set_flash_responsible_confirmation!
45
+ status = :ok
46
+ else
47
+ set_flash_responsible_alert!
48
+ status = :conflict
49
+ end
50
+
51
+ render :partial => 'layouts/toast', status: status
52
+ end
53
+
40
54
  def create
41
55
  discussion = @debatable.discuss! current_user, discussion_params
42
56
  redirect_to [@debatable, discussion]
@@ -57,6 +71,19 @@ class DiscussionsController < ApplicationController
57
71
  @debatable = Discussion.debatable_for(@debatable_class, params)
58
72
  end
59
73
 
74
+ def set_flash_responsible_confirmation!
75
+ subject.any_responsible? ?
76
+ flash.now.notice = I18n.t('moderator_take_care.you_will_confirmation') :
77
+ flash.now.notice = I18n.t('moderator_take_care.you_wont_confirmation')
78
+
79
+ end
80
+
81
+ def set_flash_responsible_alert!
82
+ subject.any_responsible? ?
83
+ flash.now.alert = I18n.t('moderator_take_care.someone_else_will') :
84
+ flash.now.alert = I18n.t('moderator_take_care.status_changed')
85
+ end
86
+
60
87
  def subject
61
88
  @discussion ||= Discussion.find_by(id: params[:id])
62
89
  end
@@ -196,6 +196,22 @@ module DiscussionsHelper
196
196
  content_tag :a, discussion_user_name(user)
197
197
  end
198
198
 
199
+ def responsible_moderator_text_for(discussion, user)
200
+ if discussion.responsible?(user)
201
+ t('moderator_take_care.you_are')
202
+ else
203
+ t('moderator_take_care.moderator_is', moderator: discussion_user_name(@discussion.responsible_moderator_by))
204
+ end
205
+ end
206
+
207
+ def responsible_icon
208
+ fa_icon 'hand-paper', text: t('moderator_take_care.i_will')
209
+ end
210
+
211
+ def not_responsible_icon
212
+ fa_icon 'hand-rock', type: :regular, text: t('moderator_take_care.i_wont')
213
+ end
214
+
199
215
  def subscription_icon
200
216
  fa_icon :bell, text: t(:subscribe)
201
217
  end
@@ -15,6 +15,12 @@
15
15
  <h3 class="flex-grow-1 me-3"><%= t :messages %></h3>
16
16
  <% if current_user && @discussion.persisted? %>
17
17
  <span class="d-flex">
18
+ <% if @discussion.can_toggle_responsible?(current_user) %>
19
+ <div class="discussion-responsible me-1">
20
+ <%= btn_toggle responsible_icon, not_responsible_icon, @discussion.any_responsible?, class: 'btn-sm',
21
+ onclick: "mumuki.Forum.discussionResponsible('#{responsible_discussion_url(@discussion)}')" %>
22
+ </div>
23
+ <% end %>
18
24
  <% if @discussion.subscribable? %>
19
25
  <div class="discussion-subscription me-1">
20
26
  <%= btn_toggle subscription_icon, unsubscription_icon, current_user.subscribed_to?(@discussion), class: 'btn-sm',
@@ -31,11 +31,10 @@
31
31
  <span><%= discussion_info(@discussion) unless @discussion.new_record? %></span>
32
32
  </span>
33
33
  </div>
34
- <% if @discussion.last_moderator_access_visible_for?(current_user) %>
34
+ <% if @discussion.current_responsible_visible_for?(current_user) %>
35
35
  <h5 class="my-2 me-3">
36
- <span class="badge bg-primary text-wrap">
37
- <%= t :last_seen, name: discussion_user_name(@discussion.last_moderator_access_by) %>
38
- <%= t :time_since, time: time_ago_in_words(@discussion.last_moderator_access_at) %>
36
+ <span class="badge bg-primary text-wrap responsible-moderator-badge">
37
+ <%= responsible_moderator_text_for(@discussion, current_user) %>
39
38
  </span>
40
39
  </h5>
41
40
  <% end %>
@@ -58,11 +58,11 @@
58
58
  </span>
59
59
  <span class="d-none d-lg-inline"><%= discussion.exercise.guide.name %> -</span>
60
60
  <span><%= discussion.exercise.name %></span>
61
- <% if discussion.last_moderator_access_visible_for?(current_user) %>
61
+ <% if discussion.current_responsible_visible_for?(current_user) %>
62
62
  <div class="float-end discussion-moderator-access" >
63
- <%= profile_picture_for(discussion.last_moderator_access_by, height: 32) %>
63
+ <%= profile_picture_for(discussion.responsible_moderator_by, height: 32) %>
64
64
  <span class="moderator-initials">
65
- <%= discussion.last_moderator_access_by.name_initials %>
65
+ <%= discussion.responsible_moderator_by.name_initials %>
66
66
  </span>
67
67
  </div>
68
68
  <% end %>
@@ -28,17 +28,11 @@
28
28
 
29
29
  <%= yield :navbar %>
30
30
 
31
- <div class="<%= exercise_container_type %><%= ' mb-5' unless input_kids? %>" id="wrap">
32
- <div class="toast-container bottom-0 start-0 p-3">
33
- <% if notice %>
34
- <%= toast_notice(notice) %>
35
- <% elsif alert %>
36
- <%= toast_alert(alert) %>
37
- <% elsif info %>
38
- <%= toast_info(info) %>
39
- <% end %>
40
- </div>
31
+ <div class="toast-container bottom-0 start-0 p-3">
32
+ <%= render partial: 'layouts/toast' %>
33
+ </div>
41
34
 
35
+ <div class="<%= exercise_container_type %><%= ' mb-5' unless input_kids? %>" id="wrap">
42
36
  <%= yield %>
43
37
  </div>
44
38
 
@@ -0,0 +1,7 @@
1
+ <% if notice %>
2
+ <%= toast_notice(notice) %>
3
+ <% elsif alert %>
4
+ <%= toast_alert(alert) %>
5
+ <% elsif info %>
6
+ <%= toast_info(info) %>
7
+ <% end %>
data/config/routes.rb CHANGED
@@ -12,6 +12,8 @@ Rails.application.routes.draw do
12
12
  end
13
13
  end
14
14
 
15
+ post '/discussions/:id/responsible' => 'discussions#responsible', as: :responsible_discussion
16
+
15
17
  get '/discussions/terms' => 'book_discussions#terms'
16
18
  concerns :debatable, controller: 'book_discussions', only: :index
17
19
 
@@ -168,7 +168,6 @@ en:
168
168
  kids_default_success: You did it great!
169
169
  language: Language
170
170
  last_name: Last Name
171
- last_seen: "Seen by %{name}"
172
171
  last_submission_date: Last submission
173
172
  latest_exercises: Latest exercises
174
173
  learning: Learning
@@ -190,11 +189,20 @@ en:
190
189
  message_deleted: This message was deleted because it %{motive}, which violates the %{forum_terms}.
191
190
  messages: Messages
192
191
  messages_error: Previous messages are unavailable. Please try again later.
193
- moderation: Mentoring
194
192
  messages_pluralized:
195
193
  one: Message
196
194
  other: Messages
195
+ moderation: Mentoring
197
196
  moderator: Mentor
197
+ moderator_take_care:
198
+ i_will: I'll take care of it
199
+ i_wont: I won't take care of it
200
+ moderator_is: "%{moderator} is taking care of it"
201
+ status_changed: The discussion status changed. Please refresh the page.
202
+ someone_else_will: Someone else already marked they'll take care of it.
203
+ you_are: You're taking care of it
204
+ you_will_confirmation: You'll take care of this discussion. If you change your mind, click the button again.
205
+ you_wont_confirmation: You won't take care of this discussion.
198
206
  more_messages: More
199
207
  my_account: My account
200
208
  my_doubts: My doubts
@@ -197,6 +197,15 @@ es-CL:
197
197
  minutes: minutos
198
198
  moderation: Mentoría
199
199
  moderator: Mentor
200
+ moderator_take_care:
201
+ i_will: Yo me ocupo
202
+ i_wont: Mejor no me ocupo
203
+ moderator_is: "%{moderator} se está ocupando"
204
+ status_changed: La consulta cambió de estado. Por favor, actualiza la página.
205
+ someone_else_will: Alguien más ya marcó que se va a ocupar.
206
+ you_are: Te estás ocupando
207
+ you_will_confirmation: Te vas a ocupar de esta consulta. Si cambias de parecer, toca nuevamente el botón.
208
+ you_wont_confirmation: No te vas a ocupar de esta consulta.
200
209
  more_messages: Ver mensajes anteriores
201
210
  my_account: Mi cuenta
202
211
  my_doubts: Mis consultas
@@ -179,7 +179,6 @@ es:
179
179
  language: Lenguaje
180
180
  languages: Lenguajes
181
181
  last_name: Apellido
182
- last_seen: "Visto por %{name}"
183
182
  last_submission_date: Última solución
184
183
  latest_exercises: Últimos ejercicios
185
184
  learning: Aprendizaje
@@ -208,6 +207,15 @@ es:
208
207
  minutes: minutos
209
208
  moderation: Mentoría
210
209
  moderator: Mentor
210
+ moderator_take_care:
211
+ i_will: Yo me ocupo
212
+ i_wont: Mejor no me ocupo
213
+ moderator_is: "%{moderator} se está ocupando"
214
+ status_changed: La consulta cambió de estado. Por favor, actualizá la página.
215
+ someone_else_will: Alguien más ya marcó que se va a ocupar.
216
+ you_are: Te estás ocupando
217
+ you_will_confirmation: Te vas a ocupar de esta consulta. Si cambiás de parecer, tocá nuevamente el botón.
218
+ you_wont_confirmation: No te vas a ocupar de esta consulta.
211
219
  more_messages: Ver mensajes anteriores
212
220
  my_account: Mi cuenta
213
221
  my_doubts: Mis consultas
@@ -172,7 +172,6 @@ pt:
172
172
  language: Linguagem
173
173
  languages: Linguagens
174
174
  last_name: Sobrenome
175
- last_seen: "Visto por %{name}"
176
175
  last_submission_date: Última solução
177
176
  latest_exercises: Últimos exercícios
178
177
  learning: Aprendendo
@@ -202,6 +201,15 @@ pt:
202
201
  minutes: minutos
203
202
  moderation: Mentoria
204
203
  moderator: Mentor
204
+ moderator_take_care:
205
+ i_will: Eu cuidarei disso
206
+ i_wont: Eu não vou cuidar
207
+ moderator_is: "%{moderator} está cuidando"
208
+ status_changed: A consulta mudou de estado. Por favor, atualize para ver a consulta atualizada.
209
+ someone_else_will: Alguém já marcou que vai cuidar disso.
210
+ you_are: Você está cuidando
211
+ you_will_confirmation: Você vai cuidar dessa consulta. Se mudar de ideia, clique no botão novamente.
212
+ you_wont_confirmation: Você não vai cuidar dessa consulta.
205
213
  more_messages: Ver as mensagens anteriores
206
214
  my_account: Minha conta
207
215
  my_doubts: Minhas duvidas
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Laboratory
3
- VERSION = '9.4.1'
3
+ VERSION = '9.5.0'
4
4
  end
5
5
  end
@@ -62,6 +62,48 @@ describe DiscussionsController, organization_workspace: :test do
62
62
  end
63
63
  end
64
64
 
65
+ describe 'responsible' do
66
+ let(:discussion) { create(:discussion, {organization: Organization.current}) }
67
+
68
+ describe 'user wants to be responsible' do
69
+ before { post :responsible, params: {id: discussion.id} }
70
+
71
+ it { expect(response.status).to eq 403 }
72
+ it { expect(discussion.reload.responsible? user).to be false }
73
+ end
74
+
75
+ describe 'moderator' do
76
+ let(:moderator) { create(:user, permissions: {student: '*', moderator: '*'}) }
77
+ before { set_current_user! moderator }
78
+
79
+ describe 'wants to be responsible' do
80
+ before { post :responsible, params: {id: discussion.id} }
81
+
82
+ it { expect(response.status).to eq 200 }
83
+ it { expect(discussion.reload.responsible? moderator).to be true }
84
+ end
85
+
86
+ describe 'wants to be responsible but changes their mind' do
87
+ before { 2.times{ post :responsible, params: {id: discussion.id} } }
88
+
89
+ it { expect(response.status).to eq 200 }
90
+ it { expect(discussion.reload.responsible? moderator).to be false }
91
+ end
92
+
93
+ describe 'wants to be responsible but someone else already is' do
94
+ let(:another_moderator) { create(:user, permissions: {student: '*', moderator: '*'}) }
95
+ before do
96
+ discussion.toggle_responsible! another_moderator
97
+ post :responsible, params: {id: discussion.id}
98
+ end
99
+
100
+ it { expect(response.status).to eq 409 }
101
+ it { expect(discussion.reload.responsible? moderator).to be false }
102
+ it { expect(discussion.reload.responsible? another_moderator).to be true }
103
+ end
104
+ end
105
+ end
106
+
65
107
  describe 'when the minimal role for discussions is teacher' do
66
108
  before { Organization.current.tap { |it| it.forum_discussions_minimal_role = 'teacher' }.save! }
67
109
 
@@ -10,7 +10,7 @@
10
10
  #
11
11
  # It's strongly recommended that you check this file into your version control system.
12
12
 
13
- ActiveRecord::Schema.define(version: 20210330175706) do
13
+ ActiveRecord::Schema.define(version: 20210518100153) do
14
14
 
15
15
  # These are extensions that must be enabled in order to support this database
16
16
  enable_extension "plpgsql"
@@ -151,8 +151,8 @@ ActiveRecord::Schema.define(version: 20210330175706) do
151
151
  t.integer "messages_count", default: 0
152
152
  t.integer "validated_messages_count", default: 0
153
153
  t.boolean "requires_moderator_response", default: true
154
- t.string "last_moderator_access_by_id"
155
- t.datetime "last_moderator_access_at"
154
+ t.string "responsible_moderator_by_id"
155
+ t.datetime "responsible_moderator_at"
156
156
  t.bigint "status_updated_by_id"
157
157
  t.datetime "status_updated_at"
158
158
  t.index ["initiator_id"], name: "index_discussions_on_initiator_id"
@@ -174,6 +174,7 @@ feature 'Discussion Flow', organization_workspace: :test do
174
174
  expect(page).to have_text(problem_2.name)
175
175
  expect(page).to have_text('Open')
176
176
  expect(page).to have_text('Messages')
177
+ expect(page).to_not have_text('I\'ll take care')
177
178
  expect(page).not_to have_text('Preview')
178
179
  expect(page).to have_text(problem_2_discussion_message.content)
179
180
  expect(page).not_to have_text(another_problem_2_discussion_message.content)
@@ -188,20 +189,43 @@ feature 'Discussion Flow', organization_workspace: :test do
188
189
  context 'for moderator' do
189
190
  before { set_current_user! moderator }
190
191
 
191
- scenario 'newly created discussion' do
192
- visit current_path
193
- expect(page).to have_text(problem_2.name)
194
- expect(page).to have_text('Open')
195
- expect(page).to have_text('Messages')
196
- expect(page).to have_text('Preview')
197
- expect(page).to have_text(problem_2_discussion_message.content)
198
- expect(page).to have_text(another_problem_2_discussion_message.content)
199
- expect(page).to have_xpath("//div[@class='discussion-actions']")
200
- expect(page).to have_text('Includes inappropriate content')
201
- expect(page).to have_text('Shares the correct solution')
202
- expect(page).to have_text('Discloses personal information')
203
- expect(page).to have_text('included inappropriate content')
204
- expect(page).to have_text ("Deleted by #{moderator.name}")
192
+ context 'on a newly created discussion' do
193
+ scenario do
194
+ visit current_path
195
+ expect(page).to have_text(problem_2.name)
196
+ expect(page).to have_text('Open')
197
+ expect(page).to have_text('Messages')
198
+ expect(page).to have_text('I\'ll take care')
199
+ expect(page).to have_text('Preview')
200
+ expect(page).to have_text(problem_2_discussion_message.content)
201
+ expect(page).to have_text(another_problem_2_discussion_message.content)
202
+ expect(page).to have_xpath("//div[@class='discussion-actions']")
203
+ expect(page).to have_text('Includes inappropriate content')
204
+ expect(page).to have_text('Shares the correct solution')
205
+ expect(page).to have_text('Discloses personal information')
206
+ expect(page).to have_text('included inappropriate content')
207
+ expect(page).to have_text ("Deleted by #{moderator.name}")
208
+ end
209
+ end
210
+
211
+ context 'on a discussion where they are responsible' do
212
+ before { problem_2_discussions.first.toggle_responsible! moderator }
213
+
214
+ scenario do
215
+ visit current_path
216
+ expect(page).to have_text('I won\'t take care')
217
+ end
218
+ end
219
+
220
+ context 'on a discussion where someone else is responsible' do
221
+ let(:another_moderator) { create(:user, permissions: {moderator: '*', student: '*'}) }
222
+ before { problem_2_discussions.first.toggle_responsible! another_moderator }
223
+
224
+ scenario do
225
+ visit current_path
226
+ expect(page).not_to have_text('I\'ll take care')
227
+ expect(page).not_to have_text('I won\'t take care')
228
+ end
205
229
  end
206
230
  end
207
231
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumuki-laboratory
3
3
  version: !ruby/object:Gem::Version
4
- version: 9.4.1
4
+ version: 9.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-06-08 00:00:00.000000000 Z
11
+ date: 2021-06-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 9.4.0
33
+ version: 9.5.0
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 9.4.0
40
+ version: 9.5.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mumukit-bridge
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -648,6 +648,7 @@ files:
648
648
  - app/views/layouts/_terms_acceptance_disclaimer.html.erb
649
649
  - app/views/layouts/_test_results.html.erb
650
650
  - app/views/layouts/_timer.html.erb
651
+ - app/views/layouts/_toast.html.erb
651
652
  - app/views/layouts/_user_menu.html.erb
652
653
  - app/views/layouts/application.html.erb
653
654
  - app/views/layouts/embedded.html.erb