mumuki-laboratory 9.0.1 → 9.0.6
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 +4 -4
- data/app/assets/javascripts/mumuki_laboratory/application/certificate.js +17 -0
- data/app/assets/javascripts/mumuki_laboratory/application/codemirror.js +10 -2
- data/app/assets/javascripts/mumuki_laboratory/application/discussions.js +43 -2
- data/app/assets/javascripts/mumuki_laboratory/application/faqs.js +90 -0
- data/app/assets/javascripts/mumuki_laboratory/application/multiple-files.js +7 -0
- data/app/assets/javascripts/mumuki_laboratory/application/organization.js +32 -0
- data/app/assets/javascripts/mumuki_laboratory/application/submissions-store.js +1 -1
- data/app/assets/javascripts/mumuki_laboratory/application/user.js +49 -5
- data/app/assets/stylesheets/mumuki_laboratory/application/_modules.scss +3 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_activity.scss +12 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_certificate.scss +33 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_discussion.scss +14 -2
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_faqs.scss +84 -0
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_terms.scss +3 -2
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_menu.scss +30 -2
- data/app/assets/stylesheets/mumuki_laboratory/application/modules/_user_profile.scss +1 -0
- data/app/controllers/certificates_controller.rb +28 -0
- data/app/controllers/concerns/with_certificate_render.rb +25 -0
- data/app/controllers/discussions_messages_controller.rb +6 -2
- data/app/controllers/faqs_controller.rb +6 -0
- data/app/controllers/users_controller.rb +17 -0
- data/app/helpers/certificate_helper.rb +13 -0
- data/app/helpers/discussions_helper.rb +5 -1
- data/app/helpers/links_helper.rb +8 -0
- data/app/helpers/menu_bar_helper.rb +14 -10
- data/app/helpers/multiple_file_editor_helper.rb +2 -1
- data/app/helpers/user_activity_helper.rb +48 -0
- data/app/helpers/user_menu_helper.rb +27 -5
- data/app/mailers/application_mailer.rb +0 -1
- data/app/mailers/user_mailer.rb +9 -0
- data/app/views/certificates/_certificate.html.erb +44 -0
- data/app/views/certificates/_download.html.erb +20 -0
- data/app/views/certificates/verify.html.erb +40 -0
- data/app/views/discussions/_description_message.html.erb +1 -1
- data/app/views/discussions/_message.html.erb +1 -1
- data/app/views/discussions/_new_message.html.erb +13 -2
- data/app/views/discussions/new.html.erb +1 -1
- data/app/views/faqs/index.html.erb +20 -0
- data/app/views/layouts/_main.html.erb +4 -0
- data/app/views/layouts/_user_menu.html.erb +13 -16
- data/app/views/layouts/application.html.erb +6 -1
- data/app/views/layouts/exercise_inputs/editors/_code.html.erb +2 -1
- data/app/views/layouts/exercise_inputs/editors/_multiple_files.html.erb +1 -1
- data/app/views/user_mailer/certificate.html.erb +339 -0
- data/app/views/user_mailer/certificate.text.erb +10 -0
- data/app/views/users/_activity_indicator.html.erb +17 -0
- data/app/views/users/activity.html.erb +37 -0
- data/app/views/users/certificates.html.erb +32 -0
- data/config/initializers/inflections.rb +3 -0
- data/config/routes.rb +9 -0
- data/lib/mumuki/laboratory/extensions.rb +1 -0
- data/lib/mumuki/laboratory/extensions/date_and_time.rb +11 -0
- data/lib/mumuki/laboratory/locales/en.yml +20 -1
- data/lib/mumuki/laboratory/locales/es-CL.yml +25 -3
- data/lib/mumuki/laboratory/locales/es.yml +25 -3
- data/lib/mumuki/laboratory/locales/pt.yml +26 -1
- data/lib/mumuki/laboratory/version.rb +1 -1
- data/spec/controllers/certificates_controller_spec.rb +15 -0
- data/spec/controllers/discussions_messages_controller_spec.rb +20 -1
- data/spec/dummy/db/schema.rb +20 -3
- data/spec/features/certificate_programs_flow_spec.rb +17 -0
- data/spec/features/discussion_flow_spec.rb +2 -0
- data/spec/features/menu_bar_spec.rb +20 -0
- data/spec/features/profile_flow_spec.rb +12 -0
- data/spec/features/user_activity_flow_spec.rb +65 -0
- data/spec/helpers/certificate_helper_spec.rb +15 -0
- data/spec/helpers/user_activity_helper_spec.rb +32 -0
- metadata +170 -97
@@ -1,12 +1,41 @@
|
|
1
1
|
.mu-user-menu {
|
2
2
|
display: flex;
|
3
|
+
margin-top: 25px;
|
4
|
+
}
|
5
|
+
|
6
|
+
.mu-user-menu-content {
|
7
|
+
flex-grow: 1;
|
3
8
|
}
|
4
9
|
|
5
10
|
.mu-user-menu-header {
|
6
11
|
font-size: 15px;
|
7
12
|
color: $mu-color-disabled;
|
8
13
|
text-transform: uppercase;
|
9
|
-
|
14
|
+
padding-left: 2px;
|
15
|
+
cursor: pointer;
|
16
|
+
|
17
|
+
@media only screen and (min-width: $screen-md-min) {
|
18
|
+
padding-left: 15px;
|
19
|
+
cursor: auto;
|
20
|
+
|
21
|
+
i {
|
22
|
+
display: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
span {
|
26
|
+
padding-left: 0px;
|
27
|
+
}
|
28
|
+
}
|
29
|
+
}
|
30
|
+
|
31
|
+
.mu-user-menu-items {
|
32
|
+
margin-top: 30px;
|
33
|
+
|
34
|
+
&.mu-hidden-sm-screen {
|
35
|
+
@media only screen and (max-width: $screen-sm-max) {
|
36
|
+
display: none;
|
37
|
+
}
|
38
|
+
}
|
10
39
|
}
|
11
40
|
|
12
41
|
.mu-user-menu-item {
|
@@ -30,6 +59,5 @@
|
|
30
59
|
border-top: 2px solid $mu-color-separator;
|
31
60
|
|
32
61
|
margin: 25px 0;
|
33
|
-
width: 100%
|
34
62
|
}
|
35
63
|
}
|
@@ -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
|
@@ -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
|
@@ -3,7 +3,7 @@ class DiscussionsMessagesController < AjaxController
|
|
3
3
|
|
4
4
|
before_action :set_discussion!, only: [:create, :destroy]
|
5
5
|
before_action :authorize_user!, only: [:destroy]
|
6
|
-
before_action :authorize_moderator!, only: [:question, :approve]
|
6
|
+
before_action :authorize_moderator!, only: [:question, :approve, :preview]
|
7
7
|
|
8
8
|
def create
|
9
9
|
@discussion.submit_message! message_params, current_user
|
@@ -15,8 +15,12 @@ class DiscussionsMessagesController < AjaxController
|
|
15
15
|
redirect_back(fallback_location: root_path)
|
16
16
|
end
|
17
17
|
|
18
|
+
def preview
|
19
|
+
render json: { preview: Message.new(content: params[:content]).content_html }
|
20
|
+
end
|
21
|
+
|
18
22
|
def approve
|
19
|
-
current_message.toggle_approved!
|
23
|
+
current_message.toggle_approved! current_user
|
20
24
|
head :ok
|
21
25
|
end
|
22
26
|
|
@@ -30,6 +30,14 @@ class UsersController < ApplicationController
|
|
30
30
|
@watched_discussions = current_user.watched_discussions_in_organization
|
31
31
|
end
|
32
32
|
|
33
|
+
def activity
|
34
|
+
@activity = UserStats.stats_for(current_user).activity date_range_params
|
35
|
+
end
|
36
|
+
|
37
|
+
def certificates
|
38
|
+
@certificates ||= current_user.certificates_in_organization
|
39
|
+
end
|
40
|
+
|
33
41
|
def unsubscribe
|
34
42
|
user_id = User.unsubscription_verifier.verify(params[:id])
|
35
43
|
User.find(user_id).unsubscribe_from_reminders!
|
@@ -50,4 +58,13 @@ class UsersController < ApplicationController
|
|
50
58
|
@user = current_user
|
51
59
|
end
|
52
60
|
|
61
|
+
def date_range_params
|
62
|
+
@date_from = params[:date_from].try { |it| Date.parse it }
|
63
|
+
to = params[:date_to].try { |it| Date.parse it }
|
64
|
+
if @date_from && to
|
65
|
+
@date_from.beginning_of_day..(to - 1.day).end_of_day
|
66
|
+
else
|
67
|
+
nil
|
68
|
+
end
|
69
|
+
end
|
53
70
|
end
|
@@ -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
|
@@ -12,7 +12,7 @@ module DiscussionsHelper
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def user_discussions_link
|
15
|
-
discussions_link
|
15
|
+
discussions_link(user_discussions_icon(t(:my_doubts)), discussions_user_path) if current_user.watched_discussions.present?
|
16
16
|
end
|
17
17
|
|
18
18
|
def others_discussions_icon(text)
|
@@ -195,4 +195,8 @@ module DiscussionsHelper
|
|
195
195
|
def should_show_approved_for?(user, message)
|
196
196
|
!user&.moderator_here? && message.approved? && !message.from_moderator?
|
197
197
|
end
|
198
|
+
|
199
|
+
def discussion_user_name(user)
|
200
|
+
user.name
|
201
|
+
end
|
198
202
|
end
|
data/app/helpers/links_helper.rb
CHANGED
@@ -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)
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module MenuBarHelper
|
2
2
|
def menu_bar_links
|
3
3
|
[
|
4
|
-
|
5
|
-
|
6
|
-
|
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
|
20
|
+
def menu_link_to_profile
|
21
21
|
menu_item('user', :my_account, user_path)
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
24
|
+
def menu_link_to_classroom
|
25
|
+
menu_link_to_application 'graduation-cap', :classroom_ui, :teacher_here?
|
26
26
|
end
|
27
27
|
|
28
|
-
def
|
29
|
-
|
28
|
+
def menu_link_to_bibliotheca
|
29
|
+
menu_link_to_application :book, :bibliotheca_ui, :writer?
|
30
30
|
end
|
31
31
|
|
32
|
-
def
|
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
|
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
|
@@ -9,6 +9,7 @@ module MultipleFileEditorHelper
|
|
9
9
|
|
10
10
|
def multifile_hidden_inputs
|
11
11
|
hidden_field_tag('highlight-modes', highlight_modes.to_json) +
|
12
|
-
hidden_field_tag('multifile-locales', multifile_locales.to_json)
|
12
|
+
hidden_field_tag('multifile-locales', multifile_locales.to_json) +
|
13
|
+
hidden_field_tag('multifile-default-content', @files.to_json)
|
13
14
|
end
|
14
15
|
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module UserActivityHelper
|
2
|
+
def activity_selector_week_range_for(organization = Organization.current)
|
3
|
+
start = organization.activity_start_date min_week
|
4
|
+
(start.prev_occurring(:monday) + 1..Date.today)
|
5
|
+
.step(7)
|
6
|
+
.to_a
|
7
|
+
.reverse
|
8
|
+
.map { |it| [it, it + 7.days] }
|
9
|
+
end
|
10
|
+
|
11
|
+
def mark_period_if_active(period_start)
|
12
|
+
active_period?(period_start) && 'class=active'
|
13
|
+
end
|
14
|
+
|
15
|
+
def solved_exercises_percentage
|
16
|
+
percentage = @activity[:exercises][:solved_count].to_f / @activity[:exercises][:count] * 100
|
17
|
+
"#{percentage.ceil}%"
|
18
|
+
end
|
19
|
+
|
20
|
+
def exercises_activity_stats
|
21
|
+
solved_count = {
|
22
|
+
name: t(:solved_exercises_count, count: @activity[:exercises][:solved_count]),
|
23
|
+
value: @activity[:exercises][:solved_count] }
|
24
|
+
solved_percentage = {
|
25
|
+
name: t(:solved_exercises_percentage),
|
26
|
+
value: solved_exercises_percentage }
|
27
|
+
|
28
|
+
@date_from ? [solved_count] : [solved_count, solved_percentage]
|
29
|
+
end
|
30
|
+
|
31
|
+
def messages_activity_stats
|
32
|
+
count = @activity[:messages][:count]
|
33
|
+
approved = @activity[:messages][:approved]
|
34
|
+
|
35
|
+
[{name: t(:messages_pluralized, count: count), value: count},
|
36
|
+
{name: t(:approved_messages, count: approved), value: approved}]
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def active_period?(period_start)
|
42
|
+
period_start ? @date_from == period_start : !@date_from
|
43
|
+
end
|
44
|
+
|
45
|
+
def min_week
|
46
|
+
8.week.ago.to_date
|
47
|
+
end
|
48
|
+
end
|
@@ -1,18 +1,40 @@
|
|
1
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
|
+
|
2
10
|
def profile_user_menu_link
|
3
|
-
|
11
|
+
user_menu_item t(:my_profile), user_path, 'show'
|
4
12
|
end
|
5
13
|
|
6
14
|
def messages_user_menu_link
|
7
|
-
|
15
|
+
user_menu_item t(:messages), messages_user_path, 'messages'
|
8
16
|
end
|
9
17
|
|
10
18
|
def discussions_user_menu_link
|
11
|
-
|
19
|
+
user_menu_item t(:discussions), discussions_user_path, 'discussions' if current_user&.can_discuss_here?
|
20
|
+
end
|
21
|
+
|
22
|
+
def activity_user_menu_link
|
23
|
+
user_menu_item t(:activity), activity_user_path, 'activity'
|
12
24
|
end
|
13
25
|
|
14
|
-
def
|
26
|
+
def certificates_user_menu_link
|
27
|
+
user_menu_item t(:certificates), certificates_user_path, 'certificates'
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def user_menu_item(label, path, active_on)
|
15
33
|
link_klass = 'active' if action_name == active_on
|
16
|
-
link_to
|
34
|
+
content_tag :div, link_to(label, path, { class: link_klass }.compact), class: 'mu-user-menu-item'
|
35
|
+
end
|
36
|
+
|
37
|
+
def user_menu_header_icon
|
38
|
+
fa_icon('chevron-down', text: t(:my_account), id: 'mu-user-menu-header-icon', right: true)
|
17
39
|
end
|
18
40
|
end
|
data/app/mailers/user_mailer.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
class UserMailer < ApplicationMailer
|
2
|
+
include WithCertificateRender
|
3
|
+
|
2
4
|
def welcome_email(user, organization)
|
3
5
|
with_locale(user, organization) do
|
4
6
|
organization_name = organization.display_name || t(:your_new_organization)
|
@@ -18,6 +20,13 @@ class UserMailer < ApplicationMailer
|
|
18
20
|
end
|
19
21
|
end
|
20
22
|
|
23
|
+
def certificate(certificate)
|
24
|
+
with_locale certificate.user, certificate.organization do
|
25
|
+
attachments[certificate.filename] = pdf_for(certificate)
|
26
|
+
mail to: certificate.user.email, subject: t(:certificate)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
21
30
|
def with_locale(user, organization = nil, &block)
|
22
31
|
@user = user
|
23
32
|
@unsubscribe_code = User.unsubscription_verifier.generate(user.id)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
<style>
|
2
|
+
|
3
|
+
.mu-certificate-box {
|
4
|
+
overflow: hidden;
|
5
|
+
height: 210mm;
|
6
|
+
width: 297mm;
|
7
|
+
max-height: 210mm;
|
8
|
+
max-width: 297mm;
|
9
|
+
min-height: 210mm;
|
10
|
+
min-width: 297mm;
|
11
|
+
margin: 0;
|
12
|
+
box-sizing: border-box;
|
13
|
+
position: relative;
|
14
|
+
border: 1px solid lightgrey;
|
15
|
+
}
|
16
|
+
|
17
|
+
.mu-certificate-box section {
|
18
|
+
background: transparent;
|
19
|
+
z-index: 10;
|
20
|
+
}
|
21
|
+
|
22
|
+
.background-image {
|
23
|
+
position: absolute;
|
24
|
+
height: 210mm;
|
25
|
+
width: 297mm;
|
26
|
+
margin: 0;
|
27
|
+
padding: 0;
|
28
|
+
z-index: 0;
|
29
|
+
}
|
30
|
+
|
31
|
+
.qr-code {
|
32
|
+
position: absolute;
|
33
|
+
z-index: 10;
|
34
|
+
}
|
35
|
+
|
36
|
+
</style>
|
37
|
+
|
38
|
+
<div class="mu-certificate-box">
|
39
|
+
<img src="<%= certificate.background_image_url %>" class="background-image"/>
|
40
|
+
<%= render inline: certificate.template_html_erb, locals: certificate.template_locals %>
|
41
|
+
<a href="<%= verify_certificate_path certificate.code %>" target="_blank">
|
42
|
+
<div><img class="qr-code" src="<%= qr_for certificate %>"/></div>
|
43
|
+
</a>
|
44
|
+
</div>
|