mumuki-laboratory 9.11.0 → 9.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/bridge.js +15 -9
  3. data/app/assets/javascripts/mumuki_laboratory/application/confirmation.js +10 -8
  4. data/app/assets/javascripts/mumuki_laboratory/application/editors.js +5 -3
  5. data/app/assets/javascripts/mumuki_laboratory/application/progress.js +4 -4
  6. data/app/assets/javascripts/mumuki_laboratory/application/results-renderer.js +12 -1
  7. data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +8 -2
  8. data/app/controllers/users_controller.rb +1 -1
  9. data/app/helpers/time_zone_helper.rb +5 -0
  10. data/app/mailers/user_mailer.rb +2 -0
  11. data/app/views/notifications/previews/_exam_authorization_request_updated.html.erb +1 -1
  12. data/app/views/user_mailer/_mail_template.erb +1 -1
  13. data/app/views/user_mailer/notifications/_exam_registration.html.erb +1 -1
  14. data/app/views/user_mailer/notifications/exam_authorization_request_updated/_approved.html.erb +1 -1
  15. data/app/views/users/manage_notifications.html.erb +1 -1
  16. data/app/views/users/notifications.html.erb +1 -1
  17. data/lib/mumuki/laboratory.rb +1 -0
  18. data/lib/mumuki/laboratory/locales/en.yml +1 -0
  19. data/lib/mumuki/laboratory/locales/es-CL.yml +1 -0
  20. data/lib/mumuki/laboratory/locales/es.yml +1 -0
  21. data/lib/mumuki/laboratory/locales/pt.yml +1 -0
  22. data/lib/mumuki/laboratory/mailers/message_delivery.rb +15 -0
  23. data/lib/mumuki/laboratory/version.rb +1 -1
  24. data/spec/javascripts/editors-spec.js +21 -3
  25. data/spec/javascripts/submissions-store-spec.js +11 -0
  26. data/spec/mailers/previews/user_mailer_preview.rb +48 -6
  27. metadata +6 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b34448fea9940dfe1784874284974ae37cce2df2b56277e2eb1afdff93cab092
4
- data.tar.gz: 14cafb4c1d57a5ecf4569c3de18a8ab35f2b0d7030dcd3f0c925945d3f9f2200
3
+ metadata.gz: 81fd1285c7db44af533a878ee10b6388976451e1d9b493c5aabf5db6516855bd
4
+ data.tar.gz: b76dcfb86b425f80fa31e2683561d897c7e047fb7ff538319317c1cee40a327c
5
5
  SHA512:
6
- metadata.gz: f8796a932c0f10ffd127de92b75d21cae746aae645e57a1a8c40e714c139ef9cd5b833cacd7e1c2259ca298ea8a076d1248c7891aacb49f69066a85bd37cbfce
7
- data.tar.gz: 77ebd8d9a64a7eb2767e35a67befe8cf9f96346e4df97a92b23ad805c71c275252d1965744193d8c9b16d7f7b555323fd7d38b6176bacf32511944dd3296b3a0
6
+ metadata.gz: 9d583004159cc6bc0d88e318b3b0da5c9363b61a2b57f228f25f9ee19a016acb518f420b70015254367b2dd930b1887ae1ef61332cd349982251a1ecce995199
7
+ data.tar.gz: fec344b55bec29c42ca6b8017b7500ec08e0209aaf7649391890268adcc6f626077cb91040bfb2f391a38fe0a67e7af266b93fa0ccb28b6e01c2f9a5ebe9076b
@@ -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) {
@@ -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
@@ -0,0 +1,5 @@
1
+ module TimeZoneHelper
2
+ def local_time(time, time_zone = Time.zone.name)
3
+ "#{l(time.in_time_zone(time_zone), format: :long)} (#{time_zone})"
4
+ end
5
+ end
@@ -1,6 +1,8 @@
1
1
  class UserMailer < ApplicationMailer
2
2
  include WithCertificateRender
3
3
 
4
+ helper :time_zone
5
+
4
6
  def welcome_email(user, organization)
5
7
  with_locale(user, organization) do
6
8
  organization_name = organization.display_name || t(:your_new_organization)
@@ -1,2 +1,2 @@
1
1
  <% target = notification.target %>
2
- <%= notification_preview_item :book_open, :exam_authorization_request_updated, exam_authorizations_user_path, { description: target.exam_registration.description } %>
2
+ <%= notification_preview_item :book_open, :exam_authorization_request_updated, exam_authorizations_user_path(notification.organization), { description: target.exam_registration.description } %>
@@ -107,7 +107,7 @@
107
107
  <tbody>
108
108
  <tr>
109
109
  <td align="center" valign="middle" class="muMailButtonContent" style="font-family: Helvetica; font-size: 18px; padding: 18px;">
110
- <a class="muMailButton " title=<%= button %> href=<%= url %> target="_blank" style="font-weight: bold;letter-spacing: -0.5px;line-height: 100%;text-align: center;text-decoration: none;color: #FFFFFF;"><%= button %></a>
110
+ <a class="muMailButton" title="<%= button %>" href="<%= url %>" target="_blank" style="font-weight: bold;letter-spacing: -0.5px;line-height: 100%;text-align: center;text-decoration: none;color: #FFFFFF;"><%= button %></a>
111
111
  </td>
112
112
  </tr>
113
113
  </tbody>
@@ -1,7 +1,7 @@
1
1
  <%= render partial: 'user_mailer/mail_template', locals: {
2
2
  title: t("mailer.title.exam_registration"),
3
3
  subtitle: t("mailer.subtitle.exam_registration"),
4
- text: t("mailer.text.exam_registration", exam_registration_deadline: l(@notification.target.end_time, format: :long)),
4
+ text: t("mailer.text.exam_registration", exam_registration_deadline: local_time(@notification.target.end_time, @organization.time_zone)),
5
5
  button: t("mailer.button.exam_registration"),
6
6
  url: exam_registration_url(@organization, @notification.target)
7
7
  } %>
@@ -1,7 +1,7 @@
1
1
  <%= render partial: 'user_mailer/mail_template', locals: {
2
2
  title: t(:congratulations),
3
3
  subtitle: t('mailer.subtitle.exam_authorization_request_approved'),
4
- text: t('mailer.text.exam_authorization_request_approved', exam_start_date: l(@notification.target.exam.start_time, format: :long)),
4
+ text: t('mailer.text.exam_authorization_request_approved', exam_start_date: local_time(@notification.target.exam.start_time, @organization.time_zone)),
5
5
  button: t('mailer.button.exam_authorization_request_approved'),
6
6
  url: @organization.url
7
7
  } %>
@@ -9,7 +9,7 @@
9
9
  <h1><%= t(:manage_notifications) %></h1>
10
10
  <div class="mu-profile-actions d-none d-md-block">
11
11
  <a class="btn btn-secondary" href="<%= notifications_user_path %>">Cancelar</a>
12
- <button type="submit" class="btn btn-complementary"><%= t :submit %></button>
12
+ <button type="submit" class="btn btn-complementary"><%= t :save %></button>
13
13
  </div>
14
14
  </div>
15
15
 
@@ -32,7 +32,7 @@
32
32
  <% @notifications.each do |notification| %>
33
33
  <tr class="<%= background_for_notification notification %>" >
34
34
  <td class="col-md-1">
35
- <%= link_to icon_for_read(notification.read?), "notifications/#{notification.id}/toggle_read", tooltip_options(:read).merge(method: :post, role: :button) %>
35
+ <%= link_to icon_for_read(notification.read?), "notifications/#{notification.id}/toggle_read", tooltip_options(:toggle_read).merge(method: :post, role: :button) %>
36
36
  </td>
37
37
  <td class="col-md-8 text-break">
38
38
  <%= render partial: "notifications/#{notification.subject}", locals: { notification: notification } %>
@@ -59,3 +59,4 @@ require_relative './laboratory/version'
59
59
  require_relative './laboratory/extensions'
60
60
  require_relative './laboratory/controllers'
61
61
  require_relative './laboratory/engine'
62
+ require_relative './laboratory/mailers/message_delivery'
@@ -366,6 +366,7 @@ en:
366
366
  to_opened: Reopen
367
367
  to_pending_review: Mark as solved
368
368
  to_solved: Mark as solved
369
+ toggle_read: "Toggle read/unread"
369
370
  unauthorized_explanation: You have no permissions for this content. Maybe you logged in with another account.
370
371
  total: Total
371
372
  undo_upvote: Undo upvote
@@ -378,6 +378,7 @@ es-CL:
378
378
  to_opened: Reabrir
379
379
  to_pending_review: Resolver
380
380
  to_solved: Resolver
381
+ toggle_read: "Marcar como leído/no leído"
381
382
  unauthorized_explanation: ¡Ups! Esto es lo que se conoce como %{error}, es decir que no iniciaste sesión.
382
383
  total: Total
383
384
  uncategorized: No categorizado
@@ -392,6 +392,7 @@ es:
392
392
  to_opened: Reabrir
393
393
  to_pending_review: Resolver
394
394
  to_solved: Resolver
395
+ toggle_read: "Marcar como leído/no leído"
395
396
  unauthorized_explanation: ¡Ups! Esto es lo que se conoce como %{error}, es decir que no iniciaste sesión.
396
397
  total: Total
397
398
  uncategorized: No categorizado
@@ -379,6 +379,7 @@ pt:
379
379
  to_opened: Reabrir
380
380
  to_pending_review: Marcar como resolvida
381
381
  to_solved: Marcar como resolvida
382
+ toggle_read: "Marcar como lido / não lido"
382
383
  unauthorized_explanation: Opa! Isso é o que é conhecido como %{error}, ou seja, você não fez logon.
383
384
  total: Total
384
385
  uncategorized: Não classificado
@@ -0,0 +1,15 @@
1
+ class ActionMailer::MessageDelivery
2
+ def post!
3
+ if in_rake_task?
4
+ deliver_now
5
+ else
6
+ deliver_later
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ def in_rake_task?
13
+ defined? Rake.application
14
+ end
15
+ end
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Laboratory
3
- VERSION = '9.11.0'
3
+ VERSION = '9.12.0'
4
4
  end
5
5
  end
@@ -33,7 +33,7 @@ describe('editors', () => {
33
33
  }
34
34
  });
