FEATURE: Implement edit functionality for post notices (#11140)

All post notice related custom fields were moved to a single one.
This commit is contained in:
Dan Ungureanu 2020-11-11 14:49:53 +02:00 committed by GitHub
parent 84e2915e71
commit ab314218d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 250 additions and 224 deletions

View File

@ -1,63 +0,0 @@
import discourseComputed from "discourse-common/utils/decorators";
import { isEmpty } from "@ember/utils";
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { cookAsync } from "discourse/lib/text";
export default Controller.extend(ModalFunctionality, {
post: null,
resolve: null,
reject: null,
notice: null,
saving: false,
@discourseComputed("saving", "notice")
disabled(saving, notice) {
return saving || isEmpty(notice);
},
onShow() {
this.setProperties({
notice: "",
saving: false,
});
},
onClose() {
const reject = this.reject;
if (reject) {
reject();
}
},
actions: {
setNotice() {
this.set("saving", true);
const post = this.post;
const resolve = this.resolve;
const reject = this.reject;
const notice = this.notice;
// Let `updatePostField` handle state.
this.setProperties({ resolve: null, reject: null });
post
.updatePostField("notice", notice)
.then(() => cookAsync(notice, { features: { onebox: false } }))
.then((cookedNotice) => {
post.setProperties({
notice_type: "custom",
notice_args: cookedNotice.string,
});
resolve();
this.send("closeModal");
})
.catch(() => {
reject();
this.send("closeModal");
});
},
},
});

View File

@ -0,0 +1,58 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { isEmpty } from "@ember/utils";
import discourseComputed from "discourse-common/utils/decorators";
import { cookAsync } from "discourse/lib/text";
import ModalFunctionality from "discourse/mixins/modal-functionality";
export default Controller.extend(ModalFunctionality, {
post: null,
resolve: null,
reject: null,
notice: null,
saving: false,
@discourseComputed("saving", "notice")
disabled(saving, notice) {
return saving || isEmpty(notice);
},
onShow() {
this.setProperties({ notice: "", saving: false });
},
onClose() {
if (this.reject) {
this.reject();
}
},
@action
setNotice(notice) {
const { resolve, reject } = this;
this.setProperties({ saving: true, resolve: null, reject: null });
this.model
.updatePostField("notice", notice)
.then(() => {
if (notice) {
return cookAsync(notice, { features: { onebox: false } });
}
})
.then((cooked) =>
this.model.set(
"notice",
cooked
? {
type: "custom",
raw: notice,
cooked: cooked.string,
}
: null
)
)
.then(resolve, reject)
.finally(() => this.send("closeModal"));
},
});

View File

@ -857,20 +857,15 @@ export default Controller.extend(bufferedProperty("model"), {
this.send("showGrantBadgeModal"); this.send("showGrantBadgeModal");
}, },
addNotice(post) { changeNotice(post) {
return new Promise(function (resolve, reject) { return new Promise(function (resolve, reject) {
const modal = showModal("add-post-notice"); const modal = showModal("change-post-notice", { model: post });
modal.setProperties({ post, resolve, reject }); modal.setProperties({
resolve,
reject,
notice: post.notice ? post.notice.raw : "",
});
}); });
},
removeNotice(post) {
return post.updatePostField("notice", null).then(() =>
post.setProperties({
notice_type: null,
notice_args: null,
})
);
}, },
toggleParticipant(user) { toggleParticipant(user) {

View File

@ -140,12 +140,10 @@ export default function transformPost(
postAtts.topicUrl = topic.get("url"); postAtts.topicUrl = topic.get("url");
postAtts.isSaving = post.isSaving; postAtts.isSaving = post.isSaving;
if (post.notice_type) { if (post.notice) {
postAtts.noticeType = post.notice_type; postAtts.notice = post.notice;
if (postAtts.noticeType === "custom") { if (postAtts.notice.type === "returning_user") {
postAtts.noticeMessage = post.notice_args; postAtts.notice.lastPostedAt = new Date(post.notice.last_posted_at);
} else if (postAtts.noticeType === "returning_user") {
postAtts.noticeTime = new Date(post.notice_args);
} }
} }

View File

@ -1,12 +0,0 @@
{{#d-modal-body title="post.controls.add_post_notice"}}
<form>{{textarea value=notice}}</form>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button
class="btn-primary"
action=(action "setNotice")
disabled=disabled
label=(if saving "saving" "save")}}
{{d-modal-cancel close=(route-action "closeModal")}}
</div>

View File

@ -0,0 +1,11 @@
{{#d-modal-body title=(if model.notice "post.controls.change_post_notice" "post.controls.add_post_notice")}}
<form>{{textarea value=notice}}</form>
{{/d-modal-body}}
<div class="modal-footer">
{{d-button class="btn-primary" label=(if saving "saving" "save") action=(action "setNotice" notice) disabled=disabled}}
{{#if model.notice}}
{{d-button class="btn-danger" label="post.controls.delete_post_notice" action=(action "setNotice") disabled=saving}}
{{/if}}
{{d-modal-cancel close=(route-action "closeModal")}}
</div>

View File

@ -215,8 +215,7 @@
rebakePost=(action "rebakePost") rebakePost=(action "rebakePost")
changePostOwner=(action "changePostOwner") changePostOwner=(action "changePostOwner")
grantBadge=(action "grantBadge") grantBadge=(action "grantBadge")
addNotice=(action "addNotice") changeNotice=(action "changeNotice")
removeNotice=(action "removeNotice")
lockPost=(action "lockPost") lockPost=(action "lockPost")
unlockPost=(action "unlockPost") unlockPost=(action "unlockPost")
unhidePost=(action "unhidePost") unhidePost=(action "unhidePost")

View File

@ -53,21 +53,16 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
} }
if (attrs.canEditStaffNotes) { if (attrs.canEditStaffNotes) {
if (attrs.noticeType) {
contents.push({ contents.push({
icon: "user-shield", icon: "user-shield",
label: "post.controls.remove_post_notice", label: attrs.notice
action: "removeNotice", ? "post.controls.change_post_notice"
className: "popup-menu-button remove-notice", : "post.controls.add_post_notice",
action: "changeNotice",
className: attrs.notice
? "popup-menu-button change-notice"
: "popup-menu-button add-notice",
}); });
} else {
contents.push({
icon: "user-shield",
label: "post.controls.add_post_notice",
action: "addNotice",
className: "popup-menu-button add-notice",
});
}
} }
if (attrs.canManage && attrs.hidden) { if (attrs.canManage && attrs.hidden) {

View File

@ -471,7 +471,7 @@ createWidget("post-notice", {
tagName: "div.post-notice", tagName: "div.post-notice",
buildClasses(attrs) { buildClasses(attrs) {
const classes = [attrs.noticeType.replace(/_/g, "-")]; const classes = [attrs.notice.type.replace(/_/g, "-")];
if ( if (
new Date() - new Date(attrs.created_at) > new Date() - new Date(attrs.created_at) >
@ -484,30 +484,33 @@ createWidget("post-notice", {
}, },
html(attrs) { html(attrs) {
if (attrs.notice.type === "custom") {
return [
iconNode("user-shield"),
new RawHtml({ html: `<div>${attrs.notice.cooked}</div>` }),
];
}
const user = const user =
this.siteSettings.display_name_on_posts && prioritizeNameInUx(attrs.name) this.siteSettings.display_name_on_posts && prioritizeNameInUx(attrs.name)
? attrs.name ? attrs.name
: attrs.username; : attrs.username;
let text, icon;
if (attrs.noticeType === "custom") { if (attrs.notice.type === "new_user") {
icon = "user-shield"; return [
text = new RawHtml({ html: `<div>${attrs.noticeMessage}</div>` }); iconNode("hands-helping"),
} else if (attrs.noticeType === "new_user") { h("p", I18n.t("post.notice.new_user", { user })),
icon = "hands-helping"; ];
text = h("p", I18n.t("post.notice.new_user", { user }));
} else if (attrs.noticeType === "returning_user") {
icon = "far-smile";
const distance = (new Date() - new Date(attrs.noticeTime)) / 1000;
text = h(
"p",
I18n.t("post.notice.returning_user", {
user,
time: relativeAgeMediumSpan(distance, true),
})
);
} }
return [iconNode(icon), text]; if (attrs.notice.type === "returning_user") {
const timeAgo = (new Date() - new Date(attrs.notice.lastPostedAt)) / 1000;
const time = relativeAgeMediumSpan(timeAgo, true);
return [
iconNode("far-smile"),
h("p", I18n.t("post.notice.returning_user", { user, time })),
];
}
}, },
}); });
@ -593,7 +596,7 @@ createWidget("post-article", {
); );
} }
if (attrs.noticeType) { if (!attrs.deleted_at && attrs.notice) {
rows.push(h("div.row", [this.attach("post-notice", attrs)])); rows.push(h("div.row", [this.attach("post-notice", attrs)]));
} }

View File

@ -5174,7 +5174,7 @@ export default {
edit_reason: null, edit_reason: null,
can_view_edit_history: true, can_view_edit_history: true,
wiki: false, wiki: false,
notice_type: "new-user", notice: { type: "new_user" }
}, },
], ],
stream: [25, 26, 27], stream: [25, 26, 27],

View File

@ -934,11 +934,13 @@ widgetTest("post notice - with username", {
this.siteSettings.prioritize_username_in_ux = true; this.siteSettings.prioritize_username_in_ux = true;
this.siteSettings.old_post_notice_days = 14; this.siteSettings.old_post_notice_days = 14;
this.set("args", { this.set("args", {
noticeType: "returning_user",
noticeTime: twoDaysAgo,
username: "codinghorror", username: "codinghorror",
name: "Jeff", name: "Jeff",
created_at: new Date(), created_at: new Date(),
notice: {
type: "returning_user",
lastPostedAt: twoDaysAgo,
},
}); });
}, },
test(assert) { test(assert) {
@ -959,10 +961,10 @@ widgetTest("post notice - with name", {
this.siteSettings.prioritize_username_in_ux = false; this.siteSettings.prioritize_username_in_ux = false;
this.siteSettings.old_post_notice_days = 14; this.siteSettings.old_post_notice_days = 14;
this.set("args", { this.set("args", {
noticeType: "new_user",
username: "codinghorror", username: "codinghorror",
name: "Jeff", name: "Jeff",
created_at: new Date(2019, 0, 1), created_at: new Date(2019, 0, 1),
notice: { type: "new_user" },
}); });
}, },
test(assert) { test(assert) {

View File

@ -95,3 +95,9 @@
} }
} }
} }
.change-post-notice-modal {
.modal-body {
min-width: 450px;
}
}

View File

@ -477,18 +477,25 @@ class PostsController < ApplicationController
post = find_post_from_params post = find_post_from_params
raise Discourse::NotFound unless guardian.can_edit_staff_notes?(post.topic) raise Discourse::NotFound unless guardian.can_edit_staff_notes?(post.topic)
previous_notice = post.custom_fields[Post::NOTICE_ARGS] old_notice = post.custom_fields[Post::NOTICE]
if params[:notice].present? if params[:notice].present?
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:custom] post.custom_fields[Post::NOTICE] = {
post.custom_fields[Post::NOTICE_ARGS] = PrettyText.cook(params[:notice], features: { onebox: false }) type: Post.notices[:custom],
post.save_custom_fields raw: params[:notice],
cooked: PrettyText.cook(params[:notice], features: { onebox: false })
}
else else
post.delete_post_notices post.custom_fields.delete(Post::NOTICE)
end end
details = { new_raw_value: params[:notice], old_value: previous_notice } post.save_custom_fields
StaffActionLogger.new(current_user).log_post_staff_note(post, details)
StaffActionLogger.new(current_user).log_post_staff_note(
post,
old_value: old_notice&.[]("raw"),
new_value: params[:notice]
)
render body: nil render body: nil
end end

View File

@ -67,8 +67,7 @@ class Post < ActiveRecord::Base
DOWNLOADED_IMAGES ||= "downloaded_images" DOWNLOADED_IMAGES ||= "downloaded_images"
MISSING_UPLOADS ||= "missing uploads" MISSING_UPLOADS ||= "missing uploads"
MISSING_UPLOADS_IGNORED ||= "missing uploads ignored" MISSING_UPLOADS_IGNORED ||= "missing uploads ignored"
NOTICE_TYPE ||= "notice_type" NOTICE ||= "notice"
NOTICE_ARGS ||= "notice_args"
SHORT_POST_CHARS ||= 1200 SHORT_POST_CHARS ||= 1200
@ -79,6 +78,8 @@ class Post < ActiveRecord::Base
register_custom_field_type(MISSING_UPLOADS, :json) register_custom_field_type(MISSING_UPLOADS, :json)
register_custom_field_type(MISSING_UPLOADS_IGNORED, :boolean) register_custom_field_type(MISSING_UPLOADS_IGNORED, :boolean)
register_custom_field_type(NOTICE, :json)
scope :private_posts_for_user, ->(user) { scope :private_posts_for_user, ->(user) {
where("posts.topic_id IN (#{Topic::PRIVATE_MESSAGES_SQL})", user_id: user.id) where("posts.topic_id IN (#{Topic::PRIVATE_MESSAGES_SQL})", user_id: user.id)
} }
@ -225,7 +226,7 @@ class Post < ActiveRecord::Base
def trash!(trashed_by = nil) def trash!(trashed_by = nil)
self.topic_links.each(&:destroy) self.topic_links.each(&:destroy)
self.delete_post_notices self.save_custom_fields if self.custom_fields.delete(Post::NOTICE)
super(trashed_by) super(trashed_by)
end end
@ -428,8 +429,7 @@ class Post < ActiveRecord::Base
end end
def delete_post_notices def delete_post_notices
self.custom_fields.delete(Post::NOTICE_TYPE) self.custom_fields.delete(Post::NOTICE)
self.custom_fields.delete(Post::NOTICE_ARGS)
self.save_custom_fields self.save_custom_fields
end end

View File

@ -78,8 +78,7 @@ class PostSerializer < BasicPostSerializer
:is_auto_generated, :is_auto_generated,
:action_code, :action_code,
:action_code_who, :action_code_who,
:notice_type, :notice,
:notice_args,
:last_wiki_edit, :last_wiki_edit,
:locked, :locked,
:excerpt, :excerpt,
@ -438,12 +437,14 @@ class PostSerializer < BasicPostSerializer
include_action_code? && action_code_who.present? include_action_code? && action_code_who.present?
end end
def notice_type def notice
post_custom_fields[Post::NOTICE_TYPE] post_custom_fields[Post::NOTICE]
end end
def include_notice_type? def include_notice?
case notice_type return false if notice.blank?
case notice["type"]
when Post.notices[:custom] when Post.notices[:custom]
return true return true
when Post.notices[:new_user] when Post.notices[:new_user]
@ -454,17 +455,7 @@ class PostSerializer < BasicPostSerializer
return false return false
end end
scope.user && scope.user.id && object.user && scope.user && scope.user.id != object.user_id && scope.user.has_trust_level?(min_trust_level)
scope.user.id != object.user_id &&
scope.user.has_trust_level?(min_trust_level)
end
def notice_args
post_custom_fields[Post::NOTICE_ARGS]
end
def include_notice_args?
notice_args.present? && include_notice_type?
end end
def locked def locked

View File

@ -31,8 +31,7 @@ class WebHookPostSerializer < PostSerializer
primary_group_flair_url primary_group_flair_url
primary_group_flair_bg_color primary_group_flair_bg_color
primary_group_flair_color primary_group_flair_color
notice_args notice
notice_type
}.each do |attr| }.each do |attr|
define_method("include_#{attr}?") do define_method("include_#{attr}?") do
false false

