mumuki-domain 8.2.0 → 8.3.1

Sign up to get free protection for your applications and to get access to all the features.
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