mumuki-domain 7.4.1 → 7.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/app/models/assignment.rb +1 -1
  3. data/app/models/avatar.rb +5 -0
  4. data/app/models/book.rb +13 -0
  5. data/app/models/concerns/{with_content.rb → container.rb} +1 -1
  6. data/app/models/concerns/contextualization.rb +53 -2
  7. data/app/models/concerns/disabling.rb +37 -0
  8. data/app/models/concerns/guide_container.rb +1 -1
  9. data/app/models/concerns/submittable/solvable.rb +8 -2
  10. data/app/models/concerns/topic_container.rb +1 -1
  11. data/app/models/concerns/with_discussions.rb +8 -5
  12. data/app/models/concerns/with_editor.rb +1 -1
  13. data/app/models/concerns/with_layout.rb +5 -1
  14. data/app/models/concerns/with_progress.rb +4 -0
  15. data/app/models/concerns/with_scoped_queries.rb +9 -9
  16. data/app/models/concerns/with_scoped_queries/page.rb +1 -1
  17. data/app/models/concerns/with_scoped_queries/sort.rb +6 -1
  18. data/app/models/course.rb +18 -7
  19. data/app/models/discussion.rb +47 -9
  20. data/app/models/exam.rb +53 -23
  21. data/app/models/exam/passing_criterion.rb +53 -0
  22. data/app/models/exercise.rb +3 -3
  23. data/app/models/exercise/challenge.rb +1 -1
  24. data/app/models/exercise/problem.rb +6 -0
  25. data/app/models/exercise/reading.rb +4 -0
  26. data/app/models/guide.rb +1 -1
  27. data/app/models/invitation.rb +7 -1
  28. data/app/models/message.rb +28 -4
  29. data/app/models/organization.rb +16 -0
  30. data/app/models/user.rb +48 -3
  31. data/db/migrate/20200508191543_create_avatars.rb +8 -0
  32. data/db/migrate/20200518135658_add_avatar_to_users.rb +5 -0
  33. data/db/migrate/20200527180729_add_disabled_at_to_users.rb +6 -0
  34. data/db/migrate/20200601203033_add_course_to_exam.rb +5 -0
  35. data/db/migrate/20200605161350_add_passing_criterions_to_exam.rb +6 -0
  36. data/db/migrate/20200608132959_add_progressive_display_lookahead_to_organizations.rb +5 -0
  37. data/db/migrate/20200702165503_add_messages_count_to_discussion.rb +6 -0
  38. data/db/migrate/20200728162727_add_not_actually_a_question_field_to_messages.rb +5 -0
  39. data/db/migrate/20200728163038_add_requires_moderator_response_to_discussions.rb +5 -0
  40. data/db/migrate/20200731081757_add_last_moderator_access_fields_to_discussion.rb +6 -0
  41. data/lib/mumuki/domain.rb +2 -0
  42. data/lib/mumuki/domain/area.rb +13 -0
  43. data/lib/mumuki/domain/exceptions.rb +3 -0
  44. data/lib/mumuki/domain/exceptions/disabled_error.rb +2 -0
  45. data/lib/mumuki/domain/exceptions/disabled_organization_error.rb +2 -0
  46. data/lib/mumuki/domain/exceptions/unprepared_organization_error.rb +2 -0
  47. data/lib/mumuki/domain/extensions/hash.rb +11 -2
  48. data/lib/mumuki/domain/extensions/string.rb +44 -0
  49. data/lib/mumuki/domain/factories/book_factory.rb +2 -2
  50. data/lib/mumuki/domain/factories/chapter_factory.rb +2 -2
  51. data/lib/mumuki/domain/factories/course_factory.rb +2 -0
  52. data/lib/mumuki/domain/factories/exam_factory.rb +2 -1
  53. data/lib/mumuki/domain/factories/guide_factory.rb +2 -2
  54. data/lib/mumuki/domain/factories/invitation_factory.rb +1 -1
  55. data/lib/mumuki/domain/factories/topic_factory.rb +2 -2
  56. data/lib/mumuki/domain/factories/user_factory.rb +1 -0
  57. data/lib/mumuki/domain/helpers/organization.rb +6 -1
  58. data/lib/mumuki/domain/helpers/user.rb +7 -7
  59. data/lib/mumuki/domain/locales/activerecord/en.yml +1 -0
  60. data/lib/mumuki/domain/locales/activerecord/es.yml +1 -0
  61. data/lib/mumuki/domain/locales/activerecord/pt.yml +1 -0
  62. data/lib/mumuki/domain/organization/settings.rb +12 -1
  63. data/lib/mumuki/domain/status/discussion/discussion.rb +4 -0
  64. data/lib/mumuki/domain/status/discussion/opened.rb +0 -8
  65. data/lib/mumuki/domain/submission/base.rb +6 -0
  66. data/lib/mumuki/domain/submission/solution.rb +3 -1
  67. data/lib/mumuki/domain/version.rb +1 -1
  68. data/lib/mumuki/domain/workspace.rb +38 -0
  69. metadata +24 -7
