DEV: convert grant badge modal to component API (#23378)

* DEV: convert grant-badge to use component modal API
* DEV: add system test for grant badge modal happy path
This commit is contained in:
Kelv 2023-09-11 13:56:31 +08:00 committed by GitHub
parent 29439af375
commit d28f113ce0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 206 additions and 108 deletions

View File

@ -0,0 +1,34 @@
<DModal
@bodyClass="grant-badge"
@closeModal={{@closeModal}}
@flash={{this.flash}}
@flashType={{this.flashType}}
@title={{i18n "admin.badges.grant_badge"}}
class="grant-badge-modal"
{{did-insert this.loadBadges}}
>
<:body>
<ConditionalLoadingSpinner @condition={{this.loading}}>
{{#if this.noGrantableBadges}}
<p>{{i18n "admin.badges.no_badges"}}</p>
{{else}}
<p>
<ComboBox
@value={{this.selectedBadgeId}}
@content={{this.grantableBadges}}
@onChange={{action (mut this.selectedBadgeId)}}
@options={{hash filterable=true none="badges.none"}}
/>
</p>
{{/if}}
</ConditionalLoadingSpinner>
</:body>
<:footer>
<DButton
@disabled={{this.buttonDisabled}}
@action={{this.performGrantBadge}}
@label="admin.badges.grant"
class="btn-primary"
/>
</:footer>
</DModal>

View File

@ -0,0 +1,80 @@
import { action } from "@ember/object";
import Component from "@ember/component";
import Badge from "discourse/models/badge";
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
import I18n from "I18n";
import UserBadge from "discourse/models/user-badge";
import discourseComputed from "discourse-common/utils/decorators";
import { extractError } from "discourse/lib/ajax-error";
import getURL from "discourse-common/lib/get-url";
export default class GrantBadgeModal extends Component.extend(
GrantBadgeController
) {
loading = true;
saving = false;
selectedBadgeId = null;
flash = null;
flashType = null;
allBadges = [];
userBadges = [];
@discourseComputed("model.selectedPost")
post() {
return this.get("model.selectedPost");
}
@discourseComputed("saving", "selectedBadgeGrantable")
buttonDisabled(saving, selectedBadgeGrantable) {
return saving || !selectedBadgeGrantable;
}
@action
async loadBadges() {
this.set("loading", true);
try {
const allBadges = await Badge.findAll();
const userBadges = await UserBadge.findByUsername(
this.get("post.username")
);
this.setProperties({
allBadges,
userBadges,
});
} catch (e) {
this.setProperties({
flash: extractError(e),
flashType: "error",
});
} finally {
this.set("loading", false);
}
}
@action
async performGrantBadge() {
try {
this.set("saving", true);
const username = this.get("post.username");
const newBadge = await this.grantBadge(
this.selectedBadgeId,
username,
getURL(this.get("post.url"))
);
this.set("selectedBadgeId", null);
this.setProperties({
flash: I18n.t("badges.successfully_granted", {
username,
badge: newBadge.get("badge.name"),
}),
flashType: "success",
});
} catch (e) {
this.setProperties({
flash: extractError(e),
flashType: "error",
});
} finally {
this.set("saving", false);
}
}
}

View File

@ -1,80 +0,0 @@
import Controller, { inject as controller } from "@ember/controller";
import Badge from "discourse/models/badge";
import GrantBadgeController from "discourse/mixins/grant-badge-controller";
import I18n from "I18n";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import UserBadge from "discourse/models/user-badge";
import { all } from "rsvp";
import discourseComputed from "discourse-common/utils/decorators";
import { flashAjaxError } from "discourse/lib/ajax-error";
export default Controller.extend(ModalFunctionality, GrantBadgeController, {
topicController: controller("topic"),
loading: true,
saving: false,
selectedBadgeId: null,
init() {
this._super(...arguments);
this.allBadges = [];
this.userBadges = [];
},
@discourseComputed("topicController.selectedPosts")
post() {
return this.get("topicController.selectedPosts")[0];
},
@discourseComputed("post")
badgeReason(post) {
const url = post.get("url");
const protocolAndHost =
window.location.protocol + "//" + window.location.host;
return url.startsWith("/") ? protocolAndHost + url : url;
},
@discourseComputed("saving", "selectedBadgeGrantable")
buttonDisabled(saving, selectedBadgeGrantable) {
return saving || !selectedBadgeGrantable;
},
onShow() {
this.set("loading", true);
all([
Badge.findAll(),
UserBadge.findByUsername(this.get("post.username")),
]).then(([allBadges, userBadges]) => {
this.setProperties({
allBadges,
userBadges,
loading: false,
});
});
},
actions: {
grantBadge() {
this.set("saving", true);
this.grantBadge(
this.selectedBadgeId,
this.get("post.username"),
this.badgeReason
)
.then((newBadge) => {
this.set("selectedBadgeId", null);
this.flash(
I18n.t("badges.successfully_granted", {
username: this.get("post.username"),
badge: newBadge.get("badge.name"),
}),
"success"
);
}, flashAjaxError(this))
.finally(() => this.set("saving", false));
},
},
});

View File

@ -17,6 +17,7 @@ import ChangeTimestampModal from "discourse/components/modal/change-timestamp";
import EditTopicTimerModal from "discourse/components/modal/edit-topic-timer";
import FeatureTopicModal from "discourse/components/modal/feature-topic";
import FlagModal from "discourse/components/modal/flag";
import GrantBadgeModal from "discourse/components/modal/grant-badge";
import MoveToTopicModal from "discourse/components/modal/move-to-topic";
import RawEmailModal from "discourse/components/modal/raw-email";
@ -200,9 +201,11 @@ const TopicRoute = DiscourseRoute.extend({
@action
showGrantBadgeModal() {
showModal("grant-badge", {
model: this.modelFor("topic"),
title: "admin.badges.grant_badge",
const topicController = this.controllerFor("topic");
this.modal.show(GrantBadgeModal, {
model: {
selectedPost: topicController.selectedPosts[0],
},
});
},

View File

@ -1,25 +0,0 @@
<DModalBody @class="grant-badge">
<ConditionalLoadingSpinner @condition={{this.loading}}>
{{#if this.noGrantableBadges}}
<p>{{i18n "admin.badges.no_badges"}}</p>
{{else}}
<p>
<ComboBox
@value={{this.selectedBadgeId}}
@content={{this.grantableBadges}}
@onChange={{action (mut this.selectedBadgeId)}}
@options={{hash filterable=true none="badges.none"}}
/>
</p>
{{/if}}
</ConditionalLoadingSpinner>
</DModalBody>
<div class="modal-footer">
<DButton
@disabled={{this.buttonDisabled}}
@action={{action "grantBadge"}}
@label="admin.badges.grant"
class="btn-primary"
/>
</div>

View File

@ -6,3 +6,8 @@ Fabricator(:badge) do
name { sequence(:name) { |i| "Badge #{i}" } }
badge_type
end
Fabricator(:manually_grantable_badge, from: :badge) do
system false
query nil
end

View File

@ -0,0 +1,36 @@
# frozen_string_literal: true
describe "Granting Badges", type: :system do
before { SiteSetting.enable_badges = true }
context "when in topic" do
fab!(:post) { Fabricate(:post, raw: "This is some post to bookmark") }
fab!(:admin) { Fabricate(:admin) }
fab!(:badge_to_grant) { Fabricate(:manually_grantable_badge) }
fab!(:other_badge) { Fabricate(:manually_grantable_badge) }
let(:user) { post.user }
let(:topic) { post.topic }
let(:topic_page) { PageObjects::Pages::Topic.new }
let(:badge_modal) { PageObjects::Modals::Badge.new }
before { sign_in(admin) }
def visit_topic_and_open_badge_modal(post)
topic_page.visit_topic(topic)
topic_page.expand_post_actions(post)
topic_page.expand_post_admin_actions(post)
topic_page.click_post_admin_action_button(post, :grant_badge)
end
it "grants badge with the correct badge reason which links the right post" do
visit_topic_and_open_badge_modal(post)
badge_modal.select_badge(badge_to_grant.name)
badge_modal.grant
expect(badge_modal).to have_success_flash_visible
granted_badge = UserBadge.last
expect(granted_badge.badge_id).to eq badge_to_grant.id
expect(granted_badge.post_id).to eq post.id
end
end
end

View File

@ -0,0 +1,32 @@
# frozen_string_literal: true
module PageObjects
module Modals
class Badge < PageObjects::Pages::Base
GRANTABLE_BADGES_DROPDOWN = ".select-kit"
def modal
find(".grant-badge-modal")
end
def select_badge(badge_name)
within(modal) do
grantable_badges_dropdown.expand
grantable_badges_dropdown.select_row_by_name(badge_name)
end
end
def grant
within(modal) { find(".modal-footer .btn").click }
end
def has_success_flash_visible?
within(modal) { has_css?(".alert-success") }
end
def grantable_badges_dropdown
@grantable_badges_dropdown ||=
PageObjects::Components::SelectKit.new(GRANTABLE_BADGES_DROPDOWN)
end
end
end
end

View File

@ -87,6 +87,19 @@ module PageObjects
end
end
def expand_post_admin_actions(post)
post_by_number(post).find(".show-post-admin-menu").click
end
def click_post_admin_action_button(post, button)
element_klass = ".popup-menu-button"
case button
when :grant_badge
element_klass += ".grant-badge"
end
post_by_number(post).find(element_klass).click
end
def click_topic_footer_button(button)
find_topic_footer_button(button).click
end