mumuki-laboratory 9.0.0 → 9.0.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/app/assets/javascripts/mumuki_laboratory/application/certificate.js +17 -0
- 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/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/_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 +9 -1
- data/app/helpers/menu_bar_helper.rb +14 -10
- 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 +12 -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/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/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 +5 -1
- 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/application_helper_spec.rb +10 -0
- data/spec/helpers/certificate_helper_spec.rb +15 -0
- data/spec/helpers/user_activity_helper_spec.rb +32 -0
- metadata +171 -99
@@ -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)
|
@@ -84,6 +92,6 @@ module LinksHelper
|
|
84
92
|
end
|
85
93
|
|
86
94
|
def url_for_bibliotheca_guide(guide)
|
87
|
-
"#{url_for_application(:bibliotheca_ui)}
|
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
|
-
|
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
|
@@ -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>
|