35
35
 
36
- expect(mumuki.editors.getSubmission()).toEqual({"solution[content]":"the custom solution"});
36
+ expect(mumuki.editors.getSubmission()).toEqual({"_pristine": true, "solution[content]":"the custom solution"});
37
37
  });
38
38
 
39
39
  it('reads the form if no sources', () => {
@@ -43,7 +43,24 @@ describe('editors', () => {
43
43
  <textarea class="form-control editor" name="solution[content]" id="solution_content">the solution</textarea>
44
44
  </div>
45
45
  </form>`);
46
- expect(mumuki.editors.getSubmission()).toEqual({"solution[content]":"the solution"});
46
+ expect(mumuki.editors.getSubmission()).toEqual({"_pristine": true, "solution[content]":"the solution"});
47
+ });
48
+
49
+ it('reads the form when it is not the first submission', () => {
50
+ $('body').html(`
51
+ <form role="form" class="new_solution">
52
+ <div class="editor-code">
53
+ <textarea class="form-control editor" name="solution[content]" id="solution_content">the solution</textarea>
54
+ </div>
55
+ </form>
56
+ <div class=" submission-results">
57
+ <div class="bs-callout bs-callout-success">
58
+ <h4 class="text-success">
59
+ <strong><i class="fas fa-check-circle"></i> ¡Muy bien!</strong>
60
+ </h4>
61
+ </div>
62
+ </div>`);
63
+ expect(mumuki.editors.getSubmission()).toEqual({"_pristine": false, "solution[content]":"the solution"});
47
64
  });
48
65
 
49
66
  it('reads the form if no sources and exercise is multifile', () => {
@@ -63,6 +80,7 @@ describe('editors', () => {
63
80
  </div>
64
81
  </form>`);
65
82
  expect(mumuki.editors.getSubmission()).toEqual({
83
+ "_pristine": true,
66
84
  "solution[content[index.html]]": "some html",
67
85
  "solution[content[receta.css]]": "some css"
68
86
  });
@@ -70,6 +88,6 @@ describe('editors', () => {
70
88
 
71
89
  it('produces empty submission if no form nor sources', () => {
72
90
  $('body').html(``);
73
- expect(mumuki.editors.getSubmission()).toEqual({});
91
+ expect(mumuki.editors.getSubmission()).toEqual({_pristine: true});
74
92
  });
75
93
  });
@@ -18,6 +18,17 @@ describe("SubmissionsStore", () => {
18
18
  mumuki.SubmissionsStore.setSubmissionResultFor(1, passedEmptyProgramSubmissionAndResult);
19
19
  expect(mumuki.SubmissionsStore.getLastSubmissionAndResult(1)).toEqual(passedEmptyProgramSubmissionAndResult);
20
20
  });
21
+
22
+ it("answers the last submission result, ignoring pristiness", () => {
23
+ mumuki.SubmissionsStore.setSubmissionResultFor(1, {
24
+ submission: {
25
+ ...emptyProgramSubmission,
26
+ _pristine: true
27
+ },
28
+ result: passedSubmissionResult
29
+ });
30
+ expect(mumuki.SubmissionsStore.getLastSubmissionAndResult(1)).toEqual(passedEmptyProgramSubmissionAndResult);
31
+ });
21
32
  });
22
33
 
23
34
  describe('getLastSubmissionStatus', () => {
@@ -7,19 +7,64 @@ class UserMailerPreview < ActionMailer::Preview
7
7
  end
8
8
 
9
9
  def custom_content_plain_text_notification
10
- UserMailer.notification notification
10
+ UserMailer.notification notification subject: :custom,
11
+ custom_content_plain_text: 'This is the text of the mail. Awesome!',
12
+ custom_title: 'This is the title!'
11
13
  end
12
14
 
13
15
  def custom_content_html_notification
14
- UserMailer.notification notification(custom_content_html: 'This is <em>the text</em> of the mail. <strong>Awesome!</strong>')
16
+ UserMailer.notification notification subject: :custom,
17
+ custom_content_html: 'This is <em>the text</em> of the mail. <strong>Awesome!</strong>',
18
+ custom_title: 'This is the title!'
15
19
  end
16
20
 
17
21
  def certificate_preview
18
22
  UserMailer.certificate certificate
19
23
  end
20
24
 
25
+ def exam_registration_preview
26
+ notification = notification target: exam_registration, subject: :exam_registration
27
+
28
+ UserMailer.notification notification
29
+ end
30
+
31
+ def exam_authorization_request_approved
32
+ notification = notification target: exam_authorization_request('approved'), subject: :exam_authorization_request_updated
33
+
34
+ UserMailer.notification notification
35
+ end
36
+
37
+ def exam_authorization_request_rejected
38
+ notification = notification target: exam_authorization_request('rejected'), subject: :exam_authorization_request_updated
39
+
40
+ UserMailer.notification notification
41
+ end
42
+
21
43
  private
22
44
 
45
+ def exam_registration
46
+ ExamRegistration.new id: 1,
47
+ organization: organization,
48
+ description: 'Some test description',
49
+ start_time: 5.minute.ago,
50
+ end_time: 5.minutes.since
51
+
52
+ end
53
+
54
+ def exam_authorization_request(status)
55
+ ExamAuthorizationRequest.new id: 1,
56
+ user: user,
57
+ status: status,
58
+ exam: exam,
59
+ organization: organization
60
+ end
61
+
62
+ def exam
63
+ Exam.new organization: organization,
64
+ start_time: 5.minute.ago,
65
+ end_time: 5.minutes.since
66
+ end
67
+
23
68
  def user
24
69
  User.new uid: 'some_user@gmail.com',
25
70
  first_name: 'Some',
@@ -45,9 +90,6 @@ class UserMailerPreview < ActionMailer::Preview
45
90
 
46
91
  def notification(**hash)
47
92
  Notification.new({user: user,
48
- organization: organization,
49
- subject: :custom,
50
- custom_content_plain_text: 'This is the text of the mail. Awesome!',
51
- custom_title: 'This is the title!'}.merge hash)
93
+ organization: organization}.merge hash)
52
94
  end
53
95
  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.11.0
4
+ version: 9.12.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-08-06 00:00:00.000000000 Z
11
+ date: 2021-08-12 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.11.0
33
+ version: 9.12.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.11.0
40
+ version: 9.12.0
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mumukit-bridge
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -573,6 +573,7 @@ files:
573
573
  - app/helpers/progress_bar_helper.rb
574
574
  - app/helpers/progress_helper.rb
575
575
  - app/helpers/runner_assets_helper.rb
576
+ - app/helpers/time_zone_helper.rb
576
577
  - app/helpers/toast_helper.rb
577
578
  - app/helpers/user_activity_helper.rb
578
579
  - app/helpers/user_discussions_helper.rb
@@ -772,6 +773,7 @@ files:
772
773
  - lib/mumuki/laboratory/locales/es.yml
773
774
  - lib/mumuki/laboratory/locales/pt.yml
774
775
  - lib/mumuki/laboratory/locales/views.es.yml
776
+ - lib/mumuki/laboratory/mailers/message_delivery.rb
775
777
  - lib/mumuki/laboratory/version.rb
776
778
  - lib/tasks/assignments.rake
777
779
  - lib/tasks/events.rake