mumuki-domain 8.1.3 → 8.6.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/application_record.rb +6 -0
- data/app/models/assignment.rb +10 -1
- 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_notifications.rb +17 -0
- data/app/models/concerns/with_preferences.rb +7 -0
- data/app/models/concerns/with_timed_enablement.rb +11 -0
- data/app/models/discussion.rb +4 -0
- data/app/models/exam.rb +4 -8
- data/app/models/exam_authorization_request.rb +26 -0
- data/app/models/exam_registration.rb +46 -0
- data/app/models/exam_registration/authorization_criterion.rb +61 -0
- data/app/models/exercise.rb +5 -0
- data/app/models/guide.rb +7 -2
- data/app/models/indicator.rb +35 -0
- data/app/models/message.rb +4 -0
- data/app/models/notification.rb +9 -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 +8 -5
- 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/factories.rb +3 -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/factories/notification_factory.rb +5 -0
- data/lib/mumuki/domain/helpers/organization.rb +4 -0
- data/lib/mumuki/domain/incognito.rb +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: 0fc67d5d416345806e5d570dae375114d9bd32e3be5af26485a926c9957d10f2
|
4
|
+
data.tar.gz: cb223b383c8f176ac852091b8cf79ed2f496404bc75d1d7dcc3ccfbde70e49f2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3a891889e37c9fd9a55c383f2ffe6aa2f9368193bb7550ba34d9253fb8fbab3534be85ac6acc8a162f02907059acc57da14658ee6df7ad19ad93523ee1ef998d
|
7
|
+
data.tar.gz: e830934dd132b0bada6f45348249f06d170438c98fbb07528fa1e608397b40945485adeb4a79e732d701371e4196a9fae22c5e4ba68ef983a71dc7ae40236704
|
data/Rakefile
CHANGED
@@ -129,6 +129,12 @@ class ApplicationRecord < ActiveRecord::Base
|
|
129
129
|
end
|
130
130
|
end
|
131
131
|
|
132
|
+
def self.enum_prefixed_translations_for(selector)
|
133
|
+
send(selector.to_s.pluralize).map do |key, _|
|
134
|
+
[I18n.t("#{selector}_#{key}", default: key.to_sym), key]
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
132
138
|
private
|
133
139
|
|
134
140
|
def raise_foreign_key_error!
|
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
|
|
@@ -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
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module WithNotifications
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
def unread_messages
|
5
|
+
messages.where read: false
|
6
|
+
end
|
7
|
+
|
8
|
+
def unread_notifications
|
9
|
+
# TODO: message and discussion should trigger a notification instead of being one
|
10
|
+
all = notifications.where(read: false) + unread_messages + unread_discussions
|
11
|
+
all.sort_by(&:created_at).reverse
|
12
|
+
end
|
13
|
+
|
14
|
+
def read_notification!(target)
|
15
|
+
notifications.find_by(target: target)&.mark_as_read!
|
16
|
+
end
|
17
|
+
end
|
data/app/models/discussion.rb
CHANGED
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,26 @@
|
|
1
|
+
class ExamAuthorizationRequest < ApplicationRecord
|
2
|
+
include TerminalNavigation
|
3
|
+
|
4
|
+
belongs_to :exam
|
5
|
+
belongs_to :user
|
6
|
+
belongs_to :organization
|
7
|
+
belongs_to :exam_registration
|
8
|
+
|
9
|
+
enum status: %i(pending approved rejected)
|
10
|
+
|
11
|
+
after_update :notify_user!
|
12
|
+
|
13
|
+
def try_authorize!
|
14
|
+
exam.authorize! user if approved?
|
15
|
+
end
|
16
|
+
|
17
|
+
def name
|
18
|
+
exam_registration.description
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def notify_user!
|
24
|
+
Notification.create! organization: organization, user: user, target: self if saved_change_to_status?
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class ExamRegistration < ApplicationRecord
|
2
|
+
include WithTimedEnablement
|
3
|
+
include TerminalNavigation
|
4
|
+
|
5
|
+
belongs_to :organization
|
6
|
+
has_and_belongs_to_many :exams
|
7
|
+
has_many :authorization_requests, class_name: 'ExamAuthorizationRequest'
|
8
|
+
|
9
|
+
enum authorization_criterion_type: %i(none passed_exercises), _prefix: :authorization_criterion
|
10
|
+
|
11
|
+
before_save :ensure_valid_authorization_criterion!
|
12
|
+
|
13
|
+
delegate :meets_authorization_criteria?, :process_request!, to: :authorization_criterion
|
14
|
+
|
15
|
+
alias_attribute :name, :description
|
16
|
+
|
17
|
+
def authorization_criterion
|
18
|
+
@authorization_criterion ||= ExamRegistration::AuthorizationCriterion.parse(authorization_criterion_type, authorization_criterion_value)
|
19
|
+
end
|
20
|
+
|
21
|
+
def ensure_valid_authorization_criterion!
|
22
|
+
authorization_criterion.ensure_valid!
|
23
|
+
end
|
24
|
+
|
25
|
+
def start!(users)
|
26
|
+
users.each &method(:notify_user!)
|
27
|
+
end
|
28
|
+
|
29
|
+
def process_requests!
|
30
|
+
authorization_requests.each do |it|
|
31
|
+
process_request! it
|
32
|
+
it.try_authorize!
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def authorization_request_for(user)
|
37
|
+
authorization_requests.find_by(user: user) ||
|
38
|
+
ExamAuthorizationRequest.new(exam_registration: self, organization: organization)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def notify_user!(user)
|
44
|
+
Notification.create! organization: organization, user: user, target: self
|
45
|
+
end
|
46
|
+
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
@@ -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?
|
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/message.rb
CHANGED
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
@@ -3,15 +3,18 @@ class User < ApplicationRecord
|
|
3
3
|
include WithProfile,
|
4
4
|
WithUserNavigation,
|
5
5
|
WithReminders,
|
6
|
+
WithNotifications,
|
6
7
|
WithDiscussionCreation,
|
7
8
|
Awardee,
|
8
9
|
Disabling,
|
9
10
|
WithTermsAcceptance,
|
11
|
+
WithPreferences,
|
10
12
|
Mumuki::Domain::Helpers::User
|
11
13
|
|
12
14
|
serialize :permissions, Mumukit::Auth::Permissions
|
13
15
|
|
14
16
|
|
17
|
+
has_many :notifications
|
15
18
|
has_many :assignments, foreign_key: :submitter_id
|
16
19
|
has_many :messages, -> { order(created_at: :desc) }, through: :assignments
|
17
20
|
|
@@ -49,6 +52,10 @@ class User < ApplicationRecord
|
|
49
52
|
last_guide.try(:lesson)
|
50
53
|
end
|
51
54
|
|
55
|
+
def passed_submissions_count_in(organization)
|
56
|
+
assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
|
57
|
+
end
|
58
|
+
|
52
59
|
def submissions_count
|
53
60
|
assignments.pluck(:submissions_count).sum
|
54
61
|
end
|
@@ -69,10 +76,6 @@ class User < ApplicationRecord
|
|
69
76
|
assignments.where(status: Mumuki::Domain::Status::Submission::Passed.to_i)
|
70
77
|
end
|
71
78
|
|
72
|
-
def unread_messages
|
73
|
-
messages.where read: false
|
74
|
-
end
|
75
|
-
|
76
79
|
def visit!(organization)
|
77
80
|
update!(last_organization: organization) if organization != last_organization
|
78
81
|
end
|
@@ -206,7 +209,7 @@ class User < ApplicationRecord
|
|
206
209
|
end
|
207
210
|
|
208
211
|
def build_assignment(exercise, organization)
|
209
|
-
|
212
|
+
Assignment.build_for(self, exercise, organization)
|
210
213
|
end
|
211
214
|
|
212
215
|
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
@@ -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'
|
@@ -14,6 +16,7 @@ require_relative './factories/lesson_factory'
|
|
14
16
|
require_relative './factories/login_settings_factory'
|
15
17
|
require_relative './factories/medal_factory'
|
16
18
|
require_relative './factories/message_factory'
|
19
|
+
require_relative './factories/notification_factory'
|
17
20
|
require_relative './factories/organization_factory'
|
18
21
|
require_relative './factories/term_factory'
|
19
22
|
require_relative './factories/topic_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.6.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-11 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
|
@@ -285,7 +286,9 @@ files:
|
|
285
286
|
- app/models/concerns/with_medal.rb
|
286
287
|
- app/models/concerns/with_messages.rb
|
287
288
|
- app/models/concerns/with_name.rb
|
289
|
+
- app/models/concerns/with_notifications.rb
|
288
290
|
- app/models/concerns/with_number.rb
|
291
|
+
- app/models/concerns/with_preferences.rb
|
289
292
|
- app/models/concerns/with_profile.rb
|
290
293
|
- app/models/concerns/with_progress.rb
|
291
294
|
- app/models/concerns/with_randomizations.rb
|
@@ -297,6 +300,7 @@ files:
|
|
297
300
|
- app/models/concerns/with_slug.rb
|
298
301
|
- app/models/concerns/with_target_audience.rb
|
299
302
|
- app/models/concerns/with_terms_acceptance.rb
|
303
|
+
- app/models/concerns/with_timed_enablement.rb
|
300
304
|
- app/models/concerns/with_usages.rb
|
301
305
|
- app/models/concerns/with_user_navigation.rb
|
302
306
|
- app/models/content.rb
|
@@ -306,6 +310,9 @@ files:
|
|
306
310
|
- app/models/exam.rb
|
307
311
|
- app/models/exam/passing_criterion.rb
|
308
312
|
- app/models/exam_authorization.rb
|
313
|
+
- app/models/exam_authorization_request.rb
|
314
|
+
- app/models/exam_registration.rb
|
315
|
+
- app/models/exam_registration/authorization_criterion.rb
|
309
316
|
- app/models/exercise.rb
|
310
317
|
- app/models/exercise/challenge.rb
|
311
318
|
- app/models/exercise/interactive.rb
|
@@ -320,7 +327,9 @@ files:
|
|
320
327
|
- app/models/lesson.rb
|
321
328
|
- app/models/medal.rb
|
322
329
|
- app/models/message.rb
|
330
|
+
- app/models/notification.rb
|
323
331
|
- app/models/organization.rb
|
332
|
+
- app/models/preferences.rb
|
324
333
|
- app/models/progress.rb
|
325
334
|
- app/models/stats.rb
|
326
335
|
- app/models/subscription.rb
|
@@ -637,6 +646,12 @@ files:
|
|
637
646
|
- db/migrate/20201027134205_add_immersible_to_organization.rb
|
638
647
|
- db/migrate/20201027152806_create_terms.rb
|
639
648
|
- db/migrate/20201130163114_add_banned_from_forum_to_users.rb
|
649
|
+
- db/migrate/20210111125810_add_uppercase_mode_to_user.rb
|
650
|
+
- db/migrate/20210114200545_create_exam_registrations.rb
|
651
|
+
- db/migrate/20210118180941_create_exam_authorization_request.rb
|
652
|
+
- db/migrate/20210118194904_create_notification.rb
|
653
|
+
- db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb
|
654
|
+
- db/migrate/20210119190204_create_exam_registration_exam_join_table.rb
|
640
655
|
- lib/mumuki/domain.rb
|
641
656
|
- lib/mumuki/domain/area.rb
|
642
657
|
- lib/mumuki/domain/engine.rb
|
@@ -669,7 +684,9 @@ files:
|
|
669
684
|
- lib/mumuki/domain/factories/complement_factory.rb
|
670
685
|
- lib/mumuki/domain/factories/course_factory.rb
|
671
686
|
- lib/mumuki/domain/factories/discussion_factory.rb
|
687
|
+
- lib/mumuki/domain/factories/exam_authorization_request_factory.rb
|
672
688
|
- lib/mumuki/domain/factories/exam_factory.rb
|
689
|
+
- lib/mumuki/domain/factories/exam_registration_factory.rb
|
673
690
|
- lib/mumuki/domain/factories/exercise_factory.rb
|
674
691
|
- lib/mumuki/domain/factories/guide_factory.rb
|
675
692
|
- lib/mumuki/domain/factories/invitation_factory.rb
|
@@ -677,6 +694,7 @@ files:
|
|
677
694
|
- lib/mumuki/domain/factories/login_settings_factory.rb
|
678
695
|
- lib/mumuki/domain/factories/medal_factory.rb
|
679
696
|
- lib/mumuki/domain/factories/message_factory.rb
|
697
|
+
- lib/mumuki/domain/factories/notification_factory.rb
|
680
698
|
- lib/mumuki/domain/factories/organization_factory.rb
|
681
699
|
- lib/mumuki/domain/factories/term_factory.rb
|
682
700
|
- lib/mumuki/domain/factories/topic_factory.rb
|
@@ -704,6 +722,10 @@ files:
|
|
704
722
|
- lib/mumuki/domain/organization/profile.rb
|
705
723
|
- lib/mumuki/domain/organization/settings.rb
|
706
724
|
- lib/mumuki/domain/organization/theme.rb
|
725
|
+
- lib/mumuki/domain/progress_transfer.rb
|
726
|
+
- lib/mumuki/domain/progress_transfer/base.rb
|
727
|
+
- lib/mumuki/domain/progress_transfer/copy.rb
|
728
|
+
- lib/mumuki/domain/progress_transfer/move.rb
|
707
729
|
- lib/mumuki/domain/seed.rb
|
708
730
|
- lib/mumuki/domain/status.rb
|
709
731
|
- lib/mumuki/domain/status/discussion/closed.rb
|