mumuki-laboratory 5.8.3 → 5.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/application/submission.js +25 -4
  3. data/app/controllers/application_controller.rb +4 -1
  4. data/app/controllers/concerns/organizations_controller_template.rb +3 -2
  5. data/app/controllers/exercise_solutions_controller.rb +1 -1
  6. data/app/controllers/exercises_controller.rb +3 -7
  7. data/app/controllers/login_controller.rb +6 -0
  8. data/app/helpers/contextualization_result_helper.rb +1 -1
  9. data/app/helpers/exercise_input_helper.rb +20 -5
  10. data/app/helpers/messages_helper.rb +4 -4
  11. data/app/helpers/organization_list_helper.rb +4 -0
  12. data/app/models/assignment.rb +50 -7
  13. data/app/models/concerns/contextualization.rb +3 -3
  14. data/app/models/concerns/guide_container.rb +15 -0
  15. data/app/models/concerns/submittable/submittable.rb +1 -1
  16. data/app/models/concerns/with_assignments.rb +10 -41
  17. data/app/models/concerns/with_discussions.rb +1 -1
  18. data/app/models/discussion.rb +7 -1
  19. data/app/models/exam.rb +14 -0
  20. data/app/models/exercise.rb +17 -1
  21. data/app/models/exercise/challenge.rb +1 -5
  22. data/app/models/guide.rb +1 -1
  23. data/app/models/language.rb +25 -8
  24. data/app/models/submission/submission.rb +1 -1
  25. data/app/views/discussions/_message.html.erb +1 -1
  26. data/app/views/exercise_solutions/_out_of_attempts.html.erb +6 -0
  27. data/app/views/exercise_solutions/_results.html.erb +3 -3
  28. data/app/views/exercises/_read_only.html.erb +1 -1
  29. data/app/views/layouts/_kids.html.erb +1 -1
  30. data/app/views/layouts/_organizations_listing.html.erb +1 -1
  31. data/app/views/layouts/exercise_inputs/forms/_interactive_form.html.erb +6 -6
  32. data/app/views/layouts/exercise_inputs/forms/_kids_form.html.erb +1 -1
  33. data/app/views/layouts/exercise_inputs/forms/_playground_form.html.erb +1 -1
  34. data/app/views/layouts/exercise_inputs/forms/_problem_form.html.erb +5 -5
  35. data/app/views/layouts/exercise_inputs/read_only_editors/_text.erb +3 -0
  36. data/app/views/layouts/modals/_kids_results.html.erb +1 -1
  37. data/db/migrate/20180725145801_add_submissions_caps_to_exams.rb +6 -0
  38. data/lib/mumuki/laboratory/controllers/results_rendering.rb +7 -5
  39. data/lib/mumuki/laboratory/locales/en.yml +4 -0
  40. data/lib/mumuki/laboratory/locales/es.yml +5 -0
  41. data/lib/mumuki/laboratory/locales/pt.yml +4 -0
  42. data/lib/mumuki/laboratory/mumukit/directives.rb +1 -0
  43. data/lib/mumuki/laboratory/status.rb +4 -0
  44. data/lib/mumuki/laboratory/status/discussion/discussion.rb +2 -14
  45. data/lib/mumuki/laboratory/status/submission/aborted.rb +4 -4
  46. data/lib/mumuki/laboratory/status/submission/errored.rb +4 -0
  47. data/lib/mumuki/laboratory/status/submission/failed.rb +4 -0
  48. data/lib/mumuki/laboratory/status/submission/manual_evaluation_pending.rb +1 -1
  49. data/lib/mumuki/laboratory/status/submission/passed.rb +0 -4
  50. data/lib/mumuki/laboratory/status/submission/passed_with_warnings.rb +5 -1
  51. data/lib/mumuki/laboratory/status/submission/pending.rb +4 -0
  52. data/lib/mumuki/laboratory/status/submission/running.rb +4 -0
  53. data/lib/mumuki/laboratory/status/submission/submission.rb +7 -17
  54. data/lib/mumuki/laboratory/version.rb +1 -1
  55. data/spec/controllers/discussions_controller_spec.rb +7 -0
  56. data/spec/controllers/exercise_solutions_controller_spec.rb +6 -1
  57. data/spec/controllers/messages_controller_spec.rb +7 -0
  58. data/spec/dummy/db/schema.rb +2 -0
  59. data/spec/factories/exam_factory.rb +1 -2
  60. data/spec/features/progressive_tips_spec.rb +4 -4
  61. data/spec/helpers/organization_list_helper_spec.rb +20 -0
  62. data/spec/models/assignment_spec.rb +2 -0
  63. data/spec/models/event_generation_spec.rb +6 -6
  64. data/spec/models/exercise_spec.rb +39 -32
  65. data/spec/models/guide_spec.rb +2 -2
  66. data/spec/models/interactive_spec.rb +1 -1
  67. data/spec/models/query_spec.rb +1 -2
  68. data/spec/models/question_spec.rb +1 -3
  69. data/spec/models/solution_spec.rb +33 -2
  70. data/spec/spec_helper.rb +1 -0
  71. data/vendor/assets/javascripts/codemirror-modes/php.js +236 -0
  72. metadata +14 -8
@@ -2,7 +2,7 @@ module WithDiscussions
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- has_many :discussions, as: :item
5
+ has_many :discussions, as: :item, dependent: :delete_all
6
6
  end
7
7
 
8
8
  def discuss!(user, discussion)
@@ -2,7 +2,7 @@ class Discussion < ApplicationRecord
2
2
  include WithDiscussionStatus, ParentNavigation, WithScopedQueries, Contextualization
3
3
 
4
4
  belongs_to :item, polymorphic: true
5
- has_many :messages, -> { order(:created_at) }
5
+ has_many :messages, -> { order(:created_at) }, dependent: :delete_all
6
6
  belongs_to :initiator, class_name: 'User'
7
7
  belongs_to :exercise, foreign_type: :exercise, foreign_key: 'item_id'
8
8
  has_many :subscriptions
@@ -123,6 +123,12 @@ class Discussion < ApplicationRecord
123
123
  responses_count > 0
124
124
  end
125
125
 
126
+ def extra_preview_html
127
+ # FIXME this is buggy, because the extra
128
+ # may have changed since the submission of this discussion
129
+ exercise.assignment_for(initiator).extra_preview_html
130
+ end
131
+
126
132
  def self.debatable_for(klazz, params)
127
133
  debatable_id = params[:"#{klazz.underscore}_id"]
128
134
  klazz.constantize.find(debatable_id)
@@ -126,4 +126,18 @@ class Exam < ApplicationRecord
126
126
  exams.destroy_all
127
127
  end
128
128
  end
129
+
130
+ def attempts_left_for(assignment)
131
+ max_attempts_for(assignment) - (assignment.attempts_count || 0)
132
+ end
133
+
134
+ def limited?
135
+ true
136
+ end
137
+
138
+ private
139
+
140
+ def max_attempts_for(assignment)
141
+ assignment.choice? ? max_choice_submissions : max_problem_submissions
142
+ end
129
143
  end
@@ -22,6 +22,7 @@ class Exercise < ApplicationRecord
22
22
  :guide
23
23
 
24
24
  randomize :description, :hint, :extra, :test, :default_content
25
+ delegate :limited?, :timed?, to: :navigable_parent
25
26
 
26
27
  def console?
27
28
  queriable?
@@ -69,7 +70,7 @@ class Exercise < ApplicationRecord
69
70
  end
70
71
 
71
72
  def submission_for(user)
72
- assignment_for(user)&.submission
73
+ assignment_for(user).submission
73
74
  end
74
75
 
75
76
  def new_solution
@@ -142,6 +143,21 @@ class Exercise < ApplicationRecord
142
143
  {}
143
144
  end
144
145
 
146
+ def default_content
147
+ self[:default_content] || ''
148
+ end
149
+
150
+ # Submits the user solution
151
+ # only if the corresponding assignment has attemps left
152
+ def try_submit_solution!(user, solution={})
153
+ assignment = assignment_for(user)
154
+ if assignment.attempts_left?
155
+ submit_solution!(user, solution)
156
+ else
157
+ assignment
158
+ end
159
+ end
160
+
145
161
  private