View File

@ -168,10 +168,10 @@ class StaffActionLogger
raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post) raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post)
args = params(opts).merge( args = params(opts).merge(
action: UserHistory.actions[opts[:new_raw_value].present? ? :post_staff_note_create : :post_staff_note_destroy], action: UserHistory.actions[opts[:new_value].present? ? :post_staff_note_create : :post_staff_note_destroy],
post_id: post.id post_id: post.id
) )
args[:new_value] = opts[:new_raw_value] if opts[:new_raw_value].present? args[:new_value] = opts[:new_value] if opts[:new_value].present?
args[:previous_value] = opts[:old_value] if opts[:old_value].present? args[:previous_value] = opts[:old_value] if opts[:old_value].present?
UserHistory.create!(params(opts).merge(args)) UserHistory.create!(params(opts).merge(args))

View File

@ -2823,7 +2823,8 @@ en:
delete_topic_error: "An error occurred while deleting this topic" delete_topic_error: "An error occurred while deleting this topic"
delete_topic: "delete topic" delete_topic: "delete topic"
add_post_notice: "Add Staff Notice" add_post_notice: "Add Staff Notice"
remove_post_notice: "Remove Staff Notice" change_post_notice: "Change Staff Notice"
delete_post_notice: "Delete Staff Notice"
remove_timer: "remove timer" remove_timer: "remove timer"
actions: actions:

View File

@ -0,0 +1,52 @@
# frozen_string_literal: true
class MovePostNoticesToJson < ActiveRecord::Migration[6.0]
def up
execute <<~SQL
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
SELECT
posts.id,
'notice',
CASE
WHEN pcf_type.value = 'custom' THEN json_build_object('type', pcf_type.value, 'raw', pcf_args.value, 'cooked', pcf_args.value)
WHEN pcf_type.value = 'new_user' THEN json_build_object('type', pcf_type.value)
WHEN pcf_type.value = 'returning_user' THEN json_build_object('type', pcf_type.value, 'last_posted_at', pcf_args.value)
END,
pcf_type.created_at created_at,
pcf_type.updated_at updated_at
FROM posts
JOIN post_custom_fields pcf_type ON posts.id = pcf_type.post_id AND pcf_type.name = 'notice_type'
LEFT JOIN post_custom_fields pcf_args ON posts.id = pcf_args.post_id AND pcf_args.name = 'notice_args'
SQL
execute "DELETE FROM post_custom_fields WHERE name = 'notice_type' OR name = 'notice_args'"
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice", where: "name = 'notice'"
remove_index :post_custom_fields, name: "index_post_custom_fields_on_notice_type"
remove_index :post_custom_fields, name: "index_post_custom_fields_on_notice_args"
end
def down
execute <<~SQL
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
SELECT post_id, 'notice_type', value::json->>'type', created_at, updated_at
FROM post_custom_fields
WHERE name = 'notice'
SQL
execute <<~SQL
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
SELECT post_id, 'notice_args', COALESCE(value::json->>'cooked', value::json->>'last_posted_at'), created_at, updated_at
FROM post_custom_fields
WHERE name = 'notice'
SQL
execute "DELETE FROM post_custom_fields WHERE name = 'notice'"
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice_type", where: "name = 'notice_type'"
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice_args", where: "name = 'notice_args'"
remove_index :index_post_custom_fields_on_notice
end
end

