mumuki-domain 7.7.1 → 7.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/assignment.rb +12 -1
  3. data/app/models/avatar.rb +7 -1
  4. data/app/models/book.rb +4 -2
  5. data/app/models/concerns/contextualization.rb +2 -1
  6. data/app/models/concerns/gamified.rb +15 -0
  7. data/app/models/concerns/navigation/siblings_navigation.rb +2 -2
  8. data/app/models/concerns/submittable/solvable.rb +1 -1
  9. data/app/models/concerns/submittable/submittable.rb +1 -1
  10. data/app/models/concerns/with_assignments.rb +1 -1
  11. data/app/models/concerns/with_discussions.rb +1 -1
  12. data/app/models/concerns/with_progress.rb +1 -1
  13. data/app/models/concerns/with_reminders.rb +1 -1
  14. data/app/models/concerns/with_target_audience.rb +13 -0
  15. data/app/models/discussion.rb +6 -6
  16. data/app/models/guide.rb +1 -1
  17. data/app/models/organization.rb +9 -11
  18. data/app/models/user.rb +93 -5
  19. data/app/models/user_stats.rb +16 -0
  20. data/db/migrate/20200616160640_create_user_stats.rb +10 -0
  21. data/db/migrate/20200617142217_add_top_submission_status_to_assignments.rb +5 -0
  22. data/db/migrate/20200717143830_add_target_audience_to_organizations_and_avatars.rb +6 -0
  23. data/db/migrate/20200730221001_add_trusted_for_forum_to_user.rb +5 -0
  24. data/db/migrate/20200804191643_add_incognito_mode_enabled_to_organization.rb +5 -0
  25. data/lib/mumuki/domain.rb +1 -0
  26. data/lib/mumuki/domain/extensions.rb +1 -0
  27. data/lib/mumuki/domain/extensions/time.rb +5 -0
  28. data/lib/mumuki/domain/factories.rb +2 -1
  29. data/lib/mumuki/domain/factories/avatar_factory.rb +6 -0
  30. data/lib/mumuki/domain/factories/discussion_factory.rb +2 -2
  31. data/lib/mumuki/domain/factories/message_factory.rb +1 -6
  32. data/lib/mumuki/domain/factories/user_factory.rb +0 -1
  33. data/lib/mumuki/domain/helpers/organization.rb +4 -0
  34. data/lib/mumuki/domain/helpers/user.rb +6 -10
  35. data/lib/mumuki/domain/incognito.rb +119 -0
  36. data/lib/mumuki/domain/locales/activerecord/es-CL.yml +59 -0
  37. data/lib/mumuki/domain/locales/console_submission/es-CL.yml +3 -0
  38. data/lib/mumuki/domain/organization/profile.rb +2 -1
  39. data/lib/mumuki/domain/organization/settings.rb +12 -10
  40. data/lib/mumuki/domain/status/discussion/discussion.rb +0 -8
  41. data/lib/mumuki/domain/status/discussion/opened.rb +8 -0
  42. data/lib/mumuki/domain/status/submission/passed.rb +4 -0
  43. data/lib/mumuki/domain/status/submission/passed_with_warnings.rb +4 -0
  44. data/lib/mumuki/domain/status/submission/skipped.rb +4 -0
  45. data/lib/mumuki/domain/status/submission/submission.rb +8 -0
  46. data/lib/mumuki/domain/submission/base.rb +5 -1
  47. data/lib/mumuki/domain/version.rb +1 -1
  48. metadata +19 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f1efee52b12a7be242fdb13883331838d1ee17e0a396d5b0df95b54c3d209bdb
4
- data.tar.gz: a6e75dfa224cd6a3642c77d23d1190906d427506b0fb90905b1f636f2c9ecd72
3
+ metadata.gz: e4d324f2eb4b3e06fb42a0aabbbd007c4c828569899c89a789ad681432bb97ba
4
+ data.tar.gz: 3f540fc72a7180e247ee11535316d977920872ecc888c77c7c68346eae0c276e
5
5
  SHA512:
6
- metadata.gz: f66e5cb6acf1f40de30d9a1da7056626eeb513db95822a75b4b59fae0fbe0718795fd5ad33c38fd2df1e5a277d3a1b5390c78b87558c4976b4cc72921912f86f
7
- data.tar.gz: 6f2e61ba1908513448c72c448cb1e0f92582e502304d6756bdb82f4ca4d0a9dfe783fc12669613ea4bf99cd3e1bf2b8397ac6142e28cad8517dd0123175dbfcb
6
+ metadata.gz: ac7828f09dec30a19e4f4b96bfcfb472fe7fcfc5b53aaeb81e4fb4f5b52533d10b462e9e923d2a2857cec7367cda7d40d6222bd8f8a105dfc84e394b35e552c5
7
+ data.tar.gz: 40cede7e7ff373b1a3a170fbd6bf55bb40faf5c2a814af0849a698f2205a15bff49bca7ba5737577620c48265bad4e42d68e8b4befcb4bbb5d78f9ec82bf23fe
@@ -1,6 +1,7 @@
1
1
  class Assignment < Progress
2
2
  include Contextualization
3
3
  include WithMessages
4
+ include Gamified
4
5
 
5
6
  markdown_on :extra_preview