146
162
 
147
163
  def evaluation_class
@@ -1,11 +1,7 @@
1
1
  class Challenge < Exercise
2
2
  include WithLayout
3
3
 
4
- markdown_on :hint, :extra_preview
5
-
6
- def extra_preview(user)
7
- Mumukit::ContentType::Markdown.highlighted_code(language.name, extra_for(user))
8
- end
4
+ markdown_on :hint
9
5
 
10
6
  def reset!
11
7
  super
@@ -15,7 +15,7 @@ class Guide < Content
15
15
  def clear_progress!(user)
16
16
  transaction do
17
17
  exercises.each do |exercise|
18
- exercise.assignment_for(user)&.destroy!
18
+ exercise.find_assignment_for(user)&.destroy!
19
19
  end
20
20
  end
21
21
  end
@@ -65,6 +65,20 @@ class Language < ApplicationRecord
65
65
  new_directive Mumukit::Directives::Sections
66
66
  end
67
67
 
68
+
69
+ def assets_urls_for(kind, content_type)
70
+ send "#{kind}_#{content_type}_urls"
71
+ end
72
+
73
+ # TODO this should be a Mumukit::Directives::Directive
74
+ # and be part of a pipeline
75
+ def interpolate_references_for(assignment, field)
76
+ interpolate(field, assignment.submitter.interpolations, lambda { |content| replace_content_reference(assignment, content) })
77
+ end
78
+
79
+ private
80
+
81
+ # TODO we should use Mumukit::Directives::Pipeline
68
82
  def interpolate(interpolee, *interpolations)
69
83
  interpolations.inject(interpolee) { |content, interpolation| directives_interpolations.interpolate(content, interpolation).first }
70
84
  end
@@ -73,17 +87,20 @@ class Language < ApplicationRecord
73
87
  new_directive Mumukit::Directives::Interpolations
74
88
  end
75
89
 
76
- def directives_comment_type
77
- Mumukit::Directives::CommentType.parse comment_type
90
+ def replace_content_reference(assignment, interpolee)
91
+ case interpolee
92
+ when /previousContent|previousSolution/
93
+ assignment.current_content_at(-1)
94
+ when /(solution|content)\[(-?\d*)\]/
95
+ assignment.current_content_at($2.to_i)
96
+ end
78
97
  end
79
98
 
80
- def assets_urls_for(kind, content_type)
81
- send "#{kind}_#{content_type}_urls"
82
- end
83
-
84
- private
85
-
86
99
  def new_directive(directive_type)
87
100
  directive_type.new.tap { |it| it.comment_type = directives_comment_type }
88
101
  end
102
+
103
+ def directives_comment_type
104
+ Mumukit::Directives::CommentType.parse comment_type
105
+ end
89
106
  end
@@ -46,7 +46,7 @@ class Submission
46
46
 
47
47
  def save_results!(results, assignment)
48
48
  assignment.assign_attributes results
49
- assignment.increment_attemps!
49
+ assignment.increment_attempts!
50
50
  assignment.save! results
51
51
  end
52
52
 
@@ -21,7 +21,7 @@
21
21
  </div>
22
22
  <div class="discussion-message-bubble-content">
23
23
  <div class="discussion-message-content">
24
- <%= message.content_html %>
24
+ <%= sanitize message.content_html %>
25
25
  </div>
26
26
  </div>
27
27
  </div>
@@ -0,0 +1,6 @@
1
+ <div class="bs-callout bs-callout-broken submission-result-pending">
2
+ <h4>
3
+ <strong><%= t :out_of_attempts %></strong>
4
+ </h4>
5
+ </div>
6
+ <%= render partial: 'exercise_solutions/results_button', locals: {assignment: assignment} %>
@@ -29,9 +29,9 @@
29
29
  <%= solution_download_link assignment %>
30
30
  <% end %>
31
31
 
32
- <% if assignment.should_retry? %>
33
- <%= assistance_box assignment %>
34
- <% else %>
32
+ <% if assignment.passed? %>
35
33
  <%= corollary_box @exercise %>
34
+ <% else %>
35
+ <%= assistance_box assignment %>
36
36
  <% end %>