View File

@ -616,10 +616,12 @@ class PostCreator
.first .first
if !last_post_time if !last_post_time
@post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:new_user] @post.custom_fields[Post::NOTICE] = { type: Post.notices[:new_user] }
elsif SiteSetting.returning_users_days > 0 && last_post_time < SiteSetting.returning_users_days.days.ago elsif SiteSetting.returning_users_days > 0 && last_post_time < SiteSetting.returning_users_days.days.ago
@post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user] @post.custom_fields[Post::NOTICE] = {
@post.custom_fields[Post::NOTICE_ARGS] = last_post_time.iso8601 type: Post.notices[:returning_user],
last_posted_at: last_post_time.iso8601
}
end end
end end

View File

@ -35,7 +35,7 @@ class TopicView
end end
def self.default_post_custom_fields def self.default_post_custom_fields
@default_post_custom_fields ||= [Post::NOTICE_TYPE, Post::NOTICE_ARGS, "action_code_who"] @default_post_custom_fields ||= [Post::NOTICE, "action_code_who"]
end end
def self.post_custom_fields_allowlisters def self.post_custom_fields_allowlisters

View File

@ -1627,10 +1627,10 @@ describe PostCreator do
it "generates post notices for new users" do it "generates post notices for new users" do
post = PostCreator.create!(user, title: "one of my first topics", raw: "one of my first posts") post = PostCreator.create!(user, title: "one of my first topics", raw: "one of my first posts")
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:new_user]) expect(post.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:new_user])
post = PostCreator.create!(user, title: "another one of my first topics", raw: "another one of my first posts") post = PostCreator.create!(user, title: "another one of my first topics", raw: "another one of my first posts")
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil) expect(post.custom_fields[Post::NOTICE]).to eq(nil)
end end
it "generates post notices for returning users" do it "generates post notices for returning users" do
@ -1638,12 +1638,10 @@ describe PostCreator do
old_post = Fabricate(:post, user: user, created_at: 31.days.ago) old_post = Fabricate(:post, user: user, created_at: 31.days.ago)
post = PostCreator.create!(user, title: "this is a returning topic", raw: "this is a post") post = PostCreator.create!(user, title: "this is a returning topic", raw: "this is a post")
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:returning_user]) expect(post.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:returning_user], "last_posted_at" => old_post.created_at.iso8601)
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(old_post.created_at.iso8601)
post = PostCreator.create!(user, title: "this is another topic", raw: "this is my another post") post = PostCreator.create!(user, title: "this is another topic", raw: "this is my another post")
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil) expect(post.custom_fields[Post::NOTICE]).to eq(nil)
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
end end
it "does not generate for non-human, staged or anonymous users" do it "does not generate for non-human, staged or anonymous users" do
@ -1652,8 +1650,7 @@ describe PostCreator do
[anonymous, Discourse.system_user, staged].each do |user| [anonymous, Discourse.system_user, staged].each do |user|
expect(user.posts.size).to eq(0) expect(user.posts.size).to eq(0)
post = PostCreator.create!(user, title: "#{user.username}'s first topic", raw: "#{user.name}'s first post") post = PostCreator.create!(user, title: "#{user.username}'s first topic", raw: "#{user.name}'s first post")
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil) expect(post.custom_fields[Post::NOTICE]).to eq(nil)
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
end end
end end
end end