@@ -0,0 +1,8 @@
1
+ class CreateAvatars < ActiveRecord::Migration[5.1]
2
+ def change
3
+ create_table :avatars do |t|
4
+ t.string :image_url
5
+ t.string :description
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,5 @@
1
+ class AddAvatarToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :users, :avatar, index: false
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddDisabledAtToUsers < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :users, :disabled_at, :datetime
4
+ add_index :users, :disabled_at
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddCourseToExam < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_reference :exams, :course
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddPassingCriterionsToExam < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :exams, :passing_criterion_type, :integer, default: 0
4
+ add_column :exams, :passing_criterion_value, :integer
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddProgressiveDisplayLookaheadToOrganizations < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :organizations, :progressive_display_lookahead, :integer
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddMessagesCountToDiscussion < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :discussions, :messages_count, :integer, default: 0
4
+ add_column :discussions, :validated_messages_count, :integer, default: 0
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ class AddNotActuallyAQuestionFieldToMessages < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :messages, :not_actually_a_question, :boolean, default: false
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddRequiresModeratorResponseToDiscussions < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :discussions, :requires_moderator_response, :boolean, default: true
4
+ end
5
+ end
@@ -0,0 +1,6 @@
1
+ class AddLastModeratorAccessFieldsToDiscussion < ActiveRecord::Migration[5.1]
2
+ def change
3
+ add_column :discussions, :last_moderator_access_by_id, :string
4
+ add_column :discussions, :last_moderator_access_at, :datetime
5
+ end
6
+ end
@@ -25,6 +25,7 @@ Mumukit::Platform.configure do |config|
25
25
  config.organization_class_name = 'Organization'
26
26
  end
27
27
 
28
+ require_relative './domain/area'
28
29
  require_relative './domain/evaluation'
29
30
  require_relative './domain/submission'
30
31
  require_relative './domain/status'
@@ -32,6 +33,7 @@ require_relative './domain/exceptions'
32
33
  require_relative './domain/file'
33
34
  require_relative './domain/extensions'
34
35
  require_relative './domain/organization'
36
+ require_relative './domain/workspace'
35
37
  require_relative './domain/helpers'
36
38
  require_relative './domain/syncable'
37
39
  require_relative './domain/store'
@@ -0,0 +1,13 @@
1
+ module Mumuki::Domain::Area
2
+ extend ActiveSupport::Concern
3
+
4
+ def to_mumukit_grant
5
+ slug.to_mumukit_grant
6
+ end
7
+
8
+ def to_mumukit_slug
9
+ slug
10
+ end
11
+
12
+ required :to_organization
13
+ end
@@ -2,4 +2,7 @@ require_relative './exceptions/forbidden_error'
2
2
  require_relative './exceptions/gone_error'
3
3
  require_relative './exceptions/not_found_error'
4
4
  require_relative './exceptions/unauthorized_error'
5
+ require_relative './exceptions/disabled_error'
5
6
  require_relative './exceptions/blocked_forum_error'
7
+ require_relative './exceptions/unprepared_organization_error'
8
+ require_relative './exceptions/disabled_organization_error'
@@ -0,0 +1,2 @@
1
+ class Mumuki::Domain::DisabledError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class Mumuki::Domain::DisabledOrganizationError < StandardError
2
+ end
@@ -0,0 +1,2 @@
1
+ class Mumuki::Domain::UnpreparedOrganizationError < StandardError
2
+ end
@@ -1,5 +1,14 @@
1
1
  class Hash
