mumuki-domain 8.2.0 → 8.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5ea9b10953e5031cfcfa680763efb0eac02e63b91219c3dbf71b0d30b5dd3250
4
- data.tar.gz: 8fda448216e9d80cb4de41a7f43825882e043895705c66c1c6b9b4bc68c5bfec
3
+ metadata.gz: 05e92b25906e2bdad81e9af37f88d03423ce78e18e60e911883e1fd9933056d4
4
+ data.tar.gz: 3fc2d20e7199cd604ba3462a5755084ed1ed9bbcf4b434d71bd3ed340677d650
5
5
  SHA512:
6
- metadata.gz: f2f8f341502367fa4a7ba037eb086f0506c19fede8290aba4921430fb22e33cc338faf01bc85d23d9df89fca006dba97705e377f48c3eae789170291644562e5
7
- data.tar.gz: e5044aa8475aba10ea9d1140eceefccd9fc36de384cf215007f4f239568aebf9f06583074249af630fff81db9da8d429604fa428db0c509a55c4f9092b25965d
6
+ metadata.gz: 8b75c52aeec6f2403888816619740882acf061c684d09f71fd52094c9cc2b13561203433b13eaca1499a4d7c6f7046e596d3e01d95ac0e02e983bf1107fc2923
7
+ data.tar.gz: 56ed033125bb4eea0d39ba693dca8e142532846159dea3e68329f3b79984ac201a3852ec6019c9428f2696d1e900583c09cd55f8b80c38215fe02ebefd014ed0
data/Rakefile CHANGED
@@ -1,3 +1,5 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
1
3
  begin
2
4
  require 'bundler/setup'
3
5
  rescue LoadError
@@ -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
@@ -0,0 +1,11 @@
1
+ module WithTimedEnablement
2
+ extend ActiveSupport::Concern
3
+
4
+ def enabled?
5
+ enabled_range.cover? DateTime.current
6
+ end
7
+
8
+ def enabled_range
9
+ start_time..end_time
10
+ end
11
+ end
@@ -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
@@ -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?
@@ -42,7 +42,11 @@ class Guide < Content
42
42
  end
43
43
 
44
44
  def next_exercise(user)
45
- user.next_exercise_at(self)
45
+ if user.present?
46
+ user.next_exercise_at(self)
47
+ else
48
+ first_exercise
49
+ end
46
50
  end
47
51
 
48
52
  # TODO: Make use of pending_siblings logic
@@ -0,0 +1,5 @@
1
+ class Notification < ApplicationRecord
2
+ belongs_to :user
3
+ belongs_to :organization
4
+ belongs_to :target, polymorphic: true
5
+ end
@@ -207,7 +207,7 @@ class Organization < ApplicationRecord
207
207
  end
208
208
 
209
209
  def silenced?
210
- !Mumukit::Platform::Organization.current? || current.silent?
210
+ !current? || current.silent?
211
211
  end
212
212
 
213
213
  def sync_key_id_field
@@ -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)
@@ -12,6 +12,7 @@ class User < ApplicationRecord
12
12
  serialize :permissions, Mumukit::Auth::Permissions
13
13
 
14
14
 
15
+ has_many :notifications
15
16
  has_many :assignments, foreign_key: :submitter_id
16
17
  has_many :messages, -> { order(created_at: :desc) }, through: :assignments
17
18
 
@@ -49,6 +50,10 @@ class User < ApplicationRecord
49
50
  last_guide.try(:lesson)
50
51
  end
51
52
 
53
+ def passed_submissions_count_in(organization)
54
+ assignments.where(top_submission_status: Mumuki::Domain::Status::Submission::Passed.to_i, organization: organization).count
55
+ end
56
+
52
57
  def submissions_count
53
58
  assignments.pluck(:submissions_count).sum
54
59
  end
@@ -1,5 +1,6 @@
1
1
  module WithStats
2
2
  def stats_for(user)
3
+ return unless user.present?
3
4
  Stats.from_statuses exercises.map { |it| it.status_for(user) }
4
5
  end
5
6
 
@@ -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
@@ -0,0 +1,5 @@
1
+ class AddPreventManualEvaluationContentToOrganizations < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :prevent_manual_evaluation_content, :boolean
4
+ end
5
+ end
@@ -0,0 +1,8 @@
1
+ class CreateExamRegistrationExamJoinTable < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_join_table :exams, :exam_registrations do |t|
4
+ t.index :exam_id
5
+ t.index :exam_registration_id
6
+ end
7
+ end
8
+ 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'
@@ -0,0 +1,6 @@
1
+ FactoryBot.define do
2
+ factory :exam_authorization_request do
3
+ organization { Organization.current }
4
+ exam { create(:exam) }
5
+ end
6
+ end
@@ -0,0 +1,8 @@
1
+ FactoryBot.define do
2
+ factory :exam_registration do
3
+ description { Faker::Lorem.sentence(word_count: 5) }
4
+ organization { Organization.current }
5
+ start_time { 5.minutes.ago }
6
+ end_time { 10.minutes.since }
7
+ end
8
+ end
@@ -74,6 +74,10 @@ module Mumuki::Domain::Helpers::Organization
74
74
  Mumukit::Platform::Organization.current?
75
75
  end
76
76
 
77
+ def safe_current
78
+ current if current?
79
+ end
80
+
77
81
  def parse(json)
78
82
  json
79
83
  .slice(:name)
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '8.2.0'
3
+ VERSION = '8.3.1'
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: 8.2.0
4
+ version: 8.3.1
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: 2021-01-07 00:00:00.000000000 Z
11
+ date: 2021-01-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -297,6 +297,7 @@ files:
297
297
  - app/models/concerns/with_slug.rb
298
298
  - app/models/concerns/with_target_audience.rb
299
299
  - app/models/concerns/with_terms_acceptance.rb
300
+ - app/models/concerns/with_timed_enablement.rb
300
301
  - app/models/concerns/with_usages.rb
301
302
  - app/models/concerns/with_user_navigation.rb
302
303
  - app/models/content.rb
@@ -306,6 +307,9 @@ files:
306
307
  - app/models/exam.rb
307
308
  - app/models/exam/passing_criterion.rb
308
309
  - app/models/exam_authorization.rb
310
+ - app/models/exam_authorization_request.rb
311
+ - app/models/exam_registration.rb
312
+ - app/models/exam_registration/authorization_criterion.rb
309
313
  - app/models/exercise.rb
310
314
  - app/models/exercise/challenge.rb
311
315
  - app/models/exercise/interactive.rb
@@ -320,6 +324,7 @@ files:
320
324
  - app/models/lesson.rb
321
325
  - app/models/medal.rb
322
326
  - app/models/message.rb
327
+ - app/models/notification.rb
323
328
  - app/models/organization.rb
324
329
  - app/models/progress.rb
325
330
  - app/models/stats.rb
@@ -637,6 +642,11 @@ files:
637
642
  - db/migrate/20201027134205_add_immersible_to_organization.rb
638
643
  - db/migrate/20201027152806_create_terms.rb
639
644
  - db/migrate/20201130163114_add_banned_from_forum_to_users.rb
645
+ - db/migrate/20210114200545_create_exam_registrations.rb
646
+ - db/migrate/20210118180941_create_exam_authorization_request.rb
647
+ - db/migrate/20210118194904_create_notification.rb
648
+ - db/migrate/20210119160440_add_prevent_manual_evaluation_content_to_organizations.rb
649
+ - db/migrate/20210119190204_create_exam_registration_exam_join_table.rb
640
650
  - lib/mumuki/domain.rb
641
651
  - lib/mumuki/domain/area.rb
642
652
  - lib/mumuki/domain/engine.rb
@@ -669,7 +679,9 @@ files:
669
679
  - lib/mumuki/domain/factories/complement_factory.rb
670
680
  - lib/mumuki/domain/factories/course_factory.rb
671
681
  - lib/mumuki/domain/factories/discussion_factory.rb
682
+ - lib/mumuki/domain/factories/exam_authorization_request_factory.rb
672
683
  - lib/mumuki/domain/factories/exam_factory.rb
684
+ - lib/mumuki/domain/factories/exam_registration_factory.rb
673
685
  - lib/mumuki/domain/factories/exercise_factory.rb
674
686
  - lib/mumuki/domain/factories/guide_factory.rb
675
687
  - lib/mumuki/domain/factories/invitation_factory.rb