View File

@ -136,24 +136,18 @@ describe Post do
end end
end end
end end
end
context 'a post with notices' do context 'a post with notices' do
let(:post) { let(:post) do
post = Fabricate(:post, post_args) post = Fabricate(:post, post_args)
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user] post.upsert_custom_fields(Post::NOTICE => { type: Post.notices[:returning_user], last_posted_at: 1.day.ago })
post.custom_fields[Post::NOTICE_ARGS] = 1.day.ago
post.save_custom_fields
post post
}
describe 'recovery' do
it 'deletes notices' do
expect { post.trash! }
.to change { post.custom_fields.length }.from(2).to(0)
end
end
end end
it 'will have its notice cleared when post is trashed' do
expect { post.trash! }.to change { post.custom_fields }.to({})
end
end end
describe "with_secure_media?" do describe "with_secure_media?" do

View File

@ -463,7 +463,7 @@ describe 'posts' do
edit_reason: { type: :string, nullable: true }, edit_reason: { type: :string, nullable: true },
can_view_edit_history: { type: :boolean }, can_view_edit_history: { type: :boolean },
wiki: { type: :boolean }, wiki: { type: :boolean },
notice_type: { type: :string }, notice: { type: :object },
reviewable_id: { type: :string, nullable: true }, reviewable_id: { type: :string, nullable: true },
reviewable_score_count: { type: :integer }, reviewable_score_count: { type: :integer },
reviewable_score_pending_count: { type: :integer }, reviewable_score_pending_count: { type: :integer },

View File

@ -1914,21 +1914,17 @@ describe PostsController do
it 'can create and remove notices as a moderator' do it 'can create and remove notices as a moderator' do
sign_in(moderator) sign_in(moderator)
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" } raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
expect(response.status).to eq(200) expect(response.status).to eq(200)
public_post.reload expect(public_post.reload.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:custom], "raw" => raw_notice, "cooked" => PrettyText.cook(raw_notice, features: { onebox: false }))
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom])
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('<p>Hello <em>world</em>!</p>')
expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox')
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_create]).count).to eq(1) expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_create]).count).to eq(1)
put "/posts/#{public_post.id}/notice.json", params: { notice: nil } put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
expect(response.status).to eq(200) expect(response.status).to eq(200)
public_post.reload expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_destroy]).count).to eq(1) expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_destroy]).count).to eq(1)
end end
@ -1945,20 +1941,16 @@ describe PostsController do
end end
it 'can create and remove notices as a group moderator' do it 'can create and remove notices as a group moderator' do
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" } raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
expect(response.status).to eq(200) expect(response.status).to eq(200)
public_post.reload expect(public_post.reload.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:custom], "raw" => raw_notice, "cooked" => PrettyText.cook(raw_notice, features: { onebox: false }))
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom])
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('<p>Hello <em>world</em>!</p>')
expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox')
put "/posts/#{public_post.id}/notice.json", params: { notice: nil } put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
expect(response.status).to eq(200) expect(response.status).to eq(200)
public_post.reload expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
end end
it 'prevents a group moderator from altering notes outside of their category' do it 'prevents a group moderator from altering notes outside of their category' do