2
- def markdownify!(*keys)
3
- keys.each { |it| self[it] = Mumukit::ContentType::Markdown.to_html(self[it]) }
2
+ def markdownify!(*keys, **options)
3
+ warn "Don't use markdownify. Use markdownified! instead"
4
+ markdownified! *keys, **options
5
+ end
6
+
7
+ def markdownified!(*keys, **options)
8
+ keys.each { |it| self[it] = self[it].markdownified(**options) }
9
+ end
10
+
11
+ def markdownified(*keys, **options)
12
+ map { |k, v| key.in?(keys) ? v.markdownified(options) : v }.to_h
4
13
  end
5
14
  end
@@ -27,3 +27,47 @@ class String
27
27
  File.extname(self).delete '.'
28
28
  end
29
29
  end
30
+
31
+
32
+ # The nil-safe affable pipeline goes as follow:
33
+ #
34
+ # i18n > markdownified > sanitized > affable
35
+ #
36
+ # Where:
37
+ # * i18n: translates to current locale
38
+ # * markdownified: interpretes markdown in message and generates HTML
39
+ # * sanitized: sanitizes results HTML
40
+ # * affable: changes structure to hide low level details
41
+ #
42
+ # Other classes may polymorphically implement their own
43
+ # markdownified, sanitized and affable methods with similar semantics
44
+ # to extend this pipeline to non-strings
45
+ class String
46
+
47
+ # Creates a humman representation - but not necessary UI - representation
48
+ # of this string by interpreting its markdown as a one-liner and sanitizing it
49
+ def affable
50
+ markdownified(one_liner: true).sanitized
51
+ end
52
+
53
+ # Interprets the markdown on this string, and converts it into HTML
54
+ def markdownified(**options)
55
+ Mumukit::ContentType::Markdown.to_html self, options
56
+ end
57
+
58
+ # Sanitizes this string, escaping unsafe HTML sequences
59
+ def sanitized
60
+ Mumukit::ContentType::Sanitizer.sanitize self
61
+ end
62
+ end
63
+
64
+ class NilClass
65
+ def affable
66
+ end
67
+
68
+ def markdownified(**options)
69
+ end
70
+
71
+ def sanitized
72
+ end
73
+ end
@@ -1,7 +1,7 @@
1
1
  FactoryBot.define do
2
2
  factory :book do
3
- name { Faker::Lorem.sentence(3) }
4
- description { Faker::Lorem.sentence(30) }
3
+ name { Faker::Lorem.sentence(word_count: 3) }
4
+ description { Faker::Lorem.sentence(word_count: 30) }
5
5
  slug { "mumuki/mumuki-test-book-#{SecureRandom.uuid}" }
6
6
  end
7
7
  end
@@ -1,12 +1,12 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :chapter do
4
- number { Faker::Number.between(1, 40) }
4
+ number { Faker::Number.between(from: 1, to: 40) }
5
5
  book { Organization.current.book rescue nil }
6
6
 
7
7
  transient do
8
8
  lessons { [] }
9
- name { Faker::Lorem.sentence(3) }
9
+ name { Faker::Lorem.sentence(word_count: 3) }
10
10
  slug { "mumuki/mumuki-test-topic-#{SecureRandom.uuid}" }
11
11
  end
12
12
 
@@ -2,8 +2,10 @@ FactoryBot.define do
2
2
  factory :course do
3
3
  period { '2016' }
4
4
  shifts { %w(morning) }
5
+ code { "k1234-#{SecureRandom.uuid}" }
5
6
  days { %w(monday wednesday) }
6
7
  description { 'test' }
7
8
  organization_id { 1 }
9
+ slug { "#{Organization.current.name}/#{period}-#{code}" }
8
10
  end
9
11
  end
@@ -1,8 +1,9 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :exam, traits: [:guide_container] do
4
- duration { Faker::Number.between(10, 60).minutes }
4
+ duration { Faker::Number.between(from: 10, to:60).minutes }
5
5
  organization { Organization.current }
6
+ course { create(:course) }
6
7
  start_time { 5.minutes.ago }
7
8
  end_time { 10.minutes.since }
8
9
  end
