mumuki-domain 8.1.2 → 8.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +2 -0
- data/app/models/assignment.rb +11 -2
- data/app/models/chapter.rb +3 -0
- data/app/models/concerns/guide_container.rb +2 -1
- data/app/models/concerns/with_assignments.rb +1 -0
- data/app/models/concerns/with_assignments_batch.rb +31 -0
- data/app/models/concerns/with_expectations.rb +3 -7
- data/app/models/concerns/with_preferences.rb +7 -0
- data/app/models/concerns/with_randomizations.rb +1 -2
- data/app/models/concerns/with_timed_enablement.rb +11 -0
- data/app/models/exam.rb +4 -8
- data/app/models/exam_authorization_request.rb +19 -0
- data/app/models/exam_registration.rb +38 -0
- data/app/models/exam_registration/authorization_criterion.rb +61 -0
- data/app/models/exercise.rb +8 -5
- data/app/models/exercise/challenge.rb +4 -2
- data/app/models/exercise/problem.rb +5 -10
- data/app/models/guide.rb +7 -2
- data/app/models/indicator.rb +35 -0
- data/app/models/notification.rb +5 -0
- data/app/models/organization.rb +1 -1
- data/app/models/preferences.rb +17 -0
- data/app/models/progress.rb +35 -0
- data/app/models/stats.rb +4 -0
- data/app/models/topic.rb +16 -7
- data/app/models/user.rb +7 -1
- data/app/models/with_stats.rb +2 -1
- data/db/migrate/20210111125810_add_uppercase_mode_to_user.rb +5 -0
- data/db/migrate/20210114200545_create_exam_registrations.rb +14 -0
- data/db/migrate/20210118180941_create_exam_authorization_request.rb +13 -0
- data/db/migrate/20210118194904_create_notification.rb +13 -0
- data/db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb +5 -0
- data/db/migrate/20210119190204_create_exam_registration_exam_join_table.rb +8 -0
- data/lib/mumuki/domain.rb +1 -0
- data/lib/mumuki/domain/extensions.rb +2 -0
- data/lib/mumuki/domain/extensions/array.rb +2 -4
- data/lib/mumuki/domain/extensions/hash.rb +4 -0
- data/lib/mumuki/domain/extensions/nil.rb +17 -0
- data/lib/mumuki/domain/extensions/string.rb +2 -9
- data/lib/mumuki/domain/extensions/symbol.rb +5 -0
- data/lib/mumuki/domain/factories.rb +2 -0
- data/lib/mumuki/domain/factories/exam_authorization_request_factory.rb +6 -0
- data/lib/mumuki/domain/factories/exam_registration_factory.rb +8 -0
- data/lib/mumuki/domain/helpers/organization.rb +4 -0
- data/lib/mumuki/domain/incognito.rb +1 -1
- data/lib/mumuki/domain/locales/activerecord/en.yml +1 -1
- data/lib/mumuki/domain/locales/activerecord/es-CL.yml +1 -1
- data/lib/mumuki/domain/locales/activerecord/es.yml +1 -1
- data/lib/mumuki/domain/locales/activerecord/pt.yml +1 -1
- data/lib/mumuki/domain/progress_transfer.rb +8 -0
- data/lib/mumuki/domain/progress_transfer/base.rb +44 -0
- data/lib/mumuki/domain/progress_transfer/copy.rb +9 -0
- data/lib/mumuki/domain/progress_transfer/move.rb +14 -0
- data/lib/mumuki/domain/status/submission/manual_evaluation_pending.rb +1 -1
- data/lib/mumuki/domain/status/submission/submission.rb +4 -0
- data/lib/mumuki/domain/version.rb +1 -1
- metadata +28 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8de3b061fa4456ea064c8a2d782b6e78f4f4685c022b58b21942095f7fbbf838
|
4
|
+
data.tar.gz: 34c7fc5d21ebb806bda2b59faf5a54c2389545fbc4b5ad66bc89bc7c3a3dd8f8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9de9ccc06eeb08ad7fc4eb18b0bad4b93d095343135900e0199587efdba3a10182fe902653c828baed7ef16f1adf52913c3747d844aa19a636946b3a9d05cc44
|
7
|
+
data.tar.gz: 17d913971689eceb1aeb165dbc8c6eec0e8ea4b314097c1ea9a5d8da2e9ed1c9cb46bb5c347db11d8f74b516cc2feb72b38ff79b67eade1510d4b20498617d71
|
data/Rakefile
CHANGED
data/app/models/assignment.rb
CHANGED
@@ -23,6 +23,8 @@ class Assignment < Progress
|
|
23
23
|
|
24
24
|
delegate :completed?, :solved?, to: :submission_status
|
25
25
|
|
26
|
+
delegate :content_available_in?, to: :parent
|
27
|
+
|
26
28
|
alias_attribute :status, :submission_status
|
27
29
|
alias_attribute :attempts_count, :attemps_count
|
28
30
|
|
@@ -126,7 +128,7 @@ class Assignment < Progress
|
|
126
128
|
end
|
127
129
|
|
128
130
|
def content=(content)
|
129
|
-
if
|
131
|
+
if exercise.solvable?
|
130
132
|
self.solution = exercise.single_choice? ? exercise.choice_index_for(content) : content
|
131
133
|
end
|
132
134
|
end
|
@@ -261,8 +263,16 @@ class Assignment < Progress
|
|
261
263
|
update! misplaced: value if value != misplaced?
|
262
264
|
end
|
263
265
|
|
266
|
+
def self.build_for(user, exercise, organization)
|
267
|
+
Assignment.new submitter: user, exercise: exercise, organization: organization
|
268
|
+
end
|
269
|
+
|
264
270
|
private
|
265
271
|
|
272
|
+
def duplicates_key
|
273
|
+
{ exercise: exercise, submitter: submitter }
|
274
|
+
end
|
275
|
+
|
266
276
|
def update_submissions_count!
|
267
277
|
self.class.connection.execute(
|
268
278
|
"update public.exercises
|
@@ -278,5 +288,4 @@ class Assignment < Progress
|
|
278
288
|
def update_last_submission!
|
279
289
|
submitter.update!(last_submission_date: DateTime.current, last_exercise: exercise)
|
280
290
|
end
|
281
|
-
|
282
291
|
end
|
data/app/models/chapter.rb
CHANGED
@@ -13,6 +13,9 @@ class Chapter < ApplicationRecord
|
|
13
13
|
|
14
14
|
has_many :exercises, through: :topic
|
15
15
|
|
16
|
+
delegate :monolesson?, :monolesson, :first_lesson, to: :topic
|
17
|
+
|
18
|
+
delegate :next_exercise, :stats_for, to: :monolesson, allow_nil: true
|
16
19
|
|
17
20
|
def used_in?(organization)
|
18
21
|
organization.book == self.book
|
@@ -19,6 +19,7 @@ module WithAssignments
|
|
19
19
|
end
|
20
20
|
|
21
21
|
# TODO: When the organization is used in this one, please change guide.pending_exercises
|
22
|
+
# TODO: Please do the same on WithAssignmentsBatch
|
22
23
|
def find_assignment_for(user, _organization)
|
23
24
|
assignments.find_by(submitter: user)
|
24
25
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# WithAssignmentsBatch mirrors the WithAssignment mixin
|
2
|
+
# but implements operations in batches, so that they outperform
|
3
|
+
# their counterparts
|
4
|
+
module WithAssignmentsBatch
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
def find_assignments_for(user, _organization = Organization.current, &block)
|
8
|
+
block = block_given? ? block : lambda { |it, _e| it }
|
9
|
+
|
10
|
+
return exercises.map { |it| block.call nil, it } unless user
|
11
|
+
|
12
|
+
pairs = exercises.map { |it| [it.id, [nil, it]] }.to_h
|
13
|
+
Assignment.where(submitter: user, exercise: exercises).each do |it|
|
14
|
+
pairs[it.exercise_id][0] = it
|
15
|
+
end
|
16
|
+
|
17
|
+
pairs.values.map { |assignment, exercise| block.call assignment, exercise }
|
18
|
+
end
|
19
|
+
|
20
|
+
def statuses_for(user, organization = Organization.current)
|
21
|
+
find_assignments_for user, organization do |it|
|
22
|
+
it&.status || Mumuki::Domain::Status::Submission::Pending
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def assignments_for(user, organization = Organization.current)
|
27
|
+
find_assignments_for user, organization do |it, exercise|
|
28
|
+
it || Assignment.build_for(user, exercise, organization)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -18,16 +18,12 @@ module WithExpectations
|
|
18
18
|
self[:expectations] = expectations.map(&:stringify_keys)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
21
|
+
def raw_expectations
|
22
22
|
self[:expectations]
|
23
23
|
end
|
24
24
|
|
25
|
-
def own_custom_expectations
|
26
|
-
self[:custom_expectations]
|
27
|
-
end
|
28
|
-
|
29
25
|
def ensure_expectations_format
|
30
|
-
errors.add :
|
31
|
-
:invalid_format unless
|
26
|
+
errors.add :raw_expectations,
|
27
|
+
:invalid_format unless raw_expectations.to_a.all? { |it| Mulang::Expectation.valid? it }
|
32
28
|
end
|
33
29
|
end
|
data/app/models/exam.rb
CHANGED
@@ -3,13 +3,17 @@ class Exam < ApplicationRecord
|
|
3
3
|
include GuideContainer
|
4
4
|
include FriendlyName
|
5
5
|
include TerminalNavigation
|
6
|
+
include WithTimedEnablement
|
6
7
|
|
7
8
|
belongs_to :organization
|
8
9
|
belongs_to :course
|
9
10
|
|
10
11
|
has_many :authorizations, class_name: 'ExamAuthorization', dependent: :destroy
|
12
|
+
has_many :authorization_requests, class_name: 'ExamAuthorizationRequest', dependent: :destroy
|
11
13
|
has_many :users, through: :authorizations
|
12
14
|
|
15
|
+
has_and_belongs_to_many :exam_registrations
|
16
|
+
|
13
17
|
enum passing_criterion_type: [:none, :percentage, :passed_exercises], _prefix: :passing_criterion
|
14
18
|
|
15
19
|
validates_presence_of :start_time, :end_time
|
@@ -27,14 +31,6 @@ class Exam < ApplicationRecord
|
|
27
31
|
organization == self.organization
|
28
32
|
end
|
29
33
|
|
30
|
-
def enabled?
|
31
|
-
enabled_range.cover? DateTime.current
|
32
|
-
end
|
33
|
-
|
34
|
-
def enabled_range
|
35
|
-
start_time..end_time
|
36
|
-
end
|
37
|
-
|
38
34
|
def enabled_for?(user)
|
39
35
|
enabled_range_for(user).cover? DateTime.current
|
40
36
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
class ExamAuthorizationRequest < ApplicationRecord
|
2
|
+
belongs_to :exam
|
3
|
+
belongs_to :user
|
4
|
+
belongs_to :organization
|
5
|
+
|
6
|
+
enum status: %i(pending approved rejected)
|
7
|
+
|
8
|
+
after_update :notify_user!
|
9
|
+
|
10
|
+
def try_authorize!
|
11
|
+
exam.authorize! user if approved?
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def notify_user!
|
17
|
+
Notification.create! organization: organization, user: user, target: self if saved_change_to_status?
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class ExamRegistration < ApplicationRecord
|
2
|
+
include WithTimedEnablement
|
3
|
+
|
4
|
+
belongs_to :organization
|
5
|
+
has_and_belongs_to_many :exams
|
6
|
+
has_many :authorization_requests, class_name: 'ExamAuthorizationRequest', through: :exams
|
7
|
+
|
8
|
+
enum authorization_criterion_type: %i(none passed_exercises), _prefix: :authorization_criterion
|
9
|
+
|
10
|
+
before_save :ensure_valid_authorization_criterion!
|
11
|
+
|
12
|
+
delegate :meets_authorization_criteria?, :process_request!, to: :authorization_criterion
|
13
|
+
|
14
|
+
def authorization_criterion
|
15
|
+
@authorization_criterion ||= ExamRegistration::AuthorizationCriterion.parse(authorization_criterion_type, authorization_criterion_value)
|
16
|
+
end
|
17
|
+
|
18
|
+
def ensure_valid_authorization_criterion!
|
19
|
+
authorization_criterion.ensure_valid!
|
20
|
+
end
|
21
|
+
|
22
|
+
def start!(users)
|
23
|
+
users.each &method(:notify_user!)
|
24
|
+
end
|
25
|
+
|
26
|
+
def process_requests!
|
27
|
+
authorization_requests.each do |it|
|
28
|
+
process_request! it
|
29
|
+
it.try_authorize!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def notify_user!(user)
|
36
|
+
Notification.create! organization: organization, user: user, target: self
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
class ExamRegistration::AuthorizationCriterion
|
2
|
+
attr_reader :value
|
3
|
+
|
4
|
+
def initialize(value)
|
5
|
+
@value = value
|
6
|
+
end
|
7
|
+
|
8
|
+
def type
|
9
|
+
self.class.name.demodulize.underscore
|
10
|
+
end
|
11
|
+
|
12
|
+
def as_json
|
13
|
+
{ type: type, value: value }
|
14
|
+
end
|
15
|
+
|
16
|
+
def ensure_valid!
|
17
|
+
raise "Invalid criterion value #{value} for #{type}" unless valid?
|
18
|
+
end
|
19
|
+
|
20
|
+
def process_request!(authorization_request)
|
21
|
+
authorization_request.update! status: authorization_status_for(authorization_request)
|
22
|
+
end
|
23
|
+
|
24
|
+
def authorization_status_for(authorization_request)
|
25
|
+
meets_authorization_criteria?(authorization_request) ? :approved : :rejected
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.parse(type, value)
|
29
|
+
parse_criterion_type(type, value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.parse_criterion_type(type, value)
|
33
|
+
"ExamRegistration::AuthorizationCriterion::#{type.camelize}".constantize.new(value)
|
34
|
+
rescue
|
35
|
+
raise "Invalid criterion type #{type}"
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class ExamRegistration::AuthorizationCriterion::None < ExamRegistration::AuthorizationCriterion
|
40
|
+
def initialize(_)
|
41
|
+
@value = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
def valid?
|
45
|
+
!value
|
46
|
+
end
|
47
|
+
|
48
|
+
def meets_authorization_criteria?(_authorization_request)
|
49
|
+
true
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class ExamRegistration::AuthorizationCriterion::PassedExercises < ExamRegistration::AuthorizationCriterion
|
54
|
+
def valid?
|
55
|
+
value.positive?
|
56
|
+
end
|
57
|
+
|
58
|
+
def meets_authorization_criteria?(authorization_request)
|
59
|
+
authorization_request.user.passed_submissions_count_in(authorization_request.organization) >= value
|
60
|
+
end
|
61
|
+
end
|
data/app/models/exercise.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
class Exercise < ApplicationRecord
|
2
|
-
RANDOMIZED_FIELDS = [:default_content, :description, :extra, :hint, :test]
|
2
|
+
RANDOMIZED_FIELDS = [:default_content, :description, :extra, :hint, :test, :expectations, :custom_expectations]
|
3
3
|
BASIC_RESOURCE_FIELDS = %i(
|
4
4
|
name layout editor corollary teacher_info manual_evaluation locale
|
5
5
|
choices assistance_rules randomizations tag_list extra_visible goal
|
@@ -28,6 +28,10 @@ class Exercise < ApplicationRecord
|
|
28
28
|
|
29
29
|
defaults { self.submissions_count = 0 }
|
30
30
|
|
31
|
+
def self.default_scope
|
32
|
+
where(manual_evaluation: false) if Organization.safe_current&.prevent_manual_evaluation_content
|
33
|
+
end
|
34
|
+
|
31
35
|
alias_method :progress_for, :assignment_for
|
32
36
|
|
33
37
|
serialize :choices, Array
|
@@ -38,6 +42,7 @@ class Exercise < ApplicationRecord
|
|
38
42
|
|
39
43
|
randomize(*RANDOMIZED_FIELDS)
|
40
44
|
delegate :timed?, to: :navigable_parent
|
45
|
+
delegate :stats_for, to: :guide
|
41
46
|
|
42
47
|
def console?
|
43
48
|
queriable?
|
@@ -135,8 +140,6 @@ class Exercise < ApplicationRecord
|
|
135
140
|
language_resource_h = language.to_embedded_resource_h if language != guide.language
|
136
141
|
as_json(only: BASIC_RESOURCE_FIELDS)
|
137
142
|
.merge(id: bibliotheca_id, language: language_resource_h, type: type.underscore)
|
138
|
-
.merge(expectations: self[:expectations])
|
139
|
-
.merge(custom_expectations: self[:custom_expectations])
|
140
143
|
.merge(settings: self[:settings])
|
141
144
|
.merge(RANDOMIZED_FIELDS.map { |it| [it, self[it]] }.to_h)
|
142
145
|
.symbolize_keys
|
@@ -239,8 +242,8 @@ class Exercise < ApplicationRecord
|
|
239
242
|
guide.pending_exercises(user)
|
240
243
|
end
|
241
244
|
|
242
|
-
def
|
243
|
-
is_a? ::
|
245
|
+
def solvable?
|
246
|
+
is_a? ::Problem
|
244
247
|
end
|
245
248
|
|
246
249
|
private
|
@@ -21,20 +21,15 @@ class Problem < QueriableChallenge
|
|
21
21
|
self.expectations = []
|
22
22
|
end
|
23
23
|
|
24
|
+
alias_method :own_expectations, :expectations
|
25
|
+
alias_method :own_custom_expectations, :custom_expectations
|
26
|
+
|
24
27
|
def expectations
|
25
|
-
own_expectations +
|
28
|
+
own_expectations + guide.expectations
|
26
29
|
end
|
27
30
|
|
28
31
|
def custom_expectations
|
29
|
-
"#{own_custom_expectations}\n#{
|
30
|
-
end
|
31
|
-
|
32
|
-
def guide_expectations
|
33
|
-
guide.expectations
|
34
|
-
end
|
35
|
-
|
36
|
-
def guide_custom_expectations
|
37
|
-
guide.custom_expectations
|
32
|
+
"#{own_custom_expectations}\n#{guide.custom_expectations}"
|
38
33
|
end
|
39
34
|
|
40
35
|
def evaluation_criteria?
|
data/app/models/guide.rb
CHANGED
@@ -6,7 +6,8 @@ class Guide < Content
|
|
6
6
|
|
7
7
|
include WithStats,
|
8
8
|
WithExpectations,
|
9
|
-
WithLanguage
|
9
|
+
WithLanguage,
|
10
|
+
WithAssignmentsBatch
|
10
11
|
|
11
12
|
markdown_on :corollary, :sources, :learn_more, :teacher_info
|
12
13
|
|
@@ -42,7 +43,11 @@ class Guide < Content
|
|
42
43
|
end
|
43
44
|
|
44
45
|
def next_exercise(user)
|
45
|
-
user.
|
46
|
+
if user.present?
|
47
|
+
user.next_exercise_at(self)
|
48
|
+
else
|
49
|
+
first_exercise
|
50
|
+
end
|
46
51
|
end
|
47
52
|
|
48
53
|
# TODO: Make use of pending_siblings logic
|
data/app/models/indicator.rb
CHANGED
@@ -66,8 +66,43 @@ class Indicator < Progress
|
|
66
66
|
where(content: content, organization: organization).delete_all
|
67
67
|
end
|
68
68
|
|
69
|
+
def _move_to!(organization)
|
70
|
+
move_children_to!(organization)
|
71
|
+
super
|
72
|
+
end
|
73
|
+
|
74
|
+
def _copy_to!(organization)
|
75
|
+
progress_item = super
|
76
|
+
children.each { |it| it._copy_to! organization }
|
77
|
+
progress_item
|
78
|
+
end
|
79
|
+
|
80
|
+
def move_children_to!(organization)
|
81
|
+
children.update_all(organization_id: organization.id)
|
82
|
+
|
83
|
+
indicators.each { |it| it.move_children_to!(organization) }
|
84
|
+
end
|
85
|
+
|
86
|
+
def cascade_delete_children!
|
87
|
+
indicators.each(&:cascade_delete_children!)
|
88
|
+
children.delete_all(:delete_all)
|
89
|
+
end
|
90
|
+
|
91
|
+
def content_available_in?(organization)
|
92
|
+
content.usage_in_organization(organization).present?
|
93
|
+
end
|
94
|
+
|
95
|
+
def delete_duplicates_in!(organization)
|
96
|
+
duplicates_in(organization).each(&:cascade_delete_children!)
|
97
|
+
super
|
98
|
+
end
|
99
|
+
|
69
100
|
private
|
70
101
|
|
102
|
+
def duplicates_key
|
103
|
+
{ content: content, user: user }
|
104
|
+
end
|
105
|
+
|
71
106
|
def children
|
72
107
|
indicators.presence || assignments
|
73
108
|
end
|
data/app/models/organization.rb
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
class Preferences
|
2
|
+
include ActiveModel::Model
|
3
|
+
|
4
|
+
def self.attributes
|
5
|
+
[:uppercase_mode]
|
6
|
+
end
|
7
|
+
|
8
|
+
attr_accessor *self.attributes
|
9
|
+
|
10
|
+
def self.from_attributes(*args)
|
11
|
+
new self.attributes.zip(args).to_h
|
12
|
+
end
|
13
|
+
|
14
|
+
def uppercase?
|
15
|
+
uppercase_mode
|
16
|
+
end
|
17
|
+
end
|
data/app/models/progress.rb
CHANGED
@@ -12,4 +12,39 @@ class Progress < ApplicationRecord
|
|
12
12
|
def dirty_parent_by_submission!
|
13
13
|
parent&.dirty_by_submission!
|
14
14
|
end
|
15
|
+
|
16
|
+
def _copy_to!(organization)
|
17
|
+
dup.transfer_to!(organization)
|
18
|
+
end
|
19
|
+
|
20
|
+
def transfer_to!(organization)
|
21
|
+
update! organization: organization, parent: nil
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :_move_to!, :transfer_to!
|
26
|
+
|
27
|
+
%w(copy move).each do |transfer_type|
|
28
|
+
define_method "#{transfer_type}_to!" do |organization|
|
29
|
+
"Mumuki::Domain::ProgressTransfer::#{transfer_type.camelize}".constantize.new(self, organization).execute!
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def guide_indicator?
|
34
|
+
is_a?(Indicator) && content_type == 'Guide'
|
35
|
+
end
|
36
|
+
|
37
|
+
def has_duplicates_in?(organization)
|
38
|
+
duplicates_in(organization).present?
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete_duplicates_in!(organization)
|
42
|
+
duplicates_in(organization).delete_all
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def duplicates_in(organization)
|
48
|
+
self.class.where(duplicates_key.merge(organization: organization)).where.not(id: id)
|
49
|
+
end
|
15
50
|
end
|
data/app/models/stats.rb
CHANGED
data/app/models/topic.rb
CHANGED
@@ -35,6 +35,14 @@ class Topic < Content
|
|
35
35
|
Chapter.where(topic: self).map(&:book).each(&:reindex_usages!)
|
36
36
|
end
|
37
37
|
|
38
|
+
def monolesson
|
39
|
+
@monolesson ||= lessons.to_a.single
|
40
|
+
end
|
41
|
+
|
42
|
+
def monolesson?
|
43
|
+
monolesson.present?
|
44
|
+
end
|
45
|
+
|
38
46
|
## Forking
|
39
47
|
|
40
48
|
def fork_children_into!(dup, organization, syncer)
|
@@ -42,18 +50,19 @@ class Topic < Content
|
|
42
50
|
end
|
43
51
|
|
44
52
|
def pending_lessons(user)
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
joins(
|
53
|
+
lessons
|
54
|
+
.includes(:guide)
|
55
|
+
.references(:guide)
|
56
|
+
.joins('left join exercises exercises on exercises.guide_id = guides.id')
|
57
|
+
.joins("left join assignments assignments
|
49
58
|
on assignments.exercise_id = exercises.id
|
50
59
|
and assignments.submitter_id = #{user.id}
|
51
60
|
and assignments.submission_status in (
|
52
61
|
#{Mumuki::Domain::Status::Submission::Passed.to_i},
|
53
62
|
#{Mumuki::Domain::Status::Submission::ManualEvaluationPending.to_i}
|
54
|
-
)")
|
55
|
-
where('assignments.id is null')
|
56
|
-
group('
|
63
|
+
)")
|
64
|
+
.where('assignments.id is null')
|
65
|
+
.group('guides.id', 'lessons.number', 'lessons.id')
|
57
66
|
end
|
58
67
|
|
59
68
|
private
|
data/app/models/user.rb
CHANGED
@@ -7,11 +7,13 @@ class User < ApplicationRecord
|
|
7
7
|
Awardee,
|
8
8
|
Disabling,
|
9
9
|
WithTermsAcceptance,
|
10
|
+
WithPreferences,
|
10
11
|
Mumuki::Domain::Helpers::User
|
11
12
|
|
12
13
|
serialize :permissions, Mumukit::Auth::Permissions
|
13
14
|
|
14
15
|
|
16
|
+
has_many :notifications
|
15
17
|
has_many :assignments, foreign_key: :submitter_id
|
16
18
|
has_many :messages, -> { order(created_at: :desc) }, through: :assignments
|
17
19
|
|
@@ -49,6 +51,10 @@ class User < ApplicationRecord
|
|
49
51
|
last_guide.try(:lesson)
|
50
52
|
end
|
51
53
|
|
54
|
+
def passed_submissions_count_in(organization)
|
55
|
+
assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
|
56
|
+
end
|
57
|
+
|
52
58
|
def submissions_count
|
53
59
|
assignments.pluck(:submissions_count).sum
|
54
60
|
end
|
@@ -206,7 +212,7 @@ class User < ApplicationRecord
|
|
206
212
|
end
|
207
213
|
|
208
214
|
def build_assignment(exercise, organization)
|
209
|
-
|
215
|
+
Assignment.build_for(self, exercise, organization)
|
210
216
|
end
|
211
217
|
|
212
218
|
def pending_siblings_at(content)
|
data/app/models/with_stats.rb
CHANGED
@@ -0,0 +1,14 @@
|
|
1
|
+
class CreateExamRegistrations < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
create_table :exam_registrations do |t|
|
4
|
+
t.string :description
|
5
|
+
t.datetime :start_time, null: false
|
6
|
+
t.datetime :end_time, null: false
|
7
|
+
t.integer :authorization_criterion_type, default: 0
|
8
|
+
t.integer :authorization_criterion_value
|
9
|
+
t.references :organization, index: true
|
10
|
+
|
11
|
+
t.timestamps
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateExamAuthorizationRequest < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
create_table :exam_authorization_requests do |t|
|
4
|
+
t.integer :status, default: 0
|
5
|
+
t.references :exam, index: true
|
6
|
+
t.references :exam_registration, index: true
|
7
|
+
t.references :user, index: true
|
8
|
+
t.references :organization, index: true
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateNotification < ActiveRecord::Migration[5.1]
|
2
|
+
def change
|
3
|
+
create_table :notifications do |t|
|
4
|
+
t.integer :priority, default: 100
|
5
|
+
t.boolean :read, default: false
|
6
|
+
t.references :target, polymorphic: true
|
7
|
+
t.references :user, index: true
|
8
|
+
t.references :organization, index: true
|
9
|
+
|
10
|
+
t.timestamps
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/mumuki/domain.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
require_relative './extensions/string'
|
2
|
+
require_relative './extensions/symbol'
|
2
3
|
require_relative './extensions/array'
|
3
4
|
require_relative './extensions/module'
|
4
5
|
require_relative './extensions/hash'
|
6
|
+
require_relative './extensions/nil'
|
5
7
|
require_relative './extensions/time'
|
@@ -59,15 +59,8 @@ class String
|
|
59
59
|
def sanitized
|
60
60
|
Mumukit::ContentType::Sanitizer.sanitize self
|
61
61
|
end
|
62
|
-
end
|
63
|
-
|
64
|
-
class NilClass
|
65
|
-
def affable
|
66
|
-
end
|
67
62
|
|
68
|
-
def
|
69
|
-
|
70
|
-
|
71
|
-
def sanitized
|
63
|
+
def randomize_with(randomizer, seed)
|
64
|
+
randomizer.randomize!(self, seed)
|
72
65
|
end
|
73
66
|
end
|
@@ -7,6 +7,8 @@ require_relative './factories/complement_factory'
|
|
7
7
|
require_relative './factories/course_factory'
|
8
8
|
require_relative './factories/discussion_factory'
|
9
9
|
require_relative './factories/exam_factory'
|
10
|
+
require_relative './factories/exam_authorization_request_factory'
|
11
|
+
require_relative './factories/exam_registration_factory'
|
10
12
|
require_relative './factories/exercise_factory'
|
11
13
|
require_relative './factories/guide_factory'
|
12
14
|
require_relative './factories/invitation_factory'
|
@@ -107,7 +107,7 @@ module Mumuki::Domain
|
|
107
107
|
end
|
108
108
|
|
109
109
|
def build_assignment(exercise, organization)
|
110
|
-
Assignment.
|
110
|
+
Assignment.build_for(self, exercise, organization)
|
111
111
|
end
|
112
112
|
|
113
113
|
def pending_siblings_at(content)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
class Mumuki::Domain::ProgressTransfer::Base
|
2
|
+
attr_reader *%i(source_organization destination_organization progress_item transferred_item)
|
3
|
+
|
4
|
+
delegate :user, to: :progress_item
|
5
|
+
|
6
|
+
def initialize(progress_item, destination_organization)
|
7
|
+
@progress_item = progress_item
|
8
|
+
@destination_organization = destination_organization
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute!
|
12
|
+
ActiveRecord::Base.transaction do
|
13
|
+
pre_transfer!
|
14
|
+
transfer!
|
15
|
+
post_transfer!
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def pre_transfer!
|
20
|
+
validate_transferrable!
|
21
|
+
@source_organization = progress_item.organization
|
22
|
+
progress_item.delete_duplicates_in!(destination_organization)
|
23
|
+
end
|
24
|
+
|
25
|
+
def transfer!
|
26
|
+
@transferred_item = do_transfer!
|
27
|
+
end
|
28
|
+
|
29
|
+
def post_transfer!
|
30
|
+
transferred_item.dirty_parent_by_submission!
|
31
|
+
notify_transfer!
|
32
|
+
transferred_item
|
33
|
+
end
|
34
|
+
|
35
|
+
def validate_transferrable!
|
36
|
+
raise "Transferred progress' content must be available in destination!" unless progress_item.content_available_in?(destination_organization)
|
37
|
+
raise 'User must be student in destination organization' unless user.student_of?(destination_organization)
|
38
|
+
raise 'Transfer only supported for guide indicators' unless progress_item.guide_indicator?
|
39
|
+
end
|
40
|
+
|
41
|
+
def notify_transfer!
|
42
|
+
Mumukit::Nuntius.notify! 'progress-transfers', { from: source_organization.name, to: destination_organization.name, item_type: transferred_item.class.to_s, item_id: transferred_item.id, transfer_type: transfer_type }
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Mumuki::Domain::ProgressTransfer::Move < Mumuki::Domain::ProgressTransfer::Base
|
2
|
+
def transfer_type
|
3
|
+
:move
|
4
|
+
end
|
5
|
+
|
6
|
+
def pre_transfer!
|
7
|
+
super
|
8
|
+
progress_item.dirty_parent_by_submission!
|
9
|
+
end
|
10
|
+
|
11
|
+
def do_transfer!
|
12
|
+
progress_item._move_to!(destination_organization)
|
13
|
+
end
|
14
|
+
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: 8.
|
4
|
+
version: 8.5.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:
|
11
|
+
date: 2021-02-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -170,28 +170,28 @@ dependencies:
|
|
170
170
|
requirements:
|
171
171
|
- - "~>"
|
172
172
|
- !ruby/object:Gem::Version
|
173
|
-
version: '
|
173
|
+
version: '6.0'
|
174
174
|
type: :runtime
|
175
175
|
prerelease: false
|
176
176
|
version_requirements: !ruby/object:Gem::Requirement
|
177
177
|
requirements:
|
178
178
|
- - "~>"
|
179
179
|
- !ruby/object:Gem::Version
|
180
|
-
version: '
|
180
|
+
version: '6.0'
|
181
181
|
- !ruby/object:Gem::Dependency
|
182
182
|
name: mumukit-inspection
|
183
183
|
requirement: !ruby/object:Gem::Requirement
|
184
184
|
requirements:
|
185
185
|
- - "~>"
|
186
186
|
- !ruby/object:Gem::Version
|
187
|
-
version: '
|
187
|
+
version: '6.0'
|
188
188
|
type: :runtime
|
189
189
|
prerelease: false
|
190
190
|
version_requirements: !ruby/object:Gem::Requirement
|
191
191
|
requirements:
|
192
192
|
- - "~>"
|
193
193
|
- !ruby/object:Gem::Version
|
194
|
-
version: '
|
194
|
+
version: '6.0'
|
195
195
|
- !ruby/object:Gem::Dependency
|
196
196
|
name: sprockets
|
197
197
|
requirement: !ruby/object:Gem::Requirement
|
@@ -270,6 +270,7 @@ files:
|
|
270
270
|
- app/models/concerns/submittable/triable.rb
|
271
271
|
- app/models/concerns/topic_container.rb
|
272
272
|
- app/models/concerns/with_assignments.rb
|
273
|
+
- app/models/concerns/with_assignments_batch.rb
|
273
274
|
- app/models/concerns/with_case_insensitive_search.rb
|
274
275
|
- app/models/concerns/with_description.rb
|
275
276
|
- app/models/concerns/with_discussion_creation.rb
|
@@ -286,6 +287,7 @@ files:
|
|
286
287
|
- app/models/concerns/with_messages.rb
|
287
288
|
- app/models/concerns/with_name.rb
|
288
289
|
- app/models/concerns/with_number.rb
|
290
|
+
- app/models/concerns/with_preferences.rb
|
289
291
|
- app/models/concerns/with_profile.rb
|
290
292
|
- app/models/concerns/with_progress.rb
|
291
293
|
- app/models/concerns/with_randomizations.rb
|
@@ -297,6 +299,7 @@ files:
|
|
297
299
|
- app/models/concerns/with_slug.rb
|
298
300
|
- app/models/concerns/with_target_audience.rb
|
299
301
|
- app/models/concerns/with_terms_acceptance.rb
|
302
|
+
- app/models/concerns/with_timed_enablement.rb
|
300
303
|
- app/models/concerns/with_usages.rb
|
301
304
|
- app/models/concerns/with_user_navigation.rb
|
302
305
|
- app/models/content.rb
|
@@ -306,6 +309,9 @@ files:
|
|
306
309
|
- app/models/exam.rb
|
307
310
|
- app/models/exam/passing_criterion.rb
|
308
311
|
- app/models/exam_authorization.rb
|
312
|
+
- app/models/exam_authorization_request.rb
|
313
|
+
- app/models/exam_registration.rb
|
314
|
+
- app/models/exam_registration/authorization_criterion.rb
|
309
315
|
- app/models/exercise.rb
|
310
316
|
- app/models/exercise/challenge.rb
|
311
317
|
- app/models/exercise/interactive.rb
|
@@ -320,7 +326,9 @@ files:
|
|
320
326
|
- app/models/lesson.rb
|
321
327
|
- app/models/medal.rb
|
322
328
|
- app/models/message.rb
|
329
|
+
- app/models/notification.rb
|
323
330
|
- app/models/organization.rb
|
331
|
+
- app/models/preferences.rb
|
324
332
|
- app/models/progress.rb
|
325
333
|
- app/models/stats.rb
|
326
334
|
- app/models/subscription.rb
|
@@ -637,6 +645,12 @@ files:
|
|
637
645
|
- db/migrate/20201027134205_add_immersible_to_organization.rb
|
638
646
|
- db/migrate/20201027152806_create_terms.rb
|
639
647
|
- db/migrate/20201130163114_add_banned_from_forum_to_users.rb
|
648
|
+
- db/migrate/20210111125810_add_uppercase_mode_to_user.rb
|
649
|
+
- db/migrate/20210114200545_create_exam_registrations.rb
|
650
|
+
- db/migrate/20210118180941_create_exam_authorization_request.rb
|
651
|
+
- db/migrate/20210118194904_create_notification.rb
|
652
|
+
- db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb
|
653
|
+
- db/migrate/20210119190204_create_exam_registration_exam_join_table.rb
|
640
654
|
- lib/mumuki/domain.rb
|
641
655
|
- lib/mumuki/domain/area.rb
|
642
656
|
- lib/mumuki/domain/engine.rb
|
@@ -656,7 +670,9 @@ files:
|
|
656
670
|
- lib/mumuki/domain/extensions/array.rb
|
657
671
|
- lib/mumuki/domain/extensions/hash.rb
|
658
672
|
- lib/mumuki/domain/extensions/module.rb
|
673
|
+
- lib/mumuki/domain/extensions/nil.rb
|
659
674
|
- lib/mumuki/domain/extensions/string.rb
|
675
|
+
- lib/mumuki/domain/extensions/symbol.rb
|
660
676
|
- lib/mumuki/domain/extensions/time.rb
|
661
677
|
- lib/mumuki/domain/factories.rb
|
662
678
|
- lib/mumuki/domain/factories/api_client_factory.rb
|
@@ -667,7 +683,9 @@ files:
|
|
667
683
|
- lib/mumuki/domain/factories/complement_factory.rb
|
668
684
|
- lib/mumuki/domain/factories/course_factory.rb
|
669
685
|
- lib/mumuki/domain/factories/discussion_factory.rb
|
686
|
+
- lib/mumuki/domain/factories/exam_authorization_request_factory.rb
|
670
687
|
- lib/mumuki/domain/factories/exam_factory.rb
|
688
|
+
- lib/mumuki/domain/factories/exam_registration_factory.rb
|
671
689
|
- lib/mumuki/domain/factories/exercise_factory.rb
|
672
690
|
- lib/mumuki/domain/factories/guide_factory.rb
|
673
691
|
- lib/mumuki/domain/factories/invitation_factory.rb
|
@@ -702,6 +720,10 @@ files:
|
|
702
720
|
- lib/mumuki/domain/organization/profile.rb
|
703
721
|
- lib/mumuki/domain/organization/settings.rb
|
704
722
|
- lib/mumuki/domain/organization/theme.rb
|
723
|
+
- lib/mumuki/domain/progress_transfer.rb
|
724
|
+
- lib/mumuki/domain/progress_transfer/base.rb
|
725
|
+
- lib/mumuki/domain/progress_transfer/copy.rb
|
726
|
+
- lib/mumuki/domain/progress_transfer/move.rb
|
705
727
|
- lib/mumuki/domain/seed.rb
|
706
728
|
- lib/mumuki/domain/status.rb
|
707
729
|
- lib/mumuki/domain/status/discussion/closed.rb
|