View File

@ -201,8 +201,7 @@ describe PostSerializer do
let(:post) { let(:post) {
post = Fabricate(:post, user: user) post = Fabricate(:post, user: user)
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user] post.custom_fields[Post::NOTICE] = { type: Post.notices[:returning_user], last_posted_at: 1.day.ago }
post.custom_fields[Post::NOTICE_ARGS] = 1.day.ago
post.save_custom_fields post.save_custom_fields
post post
} }
@ -212,16 +211,16 @@ describe PostSerializer do
end end
it "is visible for TL2+ users (except poster)" do it "is visible for TL2+ users (except poster)" do
expect(json_for_user(nil)[:notice_type]).to eq(nil) expect(json_for_user(nil)[:notice]).to eq(nil)
expect(json_for_user(user)[:notice_type]).to eq(nil) expect(json_for_user(user)[:notice]).to eq(nil)
SiteSetting.returning_user_notice_tl = 2 SiteSetting.returning_user_notice_tl = 2
expect(json_for_user(user_tl1)[:notice_type]).to eq(nil) expect(json_for_user(user_tl1)[:notice]).to eq(nil)
expect(json_for_user(user_tl2)[:notice_type]).to eq(Post.notices[:returning_user]) expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
SiteSetting.returning_user_notice_tl = 1 SiteSetting.returning_user_notice_tl = 1
expect(json_for_user(user_tl1)[:notice_type]).to eq(Post.notices[:returning_user]) expect(json_for_user(user_tl1)[:notice][:type]).to eq(Post.notices[:returning_user])
expect(json_for_user(user_tl2)[:notice_type]).to eq(Post.notices[:returning_user]) expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
end end
end end

View File

@ -581,13 +581,13 @@ describe StaffActionLogger do
end end
it "creates a new UserHistory record" do it "creates a new UserHistory record" do
expect { logger.log_post_staff_note(post, { new_raw_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1) expect { logger.log_post_staff_note(post, { new_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create]) expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
expect(user_history.new_value).to eq('my note') expect(user_history.new_value).to eq('my note')
expect(user_history.previous_value).to eq(nil) expect(user_history.previous_value).to eq(nil)
expect { logger.log_post_staff_note(post, { new_raw_value: '', old_value: 'my note' }) }.to change { UserHistory.count }.by(1) expect { logger.log_post_staff_note(post, { new_value: '', old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy]) expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
expect(user_history.new_value).to eq(nil) expect(user_history.new_value).to eq(nil)
@ -603,13 +603,13 @@ describe StaffActionLogger do
end end
it "creates a new UserHistory record" do it "creates a new UserHistory record" do
expect { logger.log_post_staff_note(post, { new_raw_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1) expect { logger.log_post_staff_note(post, { new_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create]) expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
expect(user_history.new_value).to eq('my note') expect(user_history.new_value).to eq('my note')
expect(user_history.previous_value).to eq(nil) expect(user_history.previous_value).to eq(nil)
expect { logger.log_post_staff_note(post, { new_raw_value: nil, old_value: 'my note' }) }.to change { UserHistory.count }.by(1) expect { logger.log_post_staff_note(post, { new_value: nil, old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
user_history = UserHistory.last user_history = UserHistory.last
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy]) expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
expect(user_history.new_value).to eq(nil) expect(user_history.new_value).to eq(nil)