37
37
  <%= render partial: 'exercise_solutions/results_button', locals: {assignment: assignment} %>
@@ -93,7 +93,7 @@
93
93
  </div>
94
94
 
95
95
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="visible-extra">
96
- <%= exercise.extra_preview_html(@discussion.initiator) %>
96
+ <%= @discussion.extra_preview_html %>
97
97
  </div>
98
98
  </div>
99
99
  <% else %>
@@ -17,5 +17,5 @@
17
17
  <div class="description"><%= exercise.description_task %></div>
18
18
  <div class="hint" style="display: none"><%= exercise.hint_html %></div>
19
19
  </div>
20
- <div class="mu-kids-character-speech-bubble-failed <%= @assignment&.status %>" style="display: none"></div>
20
+ <div class="mu-kids-character-speech-bubble-failed <%= @assignment.status %>" style="display: none"></div>
21
21
  </div>
@@ -7,7 +7,7 @@
7
7
  <%= image_tag(org.banner_url, height: 50, class: 'pull-left') %>
8
8
  </div>
9
9
  <div class="col-md-6 organization-row">
10
- <%= link_to(t(:go_to, organization: org.name), org.url_for(request.path), class: 'btn btn-success pull-right') %>
10
+ <%= link_to(t(:go_to, organization: org.name), organization_switch_url(org), class: 'btn btn-success pull-right') %>
11
11
  </div>
12
12
  </div>
13
13
  <% end %>
@@ -1,5 +1,5 @@
1
1
  <%= hidden_field_tag :console_endpoint, exercise_tries_path(exercise) %>
2
- <%= hidden_field_tag :historical_queries, @assignment&.queries_with_results.to_json %>
2
+ <%= hidden_field_tag :historical_queries, @assignment.queries_with_results.to_json %>
3
3
 
4
4
  <% if exercise.extra_visible? %>
5
5
  <ul class="nav nav-tabs" role="tablist">
@@ -12,7 +12,7 @@
12
12
 
13
13
  <div class="tab-content">
14
14
  <div role="tabpanel" class="tab-pane mu-input-panel fade in active <%= pending_messages_filter(@assignment) %>" id="console">
15
- <% if @assignment&.pending_messages? %>
15
+ <% if @assignment.pending_messages? %>
16
16
  <span class="pending-messages-text"> <%= t :pending_messages_explanation %>
17
17
  <a href="javascript:{}"
18
18
  onclick="mumuki.Chat.submitMessagesForm('<%= messages_url(exercise) %>', '<%= read_messages_path(exercise) %>')"
@@ -29,18 +29,18 @@
29
29
  </div>
30
30
  </div>
31
31
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="visible-extra">
32
- <%= exercise.extra_preview_html(current_user) %>
32
+ <%= @assignment.extra_preview_html %>
33
33
  </div>
34
34
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="messages">
35
- <%= render partial: 'layouts/messages', locals: {messages: @assignment.messages} if @assignment %>
35
+ <%= render partial: 'layouts/messages', locals: {messages: @assignment.messages} unless @assignment.pending? %>
36
36
  </div>
37
37
  </div>
38
-
38
+ <%# FIXME Duplicated view in problem_form %>
39
39
  <% content_for :submission_results do %>
40
40
  <% if current_user? %>
41
41
  <div class="row">
42
42
  <div class="col-md-12 submission-results">
43
- <% if @assignment&.passed? %>
43
+ <% if @assignment.passed? %>
44
44
  <%= render partial: 'exercise_solutions/results',
45
45
  locals: {assignment: @assignment, guide_finished_by_solution: false} %>
46
46
  <% end %>
@@ -4,7 +4,7 @@
4
4
  <%= render_exercise_input_editor f, exercise %>
5
5
 
6
6
  <div class="actions mu-kids-submit-button">
7
- <%= render_submit_button(exercise) %>
7
+ <%= render_submit_button(@assignment) %>
8
8
  </div>
9
9
  <div class="actions mu-kids-reset-button"></div>
10
10
  <img class="mu-kids-compass-rose" src="/compass_rose.svg">
@@ -18,7 +18,7 @@
18
18
  </div>
