mumuki-laboratory 8.6.0 → 9.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/mumuki_laboratory/application/certificate.js +17 -0
  3. data/app/assets/javascripts/mumuki_laboratory/application/faqs.js +90 -0
  4. data/app/assets/javascripts/mumuki_laboratory/application/upload.js +69 -14
  5. data/app/assets/javascripts/mumuki_laboratory/application/user.js +16 -5
  6. data/app/assets/stylesheets/mumuki_laboratory/application/_layout.scss +3 -0
  7. data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +3 -0
  8. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_certificate.scss +33 -0
  9. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_content_show.scss +15 -2
  10. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +84 -0
  11. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_medal.scss +1 -1
  12. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_menu.scss +63 -0
  13. data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +11 -0
  14. data/app/controllers/api/base_controller.rb +0 -1
  15. data/app/controllers/api/courses_controller.rb +1 -1
  16. data/app/controllers/api/organizations_controller.rb +5 -2
  17. data/app/controllers/api/roles_controller.rb +4 -0
  18. data/app/controllers/api/users_controller.rb +6 -1
  19. data/app/controllers/certificates_controller.rb +28 -0
  20. data/app/controllers/concerns/with_authorization.rb +1 -16
  21. data/app/controllers/concerns/with_certificate_render.rb +25 -0
  22. data/app/controllers/concerns/with_user_params.rb +4 -0
  23. data/app/controllers/discussions_messages_controller.rb +0 -1
  24. data/app/controllers/faqs_controller.rb +6 -0
  25. data/app/controllers/users_controller.rb +12 -5
  26. data/app/helpers/breadcrumbs_helper.rb +4 -0
  27. data/app/helpers/certificate_helper.rb +13 -0
  28. data/app/helpers/content_view_helper.rb +19 -0
  29. data/app/helpers/exercise_input_helper.rb +8 -17
  30. data/app/helpers/links_helper.rb +11 -3
  31. data/app/helpers/menu_bar_helper.rb +15 -11
  32. data/app/helpers/user_menu_helper.rb +36 -0
  33. data/app/mailers/application_mailer.rb +0 -1
  34. data/app/mailers/user_mailer.rb +9 -0
  35. data/app/views/certificates/_certificate.html.erb +44 -0
  36. data/app/views/certificates/_download.html.erb +20 -0
  37. data/app/views/certificates/verify.html.erb +40 -0
  38. data/app/views/chapters/show.html.erb +15 -14
  39. data/app/views/complements/show.html.erb +1 -1
  40. data/app/views/exams/show.html.erb +1 -1
  41. data/app/views/{layouts → exercises}/_exercise_skipped.html.erb +0 -0
  42. data/app/views/exercises/_exercise_title_icons.html.erb +4 -0
  43. data/app/views/exercises/show.html.erb +4 -7
  44. data/app/views/faqs/index.html.erb +20 -0
  45. data/app/views/{layouts → guides}/_guide.html.erb +0 -0
  46. data/app/views/guides/_guide_container.html.erb +24 -0
  47. data/app/views/{layouts → guides}/_guide_title_icons.html.erb +1 -3
  48. data/app/views/layouts/_progress_listing.html.erb +1 -1
  49. data/app/views/layouts/_user_menu.html.erb +17 -0
  50. data/app/views/layouts/application.html.erb +6 -1
  51. data/app/views/layouts/exercise_inputs/editors/_code.html.erb +2 -1
  52. data/app/views/layouts/exercise_inputs/editors/_upload.html.erb +11 -2
  53. data/app/views/lessons/show.html.erb +1 -1
  54. data/app/views/user_mailer/certificate.html.erb +339 -0
  55. data/app/views/user_mailer/certificate.text.erb +10 -0
  56. data/app/views/users/_user_form.html.erb +10 -8
  57. data/app/views/users/certificates.html.erb +32 -0
  58. data/app/views/users/discussions.html.erb +28 -0
  59. data/app/views/users/edit.html.erb +1 -1
  60. data/app/views/users/messages.html.erb +27 -0
  61. data/app/views/users/show.html.erb +4 -51
  62. data/app/views/users/terms.html.erb +2 -2
  63. data/config/routes.rb +9 -0
  64. data/lib/mumuki/laboratory/locales/en.yml +13 -3
  65. data/lib/mumuki/laboratory/locales/es-CL.yml +13 -3
  66. data/lib/mumuki/laboratory/locales/es.yml +12 -2
  67. data/lib/mumuki/laboratory/locales/pt.yml +12 -2
  68. data/lib/mumuki/laboratory/version.rb +1 -1
  69. data/spec/controllers/certificates_controller_spec.rb +15 -0
  70. data/spec/controllers/organizations_api_controller_spec.rb +16 -9
  71. data/spec/dummy/db/schema.rb +25 -1
  72. data/spec/features/certificate_programs_flow_spec.rb +17 -0
  73. data/spec/features/exercise_flow_spec.rb +3 -3
  74. data/spec/features/login_flow_spec.rb +1 -1
  75. data/spec/features/menu_bar_spec.rb +44 -24
  76. data/spec/features/profile_flow_spec.rb +18 -9
  77. data/spec/features/terms_flow_spec.rb +30 -0
  78. data/spec/helpers/application_helper_spec.rb +10 -0
  79. data/spec/helpers/certificate_helper_spec.rb +15 -0
  80. data/spec/javascripts/upload-spec.js +80 -0
  81. metadata +86 -14
  82. data/app/views/layouts/_guide_container.html.erb +0 -28