6
7
 
@@ -20,6 +21,8 @@ class Assignment < Progress
20
21
  delegate :language, :name, :navigable_parent, :settings,
21
22
  :limited?, :input_kids?, :choice?, :results_hidden?, to: :exercise
22
23
 
24
+ delegate :completed?, :solved?, to: :submission_status
25
+
23
26
  alias_attribute :status, :submission_status
24
27
  alias_attribute :attempts_count, :attemps_count
25
28
 
@@ -39,6 +42,7 @@ class Assignment < Progress
39
42
  alias_method :parent_content, :guide
40
43
  alias_method :user, :submitter
41
44
 
45
+ before_save :award_experience_points!, :update_top_submission!, if: :submission_status_changed?
42
46
  after_save :dirty_parent_by_submission!, if: :completion_changed?
43
47
  before_validation :set_current_organization!, unless: :organization
44
48
 
@@ -159,7 +163,10 @@ class Assignment < Progress
159
163
  end
160
164
 
161
165
  def to_resource_h
162
- as_json(except: %i(exercise_id submission_id organization_id id submitter_id solution created_at updated_at submission_status submitted_at parent_id),
166
+ excluded_fields = %i(created_at exercise_id id organization_id parent_id solution submission_id
167
+ submission_status submitted_at submitter_id top_submission_status updated_at)
168
+
169
+ as_json(except: excluded_fields,
163
170
  include: {
164
171
  guide: {
165
172
  only: [:slug, :name],
@@ -222,6 +229,10 @@ class Assignment < Progress
222
229
  exercise.files_for(current_content)
223
230
  end
224
231
 
232
+ def update_top_submission!
233
+ self.top_submission_status = submission_status unless submission_status.improved_by?(top_submission_status)
234
+ end
235
+
225
236
  private
226
237
 
227
238
  def update_submissions_count!
@@ -1,5 +1,11 @@
1
1
  class Avatar < ApplicationRecord
2
+ include WithTargetAudience
3
+
4
+ def self.sample_for(user)
5
+ with_current_audience_for(user).sample
6
+ end
7
+
2
8
  def self.sample
3
- Avatar.order('RANDOM()').first
9
+ order('RANDOM()').first
4
10
  end
5
11
  end
@@ -8,8 +8,6 @@ class Book < Content
8
8
  has_many :complements, dependent: :destroy
9
9
 
10
10
  has_many :exercises, through: :chapters
11
- has_many :discussions, through: :exercises
12
- organic_on :discussions
13
11
 
14
12
  delegate :first_lesson, to: :first_chapter
15
13
 
@@ -19,6 +17,10 @@ class Book < Content
19
17
  slug
20
18
  end
21
19
 
20
+ def discussions_in_organization(organization = Organization.current)
21
+ Discussion.where(organization: organization).includes(exercise: [:language, :guide])
22
+ end
23
+
22
24
  def first_chapter
23
25
  chapters.first
24
26
  end
@@ -16,6 +16,7 @@ module Contextualization
16
16
  end
17
17
 
18
18
  included do
19
+ serialize :top_submission_status, Mumuki::Domain::Status::Submission
19
20
  serialize :submission_status, Mumuki::Domain::Status::Submission
20
21
  validates_presence_of :submission_status
21
22
 
@@ -25,7 +26,7 @@ module Contextualization
25
26
 
26
27
  delegate :visible_success_output?, to: :exercise
27
28
  delegate :output_content_type, to: :language
28
- delegate :should_retry?, :to_submission_status, :completed?, :solved?, *Mumuki::Domain::Status::Submission.test_selectors, to: :submission_status
29
+ delegate :should_retry?, :to_submission_status, *Mumuki::Domain::Status::Submission.test_selectors, to: :submission_status
29
30
  delegate :inspection_keywords, to: :exercise
30
31
  end
31
32
 
@@ -0,0 +1,15 @@
1
+ module Gamified
2
+ def award_experience_points!
3
+ points = net_experience
4
+
5
+ if points > 0
6
+ stats = UserStats.stats_for(submitter)
7
+ stats.add_exp!(points)
8
+ stats.save!
9
+ end
10
+ end
11
+
12
+ def net_experience
13
+ submission_status.exp_given - top_submission_status.exp_given
14
+ end
15
+ end
@@ -1,11 +1,11 @@
1
1
  module SiblingsNavigation
2
2
 
3
3
  def next_for(user)
4
- pending_siblings_for(user).select { |it| it.number > number }.sort_by(&:number).first
4
+ user.pending_siblings_at(self).select { |it| it.number > number }.sort_by(&:number).first
5
5
  end
6
6
 
7
7
  def restart(user)
8
- pending_siblings_for(user).sort_by(&:number).first
8
+ user.pending_siblings_at(self).sort_by(&:number).first
9
9
  end
10
10
 
11
11
  def siblings
@@ -1,7 +1,7 @@
1
1
  module Solvable
2
2
  def submit_solution!(user, submission_attributes={})
3
3
  assignment, _ = find_assignment_and_submit! user, solution_for(submission_attributes)
4
- try_solve_discussions(user) if assignment.solved?
4
+ try_solve_discussions!(user) if assignment.solved?
5
5
  assignment
6
6
  end
7
7
 
@@ -5,7 +5,7 @@ module Submittable
5
5
 
6
6
  def find_assignment_and_submit!(user, submission)
7
7
  assignment = assignment_for user
8
- results = submission.run! assignment, evaluation_class.new
8
+ results = user.run_submission! submission, assignment, evaluation_class.new
9
9
  [assignment, results]
10
10
  end
11
11
  end
@@ -28,6 +28,6 @@ module WithAssignments
28
28
  end
29
29
 
30
30
  def assignment_for(user, organization=Organization.current)
31
- find_assignment_for(user, organization) || user.assignments.build(exercise: self, organization: organization)
31
+ find_assignment_for(user, organization) || user.build_assignment(self, organization)
32
32
  end
33
33
  end
@@ -14,7 +14,7 @@ module WithDiscussions
14
14
  nil
15
15
  end
16
16
 
17
- def try_solve_discussions(user)
17
+ def try_solve_discussions!(user)
18
18
  discussions.where(initiator: user).map(&:try_solve!)
19
19
  end
20
20
 
@@ -1,6 +1,6 @@
1
1
  module WithProgress
2
2
  def progress_for(user, organization)
3
- Indicator.find_or_initialize_by(user: user, organization: organization, content: self)
3
+ user.progress_at(self, organization)
4
4
  end
5
5
 
6
6
  def completion_percentage_for(user, organization=Organization.current)
@@ -9,7 +9,7 @@ module WithReminders
9
9
  end
10
10
 
11
11
  def remind!
12
- build_reminder.deliver
12
+ build_reminder.deliver_now
13
13
  update! last_reminded_date: Time.now
14
14
  end
15
15
 
@@ -0,0 +1,13 @@
1
+ module WithTargetAudience
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ enum target_audience: [:grown_ups, :kids]
6
+ end
7
+
8
+ class_methods do
9
+ def with_current_audience_for(user)
10
+ where(target_audience: user.current_audience)
11
+ end
12
+ end
13
+ end
@@ -105,10 +105,6 @@ class Discussion < ApplicationRecord
105
105
  reachable_statuses_for(user).include? status
106
106
  end
107
107
 
108
- def allowed_statuses_for(user)
109
- status.allowed_statuses_for(user, self)
110
- end
111
-
112
108
  def update_status!(status, user)
113
109
  update!(status: status) if reachable_status_for?(user, status)
114
110
  end
@@ -125,6 +121,10 @@ class Discussion < ApplicationRecord
125
121
  responses_count > 0
126
122
  end
127
123
 
124
+ def has_validated_responses?
125
+ validated_messages_count > 0
126
+ end
127
+
128
128
  def subscribe_initiator!
129
129
  initiator.subscribe_to! self
130
130
  end
@@ -138,10 +138,10 @@ class Discussion < ApplicationRecord
138
138
  def update_counters!
139
139
  messages_query = messages_by_updated_at
140
140
  validated_messages = messages_query.select &:validated?
141
- requires_moderator_response = messages_query.find { |it| it.validated? || it.question? }&.from_initiator?
141
+ has_moderator_response = messages_query.find { |it| it.validated? || it.question? }&.validated?
142
142
  update! messages_count: messages_query.count,
143
143
  validated_messages_count: validated_messages.count,
144
- requires_moderator_response: requires_moderator_response
144
+ requires_moderator_response: !has_moderator_response
145
145
  end
146
146
 
147
147
  def update_last_moderator_access!(user)
@@ -42,7 +42,7 @@ class Guide < Content
42
42
  end
43
43
 
44
44
  def next_exercise(user)
45
- pending_exercises(user).order('public.exercises.number asc').first
45
+ user.next_exercise_at(self)
46
46
  end
47
47
 
48
48
  # TODO: Make use of pending_siblings logic
@@ -5,6 +5,8 @@ class Organization < ApplicationRecord
5
5
 
6
6
  include Mumukit::Login::OrganizationHelpers
7
7
 
8
+ include WithTargetAudience
9
+
8
10
  serialize :profile, Mumuki::Domain::Organization::Profile
9
11
  serialize :settings, Mumuki::Domain::Organization::Settings
10
12
  serialize :theme, Mumuki::Domain::Organization::Theme
@@ -18,6 +20,7 @@ class Organization < ApplicationRecord
18
20
  has_many :usages
19
21
 
20
22
  validates_presence_of :contact_email, :locale
23
+ validates_presence_of :welcome_email_template, if: :greet_new_users?
21
24
  validates :name, uniqueness: true,
22
25
  presence: true,
23
26
  format: { with: Mumukit::Platform::Organization.anchored_valid_name_regex }
@@ -105,17 +108,8 @@ class Organization < ApplicationRecord
105
108
  # ask for help in this organization
106
109
  #
107
110
  # Warning: this method does not strictly check user's permission
108
- def ask_for_help_enabled?(user = nil)
109
- report_issue_enabled? || community_link.present? || can_create_discussions?(user)
110
- end
111
-
112
- # Tells if the given user can
113
- # create discussion in this organization
114
- #
115
- # This is true only when this organization has a forum and the user
116
- # has the discusser pseudo-permission
117
- def can_create_discussions?(user = nil)
118
- forum_enabled? && (!user || user.discusser_of?(self))
111
+ def ask_for_help_enabled?(user)
112
+ report_issue_enabled? || community_link.present? || user.can_discuss_in?(self)
119
113
  end
120
114
 
121
115
  def import_from_resource_h!(resource_h)
@@ -136,6 +130,10 @@ class Organization < ApplicationRecord
136
130
  update! progressive_display_lookahead: lookahead
137
131
  end
138
132
 
133
+ def display_name
134
+ name.gsub(/\W/, ' ').titleize
135
+ end
136
+
139
137
  private
140
138
 
141
139
  def ensure_consistent_public_login
@@ -30,15 +30,16 @@ class User < ApplicationRecord
30
30
 
31
31
  has_many :exams, through: :exam_authorizations
32
32
 
33
- after_initialize :init
34
-
35
33
  enum gender: %i(female male other unspecified)
36
-
37
34
  belongs_to :avatar, optional: true
38
35
 
39
36
  before_validation :set_uid!
40
37
  validates :uid, presence: true
41
38
 
39
+ after_save :welcome_to_new_organizations!, if: :gained_access_to_new_orga?
40
+ after_initialize :init
41
+ PLACEHOLDER_IMAGE_URL = 'user_shape.png'.freeze
42
+
42
43
  resource_fields :uid, :social_id, :email, :permissions, :verified_first_name, :verified_last_name, *profile_fields
43
44
 
44
45
  def last_lesson
@@ -145,10 +146,14 @@ class User < ApplicationRecord
145
146
  exams.any? { |e| e.in_progress_for? self }
146
147
  end
147
148
 
148
- def profile_picture
149
+ def custom_profile_picture
149
150
  avatar&.image_url || image_url
150
151
  end
151
152
 
153
+ def profile_picture
154
+ custom_profile_picture || placeholder_image_url
155
+ end
156
+
152
157
  def bury!
153
158
  # TODO change avatar
154
159
  update! self.class.buried_profile.merge(accepts_reminders: false, gender: nil, birthdate: nil)
@@ -169,18 +174,93 @@ class User < ApplicationRecord
169
174
  sequence[0..count + lookahead - 1]
170
175
  end
171
176
 
177
+ # Tells if the given user can discuss in an organization
178
+ #
179
+ # This is true only when this organization has the forum enabled and the user
180
+ # has the discusser pseudo-permission and the discusser is trusted
181
+ def can_discuss_in?(organization)
182
+ organization.forum_enabled? && discusser_of?(organization) && trusted_as_discusser_in?(organization)
183
+ end
184
+
185
+ def trusted_as_discusser_in?(organization)
186
+ trusted_for_forum? || !organization.forum_only_for_trusted?
187
+ end
188
+
189
+ def can_discuss_here?
190
+ can_discuss_in? Organization.current
191
+ end
192
+
172
193
  def name_initials
173
194
  name.split.map(&:first).map(&:capitalize).join(' ')
174
195
  end
175
196
 
197
+ def progress_at(content, organization)
198
+ Indicator.find_or_initialize_by(user: self, organization: organization, content: content)
199
+ end
200
+
201
+ def build_assignment(exercise, organization)
202
+ assignments.build(exercise: exercise, organization: organization)
203
+ end
204
+
205
+ def pending_siblings_at(content)
206
+ content.pending_siblings_for(self)
207
+ end
208
+
209
+ def next_exercise_at(guide)
210
+ guide.pending_exercises(self).order('public.exercises.number asc').first
211
+ end
212
+
213
+ def run_submission!(submission, assignment, evaluation)
214
+ submission.run! assignment, evaluation
215
+ end
216
+
217
+ def incognito?
218
+ false
219
+ end
220
+
221
+ def current_audience
222
+ current_organic_context&.target_audience
223
+ end
224
+
225
+ def placeholder_image_url
226
+ PLACEHOLDER_IMAGE_URL
227
+ end
228
+
229
+ def age
230
+ if birthdate.present?
231
+ @age ||= Time.now.round_years_since(birthdate.to_time)
232
+ end
233
+ end
234
+
176
235
  private
177
236
 
237
+ def welcome_to_new_organizations!
238
+ new_accessible_organizations.each do |organization|
239
+ UserMailer.welcome_email(self, organization).deliver_now rescue nil if organization.greet_new_users?
240
+ end
241
+ end
242
+
243
+ def gained_access_to_new_orga?
244
+ new_accessible_organizations.present?
245
+ end
246
+
247
+ def new_accessible_organizations
248
+ return [] unless saved_change_to_permissions?
249
+
250
+ old, new = saved_change_to_permissions
251
+ new_organizations = (new.any_granted_organizations - old.any_granted_organizations).to_a
252
+ Organization.where(name: new_organizations)
253
+ end
254
+
178
255
  def set_uid!
179
256
  self.uid ||= email
180
257
  end
181
258
 
182
259
  def init
183
- self.avatar = Avatar.sample unless profile_picture.present?
260
+ if custom_profile_picture.blank?
261
+ self.avatar = Avatar.sample_for(self)
262
+ save if persisted?
263
+ end
184
264
  end
185
265
 
186
266
  def self.sync_key_id_field
@@ -205,4 +285,12 @@ class User < ApplicationRecord
205
285
  def self.buried_profile
206
286
  (@buried_profile || {}).slice(:first_name, :last_name, :email)
207
287
  end
288
+
289
+ def current_organic_context
290
+ if Organization.current?
291
+ Organization.current
292
+ else
293
+ main_organization
294
+ end
295
+ end
208
296
  end
@@ -0,0 +1,16 @@
1
+ class UserStats < ApplicationRecord
2
+ belongs_to :organization
3
+ belongs_to :user
4
+
5
+ def self.stats_for(user)
6
+ UserStats.find_or_initialize_by(user: user, organization: Organization.current)
7
+ end
8
+
9
+ def self.exp_for(user)
10
+ self.stats_for(user).exp
11
+ end
12
+
13
+ def add_exp!(points)
14
+ self.exp += points
15
+ end
16
+ end
@@ -0,0 +1,10 @@
1
+ class CreateUserStats < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :user_stats do |t|
4
+ t.integer :exp, default: 0
5
+
6
+ t.references :user, index: true
7
+ t.references :organization, index: true
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,5 @@
1
+ class AddTopSubmissionStatusToAssignments < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :assignments, :top_submission_status, :integer, default: 0
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddTargetAudienceToOrganizationsAndAvatars < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :target_audience, :integer, default: 0
4
+ add_column :avatars, :target_audience, :integer, default: 0
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddTrustedForForumToUser < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :trusted_for_forum, :boolean
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddIncognitoModeEnabledToOrganization < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :incognito_mode_enabled, :boolean
4
+ end
5
+ end
@@ -26,6 +26,7 @@ Mumukit::Platform.configure do |config|
26
26
  end
27
27
 
28
28
  require_relative './domain/area'
29
+ require_relative './domain/incognito'
29
30
  require_relative './domain/evaluation'
30
31
  require_relative './domain/submission'
31
32
  require_relative './domain/status'
@@ -2,3 +2,4 @@ require_relative './extensions/string'
2
2
  require_relative './extensions/array'
3
3
  require_relative './extensions/module'
4
4
  require_relative './extensions/hash'
5
+ require_relative './extensions/time'
@@ -0,0 +1,5 @@
1
+ class Time
2
+ def round_years_since(another_time)
3
+ (self.to_s(:number).to_i - another_time.to_s(:number).to_i) / 10e9.to_i
4
+ end
5
+ end
@@ -1,5 +1,6 @@
1
1
  require_relative './factories/api_client_factory'
2
2
  require_relative './factories/assignments_factory'
3
+ require_relative './factories/avatar_factory'
3
4
  require_relative './factories/book_factory'
4
5
  require_relative './factories/chapter_factory'
5
6
  require_relative './factories/complement_factory'
@@ -15,4 +16,4 @@ require_relative './factories/message_factory'
15
16
  require_relative './factories/organization_factory'
16
17
  require_relative './factories/topic_factory'
17
18
  require_relative './factories/user_factory'
18
- require_relative './factories/usage_factory'
19
+ require_relative './factories/usage_factory'
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :avatar do
3
+ image_url { Faker::Internet.url }
4
+ target_audience { :grown_ups }
5
+ end
6
+ end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :discussion do
3
- title { 'A discussion' }
4
- description { 'A discussion description' }
3
+ title { Faker::Lorem.sentence(word_count: 2) }
4
+ description { Faker::Lorem.sentence(word_count: 5) }
5
5
  initiator { create(:user) }
6
6
  item { create(:exercise) }
7
7
  organization { Organization.current rescue nil }
@@ -1,11 +1,6 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :message do
4
- exercise_id { Faker::Internet.number(2) }
5
- assignment
6
- submission_id { assignment.id }
7
- sender { Faker::Internet.email }
8
- type { 'success' }
9
- content { Faker::Lorem.sentence(3) }
4
+ content { Faker::Lorem.sentence(word_count: 3) }
10
5
  end
11
6
  end
@@ -6,6 +6,5 @@ FactoryBot.define do
6
6
  last_name { Faker::Name.last_name }
7
7
  gender { 1 }
8
8
  birthdate { Date.today }
9
- avatar { Avatar.new image_url: 'user_shape.png' }
10
9
  end
11
10
  end
@@ -66,6 +66,10 @@ module Mumuki::Domain::Helpers::Organization
66
66
  Mumukit::Platform::Organization.current
67
67
  end
68
68
 
69
+ def current?
70
+ Mumukit::Platform::Organization.current?
71
+ end
72
+
69
73
  def parse(json)
70
74
  json
71
75
  .slice(:name)
@@ -13,6 +13,8 @@ module Mumuki::Domain::Helpers::User
13
13
  :protect!,
14
14
  :protect_delegation!,
15
15
  :protect_permissions_assignment!,
16
+ :student_granted_organizations,
17
+ :any_granted_organizations,
16
18
  to: :permissions
17
19
 
18
20
  def platform_class_name
@@ -71,12 +73,10 @@ module Mumuki::Domain::Helpers::User
71
73
  "#{full_name} <#{email}> [#{uid}]"
72
74
  end
73
75
 
74
- ## Accesible organizations
76
+ ## Accessible organizations
75
77
 
76
- def student_granted_organizations
77
- permissions.student_granted_organizations.map do |org|
78
- Mumukit::Platform::Organization.find_by_name!(org) rescue nil
79
- end.compact
78
+ revamp_accessor :any_granted_organizations, :student_granted_organizations do |_, _, result|
79
+ result.map { |org| Mumukit::Platform::Organization.find_by_name!(org) rescue nil }.compact
80
80
  end
81
81
 
82
82
  def has_student_granted_organizations?
@@ -84,11 +84,7 @@ module Mumuki::Domain::Helpers::User
84
84
  end
85
85
 
86
86
  def main_organization
87
- student_granted_organizations.first
88
- end
89
-
90
- def has_main_organization?
91
- student_granted_organizations.length == 1
87
+ student_granted_organizations.first || any_granted_organizations.first
92
88
  end
93
89
 
94
90
  def has_immersive_main_organization?
@@ -0,0 +1,119 @@
1
+ module Mumuki::Domain
2
+ class IncognitoClass
3
+
4
+ def incognito?
5
+ true
6
+ end
7
+
8
+ # ============
9
+ # Permissions
10
+ # ============
11
+
12
+ def ensure_enabled!
13
+ end
14
+
15
+ def has_student_granted_organizations?
16
+ false
17
+ end
18
+
19
+ def teacher_here?
20
+ false
21
+ end
22
+
23
+ def teacher_of?(*)
24
+ false
25
+ end
26
+
27
+ def profile_completed?
28
+ true
29
+ end
30
+
31
+ def writer?
32
+ false
33
+ end
34
+
35
+ def moderator_here?
36
+ false
37
+ end
38
+
39
+ def can_discuss_here?
40
+ false
41
+ end
42
+
43
+ def can_discuss_in?(*)
44
+ false
45
+ end
46
+
47
+ # ========
48
+ # Visiting
49
+ # ========
50
+
51
+ def visit!(*)
52
+ end
53
+
54
+ # ========
55
+ # Progress
56
+ # ========
57
+
58
+ def next_exercise_at(guide)
59
+ guide.exercises.first
60
+ end
61
+
62
+ # def completed_containers_with_lookahead(*)
63
+ # raise 'Unsupported operation. Userless mode and progressive display modes are incompatible'
64
+ # end
65
+
66
+ def progress_at(content, organization)
67
+ Indicator.new content: content, organization: organization
68
+ end
69
+
70
+ def build_assignment(exercise, organization)
71
+ Assignment.new exercise: exercise, organization: organization, submitter: self
72
+ end
73
+
74
+ def pending_siblings_at(content)
75
+ []
76
+ end
77
+
78
+ # ============
79
+ # ActiveRecord
80
+ # ============
81
+
82
+ def id
83
+ '<incognito>'
84
+ end
85
+
86
+ def is_a?(other)
87
+ other.is_a?(Class) && other.name == 'User' || super
88
+ end
89
+
90
+ def _read_attribute(key)
91
+ return id if key == 'id'
92
+ raise "unknown attribute #{key}"
93
+ end
94
+
95
+ def new_record?
96
+ false
97
+ end
98
+
99
+ def self.primary_key
100
+ 'id'
101
+ end
102
+
103
+ # ==========
104
+ # Evaluation
105
+ # ==========
106
+
107
+ def interpolations
108
+ []
109
+ end
110
+
111
+ def run_submission!(submission, assignment, evaluation)
112
+ results = submission.dry_run! assignment, evaluation
113
+ assignment.assign_attributes results
114
+ results
115
+ end
116
+ end
117
+
118
+ Incognito = IncognitoClass.new
119
+ end
@@ -0,0 +1,59 @@
1
+ ---
2
+ es-CL:
3
+ activerecord:
4
+ attributes:
5
+ exercise:
6
+ description: Descripción
7
+ extra: Biblioteca
8
+ hint: Ayudas
9
+ language: Lenguaje
10
+ locale: Idioma
11
+ tag_list: Etiquetas
12
+ title: Título
13
+ guide:
14
+ description: Descripción
15
+ github_repository: Repositorio Github
16
+ language: Lenguaje
17
+ locale: Idioma
18
+ name: Nombre
19
+ submission:
20
+ content: Contenido
21
+ errors:
22
+ models:
23
+ guide:
24
+ attributes:
25
+ base:
26
+ in_use: 'La guía aún está siendo utilizada en la organización %{organization}'
27
+ topic:
28
+ attributes:
29
+ base:
30
+ in_use: 'El tema aún está siendo utilizado en la organización %{organization}'
31
+ exercise:
32
+ attributes:
33
+ randomizations:
34
+ invalid_format: 'formato inválido'
35
+ own_expectations:
36
+ invalid_format: 'formato inválido'
37
+ assistance_rules:
38
+ invalid_format: 'formato inválido'
39
+ name:
40
+ invalid_format: 'no debe contener /'
41
+ base:
42
+ evaluation_criteria_required: 'Tienes que proveer tests y/o expectativas'
43
+ organization:
44
+ attributes:
45
+ base:
46
+ consistent_public_login: 'Una organización pública no puede restringir los métodos de login'
47
+ invalid_activity_range: 'La fecha de inhabilitación no puede ser anterior a la de inicio del recorrido'
48
+ models:
49
+ exercise:
50
+ one: Ejercicio
51
+ other: Ejercicios
52
+ chapter:
53
+ one: Capítulo
54
+ other: Capítulos
55
+ guide: Guía
56
+ language: Lenguaje
57
+ submission:
58
+ one: Solución
59
+ other: Soluciones
@@ -0,0 +1,3 @@
1
+ es-CL:
2
+ console_submission:
3
+ try_again: ¡Ups! Algo salió mal en mumuki. Reinicia el ejercicio, espera unos segundos y vuelve a intentar.
@@ -11,7 +11,8 @@ class Mumuki::Domain::Organization::Profile < Mumukit::Platform::Model
11
11
  :contact_email,
12
12
  :terms_of_service,
13
13
  :community_link,
14
- :errors_explanations
14
+ :errors_explanations,
15
+ :welcome_email_template
15
16
 
16
17
  def locale_json
17
18
  locale_h.to_json
@@ -1,20 +1,22 @@
1
1
  class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
2
2
  include Mumukit::Login::LoginSettingsHelpers
3
3
 
4
- model_attr_accessor :login_methods,
4
+ model_attr_accessor :disabled_from,
5
+ :embeddable?,
6
+ :feedback_suggestions_enabled?,
7
+ :forum_discussions_minimal_role,
8
+ :forum_enabled?,
9
+ :forum_only_for_trusted?,
10
+ :gamification_enabled?,
11
+ :immersive?,
12
+ :in_preparation_until,
13
+ :login_methods,
5
14
  :login_provider,
6
15
  :login_provider_settings,
7
- :forum_discussions_minimal_role,
8
- :raise_hand_enabled?,
9
- :feedback_suggestions_enabled?,
10
16
  :public?,
11
- :embeddable?,
12
- :immersive?,
13
- :forum_enabled?,
17
+ :raise_hand_enabled?,
14
18
  :report_issue_enabled?,
15
- :disabled_from,
16
- :in_preparation_until,
17
- :gamification_enabled?
19
+ :greet_new_users?
18
20
 
19
21
  def private?
20
22
  !public?
@@ -14,10 +14,6 @@ module Mumuki::Domain::Status::Discussion
14
14
  define_method(selector) { false }
15
15
  end
16
16
 
17
- def allowed_for?(*)
18
- true
19
- end
20
-
21
17
  def reachable_statuses_for_moderator(*)
22
18
  []
23
19
  end
@@ -38,10 +34,6 @@ module Mumuki::Domain::Status::Discussion
38
34
  end
39
35
  end
40
36
 
41
- def allowed_statuses_for(user, discussion)
42
- STATUSES.select { |it| it.allowed_for?(user, discussion) }
43
- end
44
-
45
37
  def as_json(_options={})
46
38
  to_s
47
39
  end
@@ -5,6 +5,14 @@ module Mumuki::Domain::Status::Discussion::Opened
5
5
  true
6
6
  end
7
7
 
8
+ def self.reachable_statuses_for_initiator(discussion)
9
+ if discussion.has_responses?
10
+ [Mumuki::Domain::Status::Discussion::PendingReview]
11
+ else
12
+ [Mumuki::Domain::Status::Discussion::Closed]
13
+ end
14
+ end
15
+
8
16
  def self.reachable_statuses_for_moderator(discussion)
9
17
  if discussion.has_responses?
10
18
  [Mumuki::Domain::Status::Discussion::Closed, Mumuki::Domain::Status::Discussion::Solved]
@@ -8,4 +8,8 @@ module Mumuki::Domain::Status::Submission::Passed
8
8
  def self.iconize
9
9
  {class: :success, type: 'check-circle'}
10
10
  end
11
+
12
+ def self.exp_given
13
+ 100
14
+ end
11
15
  end
@@ -12,4 +12,8 @@ module Mumuki::Domain::Status::Submission::PassedWithWarnings
12
12
  def self.iconize
13
13
  {class: :warning, type: 'exclamation-circle'}
14
14
  end
15
+
16
+ def self.exp_given
17
+ 50
18
+ end
15
19
  end
@@ -8,4 +8,8 @@ module Mumuki::Domain::Status::Submission::Skipped
8
8
  def self.iconize
9
9
  {class: :success, type: 'check-circle'}
10
10
  end
11
+
12
+ def self.exp_given
13
+ 100
14
+ end
11
15
  end
@@ -42,4 +42,12 @@ module Mumuki::Domain::Status::Submission
42
42
  def solved?
43
43
  passed? || skipped?
44
44
  end
45
+
46
+ def improved_by?(status)
47
+ self.exp_given < status.exp_given
48
+ end
49
+
50
+ def exp_given
51
+ 0
52
+ end
45
53
  end
@@ -21,12 +21,16 @@ class Mumuki::Domain::Submission::Base
21
21
 
22
22
  def run!(assignment, evaluation)
23
23
  save_submission! assignment
24
- results = evaluation.evaluate! assignment, self
24
+ results = dry_run! assignment, evaluation
25
25
  save_results! results, assignment
26
26
  notify_results! results, assignment
27
27
  results
28
28
  end
29
29
 
30
+ def dry_run!(assignment, evaluation)
31
+ evaluation.evaluate! assignment, self
32
+ end
33
+
30
34
  def with_client_result(result)
31
35
  self.client_result = result if result.present?
32
36
  self
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '7.7.1'
3
+ VERSION = '7.9.0'
4
4
  end
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mumuki-domain
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.7.1
4
+ version: 7.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Franco Leonardo Bulgarelli
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-07-31 00:00:00.000000000 Z
11
+ date: 2020-09-11 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: '7.6'
33
+ version: '7.8'
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: '7.6'
40
+ version: '7.8'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: mumukit-assistant
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -128,14 +128,14 @@ dependencies:
128
128
  requirements:
129
129
  - - "~>"
130
130
  - !ruby/object:Gem::Version
131
- version: '5.1'
131
+ version: '5.2'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - "~>"
137
137
  - !ruby/object:Gem::Version
138
- version: '5.1'
138
+ version: '5.2'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: mumukit-sync
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -256,6 +256,7 @@ files:
256
256
  - app/models/concerns/contextualization.rb
257
257
  - app/models/concerns/disabling.rb
258
258
  - app/models/concerns/friendly_name.rb
259
+ - app/models/concerns/gamified.rb
259
260
  - app/models/concerns/guide_container.rb
260
261
  - app/models/concerns/navigation/parent_navigation.rb
261
262
  - app/models/concerns/navigation/siblings_navigation.rb
@@ -292,6 +293,7 @@ files:
292
293
  - app/models/concerns/with_scoped_queries/page.rb
293
294
  - app/models/concerns/with_scoped_queries/sort.rb
294
295
  - app/models/concerns/with_slug.rb
296
+ - app/models/concerns/with_target_audience.rb
295
297
  - app/models/concerns/with_usages.rb
296
298
  - app/models/concerns/with_user_navigation.rb
297
299
  - app/models/content.rb
@@ -322,6 +324,7 @@ files:
322
324
  - app/models/upvote.rb
323
325
  - app/models/usage.rb
324
326
  - app/models/user.rb
327
+ - app/models/user_stats.rb
325
328
  - app/models/with_stats.rb
326
329
  - db/migrate/20141120231135_create_exercises.rb
327
330
  - db/migrate/20141120231735_create_submissions.rb
@@ -608,10 +611,15 @@ files:
608
611
  - db/migrate/20200601203033_add_course_to_exam.rb
609
612
  - db/migrate/20200605161350_add_passing_criterions_to_exam.rb
610
613
  - db/migrate/20200608132959_add_progressive_display_lookahead_to_organizations.rb
614
+ - db/migrate/20200616160640_create_user_stats.rb
615
+ - db/migrate/20200617142217_add_top_submission_status_to_assignments.rb
611
616
  - db/migrate/20200702165503_add_messages_count_to_discussion.rb
617
+ - db/migrate/20200717143830_add_target_audience_to_organizations_and_avatars.rb
612
618
  - db/migrate/20200728162727_add_not_actually_a_question_field_to_messages.rb
613
619
  - db/migrate/20200728163038_add_requires_moderator_response_to_discussions.rb
620
+ - db/migrate/20200730221001_add_trusted_for_forum_to_user.rb
614
621
  - db/migrate/20200731081757_add_last_moderator_access_fields_to_discussion.rb
622
+ - db/migrate/20200804191643_add_incognito_mode_enabled_to_organization.rb
615
623
  - lib/mumuki/domain.rb
616
624
  - lib/mumuki/domain/area.rb
617
625
  - lib/mumuki/domain/engine.rb
@@ -632,9 +640,11 @@ files:
632
640
  - lib/mumuki/domain/extensions/hash.rb
633
641
  - lib/mumuki/domain/extensions/module.rb
634
642
  - lib/mumuki/domain/extensions/string.rb
643
+ - lib/mumuki/domain/extensions/time.rb
635
644
  - lib/mumuki/domain/factories.rb
636
645
  - lib/mumuki/domain/factories/api_client_factory.rb
637
646
  - lib/mumuki/domain/factories/assignments_factory.rb
647
+ - lib/mumuki/domain/factories/avatar_factory.rb
638
648
  - lib/mumuki/domain/factories/book_factory.rb
639
649
  - lib/mumuki/domain/factories/chapter_factory.rb
640
650
  - lib/mumuki/domain/factories/complement_factory.rb
@@ -656,10 +666,13 @@ files:
656
666
  - lib/mumuki/domain/helpers/course.rb
657
667
  - lib/mumuki/domain/helpers/organization.rb
658
668
  - lib/mumuki/domain/helpers/user.rb
669
+ - lib/mumuki/domain/incognito.rb
659
670
  - lib/mumuki/domain/locales/activerecord/en.yml
671
+ - lib/mumuki/domain/locales/activerecord/es-CL.yml
660
672
  - lib/mumuki/domain/locales/activerecord/es.yml
661
673
  - lib/mumuki/domain/locales/activerecord/pt.yml
662
674
  - lib/mumuki/domain/locales/console_submission/en.yml
675
+ - lib/mumuki/domain/locales/console_submission/es-CL.yml
663
676
  - lib/mumuki/domain/locales/console_submission/es.yml
664
677
  - lib/mumuki/domain/locales/console_submission/pt.yml
665
678
  - lib/mumuki/domain/organization.rb