@@ -11,8 +11,8 @@ FactoryBot.define do
11
11
  trait :guide_container do
12
12
  transient do
13
13
  exercises { [] }
14
- name { Faker::Lorem.sentence(3) }
15
- description { Faker::Lorem.sentence(10) }
14
+ name { Faker::Lorem.sentence(word_count: 3) }
15
+ description { Faker::Lorem.sentence(word_count: 10) }
16
16
  language { create(:language) }
17
17
  slug { "mumuki/mumuki-test-lesson-#{SecureRandom.uuid}" }
18
18
  end
@@ -1,6 +1,6 @@
1
1
  FactoryBot.define do
2
2
  factory :invitation do
3
- code { Faker::Lorem.sentence(6) }
3
+ code { Faker::Lorem.sentence(word_count: 6) }
4
4
  course { "foo/bar" }
5
5
  expiration_date { 5.minutes.since }
6
6
  end
@@ -1,8 +1,8 @@
1
1
  FactoryBot.define do
2
2
 
3
3
  factory :topic do
4
- name { Faker::Lorem::sentence(3) }
5
- description { Faker::Lorem.paragraph(2) }
4
+ name { Faker::Lorem::sentence(word_count: 3) }
5
+ description { Faker::Lorem.paragraph(sentence_count: 2) }
6
6
  slug { "mumuki/mumuki-sample-topic-#{SecureRandom.uuid}" }
7
7
  locale { :en }
8
8
  end
@@ -6,5 +6,6 @@ FactoryBot.define do
6
6
  last_name { Faker::Name.last_name }
7
7
  gender { 1 }
8
8
  birthdate { Date.today }
9
+ avatar { Avatar.new image_url: 'user_shape.png' }
9
10
  end
10
11
  end
@@ -4,7 +4,7 @@ module Mumuki::Domain::Helpers::Organization
4
4
 
5
5
  included do
6
6
  delegate *Mumuki::Domain::Organization::Theme.accessors, to: :theme
7
- delegate *Mumuki::Domain::Organization::Settings.accessors, :private?, :login_settings, to: :settings
7
+ delegate *Mumuki::Domain::Organization::Settings.accessors, :private?, :login_settings, :in_preparation?, :disabled?, to: :settings
8
8
  delegate *Mumuki::Domain::Organization::Profile.accessors, :locale_json, to: :profile
9
9
  end
10
10
 
@@ -48,6 +48,11 @@ module Mumuki::Domain::Helpers::Organization
48
48
  Mumukit::Platform.application.organic_domain(name)
49
49
  end
50
50
 
51
+ def validate_active!
52
+ raise Mumuki::Domain::DisabledOrganizationError if disabled?
53
+ raise Mumuki::Domain::UnpreparedOrganizationError if in_preparation?
54
+ end
55
+
51
56
  ## API Exposure
52
57
 
53
58
  def to_param
@@ -28,9 +28,9 @@ module Mumuki::Domain::Helpers::User
28
28
  role_here = "#{role}_here?"
29
29
 
30
30
  # Tells whether this user has #{role} permissions in
31
- # the given organization
32
- define_method role_of do |organization|
33
- has_permission? role, organization.slug
31
+ # the given `slug_like`
32
+ define_method role_of do |slug_like|
33
+ has_permission? role, slug_like.to_mumukit_slug
34
34
  end
35
35
 
36
36
  # Tells whether this user has #{role} permissions in
@@ -49,16 +49,16 @@ module Mumuki::Domain::Helpers::User
49
49
  (Mumukit::Auth::Roles::ROLES - [:owner]).each do |role|
50
50
 
51
51
  # Assignes the #{role} role to this user
52
- # for the given slug
53
- define_method "make_#{role}_of!" do |slug|
54
- add_permission! role, slug
52
+ # for the given `grant_like`
53
+ define_method "make_#{role}_of!" do |grant_like|
54
+ add_permission! role, grant_like.to_mumukit_grant
55
55
  end
56
56
  end
57
57
 
58
58
  ## Profile
59
59
 
60
60
  def full_name
61
- "#{first_name} #{last_name}"
61
+ "#{first_name} #{last_name}".strip
62
62
  end
63
63
 
64
64
  alias_method :name, :full_name
@@ -27,3 +27,4 @@ en:
27
27
  attributes:
28
28
  base:
29
29
  consistent_public_login: 'A public organization can not restrict login methods'
30
+ invalid_activity_range: 'The organization activity range are not valid'
@@ -44,6 +44,7 @@ es:
44
44
  attributes:
45
45
  base:
46
46
  consistent_public_login: 'Una organización pública no puede restringir los métodos de login'
47
+ invalid_activity_range: 'La fecha de deshabilitación no puede ser anterior a la de inicio del recorrido'
47
48
  models:
48
49
  exercise:
49
50
  one: Ejercicio
@@ -27,3 +27,4 @@ pt:
27
27
  attributes:
28
28
  base:
29
29
  consistent_public_login: 'Uma organização pública não pode restringir métodos de login'
30
+ invalid_activity_range: 'O intervalo de atividades da organização não é válido'
@@ -11,7 +11,10 @@ class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
11
11
  :embeddable?,
12
12
  :immersive?,
13
13
  :forum_enabled?,
14
- :report_issue_enabled?
14
+ :report_issue_enabled?,
15
+ :disabled_from,
16
+ :in_preparation_until,
17
+ :gamification_enabled?
15
18
 
16
19
  def private?
17
20
  !public?
@@ -24,4 +27,12 @@ class Mumuki::Domain::Organization::Settings < Mumukit::Platform::Model
24
27
  def forum_discussions_minimal_role
25
28
  (@forum_discussions_minimal_role || 'student').to_sym
26
29
  end
30
+
31
+ def disabled?
32
+ disabled_from.present? && disabled_from.to_datetime < DateTime.now
33
+ end
34
+
35
+ def in_preparation?
36
+ in_preparation_until.present? && in_preparation_until.to_datetime > DateTime.now
37
+ end
27
38
  end
@@ -41,4 +41,8 @@ module Mumuki::Domain::Status::Discussion
41
41
  def allowed_statuses_for(user, discussion)
42
42
  STATUSES.select { |it| it.allowed_for?(user, discussion) }
43
43
  end
44
+
45
+ def as_json(_options={})
46
+ to_s
47
+ end
44
48
  end
@@ -5,14 +5,6 @@ module Mumuki::Domain::Status::Discussion::Opened
5
5
  true
6
6
  end
7
7
 
8
- def self.reachable_statuses_for_initiator(discussion)
9
- if discussion.has_responses?
10
- [Mumuki::Domain::Status::Discussion::PendingReview]
11
- else
12
- [Mumuki::Domain::Status::Discussion::Closed]
13
- end
14
- end
15
-
16
8
  def self.reachable_statuses_for_moderator(discussion)
17
9
  if discussion.has_responses?
18
10
  [Mumuki::Domain::Status::Discussion::Closed, Mumuki::Domain::Status::Discussion::Solved]
@@ -9,6 +9,7 @@ class Mumuki::Domain::Submission::Base
9
9
  :submission_id, :queries, :query_results, :manual_evaluation_comment]
10
10
 
11
11
  attr_accessor *ATTRIBUTES
12
+ attr_accessor :client_result
12
13
 
13
14
  def self.from_attributes(*args)
14
15
  new ATTRIBUTES.zip(args).to_h
@@ -26,6 +27,11 @@ class Mumuki::Domain::Submission::Base
26
27
  results
27
28
  end
28
29
 
30
+ def with_client_result(result)
31
+ self.client_result = result if result.present?
32
+ self
33
+ end
34
+
29
35
  def evaluate!(assignment)
30
36
  try_evaluate! assignment
31
37
  rescue => e
@@ -2,6 +2,8 @@ class Mumuki::Domain::Submission::Solution < Mumuki::Domain::Submission::Persist
2
2
  attr_accessor :content
3
3
 
4
4
  def try_evaluate!(assignment)
5
- assignment.run_tests!(content: content).except(:response_type)
5
+ assignment
6
+ .run_tests!({client_result: client_result}.compact.merge(content: content))
7
+ .except(:response_type)
6
8
  end
7
9
  end
@@ -1,5 +1,5 @@
1
1
  module Mumuki
2
2
  module Domain
3
- VERSION = '7.4.1'
3
+ VERSION = '7.7.0'
4
4
  end
5
5
  end