@@ -31,11 +31,19 @@
31
31
  }
32
32
  }
33
33
 
34
+ .mu-profile-info {
35
+ display: flex;
36
+ flex-wrap: wrap;
37
+
38
+ margin-top: 20px;
39
+ }
40
+
34
41
  .mu-profile-info-left {
35
42
  display: flex;
36
43
  flex-direction: column;
37
44
  align-items: center;
38
45
  text-align: center;
46
+ flex-grow: 1;
39
47
 
40
48
  .mu-level-progress {
41
49
  position: relative;
@@ -62,8 +70,11 @@
62
70
  }
63
71
 
64
72
  .mu-profile-info-right {
73
+ flex-grow: 2;
74
+
65
75
  font-size: 20px;
66
76
  margin-top: 5px;
77
+
67
78
  div {
68
79
  margin-bottom: 15px;
69
80
  .italic {
@@ -5,7 +5,6 @@ module Api
5
5
  protect_from_forgery with: :null_session
6
6
 
7
7
  include Mumuki::Laboratory::Controllers::DynamicErrors
8
- include WithAuthorization
9
8
  include Mumuki::Laboratory::Controllers::CurrentOrganization
10
9
 
11
10
  before_action :set_current_organization!
@@ -18,7 +18,7 @@ module Api
18
18
  @course = Course.new course_params
19
19
  end
20
20
 
21
- def protection_slug
21
+ def authorization_slug
22
22
  @course.slug
23
23
  end
24
24
  end
@@ -2,8 +2,7 @@ module Api
2
2
  class OrganizationsController < BaseController
3
3
  include OrganizationsControllerTemplate
4
4
 
5
- before_action :authorize_janitor!, only: [:show, :index]
6
- before_action :authorize_admin!, only: [:update, :create]
5
+ before_action :authorize_admin!
7
6
 
8
7
  def index
9
8
  render json: { organizations: Organization.accessible_as(current_user, :janitor) }
@@ -22,6 +21,10 @@ module Api
22
21
  @organization.update! organization_params
23
22
  render json: @organization.to_resource_h
24
23
  end
24
+
25
+ def authorization_slug
26
+ '_/_'
27
+ end
25
28
  end
26
29
 
27
30
  end
@@ -48,6 +48,10 @@ module Api
48
48
  def set_slug!
49
49
  @slug = Mumukit::Auth::Slug.join_s params.to_unsafe_h
50
50
  end
51
+
52
+ def authorization_slug
53
+ @slug
54
+ end
51
55
  end
52
56
 
53
57
  end
@@ -9,8 +9,13 @@ module Api
9
9
  end
10
10
 
11
11
  def update
12
- @user.update_and_notify! user_params.except([:email, :permissions, :uid])
12
+ @user.assign_attributes user_name_params
13
+ @user.verify_name!
13
14
  render json: @user.to_resource_h
14
15
  end
16
+
17
+ def authorization_slug
18
+ '_/_'
19
+ end
15
20
  end
16
21
  end
@@ -0,0 +1,28 @@
1
+ class CertificatesController < ApplicationController
2
+ include WithCertificateRender
3
+
4
+ before_action :authorize_if_private!, only: [:show]
5
+ before_action :set_certificate!
6
+ before_action :validate_current_user!, only: [:download]
7
+
8
+ def verify
9
+ end
10
+
11
+ def show
12
+ end
13
+
14
+ def download
15
+ send_data pdf_for(@certificate), filename: @certificate.filename
16
+ end
17
+
18
+ private
19
+
20
+ def set_certificate!
21
+ @certificate = Certificate.find_by! code: params[:code]
22
+ end
23
+
24
+ def validate_current_user!
25
+ raise Mumuki::Domain::NotFoundError unless @certificate.for_user? current_user
26
+ end
27
+
28
+ end
@@ -1,27 +1,12 @@
1
1
  module WithAuthorization
2
2
  extend ActiveSupport::Concern
3
3
 
4
- def authorize_janitor!
5
- authorize! :janitor
6
- end
7
-
8
- def authorize_admin!
9
- authorize! :admin
10
- end
11
-
12
- def authorize_owner!
13
- authorize! :owner
14
- end
15
-
16
- def authorize_moderator!
17
- authorize! :moderator
18
- end
19
-
20
4
  def authorization_slug
21
5
  protection_slug || '_/_'
22
6
  end
23
7
 
24
8
  def protection_slug
9
+ warn "protection_slug is nil, which is not probably what you want" unless @slug
25
10
  @slug
26
11
  end
27
12
  end
@@ -0,0 +1,25 @@
1
+ require 'wicked_pdf'
2
+ require 'rqrcode'
3
+
4
+ module WithCertificateRender
5
+
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :qr_for
10
+ end
11
+
12
+ def qr_for(certificate)
13
+ qr = RQRCode::QRCode.new(verify_certificate_url certificate.code).as_svg(color: '0B465D')
14
+ "data:image/svg+xml,#{URI.encode(qr)}"
15
+ end
16
+
17
+ def pdf_for(certificate)
18
+ pdf_html = render_to_string(partial: 'certificates/download', locals: { certificate: certificate })
19
+ WickedPdf.new.pdf_from_string pdf_html,
20
+ orientation: 'Landscape',
21
+ page_size: 'A5',
22
+ margin: { top: 0.5, left: 1, bottom: 0.5, right: 1 }
23
+
24
+ end
25
+ end
@@ -5,6 +5,10 @@ module WithUserParams
5
5
  params.require(:user).permit(*permissible_params).to_h
6
6
  end
7
7
 
8
+ def user_name_params
9
+ params.require(:user).permit(:first_name, :last_name).to_h
10
+ end
11
+
8
12
  def permissible_params
9
13
  User.profile_fields
10
14
  end
@@ -1,5 +1,4 @@
1
1
  class DiscussionsMessagesController < AjaxController
2
- include WithAuthorization
3
2
  include WithUserDiscussionValidation
4
3
 
5
4
  before_action :set_discussion!, only: [:create, :destroy]
@@ -0,0 +1,6 @@
1
+ class FAQsController < ApplicationController
2
+ def index
3
+ @faqs = Organization.current.faqs_html
4
+ raise Mumuki::Domain::NotFoundError unless @faqs.present?
5
+ end
6
+ end
@@ -5,11 +5,6 @@ class UsersController < ApplicationController
5
5
  before_action :set_user!
6
6
  skip_before_action :validate_accepted_role_terms!
7
7
 
8
- def show
9
- @messages = current_user.messages.to_a
10
- @watched_discussions = current_user.watched_discussions_in_organization
11
- end
12
-
13
8
  def update
14
9
  current_user.update_and_notify! user_params
15
10
  current_user.accept_profile_terms!
@@ -27,6 +22,18 @@ class UsersController < ApplicationController
27
22
  @profile_terms ||= Term.profile_terms_for(current_user)
28
23
  end
29
24
 
25
+ def messages
26
+ @messages ||= current_user.messages_in_organization
27
+ end
28
+
29
+ def discussions
30
+ @watched_discussions = current_user.watched_discussions_in_organization
31
+ end
32
+
33
+ def certificates
34
+ @certificates ||= current_user.certificates_in_organization
35
+ end
36
+
30
37
  def unsubscribe
31
38
  user_id = User.unsubscription_verifier.verify(params[:id])
32
39
  User.find(user_id).unsubscribe_from_reminders!
@@ -41,6 +41,10 @@ module BreadcrumbsHelper
41
41
  discussion.friendly.truncate_words(4)
42
42
  end
43
43
 
44
+ def breadcrumbs_for_my_account
45
+ header_breadcrumbs + breadcrumb_list_item(t(:my_account), 'last')
46
+ end
47
+
44
48
  private
45
49
 
46
50
  def breadcrumbs_for_linkable(e, extra=nil, last='')
@@ -0,0 +1,13 @@
1
+ module CertificateHelper
2
+ def linkedin_post_url(certificate)
3
+ URI::HTTPS.build host: 'www.linkedin.com',
4
+ path: '/profile/add',
5
+ query: Rack::Utils.build_query(startTask: 'CERTIFICATION_NAME',
6
+ name: certificate.title,
7
+ organizationId: ENV['MUMUKI_LINKEDIN_ORGANIZATION_ID'],
8
+ issueYear: certificate.created_at.year,
9
+ issueMonth: certificate.created_at.month,
10
+ certUrl: verify_certificate_url(certificate.code),
11
+ certId: certificate.code)
12
+ end
13
+ end
@@ -0,0 +1,19 @@
1
+ module ContentViewHelper
2
+ def full_title_for(content)
3
+ "#{t(content_type_number(content), number: content.number)}: #{content.name}"
4
+ end
5
+
6
+ def short_title_for(content)
7
+ content.name
8
+ end
9
+
10
+ private
11
+
12
+ def content_type_number(content)
13
+ "#{content_type(content)}_number"
14
+ end
15
+
16
+ def content_type(content)
17
+ content.model_name.element
18
+ end
19
+ end
@@ -53,12 +53,11 @@ module ExerciseInputHelper
53
53
  waiting_text = t(options.waiting_t) if options.waiting_t.present?
54
54
  %Q{
55
55
  <div class="btn-submit-container">
56
- <#{options.tag} for="#{options.for}"
57
- class="btn btn-success btn-block btn-submit #{options.classes}"
56
+ <button class="btn btn-success btn-block btn-submit #{options.classes}"
58
57
  data-waiting="#{waiting_text}">
59
- #{fa_icon options.fa_icon}
58
+ #{fa_icon 'play'}
60
59
  #{text} #{remaining_attempts_text(assignment)}
61
- </#{options.tag}>
60
+ </button>
62
61
  </div>
63
62
  }.html_safe
64
63
  end
@@ -92,26 +91,18 @@ module ExerciseInputHelper
92
91
 
93
92
  def submit_button_options(exercise)
94
93
  if exercise.upload?
95
- struct for: 'upload-input',
96
- tag: :label,
94
+ struct classes: 'disabled',
97
95
  waiting_t: :uploading_solution,
98
- fa_icon: :upload,
99
- t: :upload_solution
96
+ t: :create_submission
100
97
  elsif exercise.hidden?
101
- struct tag: :button,
102
- classes: 'submission_control',
98
+ struct classes: 'submission_control',
103
99
  waiting_t: :working,
104
- fa_icon: :play,
105
100
  t: :continue_exercise
106
101
  elsif exercise.input_kids?
107
- struct tag: :button,
108
- classes: 'submission_control',
109
- fa_icon: :play
102
+ struct classes: 'submission_control'
110
103
  else
111
- struct tag: :button,
112
- classes: 'submission_control',
104
+ struct classes: 'submission_control',
113
105
  waiting_t: :sending_solution,
114
- fa_icon: :play,
115
106
  t: :create_submission
116
107
  end
117
108
  end
@@ -26,7 +26,7 @@ module LinksHelper
26
26
  if current_user&.teacher_here? && item.teacher_info.present?
27
27
  %Q{
28
28
  <a
29
- class="mu-content-toolbar-item mu-popover"
29
+ class="mu-popover"
30
30
  data-toggle="popover"
31
31
  data-html="true"
32
32
  title="#{t :teacher_info}"
@@ -59,10 +59,18 @@ module LinksHelper
59
59
  link_to t(:forum_terms), discussions_terms_path, target: '_blank'
60
60
  end
61
61
 
62
+ def link_to_faqs
63
+ link_to t(:faqs), faqs_path, target: '_blank' if faqs_enabled_here?
64
+ end
65
+
62
66
  def turbolinks_enable_for(exercise)
63
67
  %Q{data-turbolinks="#{!exercise.input_kids?}"}.html_safe
64
68
  end
65
69
 
70
+ def faqs_enabled_here?
71
+ Organization.current.faqs.present?
72
+ end
73
+
66
74
  private
67
75
 
68
76
  def extract_name(named, options)
@@ -80,10 +88,10 @@ module LinksHelper
80
88
  return unless current_user&.writer?
81
89
 
82
90
  url = yield
83
- link_to fixed_fa_icon('pencil-alt'), url, class: "mu-content-toolbar-item", target: "_blank", title: t(:edit)
91
+ link_to fixed_fa_icon('pencil-alt'), url, target: "_blank", title: t(:edit)
84
92
  end
85
93
 
86
94
  def url_for_bibliotheca_guide(guide)
87
- "#{url_for_application(:bibliotheca_ui)}/#/guides/#{guide.slug}"
95
+ "#{url_for_application(:bibliotheca_ui).chomp('/')}/guides/#{guide.slug}"
88
96
  end
89
97
  end
@@ -1,9 +1,9 @@
1
1
  module MenuBarHelper
2
2
  def menu_bar_links
3
3
  [
4
- link_to_profile,
5
- link_to_classroom,
6
- link_to_bibliotheca,
4
+ menu_link_to_profile,
5
+ menu_link_to_classroom,
6
+ menu_link_to_bibliotheca,
7
7
  solve_discussions_link,
8
8
  user_discussions_link
9
9
  ]
@@ -17,25 +17,25 @@ module MenuBarHelper
17
17
  content_tag :li, link
18
18
  end
19
19
 
20
- def link_to_profile
21
- menu_item('user', :profile, user_path)
20
+ def menu_link_to_profile
21
+ menu_item('user', :my_account, user_path)
22
22
  end
23
23
 
24
- def link_to_classroom
25
- link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
24
+ def menu_link_to_classroom
25
+ menu_link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
26
26
  end
27
27
 
28
- def link_to_bibliotheca
29
- link_to_application :book, :bibliotheca_ui, :writer?
28
+ def menu_link_to_bibliotheca
29
+ menu_link_to_application :book, :bibliotheca_ui, :writer?
30
30
  end
31
31
 
32
- def link_to_application(icon, app_name, minimal_permissions)
32
+ def menu_link_to_application(icon, app_name, minimal_permissions)
33
33
  return unless current_user&.send(minimal_permissions)
34
34
  url = url_for_application(app_name)
35
35
  menu_item icon, app_name, url
36
36
  end
37
37
 
38
- def logout_link
38
+ def logout_menu_link
39
39
  li_tag menu_item('sign-out-alt', :sign_out, logout_path(origin: url_for))
40
40
  end
41
41
 
@@ -46,4 +46,8 @@ module MenuBarHelper
46
46
  def any_menu_bar_links?
47
47
  menu_bar_links.any?
48
48
  end
49
+
50
+ def menu_link_to_faqs
51
+ li_tag menu_item('question', :faqs, faqs_path)
52
+ end
49
53
  end
@@ -0,0 +1,36 @@
1
+ module UserMenuHelper
2
+ def user_menu_header
3
+ content_tag :div, user_menu_header_icon, class: 'mu-user-menu-header'
4
+ end
5
+
6
+ def user_menu_divider
7
+ content_tag :div, '', class: 'mu-user-menu-divider horizontal'
8
+ end
9
+
10
+ def profile_user_menu_link
11
+ user_menu_item t(:my_profile), user_path, 'show'
12
+ end
13
+
14
+ def messages_user_menu_link
15
+ user_menu_item t(:messages), messages_user_path, 'messages'
16
+ end
17
+
18
+ def discussions_user_menu_link
19
+ user_menu_item t(:discussions), discussions_user_path, 'discussions' if current_user&.can_discuss_here?
20
+ end
21
+
22
+ def certificates_user_menu_link
23
+ user_menu_item t(:certificates), certificates_user_path, 'certificates'
24
+ end
25
+
26
+ private
27
+
28
+ def user_menu_item(label, path, active_on)
29
+ link_klass = 'active' if action_name == active_on
30
+ content_tag :div, link_to(label, path, { class: link_klass }.compact), class: 'mu-user-menu-item'
31
+ end
32
+
33
+ def user_menu_header_icon
34
+ fa_icon('chevron-down', text: t(:my_account), id: 'mu-user-menu-header-icon', right: true)
35
+ end
36
+ end