19
19
  </div>
20
20
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="visible-extra">
21
- <%= exercise.extra_preview_html(current_user) %>
21
+ <%= @assignment.extra_preview_html %>
22
22
  </div>
23
23
  </div>
24
24
  <div class="actions">
@@ -18,7 +18,7 @@
18
18
 
19
19
  <div class="tab-content">
20
20
  <div role="tabpanel" class="tab-pane mu-input-panel fade in active" id="editor">
21
- <% if @assignment&.pending_messages? %>
21
+ <% if @assignment.pending_messages? %>
22
22
  <span class="pending-messages-text"> <%= t :pending_messages_explanation %>
23
23
  <a href="javascript:{}"
24
24
  onclick="mumuki.Chat.submitMessagesForm('<%= messages_url(exercise) %>', '<%= read_messages_path(exercise) %>')"
@@ -34,7 +34,7 @@
34
34
  <%= render_exercise_input_editor f, exercise %>
35
35
 
36
36
  <div class="actions">
37
- <%= render_submit_button(exercise) %>
37
+ <%= render_submit_button(@assignment) %>
38
38
  </div>
39
39
  <% end %>
40
40
  </div>
@@ -52,10 +52,10 @@
52
52
  </div>
53
53
  </div>
54
54
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="visible-extra">
55
- <%= exercise.extra_preview_html(current_user) %>
55
+ <%= @assignment.extra_preview_html %>
56
56
  </div>
57
57
  <div role="tabpanel" class="tab-pane mu-input-panel fade" id="messages">
58
- <%= render partial: 'layouts/messages', locals: {messages: @assignment.messages} if @assignment %>
58
+ <%= render partial: 'layouts/messages', locals: {messages: @assignment.messages} unless @assignment.pending? %>
59
59
  </div>
60
60
  </div>
61
61
 
@@ -63,7 +63,7 @@
63
63
  <% if current_user? %>
64
64
  <div class="row">
65
65
  <div class="col-md-12 submission-results">
66
- <% if @assignment %>
66
+ <% unless @assignment.pending? %>
67
67
  <%= render partial: 'exercise_solutions/results',
68
68
  locals: {assignment: @assignment} %>
69
69
  <% end %>
@@ -0,0 +1,3 @@
1
+ <div class="field form-group">
2
+ <%= Mumukit::ContentType::Markdown.to_html(Mumukit::ContentType::Markdown.code(content)) %>
3
+ </div>
@@ -9,7 +9,7 @@
9
9
  <div class="col-md-12">
10
10
  <div class="row">
11
11
  <div class="col-md-12 submission-results">
12
- <% if @assignment&.passed? %>
12
+ <% if @assignment.passed? %>
13
13
  <%= render partial: 'exercise_solutions/kids_results',
14
14
  locals: {assignment: @assignment, guide_finished_by_solution: false} %>
15
15
  <% end %>
@@ -0,0 +1,6 @@
1
+ class AddSubmissionsCapsToExams < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :exams, :max_problem_submissions, :integer
4
+ add_column :exams, :max_choice_submissions, :integer
5
+ end
6
+ end
@@ -2,7 +2,7 @@ module Mumuki::Laboratory::Controllers::ResultsRendering
2
2
  extend ActiveSupport::Concern
3
3
 
4
4
  included do
5
- include ProgressBarHelper
5
+ include ProgressBarHelper, ExerciseInputHelper
6
6
 
7
7
  before_action :set_guide_previously_done!
8
8
  end
@@ -14,16 +14,18 @@ module Mumuki::Laboratory::Controllers::ResultsRendering
14
14
  html: render_results_html(assignment),
15
15
  title_html: render_results_title_html(assignment),
16
16
  button_html: render_results_button_html(assignment),
17
- expectations_html: render_results_expectations_html(assignment))
17
+ expectations_html: render_results_expectations_html(assignment),
18
+ remaining_attempts_html: remaining_attempts_text(assignment))
18
19
  end
19
20
 
20
21
  def render_results_html(assignment)
21
- render_to_string partial: results_partial,
22
+ render_to_string partial: results_partial(assignment),
22
23
  locals: {assignment: assignment}
