From 83b96d26e123ae9c4a565f3a07933507631bebca Mon Sep 17 00:00:00 2001 From: Josep Jaume Rey Date: Wed, 12 Sep 2018 09:05:25 +0200 Subject: [PATCH] Add more badges (#4089) * Add commented debates badge * Ad followers badge * Fix debates locales * Normalize locales * Improve badge display * Fix changelog * Fix rubocop issues * Fix rubocop issues * Lint erb --- .rubocop.yml | 1 + CHANGELOG.md | 2 + .../decidim/gamification/badges/followers.svg | 115 ++++++++++++++++++ decidim-core/app/cells/decidim/badge/show.erb | 4 +- .../app/cells/decidim/badge/small.erb | 5 + decidim-core/app/cells/decidim/badge_cell.rb | 14 ++- .../cells/decidim/profile_sidebar/show.erb | 25 ++++ .../app/commands/decidim/create_follow.rb | 6 + .../app/commands/decidim/delete_follow.rb | 7 ++ decidim-core/config/locales/en.yml | 12 ++ decidim-core/lib/decidim/core/engine.rb | 5 + .../commands/decidim/create_follow_spec.rb | 25 ++++ .../commands/decidim/delete_follow_spec.rb | 34 ++++++ decidim-core/spec/lib/followers_badge_spec.rb | 21 ++++ .../gamification/badges/commented_debates.svg | 78 ++++++++++++ decidim-debates/config/locales/en.yml | 10 ++ decidim-debates/lib/decidim/debates/engine.rb | 28 +++++ .../spec/lib/commented_debates_badge_spec.rb | 45 +++++++ 18 files changed, 434 insertions(+), 3 deletions(-) create mode 100644 decidim-core/app/assets/images/decidim/gamification/badges/followers.svg create mode 100644 decidim-core/app/cells/decidim/badge/small.erb create mode 100644 decidim-core/spec/commands/decidim/create_follow_spec.rb create mode 100644 decidim-core/spec/commands/decidim/delete_follow_spec.rb create mode 100644 decidim-core/spec/lib/followers_badge_spec.rb create mode 100644 decidim-debates/app/assets/images/decidim/gamification/badges/commented_debates.svg create mode 100644 decidim-debates/spec/lib/commented_debates_badge_spec.rb diff --git a/.rubocop.yml b/.rubocop.yml index 98de4da116a2..532cc1173ae5 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1213,6 +1213,7 @@ RSpec/DescribeClass: - spec/gemfiles_spec.rb - spec/js_bundles_spec.rb - spec/i18n_spec.rb + - "**/*/spec/**/*_badge_spec.rb" - decidim-core/spec/lib/global_engines_spec.rb - "**/tasks/**/*" diff --git a/CHANGELOG.md b/CHANGELOG.md index a12688c121f8..8f206dd660f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ **Added**: - **decidim-assemblies**: Add organizational chart to assemblies home. [\#4045](https://github.com/decidim/decidim/pull/4045) +- **decidim-core**: Adds the *followers* badge. [\#4089](https://github.com/decidim/decidim/pull/4089) +- **decidim-debates**: Adds the *commented debates* badge. [\#4089](https://github.com/decidim/decidim/pull/4089) - **decidim-meetings**: Add upcoming events content block and page. [\#3987](https://github.com/decidim/decidim/pull/3987) **Changed**: diff --git a/decidim-core/app/assets/images/decidim/gamification/badges/followers.svg b/decidim-core/app/assets/images/decidim/gamification/badges/followers.svg new file mode 100644 index 000000000000..8f9326dbecf7 --- /dev/null +++ b/decidim-core/app/assets/images/decidim/gamification/badges/followers.svg @@ -0,0 +1,115 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/decidim-core/app/cells/decidim/badge/show.erb b/decidim-core/app/cells/decidim/badge/show.erb index 77a588d66b05..2cdd027c1860 100644 --- a/decidim-core/app/cells/decidim/badge/show.erb +++ b/decidim-core/app/cells/decidim/badge/show.erb @@ -6,7 +6,7 @@
-
+
<%= image_tag badge.image, class: "badge__logo" %>
@@ -22,7 +22,7 @@
- <%= t "decidim.gamification.level", level: status.level %> + <%= level_title %>
<% badge.levels.each_with_index do |_threshold, level| %> diff --git a/decidim-core/app/cells/decidim/badge/small.erb b/decidim-core/app/cells/decidim/badge/small.erb new file mode 100644 index 000000000000..b4fc2a21e323 --- /dev/null +++ b/decidim-core/app/cells/decidim/badge/small.erb @@ -0,0 +1,5 @@ +<%= + image_tag badge.image, + class: "badge__logo--small external-icon", + title: "#{badge_name} (#{level_title}): #{description}" +%> diff --git a/decidim-core/app/cells/decidim/badge_cell.rb b/decidim-core/app/cells/decidim/badge_cell.rb index 56feb4e87e7a..e0abf851d9ef 100644 --- a/decidim-core/app/cells/decidim/badge_cell.rb +++ b/decidim-core/app/cells/decidim/badge_cell.rb @@ -8,6 +8,10 @@ class BadgeCell < Decidim::ViewModel delegate :current_user, to: :controller + def small + render "small" + end + def badge @options[:badge] end @@ -16,6 +20,10 @@ def user model end + def level_title + t "decidim.gamification.level", level: status.level + end + def description if user == current_user && status.level.zero? t "decidim.gamification.badges.#{badge.name}.unearned_own" @@ -44,10 +52,14 @@ def badge_name t "decidim.gamification.badges.#{badge.name}.name" end + def opacity + status.level < 1 ? 0.2 : 1 + end + private def status - @status ||= Decidim::Gamification.status_for(user, badge.name) + @status ||= options[:status] || Decidim::Gamification.status_for(user, badge.name) end end end diff --git a/decidim-core/app/cells/decidim/profile_sidebar/show.erb b/decidim-core/app/cells/decidim/profile_sidebar/show.erb index 568fe09a6577..0c827b9a30e8 100644 --- a/decidim-core/app/cells/decidim/profile_sidebar/show.erb +++ b/decidim-core/app/cells/decidim/profile_sidebar/show.erb @@ -42,6 +42,31 @@
+ +
<% if own_profile? %> diff --git a/decidim-core/app/commands/decidim/create_follow.rb b/decidim-core/app/commands/decidim/create_follow.rb index 845d37b8a1e0..73c29093b5bc 100644 --- a/decidim-core/app/commands/decidim/create_follow.rb +++ b/decidim-core/app/commands/decidim/create_follow.rb @@ -22,6 +22,7 @@ def call return broadcast(:invalid) if form.invalid? create_follow! + increment_score broadcast(:ok, follow) end @@ -36,5 +37,10 @@ def create_follow! user: current_user ) end + + def increment_score + return unless form.followable.is_a? Decidim::User + Decidim::Gamification.increment_score(form.followable, :followers) + end end end diff --git a/decidim-core/app/commands/decidim/delete_follow.rb b/decidim-core/app/commands/decidim/delete_follow.rb index f6ba833930d5..a9f8d62dd444 100644 --- a/decidim-core/app/commands/decidim/delete_follow.rb +++ b/decidim-core/app/commands/decidim/delete_follow.rb @@ -22,6 +22,7 @@ def call return broadcast(:invalid) if form.invalid? delete_follow! + decrement_score broadcast(:ok) end @@ -33,5 +34,11 @@ def call def delete_follow! form.follow.destroy! end + + def decrement_score + followable = form.follow.followable + return unless followable.is_a? Decidim::User + Decidim::Gamification.decrement_score(followable, :followers) + end end end diff --git a/decidim-core/config/locales/en.yml b/decidim-core/config/locales/en.yml index 2d5bc83dc662..4a9e27313faf 100644 --- a/decidim-core/config/locales/en.yml +++ b/decidim-core/config/locales/en.yml @@ -386,6 +386,14 @@ en: remove_this_file: Remove this file gamification: badges: + followers: + description_another: This user has %{score} followers. + description_own: "%{score} users are following you." + explanation: Users get this badge by getting followed by other users. + name: Followers + next_level_in: Get %{score} more users to follow you to reach the next level! + unearned_another: This user doesn't have any followers yet. + unearned_own: You've got no followers yet. invitations: description_another: This user has invited %{score} users. description_own: You have invited %{score} users. @@ -573,6 +581,10 @@ en: followers: Followers following: Follows notifications: Notifications + sidebar: + badges: + info: Badges are earned by performing specific activity in the platform. + title: Badges user: edit_profile: Edit profile reported_mailer: diff --git a/decidim-core/lib/decidim/core/engine.rb b/decidim-core/lib/decidim/core/engine.rb index a1502139fc9f..ba6061ba459d 100644 --- a/decidim-core/lib/decidim/core/engine.rb +++ b/decidim-core/lib/decidim/core/engine.rb @@ -332,6 +332,11 @@ class Engine < ::Rails::Engine badge.levels = [1, 5, 10, 30, 50] badge.reset = ->(user) { Decidim::User.where(invited_by: user.id).count } end + + Decidim::Gamification.register_badge(:followers) do |badge| + badge.levels = [1, 15, 30, 60, 100] + badge.reset = ->(user) { user.followers.count } + end end end end diff --git a/decidim-core/spec/commands/decidim/create_follow_spec.rb b/decidim-core/spec/commands/decidim/create_follow_spec.rb new file mode 100644 index 000000000000..f15911f36094 --- /dev/null +++ b/decidim-core/spec/commands/decidim/create_follow_spec.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe CreateFollow do + let!(:organization) { create(:organization) } + let!(:user1) { create(:user, organization: organization) } + let!(:user2) { create(:user, organization: organization) } + + let(:form) { double(followable: user2, invalid?: false) } + + it "creates a follow" do + expect { described_class.new(form, user1).call }.to broadcast(:ok) + expect(user2.reload.followers).to include(user1) + end + + it "increments the user's score" do + described_class.new(form, user1).call + + expect(Decidim::Gamification.status_for(user1, :followers).score).to eq(0) + expect(Decidim::Gamification.status_for(user2, :followers).score).to eq(1) + end + end +end diff --git a/decidim-core/spec/commands/decidim/delete_follow_spec.rb b/decidim-core/spec/commands/decidim/delete_follow_spec.rb new file mode 100644 index 000000000000..541b4cdf96b5 --- /dev/null +++ b/decidim-core/spec/commands/decidim/delete_follow_spec.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe DeleteFollow do + let!(:organization) { create(:organization) } + let!(:user1) { create(:user, organization: organization) } + let!(:user2) { create(:user, organization: organization) } + let!(:follow) { create(:follow, user: user1, followable: user2) } + + let(:form) { double(follow: follow, invalid?: false) } + + it "destroys a follow" do + expect { described_class.new(form, user1).call }.to broadcast(:ok) + + expect(user2.reload.followers).not_to include(user1) + end + + describe "gamification" do + before do + Decidim::Gamification.set_score(user1, :followers, 10) + Decidim::Gamification.set_score(user2, :followers, 10) + end + + it "decrements the user's score" do + described_class.new(form, user1).call + + expect(Decidim::Gamification.status_for(user1, :followers).score).to eq(10) + expect(Decidim::Gamification.status_for(user2, :followers).score).to eq(9) + end + end + end +end diff --git a/decidim-core/spec/lib/followers_badge_spec.rb b/decidim-core/spec/lib/followers_badge_spec.rb new file mode 100644 index 000000000000..6eef3ec54543 --- /dev/null +++ b/decidim-core/spec/lib/followers_badge_spec.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "followers badge" do + let(:organization) { create(:organization) } + + describe "reset" do + it "resets the score to the amount of followers of a user" do + user = create(:user, organization: organization) + users = create_list(:user, 5, organization: organization) + + users.each do |follower| + create(:follow, user: follower, followable: user) + end + + Decidim::Gamification.reset_badges(Decidim::User.where(id: user.id)) + expect(Decidim::Gamification.status_for(user, :followers).score).to eq(5) + end + end +end diff --git a/decidim-debates/app/assets/images/decidim/gamification/badges/commented_debates.svg b/decidim-debates/app/assets/images/decidim/gamification/badges/commented_debates.svg new file mode 100644 index 000000000000..ea2509501d08 --- /dev/null +++ b/decidim-debates/app/assets/images/decidim/gamification/badges/commented_debates.svg @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/decidim-debates/config/locales/en.yml b/decidim-debates/config/locales/en.yml index 2bdd0c33341d..beee131bdeda 100644 --- a/decidim-debates/config/locales/en.yml +++ b/decidim-debates/config/locales/en.yml @@ -135,3 +135,13 @@ en: email_outro: You have received this notification because you are following %{participatory_space_title}. You can stop receiving notifications following the previous link. email_subject: Debates now available in %{participatory_space_title} notification_title: You can now start new debates in %{participatory_space_title} + gamification: + badges: + commented_debates: + description_another: This user has participated in %{score} debates. + description_own: You have participated in %{score} debates. + explanation: Users get this badge by participating in debates. + name: Debates + next_level_in: Participate in %{score} more debates to reach the next level! + unearned_another: This user hasn't participated in any debate yet. + unearned_own: You have participated in any debates yet. diff --git a/decidim-debates/lib/decidim/debates/engine.rb b/decidim-debates/lib/decidim/debates/engine.rb index a31a06e1c777..b7a294b63578 100644 --- a/decidim-debates/lib/decidim/debates/engine.rb +++ b/decidim-debates/lib/decidim/debates/engine.rb @@ -30,6 +30,34 @@ class Engine < ::Rails::Engine Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Debates::Engine.root}/app/cells") Cell::ViewModel.view_paths << File.expand_path("#{Decidim::Debates::Engine.root}/app/views") # for partials end + + initializer "decidim.debates.commented_debates_badge" do + Decidim::Gamification.register_badge(:commented_debates) do |badge| + badge.levels = [1, 5, 10, 30, 50] + badge.reset = lambda do |user| + debates = Decidim::Comments::Comment.where( + decidim_author_id: user.id, + decidim_root_commentable_type: "Decidim::Debates::Debate"\ + ) + debates.pluck(:decidim_root_commentable_id).uniq.count + end + end + + Decidim::Comments::CommentCreation.subscribe do |data| + comment = Decidim::Comments::Comment.find(data[:comment_id]) + next unless comment.decidim_root_commentable_type == "Decidim::Debates::Debate" + + author = comment.author + + comments = Decidim::Comments::Comment.where( + decidim_root_commentable_id: comment.decidim_root_commentable_id, + decidim_root_commentable_type: comment.decidim_root_commentable_type, + author: author + ) + + Decidim::Gamification.increment_score(author, :commented_debates) if comments.count == 1 + end + end end end end diff --git a/decidim-debates/spec/lib/commented_debates_badge_spec.rb b/decidim-debates/spec/lib/commented_debates_badge_spec.rb new file mode 100644 index 000000000000..a3331fb036cf --- /dev/null +++ b/decidim-debates/spec/lib/commented_debates_badge_spec.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +require "spec_helper" + +describe "commented debates badge" do + let!(:debate) { create(:debate, :open_ama) } + let(:organization) { debate.component.organization } + let!(:user) { create(:user, organization: organization) } + + describe "commenting a debate" do + let!(:user2) { create(:user, organization: organization) } + let!(:other_comment) { create(:comment, author: user2, commentable: debate, root_commentable: debate) } + + context "when creating the first comment" do + it "increses a user's score " do + comment = create(:comment, author: user, commentable: debate, root_commentable: debate) + Decidim::Comments::CommentCreation.publish(comment, {}) + expect(Decidim::Gamification.status_for(user, :commented_debates).score).to eq(1) + end + end + + context "when other comments by the same author already exist" do + it "increses a user's score when a debate is commented" do + comment = create(:comment, author: user, commentable: debate, root_commentable: debate) + create(:comment, author: user, commentable: debate, root_commentable: debate) + Decidim::Comments::CommentCreation.publish(comment, {}) + + expect(Decidim::Gamification.status_for(user, :commented_debates).score).to eq(0) + end + end + end + + describe "badge reset" do + it "resets to the right score" do + debate2 = create(:debate, :open_ama, component: debate.component) + + create(:comment, author: user, commentable: debate, root_commentable: debate) + create(:comment, author: user, commentable: debate, root_commentable: debate) + create(:comment, author: user, commentable: debate2, root_commentable: debate2) + + Decidim::Gamification.reset_badges(Decidim::User.where(id: user.id)) + expect(Decidim::Gamification.status_for(user, :commented_debates).score).to eq(2) + end + end +end