mumuki-laboratory 9.11.0 → 9.12.0

Sign up to get free protection for your applications and to get access to all the features.
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