23
24
  end
24
25
 
25
- def results_partial
26
- @exercise&.input_kids? ? 'exercise_solutions/kids_results' : 'exercise_solutions/results'
26
+ def results_partial(assignment)
27
+ return 'out_of_attempts' unless assignment.attempts_left?
28
+ assignment.input_kids? ? 'exercise_solutions/kids_results' : 'exercise_solutions/results'
27
29
  end
28
30
 
29
31
  def render_results_title_html(assignment)
@@ -15,6 +15,9 @@ en:
15
15
  ask_community: Ask community for help
16
16
  ask_redirect: Do you want to go there?
17
17
  ask_the_first_question: Be the first one to ask!
18
+ attempts_left:
19
+ zero: "You've run out of attempts "
20
+ other: '%{count} attempts remaining'
18
21
  author: Author
19
22
  authoring: "Authoring"
20
23
  authoring_note: This guide's content was developed by %{authors} and <a href="%{collaborators}" target="_blank">many others</a>, under the terms of <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Creative Commons License Share-Alike, 4.0</a>
@@ -143,6 +146,7 @@ en:
143
146
  office: Office
144
147
  opened: Open
145
148
  opened_count: '%{count} opened'
149
+ out_of_attempts: You've run out of attempts. You should proceed to the next exercise!
146
150
  output: Output
147
151
  overview: Overview
148
152
  passed: Everything is in order! Your solution passed all our tests!
@@ -16,6 +16,10 @@ es:
16
16
  ask_community: Preguntá a la comunidad
17
17
  ask_redirect: ¿Querés que te llevemos ahí?
18
18
  ask_the_first_question: ¡Hacé la primera pregunta!
19
+ attempts_left:
20
+ zero: 'Te quedaste sin intentos'
21
+ one: '1 intento restante'
22
+ other: '%{count} intentos restantes'
19
23
  author: Autor
20
24
  authoring: "Autores"
21
25
  authoring_note: Esta guía fue desarrollada por %{authors} y <a href="%{collaborators}" target="_blank">muchas personas más</a>, bajo los términos de la <a href="https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">Licencia Creative Commons Compartir-Igual, 4.0</a>.
@@ -161,6 +165,7 @@ es:
161
165
  one: '1 abierta'
162
166
  other: '%{count} abiertas'
163
167
  organizations: Organizaciones
168
+ out_of_attempts: Te quedaste sin intentos. ¡Seguí al proximo ejercicio!
164
169
  output: Salida
165
170
  overview: Vista general
166
171
  passed: ¡Muy bien! Tu solución pasó todas las pruebas
@@ -16,6 +16,9 @@ pt:
16
16
  ask_community: Pergunte à comunidade
17
17
  ask_redirect: Você quer que nós o levemos para lá?
18
18
  ask_the_first_question: Faça a primeira pergunta!
19
+ attempts_left:
20
+ zero: 'Você ficou sem tentativas'
21
+ other: '%{count} tentativa restante'
19
22
  author: Autor
20
23
  authoring: Autores
21
24
  authoring_note: 'Este guia foi desenvolvido por %{authors} e <a href="%{collaborators}" target="_blank"> muitas outras pessoas </a>, nos termos do <a href = "https://creativecommons.org/licenses/by-sa/4.0/" target="_blank"> Creative Commons License Share-Equal, 4.0 </a>.'
@@ -154,6 +157,7 @@ pt:
154
157
  opened: Aberto
155
158
  opened_count: '%{count} aberto'
156
159
  organizations: Organizações
160
+ out_of_attempts: Você ficou sem tentativas. Você deve prosseguir para o próximo exercício!
157
161
  output: Sair
158
162
  overview: Visão global
159
163
  passed: Muito bem! Sua solução passou todos os testes
@@ -1,6 +1,7 @@
1
1
  require 'mumukit/directives'
2
2
 
3
3
  class Mumukit::Directives::Sections < Mumukit::Directives::Directive
4
+ # TODO Move this behaviour to gem
4
5
  def join(sections)
5
6
  file_declarations, file_references = sections.map do |section, content|
6
7
  [build(section, content), interpolate(section)]