FEATURE: Show when a badge has been granted for a post (#29696)
Co-authored-by: Joffrey JAFFEUX <j.jaffeux@gmail.com> Co-authored-by: Jarek Radosz <jradosz@gmail.com> Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
This commit is contained in:
parent
435fbb7408
commit
2513339955
|
@ -27,6 +27,7 @@ const FORM_FIELDS = [
|
||||||
"badge_grouping_id",
|
"badge_grouping_id",
|
||||||
"trigger",
|
"trigger",
|
||||||
"badge_type_id",
|
"badge_type_id",
|
||||||
|
"show_in_post_header",
|
||||||
];
|
];
|
||||||
|
|
||||||
export default class AdminBadgesShowController extends Controller {
|
export default class AdminBadgesShowController extends Controller {
|
||||||
|
@ -40,8 +41,6 @@ export default class AdminBadgesShowController extends Controller {
|
||||||
@tracked model;
|
@tracked model;
|
||||||
@tracked previewLoading = false;
|
@tracked previewLoading = false;
|
||||||
@tracked selectedGraphicType = null;
|
@tracked selectedGraphicType = null;
|
||||||
@tracked userBadges;
|
|
||||||
@tracked userBadgesAll;
|
|
||||||
|
|
||||||
@cached
|
@cached
|
||||||
get formData() {
|
get formData() {
|
||||||
|
@ -80,6 +79,17 @@ export default class AdminBadgesShowController extends Controller {
|
||||||
return this.model.system;
|
return this.model.system;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
postHeaderDescription(data) {
|
||||||
|
return this.disableBadgeOnPosts(data) && !data.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
disableBadgeOnPosts(data) {
|
||||||
|
const { listable, show_posts } = data;
|
||||||
|
return !listable || !show_posts;
|
||||||
|
}
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
// this is needed because the model doesnt have default values
|
// this is needed because the model doesnt have default values
|
||||||
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
|
// Using `set` here isn't ideal, but we don't know that tracking is set up on the model yet.
|
||||||
|
|
|
@ -245,7 +245,7 @@
|
||||||
</field.Menu>
|
</field.Menu>
|
||||||
</form.Field>
|
</form.Field>
|
||||||
|
|
||||||
<form.CheckboxGroup as |group|>
|
<form.CheckboxGroup @title={{i18n "admin.badges.usage_heading"}} as |group|>
|
||||||
<group.Field
|
<group.Field
|
||||||
@title={{i18n "admin.badges.allow_title"}}
|
@title={{i18n "admin.badges.allow_title"}}
|
||||||
@showTitle={{false}}
|
@showTitle={{false}}
|
||||||
|
@ -264,7 +264,12 @@
|
||||||
>
|
>
|
||||||
<field.Checkbox />
|
<field.Checkbox />
|
||||||
</group.Field>
|
</group.Field>
|
||||||
|
</form.CheckboxGroup>
|
||||||
|
|
||||||
|
<form.CheckboxGroup
|
||||||
|
@title={{i18n "admin.badges.visibility_heading"}}
|
||||||
|
as |group|
|
||||||
|
>
|
||||||
<group.Field
|
<group.Field
|
||||||
@title={{i18n "admin.badges.listable"}}
|
@title={{i18n "admin.badges.listable"}}
|
||||||
@showTitle={{false}}
|
@showTitle={{false}}
|
||||||
|
@ -284,6 +289,20 @@
|
||||||
>
|
>
|
||||||
<field.Checkbox />
|
<field.Checkbox />
|
||||||
</group.Field>
|
</group.Field>
|
||||||
|
|
||||||
|
<group.Field
|
||||||
|
@title={{i18n "admin.badges.show_in_post_header"}}
|
||||||
|
@showTitle={{false}}
|
||||||
|
@name="show_in_post_header"
|
||||||
|
@disabled={{this.disableBadgeOnPosts data}}
|
||||||
|
as |field|
|
||||||
|
>
|
||||||
|
<field.Checkbox>
|
||||||
|
{{#if (this.postHeaderDescription data)}}
|
||||||
|
{{i18n "admin.badges.show_in_post_header_disabled"}}
|
||||||
|
{{/if}}
|
||||||
|
</field.Checkbox>
|
||||||
|
</group.Field>
|
||||||
</form.CheckboxGroup>
|
</form.CheckboxGroup>
|
||||||
</form.Section>
|
</form.Section>
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,10 @@ export default class BadgeButton extends Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get showName() {
|
||||||
|
return this.args.showName ?? true;
|
||||||
|
}
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<span
|
<span
|
||||||
title={{this.title}}
|
title={{this.title}}
|
||||||
|
@ -20,7 +24,9 @@ export default class BadgeButton extends Component {
|
||||||
...attributes
|
...attributes
|
||||||
>
|
>
|
||||||
{{iconOrImage @badge}}
|
{{iconOrImage @badge}}
|
||||||
<span class="badge-display-name">{{@badge.name}}</span>
|
{{#if this.showName}}
|
||||||
|
<span class="badge-display-name">{{@badge.name}}</span>
|
||||||
|
{{/if}}
|
||||||
{{yield}}
|
{{yield}}
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -15,7 +15,7 @@ export default class UserBadge extends Component {
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<a class="user-card-badge-link" href={{this.badgeUrl}}>
|
<a class="user-card-badge-link" href={{this.badgeUrl}}>
|
||||||
<BadgeButton @badge={{@badge}}>
|
<BadgeButton @badge={{@badge}} @showName={{@showName}}>
|
||||||
{{#if this.showGrantCount}}
|
{{#if this.showGrantCount}}
|
||||||
<span class="count"> (×{{@count}})</span>
|
<span class="count"> (×{{@count}})</span>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import { isEmpty } from "@ember/utils";
|
import { isEmpty } from "@ember/utils";
|
||||||
import { userPath } from "discourse/lib/url";
|
import { userPath } from "discourse/lib/url";
|
||||||
|
import Badge from "discourse/models/badge";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { i18n } from "discourse-i18n";
|
import { i18n } from "discourse-i18n";
|
||||||
|
|
||||||
|
@ -37,6 +38,9 @@ export function transformBasicPost(post) {
|
||||||
user_id: post.user_id,
|
user_id: post.user_id,
|
||||||
usernameUrl: userPath(post.username),
|
usernameUrl: userPath(post.username),
|
||||||
username: post.username,
|
username: post.username,
|
||||||
|
badgesGranted: post.badges_granted?.map(
|
||||||
|
(badge) => Badge.createFromJson(badge)[0]
|
||||||
|
),
|
||||||
avatar_template: post.avatar_template,
|
avatar_template: post.avatar_template,
|
||||||
bookmarked: post.bookmarked,
|
bookmarked: post.bookmarked,
|
||||||
bookmarkReminderAt: post.bookmark_reminder_at,
|
bookmarkReminderAt: post.bookmark_reminder_at,
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
import { prioritizeNameInUx } from "discourse/lib/settings";
|
import { prioritizeNameInUx } from "discourse/lib/settings";
|
||||||
import { formatUsername } from "discourse/lib/utilities";
|
import { formatUsername } from "discourse/lib/utilities";
|
||||||
|
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
import { applyDecorators, createWidget } from "discourse/widgets/widget";
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
@ -139,6 +141,29 @@ export default createWidget("poster-name", {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attrs.badgesGranted) {
|
||||||
|
const badges = [];
|
||||||
|
attrs.badgesGranted.forEach((badge) => {
|
||||||
|
// Alter the badge description to show that the badge was granted for this post.
|
||||||
|
badge.description = i18n("post.badge_granted_tooltip", {
|
||||||
|
username: attrs.username,
|
||||||
|
badge_name: badge.name,
|
||||||
|
});
|
||||||
|
|
||||||
|
const badgeIcon = new RenderGlimmer(
|
||||||
|
this,
|
||||||
|
`span.user-badge-button-${badge.slug}`,
|
||||||
|
hbs`<UserBadge @badge={{@data.badge}} @user={{@data.user}} @showName={{false}} />`,
|
||||||
|
{
|
||||||
|
badge,
|
||||||
|
user: attrs.user,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
badges.push(badgeIcon);
|
||||||
|
});
|
||||||
|
nameContents.push(h("span.user-badge-buttons", badges));
|
||||||
|
}
|
||||||
|
|
||||||
const afterNameContents =
|
const afterNameContents =
|
||||||
applyDecorators(this, "after-name", attrs, this.state) || [];
|
applyDecorators(this, "after-name", attrs, this.state) || [];
|
||||||
|
|
||||||
|
|
|
@ -77,4 +77,22 @@ module("Integration | Component | badge-button", function (hooks) {
|
||||||
|
|
||||||
assert.dom(".user-badge.foo").exists();
|
assert.dom(".user-badge.foo").exists();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("setting showName to false hides the name", async function (assert) {
|
||||||
|
this.set("badge", { name: "foo" });
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`<BadgeButton @badge={{this.badge}} @showName={{false}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.dom(".badge-display-name").doesNotExist();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("showName defaults to true", async function (assert) {
|
||||||
|
this.set("badge", { name: "foo" });
|
||||||
|
|
||||||
|
await render(hbs`<BadgeButton @badge={{this.badge}} />`);
|
||||||
|
|
||||||
|
assert.dom(".badge-display-name").exists();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import { render } from "@ember/test-helpers";
|
import { render } from "@ember/test-helpers";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { module, test } from "qunit";
|
import { module, test } from "qunit";
|
||||||
|
import Badge from "discourse/models/badge";
|
||||||
|
import User from "discourse/models/user";
|
||||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
module("Integration | Component | Widget | poster-name", function (hooks) {
|
module("Integration | Component | Widget | poster-name", function (hooks) {
|
||||||
|
@ -70,4 +72,38 @@ module("Integration | Component | Widget | poster-name", function (hooks) {
|
||||||
|
|
||||||
assert.dom(".second").doesNotExist();
|
assert.dom(".second").doesNotExist();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("renders badges that are passed in", async function (assert) {
|
||||||
|
this.set("args", {
|
||||||
|
username: "eviltrout",
|
||||||
|
usernameUrl: "/u/eviltrout",
|
||||||
|
user: User.create({
|
||||||
|
username: "eviltrout",
|
||||||
|
}),
|
||||||
|
badgesGranted: [
|
||||||
|
{ id: 1, icon: "heart", slug: "badge1", name: "Badge One" },
|
||||||
|
{ id: 2, icon: "target", slug: "badge2", name: "Badge Two" },
|
||||||
|
].map((badge) => Badge.createFromJson({ badges: [badge] })[0]),
|
||||||
|
});
|
||||||
|
|
||||||
|
await render(
|
||||||
|
hbs`<MountWidget @widget="poster-name" @args={{this.args}} />`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check that the custom CSS classes are set
|
||||||
|
assert.dom("span.user-badge-button-badge1").exists();
|
||||||
|
assert.dom("span.user-badge-button-badge2").exists();
|
||||||
|
|
||||||
|
// Check that the custom titles are set
|
||||||
|
assert.dom("span.user-badge[title*='Badge One']").exists();
|
||||||
|
assert.dom("span.user-badge[title*='Badge Two']").exists();
|
||||||
|
|
||||||
|
// Check that the badges link to the correct badge page
|
||||||
|
assert
|
||||||
|
.dom("a.user-card-badge-link[href='/badges/1/badge1?username=eviltrout']")
|
||||||
|
.exists();
|
||||||
|
assert
|
||||||
|
.dom("a.user-card-badge-link[href='/badges/2/badge2?username=eviltrout']")
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -259,6 +259,19 @@
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.user-badge-buttons {
|
||||||
|
margin-left: 15px;
|
||||||
|
|
||||||
|
a {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-badge {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.post-infos {
|
.post-infos {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 0 0 auto;
|
flex: 0 0 auto;
|
||||||
|
|
|
@ -344,27 +344,28 @@ end
|
||||||
#
|
#
|
||||||
# Table name: badges
|
# Table name: badges
|
||||||
#
|
#
|
||||||
# id :integer not null, primary key
|
# id :integer not null, primary key
|
||||||
# name :string not null
|
# name :string not null
|
||||||
# description :text
|
# description :text
|
||||||
# badge_type_id :integer not null
|
# badge_type_id :integer not null
|
||||||
# grant_count :integer default(0), not null
|
# grant_count :integer default(0), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# allow_title :boolean default(FALSE), not null
|
# allow_title :boolean default(FALSE), not null
|
||||||
# multiple_grant :boolean default(FALSE), not null
|
# multiple_grant :boolean default(FALSE), not null
|
||||||
# icon :string default("fa-certificate")
|
# icon :string default("fa-certificate")
|
||||||
# listable :boolean default(TRUE)
|
# listable :boolean default(TRUE)
|
||||||
# target_posts :boolean default(FALSE)
|
# target_posts :boolean default(FALSE)
|
||||||
# query :text
|
# query :text
|
||||||
# enabled :boolean default(TRUE), not null
|
# enabled :boolean default(TRUE), not null
|
||||||
# auto_revoke :boolean default(TRUE), not null
|
# auto_revoke :boolean default(TRUE), not null
|
||||||
# badge_grouping_id :integer default(5), not null
|
# badge_grouping_id :integer default(5), not null
|
||||||
# trigger :integer
|
# trigger :integer
|
||||||
# show_posts :boolean default(FALSE), not null
|
# show_posts :boolean default(FALSE), not null
|
||||||
# system :boolean default(FALSE), not null
|
# system :boolean default(FALSE), not null
|
||||||
# long_description :text
|
# show_in_post_header :boolean default(FALSE), not null
|
||||||
# image_upload_id :integer
|
# long_description :text
|
||||||
|
# image_upload_id :integer
|
||||||
#
|
#
|
||||||
# Indexes
|
# Indexes
|
||||||
#
|
#
|
||||||
|
|
|
@ -35,6 +35,19 @@ class UserBadge < ActiveRecord::Base
|
||||||
scope :for_enabled_badges,
|
scope :for_enabled_badges,
|
||||||
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
|
-> { where("user_badges.badge_id IN (SELECT id FROM badges WHERE enabled)") }
|
||||||
|
|
||||||
|
scope :by_post_and_user,
|
||||||
|
->(posts) do
|
||||||
|
posts.reduce(UserBadge.none) do |scope, post|
|
||||||
|
scope.or(UserBadge.where(user_id: post.user_id, post_id: post.id))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
scope :for_post_header_badges,
|
||||||
|
->(posts) do
|
||||||
|
by_post_and_user(posts).where(
|
||||||
|
"user_badges.badge_id IN (SELECT id FROM badges WHERE show_posts AND enabled AND listable AND show_in_post_header)",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
|
validates :badge_id, presence: true, uniqueness: { scope: :user_id }, if: :single_grant_badge?
|
||||||
|
|
||||||
validates :user_id, presence: true
|
validates :user_id, presence: true
|
||||||
|
|
|
@ -16,7 +16,8 @@ class BadgeSerializer < ApplicationSerializer
|
||||||
:long_description,
|
:long_description,
|
||||||
:slug,
|
:slug,
|
||||||
:has_badge,
|
:has_badge,
|
||||||
:manually_grantable?
|
:manually_grantable?,
|
||||||
|
:show_in_post_header
|
||||||
|
|
||||||
has_one :badge_type
|
has_one :badge_type
|
||||||
|
|
||||||
|
|
|
@ -38,6 +38,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
:flair_bg_color,
|
:flair_bg_color,
|
||||||
:flair_color,
|
:flair_color,
|
||||||
:flair_group_id,
|
:flair_group_id,
|
||||||
|
:badges_granted,
|
||||||
:version,
|
:version,
|
||||||
:can_edit,
|
:can_edit,
|
||||||
:can_delete,
|
:can_delete,
|
||||||
|
@ -223,6 +224,18 @@ class PostSerializer < BasicPostSerializer
|
||||||
object.user&.flair_group_id
|
object.user&.flair_group_id
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def badges_granted
|
||||||
|
return [] unless SiteSetting.enable_badges && SiteSetting.show_badges_in_post_header
|
||||||
|
|
||||||
|
if @topic_view
|
||||||
|
user_badges = @topic_view.post_user_badges[object.id] || []
|
||||||
|
else
|
||||||
|
user_badges = UserBadge.for_post_header_badges([object])
|
||||||
|
end
|
||||||
|
|
||||||
|
user_badges.map { |user_badge| BasicUserBadgeSerializer.new(user_badge, scope: scope).as_json }
|
||||||
|
end
|
||||||
|
|
||||||
def link_counts
|
def link_counts
|
||||||
return @single_post_link_counts if @single_post_link_counts.present?
|
return @single_post_link_counts if @single_post_link_counts.present?
|
||||||
|
|
||||||
|
|
|
@ -30,6 +30,7 @@ class WebHookPostSerializer < PostSerializer
|
||||||
flair_color
|
flair_color
|
||||||
notice
|
notice
|
||||||
mentioned_users
|
mentioned_users
|
||||||
|
badges_granted
|
||||||
].each { |attr| define_method("include_#{attr}?") { false } }
|
].each { |attr| define_method("include_#{attr}?") { false } }
|
||||||
|
|
||||||
def topic_posts
|
def topic_posts
|
||||||
|
|
|
@ -3816,6 +3816,8 @@ en:
|
||||||
in_reply_to: "Load parent post"
|
in_reply_to: "Load parent post"
|
||||||
view_all_posts: "View all posts"
|
view_all_posts: "View all posts"
|
||||||
|
|
||||||
|
badge_granted_tooltip: "%{username} earned the '%{badge_name}' badge for this post!"
|
||||||
|
|
||||||
errors:
|
errors:
|
||||||
create: "Sorry, there was an error creating your post. Please try again."
|
create: "Sorry, there was an error creating your post. Please try again."
|
||||||
edit: "Sorry, there was an error editing your post. Please try again."
|
edit: "Sorry, there was an error editing your post. Please try again."
|
||||||
|
@ -7195,9 +7197,13 @@ en:
|
||||||
no_user_badges: "%{name} has not been granted any badges."
|
no_user_badges: "%{name} has not been granted any badges."
|
||||||
no_badges: There are no badges that can be granted.
|
no_badges: There are no badges that can be granted.
|
||||||
none_selected: "Select a badge to get started"
|
none_selected: "Select a badge to get started"
|
||||||
|
usage_heading: Usage
|
||||||
allow_title: Allow badge to be used as a title
|
allow_title: Allow badge to be used as a title
|
||||||
multiple_grant: Can be granted multiple times
|
multiple_grant: Can be granted multiple times
|
||||||
|
visibility_heading: Visibility
|
||||||
listable: Show badge on the public badges page
|
listable: Show badge on the public badges page
|
||||||
|
show_in_post_header: Show badge on the post it was granted for
|
||||||
|
show_in_post_header_disabled: Requires both 'Show badge on the public badges page' and 'Show post granting badge on badge page' to be enabled.
|
||||||
enabled: enabled
|
enabled: enabled
|
||||||
disabled: disabled
|
disabled: disabled
|
||||||
icon: Icon
|
icon: Icon
|
||||||
|
|
|
@ -1942,6 +1942,7 @@ en:
|
||||||
email_token_valid_hours: "Forgot password / activate account tokens are valid for (n) hours."
|
email_token_valid_hours: "Forgot password / activate account tokens are valid for (n) hours."
|
||||||
|
|
||||||
enable_badges: "Enable the badge system, which is a form of gamification to reinforce positive user actions. See <a href='https://meta.discourse.org/t/what-are-badges/32540' _target='blank'>What are Badges?</a> on Discourse Meta for more information."
|
enable_badges: "Enable the badge system, which is a form of gamification to reinforce positive user actions. See <a href='https://meta.discourse.org/t/what-are-badges/32540' _target='blank'>What are Badges?</a> on Discourse Meta for more information."
|
||||||
|
show_badges_in_post_header: "When a user earns a badge for a particular post, display the badge in the post header."
|
||||||
max_favorite_badges: "Maximum number of badges that user can select"
|
max_favorite_badges: "Maximum number of badges that user can select"
|
||||||
whispers_allowed_groups: "Allow private communication within topics for members of specified groups."
|
whispers_allowed_groups: "Allow private communication within topics for members of specified groups."
|
||||||
hidden_post_visible_groups: "Allow members of these groups to view hidden posts. Staff users can always view hidden posts."
|
hidden_post_visible_groups: "Allow members of these groups to view hidden posts. Staff users can always view hidden posts."
|
||||||
|
|
|
@ -342,6 +342,9 @@ basic:
|
||||||
enable_badges:
|
enable_badges:
|
||||||
client: true
|
client: true
|
||||||
default: true
|
default: true
|
||||||
|
show_badges_in_post_header:
|
||||||
|
client: true
|
||||||
|
default: true
|
||||||
enable_badge_sql:
|
enable_badge_sql:
|
||||||
client: true
|
client: true
|
||||||
default: false
|
default: false
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
class AddPostHeaderToBadge < ActiveRecord::Migration[7.1]
|
||||||
|
def change
|
||||||
|
add_column :badges, :show_in_post_header, :boolean, default: false, null: false
|
||||||
|
end
|
||||||
|
end
|
|
@ -205,6 +205,25 @@ class TopicView
|
||||||
{ users: user_badge_mapping, badges: indexed_badges }
|
{ users: user_badge_mapping, badges: indexed_badges }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def post_user_badges
|
||||||
|
return [] unless SiteSetting.enable_badges && SiteSetting.show_badges_in_post_header
|
||||||
|
|
||||||
|
@post_user_badges ||=
|
||||||
|
begin
|
||||||
|
UserBadge
|
||||||
|
.for_post_header_badges(@posts)
|
||||||
|
.reduce({}) do |hash, user_badge|
|
||||||
|
hash[user_badge.post_id] ||= []
|
||||||
|
hash[user_badge.post_id] << user_badge
|
||||||
|
hash
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return [] unless @post_user_badges
|
||||||
|
|
||||||
|
@post_user_badges
|
||||||
|
end
|
||||||
|
|
||||||
def show_read_indicator?
|
def show_read_indicator?
|
||||||
return false if !@user || !topic.private_message?
|
return false if !@user || !topic.private_message?
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,9 @@
|
||||||
},
|
},
|
||||||
"badge_type_id": {
|
"badge_type_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"show_in_post_header": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -130,7 +133,8 @@
|
||||||
"auto_revoke",
|
"auto_revoke",
|
||||||
"show_posts",
|
"show_posts",
|
||||||
"badge_type_id",
|
"badge_type_id",
|
||||||
"image_upload_id"
|
"image_upload_id",
|
||||||
|
"show_in_post_header"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -91,6 +91,9 @@
|
||||||
},
|
},
|
||||||
"badge_type_id": {
|
"badge_type_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"show_in_post_header": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -115,7 +118,8 @@
|
||||||
"auto_revoke",
|
"auto_revoke",
|
||||||
"show_posts",
|
"show_posts",
|
||||||
"badge_type_id",
|
"badge_type_id",
|
||||||
"image_upload_id"
|
"image_upload_id",
|
||||||
|
"show_in_post_header"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -106,6 +106,9 @@
|
||||||
},
|
},
|
||||||
"badge_type_id": {
|
"badge_type_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"show_in_post_header": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": [
|
"required": [
|
||||||
|
@ -130,7 +133,8 @@
|
||||||
"auto_revoke",
|
"auto_revoke",
|
||||||
"show_posts",
|
"show_posts",
|
||||||
"badge_type_id",
|
"badge_type_id",
|
||||||
"image_upload_id"
|
"image_upload_id",
|
||||||
|
"show_in_post_header"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -98,6 +98,10 @@
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"badges_granted": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {}
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
@ -109,6 +109,10 @@
|
||||||
"null"
|
"null"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"badges_granted": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {}
|
||||||
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
|
|
@ -438,6 +438,201 @@ RSpec.describe PostSerializer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "#badges_granted" do
|
||||||
|
fab!(:user)
|
||||||
|
fab!(:user2) { Fabricate(:user) }
|
||||||
|
fab!(:post) { Fabricate(:post, user: user) }
|
||||||
|
fab!(:post2) { Fabricate(:post, user: user) }
|
||||||
|
|
||||||
|
# Create twp badges that have all required flags set to true
|
||||||
|
fab!(:badge1) do
|
||||||
|
Badge.create!(
|
||||||
|
name: "SomeBadge",
|
||||||
|
badge_type_id: BadgeType::Bronze,
|
||||||
|
listable: true,
|
||||||
|
show_posts: true,
|
||||||
|
show_in_post_header: true,
|
||||||
|
multiple_grant: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:ub1) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge1.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
fab!(:badge2) do
|
||||||
|
Badge.create!(
|
||||||
|
name: "SomeOtherBadge",
|
||||||
|
badge_type_id: BadgeType::Bronze,
|
||||||
|
listable: true,
|
||||||
|
show_posts: true,
|
||||||
|
show_in_post_header: true,
|
||||||
|
multiple_grant: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:ub2) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge2.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a badge that has the show_posts flag set to false
|
||||||
|
fab!(:badge3) do
|
||||||
|
Badge.create!(
|
||||||
|
name: "YetAnotherBadge",
|
||||||
|
badge_type_id: BadgeType::Bronze,
|
||||||
|
listable: true,
|
||||||
|
show_posts: false,
|
||||||
|
show_in_post_header: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:ub3) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge3.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Re-use our first badge, but on a different post
|
||||||
|
fab!(:ub4) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge1.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post2.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Now re-use our first badge, but on a different user
|
||||||
|
fab!(:ub5) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge1.id,
|
||||||
|
user: user2,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a badge that has the listable flag set to false
|
||||||
|
fab!(:badge4) do
|
||||||
|
Badge.create!(
|
||||||
|
name: "WeirdBadge",
|
||||||
|
badge_type_id: BadgeType::Bronze,
|
||||||
|
listable: false,
|
||||||
|
show_posts: true,
|
||||||
|
show_in_post_header: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:ub6) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge4.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Create a badge that has the show_in_post_header flag set to false
|
||||||
|
fab!(:badge5) do
|
||||||
|
Badge.create!(
|
||||||
|
name: "StrangeBadge",
|
||||||
|
badge_type_id: BadgeType::Bronze,
|
||||||
|
listable: true,
|
||||||
|
show_posts: true,
|
||||||
|
show_in_post_header: false,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:ub7) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge5.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:serializer) { described_class.new(post, scope: Guardian.new(user), root: false) }
|
||||||
|
|
||||||
|
it "doesn't include badges when `enable_badges` site setting is disabled" do
|
||||||
|
SiteSetting.enable_badges = false
|
||||||
|
expect(serializer.as_json[:badges_granted]).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't include badges when `show_badges_in_post_header` site setting is disabled" do
|
||||||
|
SiteSetting.enable_badges = true
|
||||||
|
SiteSetting.show_badges_in_post_header = false
|
||||||
|
expect(serializer.as_json[:badges_granted]).to eq([])
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when `enable_badges` and `show_badges_in_post_header` site settings are enabled" do
|
||||||
|
before do
|
||||||
|
SiteSetting.enable_badges = true
|
||||||
|
SiteSetting.show_badges_in_post_header = true
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes badges that were granted for this user on this post" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].length).to eq(2)
|
||||||
|
expect(json[:badges_granted].map { |b| b[:badges][0][:id] }).to contain_exactly(
|
||||||
|
ub1.badge_id,
|
||||||
|
ub2.badge_id,
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).to contain_exactly(
|
||||||
|
ub1.id,
|
||||||
|
ub2.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a user badge that has the show_posts flag set to false" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub3.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a user badge that was not granted for this post" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub4.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a user badge that was granted for a different user" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub5.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a user badge that has the listable flag set to false" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub6.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "does not return a user badge that has the show_in_post_header flag set to false" do
|
||||||
|
json = serializer.as_json
|
||||||
|
|
||||||
|
expect(json[:badges_granted].map { |b| b[:basic_user_badge][:id] }).not_to include(ub7.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def serialized_post(u)
|
def serialized_post(u)
|
||||||
s = PostSerializer.new(post, scope: Guardian.new(u), root: false)
|
s = PostSerializer.new(post, scope: Guardian.new(u), root: false)
|
||||||
s.add_raw = true
|
s.add_raw = true
|
||||||
|
|
|
@ -33,4 +33,43 @@ describe "Granting Badges", type: :system do
|
||||||
expect(granted_badge.post_id).to eq post.id
|
expect(granted_badge.post_id).to eq post.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "when granting a badge that shows in the post header" do
|
||||||
|
fab!(:user)
|
||||||
|
fab!(:post) { Fabricate(:post, user: user) }
|
||||||
|
|
||||||
|
let(:topic_page) { PageObjects::Pages::Topic.new }
|
||||||
|
|
||||||
|
fab!(:badge) do
|
||||||
|
Fabricate(
|
||||||
|
:manually_grantable_badge,
|
||||||
|
name: "SomeBadge",
|
||||||
|
listable: true,
|
||||||
|
show_posts: true,
|
||||||
|
show_in_post_header: true,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
fab!(:user_badge) do
|
||||||
|
UserBadge.create!(
|
||||||
|
badge_id: badge.id,
|
||||||
|
user: user,
|
||||||
|
granted_by: Discourse.system_user,
|
||||||
|
granted_at: Time.now,
|
||||||
|
post_id: post.id,
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows badge in post header" do
|
||||||
|
topic_page.visit_topic(post.topic)
|
||||||
|
expect(topic_page.post_by_number(post).find(".user-badge-buttons")).to have_css(
|
||||||
|
".user-badge-button-somebadge",
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't show badge in post header when `show_badges_in_post_header` site setting is disabled" do
|
||||||
|
SiteSetting.show_badges_in_post_header = false
|
||||||
|
topic_page.visit_topic(post.topic)
|
||||||
|
expect(topic_page.post_by_number(post)).to_not have_css(".user-badge-buttons")
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue