discourse/plugins/discourse-gamification/lib/discourse_gamification/directory_integration.rb
Rafael dos Santos Silva 848c19ae48
FEATURE: Per-leaderboard scorable weights and category filters (#39062)
## Summary

- Moves scorable weight configuration and category filtering from global
site settings to individual leaderboards
- Each leaderboard can override any of the 15 score weight values and
specify its own scorable categories
- Score calculation now runs per leaderboard into a new
`gamification_leaderboard_scores` table
- Admin UI gains a "Scoring configuration" section on the leaderboard
edit form

## Details

Previously all leaderboards shared the same pre-calculated scores from
global site settings. This made it impossible to have e.g. a
"posts-only" leaderboard alongside a "likes-focused" one.

Now each leaderboard can optionally override:
- **Score weights**: Set per-action point values (empty = inherit global
default, 0 = disabled)
- **Scorable categories**: Restrict which categories count toward
scoring (empty = inherit global setting)

The old `gamification_scores` table is no longer written to — a
post-deploy migration to drop it will follow separately.

## Test plan

- [ ] Create a leaderboard with default scoring — scores match current
behavior
- [ ] Create a leaderboard with custom weights (e.g. `post_created` =
10, all others empty) — only overridden weights differ
- [ ] Set a weight to 0 — that scorable is disabled for the leaderboard
- [ ] Set per-leaderboard categories — only those categories count
- [ ] Clear categories — inherits global `scorable_categories` setting
- [ ] Existing leaderboards with no overrides behave identically after
upgrade
- [ ] Directory scores and user card scores work correctly (read from
default leaderboard)
- [ ] `bin/rspec plugins/discourse-gamification/spec/` — 120 examples, 0
failures
2026-04-23 11:23:01 -03:00

67 lines
1.7 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscourseGamification
class DirectoryIntegration
def self.query
<<~SQL
WITH default_leaderboard AS (
SELECT
id,
from_date,
to_date
FROM
gamification_leaderboards
ORDER BY
id ASC
LIMIT 1
), total_score AS (
SELECT
gls.user_id,
SUM(gls.score) AS score
FROM
gamification_leaderboard_scores gls
INNER JOIN
default_leaderboard lb ON gls.leaderboard_id = lb.id
WHERE
gls.date >= :since
AND
(
(
lb.from_date IS NULL
OR
gls.date >= lb.from_date
)
AND
(
lb.to_date IS NULL
OR
gls.date <= lb.to_date
)
)
GROUP BY
1
), scored_directory AS (
SELECT
directory_items.user_id,
COALESCE(total_score.score, 0) AS score
FROM
directory_items
LEFT JOIN
total_score ON total_score.user_id = directory_items.user_id
WHERE
directory_items.period_type = :period_type
)
UPDATE
directory_items
SET
gamification_score = scored_directory.score
FROM
scored_directory
WHERE
scored_directory.user_id = directory_items.user_id AND
directory_items.period_type = :period_type AND
scored_directory.score != directory_items.gamification_score
SQL
end
end
end