mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 11:44:49 +08:00
FEATURE: Display pending posts on user’s page
Currently when a user creates posts that are moderated (for whatever reason), a popup is displayed saying the post needs approval and the total number of the user’s pending posts. But then this piece of information is kind of lost and there is nowhere for the user to know what are their pending posts or how many there are. This patch solves this issue by adding a new “Pending” section to the user’s activity page when there are some pending posts to display. When there are none, then the “Pending” section isn’t displayed at all.
This commit is contained in:
parent
6e603799eb
commit
a5fbb90df4
|
@ -0,0 +1,8 @@
|
|||
import Category from "discourse/models/category";
|
||||
import { computed, get } from "@ember/object";
|
||||
|
||||
export default function categoryFromId(property) {
|
||||
return computed(property, function () {
|
||||
return Category.findById(get(this, property));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import RestAdapter from "discourse/adapters/rest";
|
||||
|
||||
export default RestAdapter.extend({
|
||||
jsonMode: true,
|
||||
|
||||
pathFor(_store, _type, params) {
|
||||
return `/posts/${params.username}/pending.json`;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
import Component from "@ember/component";
|
||||
import { afterRender } from "discourse-common/utils/decorators";
|
||||
import { loadOneboxes } from "discourse/lib/load-oneboxes";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { resolveAllShortUrls } from "pretty-text/upload-short-url";
|
||||
|
||||
export default Component.extend({
|
||||
didRender() {
|
||||
this._loadOneboxes();
|
||||
this._resolveUrls();
|
||||
},
|
||||
|
||||
@afterRender
|
||||
_loadOneboxes() {
|
||||
loadOneboxes(
|
||||
this.element,
|
||||
ajax,
|
||||
this.post.topic_id,
|
||||
this.post.category_id,
|
||||
this.siteSettings.max_oneboxes_per_post,
|
||||
true
|
||||
);
|
||||
},
|
||||
|
||||
@afterRender
|
||||
_resolveUrls() {
|
||||
resolveAllShortUrls(ajax, this.siteSettings, this.element, this.opts);
|
||||
},
|
||||
});
|
|
@ -35,6 +35,13 @@ export default Controller.extend({
|
|||
: I18n.t("drafts.label");
|
||||
},
|
||||
|
||||
@discourseComputed("model.pending_posts_count")
|
||||
pendingLabel(count) {
|
||||
return count > 0
|
||||
? I18n.t("pending_posts.label_with_count", { count })
|
||||
: I18n.t("pending_posts.label");
|
||||
},
|
||||
|
||||
actions: {
|
||||
exportUserArchive() {
|
||||
bootbox.confirm(
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Category from "discourse/models/category";
|
||||
import categoryFromId from "discourse-common/utils/category-macro";
|
||||
import I18n from "I18n";
|
||||
import { Promise } from "rsvp";
|
||||
import RestModel from "discourse/models/rest";
|
||||
|
@ -119,10 +119,7 @@ const Bookmark = RestModel.extend({
|
|||
return newTags;
|
||||
},
|
||||
|
||||
@discourseComputed("category_id")
|
||||
category(categoryId) {
|
||||
return Category.findById(categoryId);
|
||||
},
|
||||
category: categoryFromId("category_id"),
|
||||
|
||||
@discourseComputed("reminder_at", "currentUser")
|
||||
formattedReminder(bookmarkReminderAt, currentUser) {
|
||||
|
|
28
app/assets/javascripts/discourse/app/models/pending-post.js
Normal file
28
app/assets/javascripts/discourse/app/models/pending-post.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import RestModel from "discourse/models/rest";
|
||||
import categoryFromId from "discourse-common/utils/category-macro";
|
||||
import { userPath } from "discourse/lib/url";
|
||||
import { reads } from "@ember/object/computed";
|
||||
import { cookAsync } from "discourse/lib/text";
|
||||
|
||||
const PendingPost = RestModel.extend({
|
||||
expandedExcerpt: null,
|
||||
postUrl: reads("topic_url"),
|
||||
truncated: false,
|
||||
|
||||
init() {
|
||||
this._super(...arguments);
|
||||
cookAsync(this.raw_text).then((cooked) => {
|
||||
this.set("expandedExcerpt", cooked);
|
||||
});
|
||||
},
|
||||
|
||||
@discourseComputed("username")
|
||||
userUrl(username) {
|
||||
return userPath(username.toLowerCase());
|
||||
},
|
||||
|
||||
category: categoryFromId("category_id"),
|
||||
});
|
||||
|
||||
export default PendingPost;
|
|
@ -1,4 +1,4 @@
|
|||
import Category from "discourse/models/category";
|
||||
import categoryFromId from "discourse-common/utils/category-macro";
|
||||
import I18n from "I18n";
|
||||
import { Promise } from "rsvp";
|
||||
import RestModel from "discourse/models/rest";
|
||||
|
@ -24,10 +24,7 @@ const Reviewable = RestModel.extend({
|
|||
});
|
||||
},
|
||||
|
||||
@discourseComputed("category_id")
|
||||
category(categoryId) {
|
||||
return Category.findById(categoryId);
|
||||
},
|
||||
category: categoryFromId("category_id"),
|
||||
|
||||
update(updates) {
|
||||
// If no changes, do nothing
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { alias, and, equal, notEmpty, or } from "@ember/object/computed";
|
||||
import { fmt, propertyEqual } from "discourse/lib/computed";
|
||||
import ActionSummary from "discourse/models/action-summary";
|
||||
import Category from "discourse/models/category";
|
||||
import categoryFromId from "discourse-common/utils/category-macro";
|
||||
import Bookmark from "discourse/models/bookmark";
|
||||
import EmberObject from "@ember/object";
|
||||
import I18n from "I18n";
|
||||
|
@ -209,10 +209,7 @@ const Topic = RestModel.extend({
|
|||
return { type: "topic", id };
|
||||
},
|
||||
|
||||
@discourseComputed("category_id")
|
||||
category(categoryId) {
|
||||
return Category.findById(categoryId);
|
||||
},
|
||||
category: categoryFromId("category_id"),
|
||||
|
||||
@discourseComputed("url")
|
||||
shareUrl(url) {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { and, equal, or } from "@ember/object/computed";
|
||||
import discourseComputed, { on } from "discourse-common/utils/decorators";
|
||||
import Category from "discourse/models/category";
|
||||
import discourseComputed from "discourse-common/utils/decorators";
|
||||
import categoryFromId from "discourse-common/utils/category-macro";
|
||||
import RestModel from "discourse/models/rest";
|
||||
import User from "discourse/models/user";
|
||||
import UserActionGroup from "discourse/models/user-action-group";
|
||||
|
@ -19,7 +19,6 @@ const UserActionTypes = {
|
|||
edits: 11,
|
||||
messages_sent: 12,
|
||||
messages_received: 13,
|
||||
pending: 14,
|
||||
};
|
||||
const InvertedActionTypes = {};
|
||||
|
||||
|
@ -28,13 +27,7 @@ Object.keys(UserActionTypes).forEach(
|
|||
);
|
||||
|
||||
const UserAction = RestModel.extend({
|
||||
@on("init")
|
||||
_attachCategory() {
|
||||
const categoryId = this.category_id;
|
||||
if (categoryId) {
|
||||
this.set("category", Category.findById(categoryId));
|
||||
}
|
||||
},
|
||||
category: categoryFromId("category_id"),
|
||||
|
||||
@discourseComputed("action_type")
|
||||
descriptionKey(action) {
|
||||
|
|
|
@ -1,6 +1,36 @@
|
|||
import UserAction from "discourse/models/user-action";
|
||||
import UserActivityStreamRoute from "discourse/routes/user-activity-stream";
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default UserActivityStreamRoute.extend({
|
||||
userActionType: UserAction.TYPES.pending,
|
||||
export default DiscourseRoute.extend({
|
||||
beforeModel() {
|
||||
this.username = this.modelFor("user").username_lower;
|
||||
},
|
||||
|
||||
model() {
|
||||
return this.store.findAll("pending-post", {
|
||||
username: this.username,
|
||||
});
|
||||
},
|
||||
|
||||
activate() {
|
||||
this.appEvents.on(
|
||||
`count-updated:${this.username}:pending_posts_count`,
|
||||
this,
|
||||
"_handleCountChange"
|
||||
);
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
this.appEvents.off(
|
||||
`count-updated:${this.username}:pending_posts_count`,
|
||||
this,
|
||||
"_handleCountChange"
|
||||
);
|
||||
},
|
||||
|
||||
_handleCountChange(count) {
|
||||
this.refresh();
|
||||
if (count <= 0) {
|
||||
this.transitionTo("userActivity");
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -69,6 +69,15 @@ export default DiscourseRoute.extend({
|
|||
this.messageBus.subscribe(`/u/${user.username_lower}`, (data) =>
|
||||
user.loadUserAction(data)
|
||||
);
|
||||
this.messageBus.subscribe(`/u/${user.username_lower}/counters`, (data) => {
|
||||
user.setProperties(data);
|
||||
Object.entries(data).forEach(([key, value]) =>
|
||||
this.appEvents.trigger(
|
||||
`count-updated:${user.username_lower}:${key}`,
|
||||
value
|
||||
)
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
deactivate() {
|
||||
|
@ -76,6 +85,7 @@ export default DiscourseRoute.extend({
|
|||
|
||||
const user = this.modelFor("user");
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}`);
|
||||
this.messageBus.unsubscribe(`/u/${user.username_lower}/counters`);
|
||||
|
||||
// Remove the search context
|
||||
this.searchService.set("searchContext", null);
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
<div class="empty-state">
|
||||
<span data-test-title class="empty-state-title">{{@title}}</span>
|
||||
<div class="empty-state-body">
|
||||
<p data-test-body>{{@body}}</p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1 @@
|
|||
<UserStreamItem @item={{@post}} />
|
|
@ -0,0 +1,5 @@
|
|||
<ul class="user-stream">
|
||||
{{#each @model as |pending_post|}}
|
||||
<PendingPost @post={{pending_post}} />
|
||||
{{/each}}
|
||||
</ul>
|
|
@ -1,10 +1,8 @@
|
|||
{{#if noContent}}
|
||||
<div class="empty-state">
|
||||
<span class="empty-state-title">{{model.emptyState.title}}</span>
|
||||
<div class="empty-state-body">
|
||||
<p>{{model.emptyState.body}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
@title={{model.emptyState.title}}
|
||||
@body={{model.emptyState.body}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#load-more class="paginated-topics-list" selector=".paginated-topics-list .topic-list .topic-list-item" action=(action "loadMore")}}
|
||||
{{topic-dismiss-buttons
|
||||
|
|
|
@ -17,6 +17,12 @@
|
|||
{{/d-navigation-item}}
|
||||
{{/if}}
|
||||
|
||||
{{#if (gt model.pending_posts_count 0)}}
|
||||
{{#d-navigation-item route="userActivity.pending"}}
|
||||
{{pendingLabel}}
|
||||
{{/d-navigation-item}}
|
||||
{{/if}}
|
||||
|
||||
{{#d-navigation-item route="userActivity.likesGiven"}}{{i18n "user_action_groups.1"}}{{/d-navigation-item}}
|
||||
|
||||
{{#if user.showBookmarks}}
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
{{#if permissionDenied}}
|
||||
<div class="alert alert-info">{{i18n "bookmarks.list_permission_denied"}}</div>
|
||||
{{else if userDoesNotHaveBookmarks}}
|
||||
<div class="empty-state">
|
||||
<span class="empty-state-title">{{i18n "user.no_bookmarks_title"}}</span>
|
||||
<div class="empty-state-body">
|
||||
<p>{{emptyStateBody}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
@title={{i18n "user.no_bookmarks_title"}}
|
||||
@body={{emptyStateBody}}
|
||||
/>
|
||||
{{else}}
|
||||
<div class="inline-form full-width bookmark-search-form">
|
||||
{{input type="text"
|
||||
|
|
|
@ -7,12 +7,10 @@
|
|||
{{/if}}
|
||||
</div>
|
||||
{{else if doesNotHaveNotifications}}
|
||||
<div class="empty-state">
|
||||
<span class="empty-state-title">{{i18n "user.no_notifications_page_title"}}</span>
|
||||
<div class="empty-state-body">
|
||||
<p>{{emptyStateBody}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
@title={{i18n "user.no_notifications_page_title"}}
|
||||
@body={{emptyStateBody}}
|
||||
/>
|
||||
{{else}}
|
||||
<div class="user-notifications-filter">
|
||||
{{notifications-filter value=filter onChange=(action (mut filter))}}
|
||||
|
|
|
@ -2,12 +2,10 @@
|
|||
{{#if model.isAnotherUsersPage}}
|
||||
<div class="alert alert-info">{{model.emptyStateOthers}}</div>
|
||||
{{else}}
|
||||
<div class="empty-state">
|
||||
<span class="empty-state-title">{{model.emptyState.title}}</span>
|
||||
<div class="empty-state-body">
|
||||
<p>{{model.emptyState.body}}</p>
|
||||
</div>
|
||||
</div>
|
||||
<EmptyState
|
||||
@title={{model.emptyState.title}}
|
||||
@body={{model.emptyState.body}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{user-stream stream=model.stream}}
|
||||
|
|
|
@ -80,6 +80,7 @@
|
|||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"ember-exam": "6.1.0"
|
||||
"ember-exam": "6.1.0",
|
||||
"ember-test-selectors": "^6.0.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { acceptance } from "discourse/tests/helpers/qunit-helpers";
|
||||
import { test } from "qunit";
|
||||
import { click, visit } from "@ember/test-helpers";
|
||||
import { setupApplicationTest as EMBER_CLI_ENV } from "ember-qunit";
|
||||
|
||||
acceptance("Pending posts - no existing pending posts", function (needs) {
|
||||
if (!EMBER_CLI_ENV) {
|
||||
return; // dom helpers not available in legacy env
|
||||
}
|
||||
|
||||
needs.user();
|
||||
|
||||
test("No link to pending posts", async function (assert) {
|
||||
await visit("/u/eviltrout");
|
||||
assert.dom(".action-list").doesNotIncludeText("Pending");
|
||||
});
|
||||
});
|
||||
|
||||
acceptance("Pending posts - existing pending posts", function (needs) {
|
||||
if (!EMBER_CLI_ENV) {
|
||||
return; // dom helpers not available in legacy env
|
||||
}
|
||||
|
||||
needs.user({ pending_posts_count: 2 });
|
||||
|
||||
test("Navigate to pending posts", async function (assert) {
|
||||
await visit("/u/eviltrout");
|
||||
await click("[href='/u/eviltrout/activity/pending']");
|
||||
assert.dom(".user-stream-item").exists({ count: 2 });
|
||||
});
|
||||
});
|
32
app/assets/javascripts/discourse/tests/fixtures/pending-posts.js
vendored
Normal file
32
app/assets/javascripts/discourse/tests/fixtures/pending-posts.js
vendored
Normal file
|
@ -0,0 +1,32 @@
|
|||
export default {
|
||||
"/posts/eviltrout/pending.json": {
|
||||
pending_posts: [
|
||||
{
|
||||
id: 2,
|
||||
avatar_template: "/user_avatar/localhost/eviltrout/{size}/5275.png",
|
||||
category_id: 2,
|
||||
created_at: "2021-10-19T10:18:13.238Z",
|
||||
created_by_id: 19,
|
||||
name: "Robin Ward",
|
||||
raw_text: "**bold text**",
|
||||
title: "Lorem ipsum dolor sit amet",
|
||||
topic_id: 130,
|
||||
topic_url: "/t/lorem-ipsum-dolor-sit-amet/130",
|
||||
username: "eviltrout"
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
avatar_template: "/user_avatar/localhost/eviltrout/{size}/5275.png",
|
||||
category_id: 2,
|
||||
created_at: "2021-10-19T09:38:35.110Z",
|
||||
created_by_id: 19,
|
||||
name: "Robin Ward",
|
||||
raw_text: "This will be moderated in theory :thinking:",
|
||||
title: "Lorem ipsum dolor sit amet",
|
||||
topic_id: 130,
|
||||
topic_url: "/t/lorem-ipsum-dolor-sit-amet/130",
|
||||
username: "eviltrout"
|
||||
},
|
||||
]
|
||||
}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "ember-qunit";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
|
||||
const LEGACY_ENV = !setupRenderingTest;
|
||||
|
||||
module("Integration | Component | empty-state", function (hooks) {
|
||||
if (LEGACY_ENV) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("it renders", async function (assert) {
|
||||
await render(hbs`
|
||||
<EmptyState @title="title" @body="body" />
|
||||
`);
|
||||
|
||||
assert.dom("[data-test-title]").hasText("title");
|
||||
assert.dom("[data-test-body]").hasText("body");
|
||||
});
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "ember-qunit";
|
||||
import { render } from "@ember/test-helpers";
|
||||
import hbs from "htmlbars-inline-precompile";
|
||||
import PendingPost from "discourse/models/pending-post";
|
||||
import createStore from "discourse/tests/helpers/create-store";
|
||||
|
||||
const LEGACY_ENV = !setupRenderingTest;
|
||||
|
||||
module("Integration | Component | pending-post", function (hooks) {
|
||||
if (LEGACY_ENV) {
|
||||
return;
|
||||
}
|
||||
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("it renders", async function (assert) {
|
||||
const store = createStore();
|
||||
store.createRecord("category", { id: 2 });
|
||||
const post = PendingPost.create({
|
||||
id: 1,
|
||||
topic_url: "topic-url",
|
||||
username: "USERNAME",
|
||||
category_id: 2,
|
||||
raw_text: "**bold text**",
|
||||
});
|
||||
this.set("post", post);
|
||||
|
||||
await render(hbs`<PendingPost @post={{this.post}}/>`);
|
||||
|
||||
assert.equal(
|
||||
this.element.querySelector("p.excerpt").textContent.trim(),
|
||||
"bold text",
|
||||
"renders the cooked text"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -2,6 +2,8 @@ import config from "../config/environment";
|
|||
import { setEnvironment } from "discourse-common/config/environment";
|
||||
import { start } from "ember-qunit";
|
||||
import loadEmberExam from "ember-exam/test-support/load";
|
||||
import * as QUnit from "qunit";
|
||||
import { setup } from "qunit-dom";
|
||||
|
||||
setEnvironment("testing");
|
||||
|
||||
|
@ -20,6 +22,7 @@ document.addEventListener("discourse-booted", () => {
|
|||
`
|
||||
);
|
||||
|
||||
setup(QUnit.assert);
|
||||
setupTests(config.APP);
|
||||
let loader = loadEmberExam();
|
||||
loader.loadModules();
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
import { module, test } from "qunit";
|
||||
import PendingPost from "discourse/models/pending-post";
|
||||
import createStore from "discourse/tests/helpers/create-store";
|
||||
import { run } from "@ember/runloop";
|
||||
|
||||
module("Unit | Model | pending-post", function () {
|
||||
test("Properties", function (assert) {
|
||||
const store = createStore();
|
||||
const category = store.createRecord("category", { id: 2 });
|
||||
const post = PendingPost.create({
|
||||
id: 1,
|
||||
topic_url: "topic-url",
|
||||
username: "USERNAME",
|
||||
category_id: 2,
|
||||
});
|
||||
assert.equal(post.postUrl, "topic-url", "topic_url is aliased to postUrl");
|
||||
assert.equal(post.truncated, false, "truncated is always false");
|
||||
assert.equal(
|
||||
post.userUrl,
|
||||
"/u/username",
|
||||
"it returns user URL from the username"
|
||||
);
|
||||
assert.strictEqual(
|
||||
post.category,
|
||||
category,
|
||||
"it returns the proper category object based on category_id"
|
||||
);
|
||||
});
|
||||
test("it cooks raw_text", function (assert) {
|
||||
const post = run(() => PendingPost.create({ raw_text: "**bold text**" }));
|
||||
assert.equal(
|
||||
post.expandedExcerpt.string,
|
||||
"<p><strong>bold text</strong></p>"
|
||||
);
|
||||
});
|
||||
});
|
|
@ -4862,7 +4862,7 @@ ember-cli-babel@^7.0.0, ember-cli-babel@^7.11.0, ember-cli-babel@^7.11.1, ember-
|
|||
rimraf "^3.0.1"
|
||||
semver "^5.5.0"
|
||||
|
||||
ember-cli-babel@^7.21.0:
|
||||
ember-cli-babel@^7.21.0, ember-cli-babel@^7.26.4:
|
||||
version "7.26.6"
|
||||
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.26.6.tgz#322fbbd3baad9dd99e3276ff05bc6faef5e54b39"
|
||||
integrity sha512-040svtfj2RC35j/WMwdWJFusZaXmNoytLAMyBDGLMSlRvznudTxZjGlPV6UupmtTBApy58cEF8Fq4a+COWoEmQ==
|
||||
|
@ -5414,6 +5414,15 @@ ember-template-lint@^1.2.0:
|
|||
resolve "^1.15.1"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
ember-test-selectors@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-test-selectors/-/ember-test-selectors-6.0.0.tgz#ba9bb19550d9dec6e4037d86d2b13c2cfd329341"
|
||||
integrity sha512-PgYcI9PeNvtKaF0QncxfbS68olMYM1idwuI8v/WxsjOGqUx5bmsu6V17vy/d9hX4mwmjgsBhEghrVasGSuaIgw==
|
||||
dependencies:
|
||||
calculate-cache-key-for-tree "^2.0.0"
|
||||
ember-cli-babel "^7.26.4"
|
||||
ember-cli-version-checker "^5.1.2"
|
||||
|
||||
ember-try-config@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ember-try-config/-/ember-try-config-3.0.0.tgz#012d8c90cae9eb624e2b62040bf7e76a1aa58edc"
|
||||
|
|
|
@ -598,6 +598,14 @@ class PostsController < ApplicationController
|
|||
render_serialized(posts, AdminUserActionSerializer)
|
||||
end
|
||||
|
||||
def pending
|
||||
params.require(:username)
|
||||
user = fetch_user_from_params
|
||||
raise Discourse::NotFound unless guardian.can_edit_user?(user)
|
||||
|
||||
render_serialized(user.pending_posts.order(created_at: :desc), PendingPostSerializer, root: :pending_posts)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# We can't break the API for making posts. The new, queue supporting API
|
||||
|
|
|
@ -7,6 +7,8 @@ class ReviewableQueuedPost < Reviewable
|
|||
DiscourseEvent.trigger(:queued_post_created, self)
|
||||
end
|
||||
|
||||
after_commit :compute_user_stats, only: %i[create update]
|
||||
|
||||
def build_actions(actions, guardian, args)
|
||||
|
||||
unless approved?
|
||||
|
@ -157,6 +159,16 @@ class ReviewableQueuedPost < Reviewable
|
|||
delete_as_spammer: true
|
||||
}
|
||||
end
|
||||
|
||||
def compute_user_stats
|
||||
return unless status_changed_from_or_to_pending?
|
||||
created_by.user_stat.update_pending_posts
|
||||
end
|
||||
|
||||
def status_changed_from_or_to_pending?
|
||||
saved_change_to_id?(from: nil) && pending? ||
|
||||
saved_change_to_status?(from: self.class.statuses[:pending])
|
||||
end
|
||||
end
|
||||
|
||||
# == Schema Information
|
||||
|
|
|
@ -38,6 +38,7 @@ class User < ActiveRecord::Base
|
|||
has_many :reviewable_scores, dependent: :destroy
|
||||
has_many :invites, foreign_key: :invited_by_id, dependent: :destroy
|
||||
has_many :user_custom_fields, dependent: :destroy
|
||||
has_many :pending_posts, -> { merge(Reviewable.pending) }, class_name: 'ReviewableQueuedPost', foreign_key: :created_by_id
|
||||
|
||||
has_one :user_option, dependent: :destroy
|
||||
has_one :user_avatar, dependent: :destroy
|
||||
|
|
|
@ -291,6 +291,16 @@ class UserStat < ActiveRecord::Base
|
|||
Discourse.redis.setex(last_seen_key(id), MAX_TIME_READ_DIFF, val)
|
||||
end
|
||||
|
||||
def update_pending_posts
|
||||
update(pending_posts_count: user.pending_posts.count)
|
||||
MessageBus.publish(
|
||||
"/u/#{user.username_lower}/counters",
|
||||
{ pending_posts_count: pending_posts_count },
|
||||
user_ids: [user.id],
|
||||
group_ids: [Group::AUTO_GROUPS[:staff]]
|
||||
)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def trigger_badges
|
||||
|
@ -323,5 +333,7 @@ end
|
|||
# distinct_badge_count :integer default(0), not null
|
||||
# first_unread_pm_at :datetime not null
|
||||
# digest_attempted_at :datetime
|
||||
# draft_count :integer default(0), not null
|
||||
# post_edits_count :integer
|
||||
# draft_count :integer default(0), not null
|
||||
# pending_posts_count :integer default(0), not null
|
||||
#
|
||||
|
|
|
@ -41,7 +41,7 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
:dismissed_banner_key,
|
||||
:is_anonymous,
|
||||
:reviewable_count,
|
||||
:read_faq,
|
||||
:read_faq?,
|
||||
:automatically_unpin_topics,
|
||||
:mailing_list_mode,
|
||||
:treat_as_new_topic_start_date,
|
||||
|
@ -67,6 +67,10 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
:can_review,
|
||||
:draft_count,
|
||||
:default_calendar,
|
||||
:pending_posts_count
|
||||
|
||||
delegate :user_stat, to: :object, private: true
|
||||
delegate :any_posts, :draft_count, :pending_posts_count, :read_faq?, to: :user_stat
|
||||
|
||||
def groups
|
||||
owned_group_ids = GroupUser.where(user_id: id, owner: true).pluck(:group_id).to_set
|
||||
|
@ -93,14 +97,6 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
scope.can_create_group?
|
||||
end
|
||||
|
||||
def read_faq
|
||||
object.user_stat.read_faq?
|
||||
end
|
||||
|
||||
def any_posts
|
||||
object.user_stat.any_posts
|
||||
end
|
||||
|
||||
def hide_profile_and_presence
|
||||
object.user_option.hide_profile_and_presence
|
||||
end
|
||||
|
@ -321,8 +317,4 @@ class CurrentUserSerializer < BasicUserSerializer
|
|||
def include_has_topic_draft?
|
||||
Draft.has_topic_draft(object)
|
||||
end
|
||||
|
||||
def draft_count
|
||||
object.user_stat.draft_count
|
||||
end
|
||||
end
|
||||
|
|
27
app/serializers/pending_post_serializer.rb
Normal file
27
app/serializers/pending_post_serializer.rb
Normal file
|
@ -0,0 +1,27 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PendingPostSerializer < ApplicationSerializer
|
||||
attributes :id,
|
||||
:avatar_template,
|
||||
:category_id,
|
||||
:created_at,
|
||||
:created_by_id,
|
||||
:name,
|
||||
:raw_text,
|
||||
:title,
|
||||
:topic_id,
|
||||
:topic_url,
|
||||
:username
|
||||
|
||||
delegate :created_by, :payload, :topic, to: :object, private: true
|
||||
delegate :url, to: :topic, prefix: true, allow_nil: true
|
||||
delegate :avatar_template, :name, :username, to: :created_by, allow_nil: true
|
||||
|
||||
def raw_text
|
||||
payload["raw"]
|
||||
end
|
||||
|
||||
def title
|
||||
payload["title"] || topic.title
|
||||
end
|
||||
end
|
|
@ -65,7 +65,8 @@ class UserCardSerializer < BasicUserSerializer
|
|||
:flair_bg_color,
|
||||
:flair_color,
|
||||
:featured_topic,
|
||||
:timezone
|
||||
:timezone,
|
||||
:pending_posts_count
|
||||
|
||||
untrusted_attributes :bio_excerpt,
|
||||
:website,
|
||||
|
@ -77,6 +78,13 @@ class UserCardSerializer < BasicUserSerializer
|
|||
|
||||
has_many :featured_user_badges, embed: :ids, serializer: UserBadgeSerializer, root: :user_badges
|
||||
|
||||
delegate :user_stat, to: :object, private: true
|
||||
delegate :pending_posts_count, to: :user_stat
|
||||
|
||||
def include_pending_posts_count?
|
||||
scope.is_me?(object) || scope.is_staff?
|
||||
end
|
||||
|
||||
def include_email?
|
||||
(object.id && object.id == scope.user.try(:id)) ||
|
||||
(scope.is_staff? && object.staged?)
|
||||
|
|
|
@ -36,6 +36,7 @@ class WebHookUserSerializer < UserSerializer
|
|||
user_auth_tokens
|
||||
user_auth_token_logs
|
||||
use_logo_small_as_avatar
|
||||
pending_posts_count
|
||||
}.each do |attr|
|
||||
define_method("include_#{attr}?") do
|
||||
false
|
||||
|
|
|
@ -3514,6 +3514,9 @@ en:
|
|||
title: "This topic is a personal message"
|
||||
help: "This topic is a personal message"
|
||||
posts: "Posts"
|
||||
pending_posts:
|
||||
label: "Pending"
|
||||
label_with_count: "Pending (%{count})"
|
||||
|
||||
# keys ending with _MF use message format, see https://meta.discourse.org/t/message-format-support-for-localization/7035 for details
|
||||
posts_likes_MF: |
|
||||
|
|
|
@ -579,6 +579,7 @@ Discourse::Application.routes.draw do
|
|||
get "posts/:id/reply-ids/all" => "posts#all_reply_ids"
|
||||
get "posts/:username/deleted" => "posts#deleted_posts", constraints: { username: RouteFormat.username }
|
||||
get "posts/:username/flagged" => "posts#flagged_posts", constraints: { username: RouteFormat.username }
|
||||
get "posts/:username/pending" => "posts#pending", constraints: { username: RouteFormat.username }
|
||||
|
||||
%w{groups g}.each do |root_path|
|
||||
resources :groups, id: RouteFormat.username, path: root_path do
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AddPendingPostsCountToUserStats < ActiveRecord::Migration[6.1]
|
||||
def change
|
||||
add_column :user_stats, :pending_posts_count, :integer, null: false, default: 0
|
||||
end
|
||||
end
|
|
@ -0,0 +1,22 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class PopulatePendingPostsCountColumn < ActiveRecord::Migration[6.1]
|
||||
def up
|
||||
execute <<~SQL
|
||||
WITH to_update AS (
|
||||
SELECT COUNT(id) AS posts, created_by_id
|
||||
FROM reviewables
|
||||
WHERE type = 'ReviewableQueuedPost'
|
||||
AND status = #{ReviewableQueuedPost.statuses[:pending]}
|
||||
GROUP BY created_by_id
|
||||
)
|
||||
UPDATE user_stats
|
||||
SET pending_posts_count = to_update.posts
|
||||
FROM to_update
|
||||
WHERE to_update.created_by_id = user_stats.user_id
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
|
@ -550,6 +550,10 @@ class Guardian
|
|||
@user.has_trust_level_or_staff?(SiteSetting.min_trust_level_for_here_mention)
|
||||
end
|
||||
|
||||
def is_me?(other)
|
||||
other && authenticated? && other.is_a?(User) && @user == other
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def is_my_own?(obj)
|
||||
|
@ -562,10 +566,6 @@ class Guardian
|
|||
false
|
||||
end
|
||||
|
||||
def is_me?(other)
|
||||
other && authenticated? && other.is_a?(User) && @user == other
|
||||
end
|
||||
|
||||
def is_not_me?(other)
|
||||
@user.blank? || !is_me?(other)
|
||||
end
|
||||
|
|
|
@ -55,6 +55,7 @@
|
|||
"pretender": "^3.4.7",
|
||||
"puppeteer": "1.20",
|
||||
"qunit": "2.8.0",
|
||||
"qunit-dom": "^2.0.0",
|
||||
"route-recognizer": "^0.3.3",
|
||||
"sinon": "^9.0.2",
|
||||
"squoosh": "discourse/squoosh#dc9649d"
|
||||
|
|
|
@ -196,4 +196,36 @@ RSpec.describe ReviewableQueuedPost, type: :model do
|
|||
expect(Post.count).to eq(post_count)
|
||||
end
|
||||
end
|
||||
|
||||
describe "Callbacks" do
|
||||
context "when creating a new pending reviewable" do
|
||||
let(:reviewable) { Fabricate.build(:reviewable_queued_post_topic, category: category, created_by: user) }
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:user_stats) { user.user_stat }
|
||||
|
||||
it "updates user stats" do
|
||||
user_stats.expects(:update_pending_posts)
|
||||
reviewable.save!
|
||||
end
|
||||
end
|
||||
|
||||
context "when updating an existing reviewable" do
|
||||
let!(:reviewable) { Fabricate(:reviewable_queued_post_topic, category: category) }
|
||||
let(:user_stats) { reviewable.created_by.user_stat }
|
||||
|
||||
context "when status changes from 'pending' to something else" do
|
||||
it "updates user stats" do
|
||||
user_stats.expects(:update_pending_posts)
|
||||
reviewable.update!(status: described_class.statuses[:approved])
|
||||
end
|
||||
end
|
||||
|
||||
context "when status doesn’t change" do
|
||||
it "doesn’t update user stats" do
|
||||
user_stats.expects(:update_pending_posts).never
|
||||
reviewable.update!(score: 10)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,8 @@ describe User do
|
|||
I18n.t(:"activerecord.errors.models.user.attributes.#{keys.join('.')}")
|
||||
end
|
||||
|
||||
it { is_expected.to have_many(:pending_posts).class_name('ReviewableQueuedPost').with_foreign_key(:created_by_id) }
|
||||
|
||||
context 'validations' do
|
||||
describe '#username' do
|
||||
it { is_expected.to validate_presence_of :username }
|
||||
|
|
|
@ -261,4 +261,27 @@ describe UserStat do
|
|||
expect(user.user_stat.draft_count).to eq(3)
|
||||
end
|
||||
end
|
||||
|
||||
describe "#update_pending_posts" do
|
||||
subject(:update_pending_posts) { stat.update_pending_posts }
|
||||
|
||||
let!(:reviewable) { Fabricate(:reviewable_queued_post) }
|
||||
let(:user) { reviewable.created_by }
|
||||
let(:stat) { user.user_stat }
|
||||
|
||||
before do
|
||||
stat.update!(pending_posts_count: 0) # the reviewable callback will have set this to 1 already.
|
||||
end
|
||||
|
||||
it "sets 'pending_posts_count'" do
|
||||
expect { update_pending_posts }.to change { stat.pending_posts_count }.to 1
|
||||
end
|
||||
|
||||
it "publishes a message to clients" do
|
||||
MessageBus.expects(:publish).with("/u/#{user.username_lower}/counters",
|
||||
{ pending_posts_count: 1 },
|
||||
user_ids: [user.id], group_ids: [Group::AUTO_GROUPS[:staff]])
|
||||
update_pending_posts
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -206,6 +206,9 @@
|
|||
"pending_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"pending_posts_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
"profile_view_count": {
|
||||
"type": "integer"
|
||||
},
|
||||
|
|
|
@ -2156,6 +2156,101 @@ describe PostsController do
|
|||
end
|
||||
end
|
||||
|
||||
describe "#pending" do
|
||||
subject(:request) { get "/posts/#{user.username}/pending.json" }
|
||||
|
||||
context "when user is not logged in" do
|
||||
it_behaves_like "action requires login", :get, "/posts/system/pending.json"
|
||||
end
|
||||
|
||||
context "when user is logged in" do
|
||||
let(:pending_posts) { response.parsed_body["pending_posts"] }
|
||||
|
||||
before { sign_in(current_user) }
|
||||
|
||||
context "when current user is the same as user" do
|
||||
let(:current_user) { user }
|
||||
|
||||
context "when there are existing pending posts" do
|
||||
let!(:owner_pending_posts) { Fabricate.times(2, :reviewable_queued_post, created_by: user) }
|
||||
let!(:other_pending_post) { Fabricate(:reviewable_queued_post) }
|
||||
let(:expected_keys) do
|
||||
%w[
|
||||
avatar_template
|
||||
category_id
|
||||
created_at
|
||||
created_by_id
|
||||
name
|
||||
raw_text
|
||||
title
|
||||
topic_id
|
||||
topic_url
|
||||
username
|
||||
]
|
||||
end
|
||||
|
||||
it "returns user's pending posts" do
|
||||
request
|
||||
expect(pending_posts).to all include "id" => be_in(owner_pending_posts.map(&:id))
|
||||
expect(pending_posts).to all include(*expected_keys)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there aren't any pending posts" do
|
||||
it "returns an empty array" do
|
||||
request
|
||||
expect(pending_posts).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when current user is a staff member" do
|
||||
let(:current_user) { moderator }
|
||||
|
||||
context "when there are existing pending posts" do
|
||||
let!(:owner_pending_posts) { Fabricate.times(2, :reviewable_queued_post, created_by: user) }
|
||||
let!(:other_pending_post) { Fabricate(:reviewable_queued_post) }
|
||||
let(:expected_keys) do
|
||||
%w[
|
||||
avatar_template
|
||||
category_id
|
||||
created_at
|
||||
created_by_id
|
||||
name
|
||||
raw_text
|
||||
title
|
||||
topic_id
|
||||
topic_url
|
||||
username
|
||||
]
|
||||
end
|
||||
|
||||
it "returns user's pending posts" do
|
||||
request
|
||||
expect(pending_posts).to all include "id" => be_in(owner_pending_posts.map(&:id))
|
||||
expect(pending_posts).to all include(*expected_keys)
|
||||
end
|
||||
end
|
||||
|
||||
context "when there aren't any pending posts" do
|
||||
it "returns an empty array" do
|
||||
request
|
||||
expect(pending_posts).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when current user is another user" do
|
||||
let(:current_user) { Fabricate(:user) }
|
||||
|
||||
it "does not allow access" do
|
||||
request
|
||||
expect(response).to have_http_status :not_found
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe Plugin::Instance do
|
||||
describe '#add_permitted_post_create_param' do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe CurrentUserSerializer do
|
||||
subject(:serializer) { described_class.new(user, scope: guardian, root: false) }
|
||||
|
||||
let(:guardian) { Guardian.new }
|
||||
|
||||
context "when SSO is not enabled" do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new, root: false)
|
||||
end
|
||||
|
||||
it "should not include the external_id field" do
|
||||
payload = serializer.as_json
|
||||
|
@ -22,10 +23,6 @@ RSpec.describe CurrentUserSerializer do
|
|||
user
|
||||
end
|
||||
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new, root: false)
|
||||
end
|
||||
|
||||
it "should include the external_id" do
|
||||
SiteSetting.discourse_connect_url = "http://example.com/discourse_sso"
|
||||
SiteSetting.discourse_connect_secret = "12345678910"
|
||||
|
@ -40,9 +37,6 @@ RSpec.describe CurrentUserSerializer do
|
|||
fab!(:category1) { Fabricate(:category) }
|
||||
fab!(:category2) { Fabricate(:category) }
|
||||
fab!(:category3) { Fabricate(:category) }
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new, root: false)
|
||||
end
|
||||
|
||||
it "should include empty top_category_ids array" do
|
||||
payload = serializer.as_json
|
||||
|
@ -77,9 +71,6 @@ RSpec.describe CurrentUserSerializer do
|
|||
tag_id: tag.id
|
||||
)
|
||||
end
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new, root: false)
|
||||
end
|
||||
|
||||
it 'include muted tag ids' do
|
||||
payload = serializer.as_json
|
||||
|
@ -89,9 +80,7 @@ RSpec.describe CurrentUserSerializer do
|
|||
|
||||
context "#second_factor_enabled" do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new(user), root: false)
|
||||
end
|
||||
let(:guardian) { Guardian.new(user) }
|
||||
let(:json) { serializer.as_json }
|
||||
|
||||
it "is false by default" do
|
||||
|
@ -120,18 +109,15 @@ RSpec.describe CurrentUserSerializer do
|
|||
end
|
||||
|
||||
context "#groups" do
|
||||
fab!(:member) { Fabricate(:user) }
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(member, scope: Guardian.new, root: false)
|
||||
end
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
|
||||
it "should only show visible groups" do
|
||||
Fabricate.build(:group, visibility_level: Group.visibility_levels[:public])
|
||||
hidden_group = Fabricate.build(:group, visibility_level: Group.visibility_levels[:owners])
|
||||
public_group = Fabricate.build(:group, visibility_level: Group.visibility_levels[:public], name: "UppercaseGroupName")
|
||||
hidden_group.add(member)
|
||||
hidden_group.add(user)
|
||||
hidden_group.save!
|
||||
public_group.add(member)
|
||||
public_group.add(user)
|
||||
public_group.save!
|
||||
payload = serializer.as_json
|
||||
|
||||
|
@ -143,9 +129,6 @@ RSpec.describe CurrentUserSerializer do
|
|||
|
||||
context "#has_topic_draft" do
|
||||
fab!(:user) { Fabricate(:user) }
|
||||
let :serializer do
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new, root: false)
|
||||
end
|
||||
|
||||
it "is not included by default" do
|
||||
payload = serializer.as_json
|
||||
|
@ -169,23 +152,36 @@ RSpec.describe CurrentUserSerializer do
|
|||
|
||||
end
|
||||
|
||||
context '#can_review' do
|
||||
it 'return false for regular users' do
|
||||
serializer = serializer(Fabricate(:user))
|
||||
payload = serializer.as_json
|
||||
context "#can_review" do
|
||||
let(:guardian) { Guardian.new(user) }
|
||||
let(:payload) { serializer.as_json }
|
||||
|
||||
expect(payload[:can_review]).to eq(false)
|
||||
context "when user is a regular one" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
it "return false for regular users" do
|
||||
expect(payload[:can_review]).to eq(false)
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns trus for staff' do
|
||||
serializer = serializer(Fabricate(:admin))
|
||||
payload = serializer.as_json
|
||||
context "when user is a staff member" do
|
||||
let(:user) { Fabricate(:admin) }
|
||||
|
||||
expect(payload[:can_review]).to eq(true)
|
||||
it "returns true" do
|
||||
expect(payload[:can_review]).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def serializer(user)
|
||||
CurrentUserSerializer.new(user, scope: Guardian.new(user), root: false)
|
||||
describe "#pending_posts_count" do
|
||||
subject(:pending_posts_count) { serializer.pending_posts_count }
|
||||
|
||||
let(:user) { Fabricate(:user) }
|
||||
|
||||
before { user.user_stat.pending_posts_count = 3 }
|
||||
|
||||
it "serializes 'pending_posts_count'" do
|
||||
expect(pending_posts_count).to eq 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
59
spec/serializers/pending_post_serializer_spec.rb
Normal file
59
spec/serializers/pending_post_serializer_spec.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require "rails_helper"
|
||||
|
||||
RSpec.describe PendingPostSerializer do
|
||||
subject(:serializer) { described_class.new(post, scope: guardian, root: false) }
|
||||
|
||||
let(:guardian) { Guardian.new(author) }
|
||||
let(:author) { post.created_by }
|
||||
|
||||
before { freeze_time }
|
||||
|
||||
context "when creating a new topic" do
|
||||
let(:post) { Fabricate(:reviewable_queued_post_topic) }
|
||||
let(:expected_attributes) do
|
||||
{
|
||||
id: post.id,
|
||||
avatar_template: author.avatar_template,
|
||||
category_id: post.category_id,
|
||||
created_at: Time.current,
|
||||
created_by_id: author.id,
|
||||
name: author.name,
|
||||
raw_text: post.payload["raw"],
|
||||
title: post.payload["title"],
|
||||
topic_id: nil,
|
||||
topic_url: nil,
|
||||
username: author.username
|
||||
}
|
||||
end
|
||||
|
||||
it "serializes a pending post properly" do
|
||||
expect(serializer.as_json).to match expected_attributes
|
||||
end
|
||||
end
|
||||
|
||||
context "when not creating a new topic" do
|
||||
let(:post) { Fabricate(:reviewable_queued_post) }
|
||||
let(:topic) { post.topic }
|
||||
let(:expected_attributes) do
|
||||
{
|
||||
id: post.id,
|
||||
avatar_template: author.avatar_template,
|
||||
category_id: post.category_id,
|
||||
created_at: Time.current,
|
||||
created_by_id: author.id,
|
||||
name: author.name,
|
||||
raw_text: post.payload["raw"],
|
||||
title: topic.title,
|
||||
topic_id: topic.id,
|
||||
topic_url: topic.url,
|
||||
username: author.username
|
||||
}
|
||||
end
|
||||
|
||||
it "serializes a pending post properly" do
|
||||
expect(serializer.as_json).to match expected_attributes
|
||||
end
|
||||
end
|
||||
end
|
|
@ -37,4 +37,39 @@ describe UserCardSerializer do
|
|||
expect(json[:unconfirmed_emails]).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe "#pending_posts_count" do
|
||||
let(:user) { Fabricate(:user) }
|
||||
let(:serializer) { described_class.new(user, scope: guardian, root: false) }
|
||||
let(:json) { serializer.as_json }
|
||||
|
||||
context "when guardian is another user" do
|
||||
let(:guardian) { Guardian.new(other_user) }
|
||||
|
||||
context "when other user is not a staff member" do
|
||||
let(:other_user) { Fabricate(:user) }
|
||||
|
||||
it "does not serialize pending_posts_count" do
|
||||
expect(json.keys).not_to include :pending_posts_count
|
||||
end
|
||||
end
|
||||
|
||||
context "when other user is a staff member" do
|
||||
let(:other_user) { Fabricate(:user, moderator: true) }
|
||||
|
||||
it "serializes pending_posts_count" do
|
||||
expect(json[:pending_posts_count]).to eq 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "when guardian is the current user" do
|
||||
let(:guardian) { Guardian.new(user) }
|
||||
|
||||
it "serializes pending_posts_count" do
|
||||
expect(json[:pending_posts_count]).to eq 0
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue
Block a user