8 |
9 | # User Metadata
10 |
11 |
12 | # Directory
13 |
14 |
--------------------------------------------------------------------------------
/jobs/regular/recalculate_scores.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | module Jobs
4 | class RecalculateScores < ::Jobs::Base
5 | def execute(args)
6 | user_id = args[:user_id]
7 | raise Discourse::InvalidParameters.new(:user_id) if user_id.blank?
8 |
9 | DiscourseGamification::GamificationScore.calculate_scores(
10 | since_date: args[:since] || 10.days.ago,
11 | )
12 |
13 | ::MessageBus.publish "/recalculate_scores",
14 | {
15 | success: true,
16 | remaining:
17 | DiscourseGamification::RecalculateScoresRateLimiter.remaining,
18 | user_id: [user_id],
19 | }
20 | end
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/assets/stylesheets/common/leaderboard-admin.scss:
--------------------------------------------------------------------------------
1 | .leaderboard-admin {
2 | &__title {
3 | display: inline-block;
4 | }
5 |
6 | &__cta-new {
7 | display: flex;
8 | margin-top: 1rem;
9 | }
10 |
11 | &__btn-recalculate {
12 | float: right;
13 | margin-right: 1rem;
14 | }
15 |
16 | &__btn-new {
17 | float: right;
18 | }
19 |
20 | &__btn-back {
21 | margin-bottom: 1rem;
22 | padding-left: 0;
23 | }
24 |
25 | &__listitem-action {
26 | text-align: right;
27 | display: flex;
28 | flex-direction: row;
29 | gap: 0.5em;
30 | justify-content: flex-end;
31 | }
32 | }
33 |
34 | .leaderboard-edit {
35 | &__cancel {
36 | margin-left: 1rem;
37 | }
38 | }
39 |
40 | .new-leaderboard-container {
41 | .form-kit__row {
42 | padding-top: 0;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/spec/models/user_spec.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | require "rails_helper"
4 |
5 | describe User, type: :model do
6 | fab!(:user)
7 | fab!(:leaderboard) { Fabricate(:gamification_leaderboard) }
8 |
9 | before do
10 | Fabricate(:gamification_score, user_id: user.id, score: 10, date: 8.days.ago)
11 | Fabricate(:gamification_score, user_id: user.id, score: 25, date: 5.days.ago)
12 | leaderboard.update(from_date: 5.days.ago.to_date)
13 |
14 | DiscourseGamification::LeaderboardCachedView.create_all
15 | end
16 |
17 | describe "#gamification_score" do
18 | it "returns default leaderboard 'all_time' total score" do
19 | expect(DiscourseGamification::GamificationScore.where(user_id: user.id).sum(:score)).to eq(35)
20 | expect(user.gamification_score).to eq(25)
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/db/post_migrate/20250210133038_drop_versioned_leaderboard_materialized_views.rb:
--------------------------------------------------------------------------------
1 | # frozen_string_literal: true
2 |
3 | class DropVersionedLeaderboardMaterializedViews < ActiveRecord::Migration[7.2]
4 | def up
5 | versioned_mviews_query = <<~SQL
6 | SELECT cls.relname
7 | FROM pg_class cls
8 | INNER JOIN pg_namespace ns ON ns.oid = cls.relnamespace
9 | WHERE cls.relname ~ 'gamification_leaderboard_cache_[0-9]+_[a-zA-Z_]+_[1-9]$'
10 | AND cls.relkind = 'm'
11 | AND ns.nspname = 'public'
12 | SQL
13 |
14 | mviews = DB.query_single(versioned_mviews_query)
15 |
16 | return if mviews.empty?
17 |
18 | execute <<~SQL
19 | DROP MATERIALIZED VIEW IF EXISTS #{mviews.join(", ")} CASCADE
20 | SQL
21 | end
22 |
23 | def down
24 | raise ActiveRecord::IrreversibleMigration
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/assets/javascripts/discourse/components/gamification-score.gjs:
--------------------------------------------------------------------------------
1 | import Component from "@ember/component";
2 | import { LinkTo } from "@ember/routing";
3 | import { classNames, tagName } from "@ember-decorators/component";
4 | import fullnumber from "../helpers/fullnumber";
5 |
6 | @tagName("span")
7 | @classNames("gamification-score")
8 | export default class GamificationScore extends Component {
9 |
10 | {{#if this.site.default_gamification_leaderboard_id}}
11 |