mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 13:09:18 +08:00
FIX: Load categories with user activity and drafts (#26553)
When lazy load categories is enabled, categories should be loaded with user activity items and drafts because the categories may not be preloaded on the client side.
This commit is contained in:
parent
3733db866c
commit
8ce836c039
|
@ -8,6 +8,7 @@ import {
|
||||||
NEW_TOPIC_KEY,
|
NEW_TOPIC_KEY,
|
||||||
} from "discourse/models/composer";
|
} from "discourse/models/composer";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
import Site from "discourse/models/site";
|
||||||
import UserDraft from "discourse/models/user-draft";
|
import UserDraft from "discourse/models/user-draft";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
@ -64,6 +65,10 @@ export default class UserDraftsStream extends RestModel {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.categories?.forEach((category) =>
|
||||||
|
Site.current().updateCategory(category)
|
||||||
|
);
|
||||||
|
|
||||||
this.set("hasMore", result.drafts.size >= this.limit);
|
this.set("hasMore", result.drafts.size >= this.limit);
|
||||||
|
|
||||||
const promises = result.drafts.map((draft) => {
|
const promises = result.drafts.map((draft) => {
|
||||||
|
|
|
@ -5,6 +5,7 @@ import { url } from "discourse/lib/computed";
|
||||||
import { emojiUnescape } from "discourse/lib/text";
|
import { emojiUnescape } from "discourse/lib/text";
|
||||||
import { escapeExpression } from "discourse/lib/utilities";
|
import { escapeExpression } from "discourse/lib/utilities";
|
||||||
import RestModel from "discourse/models/rest";
|
import RestModel from "discourse/models/rest";
|
||||||
|
import Site from "discourse/models/site";
|
||||||
import UserAction from "discourse/models/user-action";
|
import UserAction from "discourse/models/user-action";
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
|
@ -110,6 +111,11 @@ export default class UserStream extends RestModel {
|
||||||
.then((result) => {
|
.then((result) => {
|
||||||
if (result && result.user_actions) {
|
if (result && result.user_actions) {
|
||||||
const copy = A();
|
const copy = A();
|
||||||
|
|
||||||
|
result.categories?.forEach((category) => {
|
||||||
|
Site.current().updateCategory(category);
|
||||||
|
});
|
||||||
|
|
||||||
result.user_actions.forEach((action) => {
|
result.user_actions.forEach((action) => {
|
||||||
action.title = emojiUnescape(escapeExpression(action.title));
|
action.title = emojiUnescape(escapeExpression(action.title));
|
||||||
copy.pushObject(UserAction.create(action));
|
copy.pushObject(UserAction.create(action));
|
||||||
|
|
|
@ -17,7 +17,15 @@ class DraftsController < ApplicationController
|
||||||
limit: fetch_limit_from_params(default: nil, max: INDEX_LIMIT),
|
limit: fetch_limit_from_params(default: nil, max: INDEX_LIMIT),
|
||||||
)
|
)
|
||||||
|
|
||||||
render json: { drafts: stream ? serialize_data(stream, DraftSerializer) : [] }
|
response = { drafts: serialize_data(stream, DraftSerializer) }
|
||||||
|
|
||||||
|
if guardian.can_lazy_load_categories?
|
||||||
|
category_ids = stream.map { |draft| draft.topic&.category_id }.compact.uniq
|
||||||
|
categories = Category.secured(guardian).with_parents(category_ids)
|
||||||
|
response[:categories] = serialize_data(categories, CategoryBadgeSerializer)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: response
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -28,7 +28,16 @@ class UserActionsController < ApplicationController
|
||||||
}
|
}
|
||||||
|
|
||||||
stream = UserAction.stream(opts).to_a
|
stream = UserAction.stream(opts).to_a
|
||||||
render_serialized(stream, UserActionSerializer, root: "user_actions")
|
|
||||||
|
response = { user_actions: serialize_data(stream, UserActionSerializer) }
|
||||||
|
|
||||||
|
if guardian.can_lazy_load_categories?
|
||||||
|
category_ids = stream.map(&:category_id).compact.uniq
|
||||||
|
categories = Category.secured(guardian).with_parents(category_ids)
|
||||||
|
response[:categories] = serialize_data(categories, CategoryBadgeSerializer)
|
||||||
|
end
|
||||||
|
|
||||||
|
render json: response
|
||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
|
|
@ -211,6 +211,12 @@ class Category < ActiveRecord::Base
|
||||||
)
|
)
|
||||||
SQL
|
SQL
|
||||||
|
|
||||||
|
scope :with_parents, ->(ids) { where(<<~SQL, ids: ids) }
|
||||||
|
id IN (:ids)
|
||||||
|
OR
|
||||||
|
id IN (SELECT DISTINCT parent_category_id FROM categories WHERE id IN (:ids))
|
||||||
|
SQL
|
||||||
|
|
||||||
delegate :post_template, to: "self.class"
|
delegate :post_template, to: "self.class"
|
||||||
|
|
||||||
# permission is just used by serialization
|
# permission is just used by serialization
|
||||||
|
|
|
@ -31,7 +31,7 @@ class PostRevision < ActiveRecord::Base
|
||||||
def categories
|
def categories
|
||||||
return [] if modifications["category_id"].blank?
|
return [] if modifications["category_id"].blank?
|
||||||
|
|
||||||
@categories ||= Category.where(id: modifications["category_id"])
|
@categories ||= Category.with_parents(modifications["category_id"])
|
||||||
end
|
end
|
||||||
|
|
||||||
def hide!
|
def hide!
|
||||||
|
|
|
@ -1,16 +1,12 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class AboutSerializer < ApplicationSerializer
|
class AboutSerializer < ApplicationSerializer
|
||||||
class CategoryAboutSerializer < ApplicationSerializer
|
|
||||||
attributes :id, :name, :color, :slug, :parent_category_id
|
|
||||||
end
|
|
||||||
|
|
||||||
class UserAboutSerializer < BasicUserSerializer
|
class UserAboutSerializer < BasicUserSerializer
|
||||||
attributes :title, :last_seen_at
|
attributes :title, :last_seen_at
|
||||||
end
|
end
|
||||||
|
|
||||||
class AboutCategoryModsSerializer < ApplicationSerializer
|
class AboutCategoryModsSerializer < ApplicationSerializer
|
||||||
has_one :category, serializer: CategoryAboutSerializer, embed: :objects
|
has_one :category, serializer: CategoryBadgeSerializer, embed: :objects
|
||||||
has_many :moderators, serializer: UserAboutSerializer, embed: :objects
|
has_many :moderators, serializer: UserAboutSerializer, embed: :objects
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
5
app/serializers/category_badge_serializer.rb
Normal file
5
app/serializers/category_badge_serializer.rb
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CategoryBadgeSerializer < ApplicationSerializer
|
||||||
|
attributes :id, :name, :color, :slug, :parent_category_id
|
||||||
|
end
|
|
@ -28,7 +28,7 @@ class PostRevisionSerializer < ApplicationSerializer
|
||||||
:wiki,
|
:wiki,
|
||||||
:can_edit
|
:can_edit
|
||||||
|
|
||||||
has_many :categories, serializer: BasicCategorySerializer, embed: :objects
|
has_many :categories, serializer: CategoryBadgeSerializer, embed: :objects
|
||||||
|
|
||||||
# Creates a field called field_name_changes with previous and
|
# Creates a field called field_name_changes with previous and
|
||||||
# current members if a field has changed in this revision
|
# current members if a field has changed in this revision
|
||||||
|
|
|
@ -223,6 +223,19 @@ RSpec.describe Category do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "with_parents" do
|
||||||
|
fab!(:category)
|
||||||
|
fab!(:subcategory) { Fabricate(:category, parent_category: category) }
|
||||||
|
|
||||||
|
it "returns parent categories and subcategories" do
|
||||||
|
expect(Category.with_parents([category.id])).to contain_exactly(category)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns only categories if top-level categories" do
|
||||||
|
expect(Category.with_parents([subcategory.id])).to contain_exactly(category, subcategory)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "security" do
|
describe "security" do
|
||||||
fab!(:category) { Fabricate(:category_with_definition) }
|
fab!(:category) { Fabricate(:category_with_definition) }
|
||||||
fab!(:category_2) { Fabricate(:category_with_definition) }
|
fab!(:category_2) { Fabricate(:category_with_definition) }
|
||||||
|
|
|
@ -50,6 +50,21 @@ RSpec.describe DraftsController do
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
expect(response.parsed_body["drafts"].first["title"]).to eq(nil)
|
expect(response.parsed_body["drafts"].first["title"]).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns categories when lazy load categories is enabled" do
|
||||||
|
SiteSetting.lazy_load_categories_groups = "#{Group::AUTO_GROUPS[:everyone]}"
|
||||||
|
category = Fabricate(:category)
|
||||||
|
topic = Fabricate(:topic, category: category)
|
||||||
|
Draft.set(topic.user, "topic_#{topic.id}", 0, "{}")
|
||||||
|
sign_in(topic.user)
|
||||||
|
|
||||||
|
get "/drafts.json"
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
draft_keys = response.parsed_body["drafts"].map { |draft| draft["draft_key"] }
|
||||||
|
expect(draft_keys).to contain_exactly("topic_#{topic.id}")
|
||||||
|
category_ids = response.parsed_body["categories"].map { |cat| cat["id"] }
|
||||||
|
expect(category_ids).to contain_exactly(category.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#show" do
|
describe "#show" do
|
||||||
|
|
|
@ -28,6 +28,14 @@ RSpec.describe UserActionsController do
|
||||||
expect(actions.first).not_to include "email"
|
expect(actions.first).not_to include "email"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "returns categories when lazy load categories is enabled" do
|
||||||
|
SiteSetting.lazy_load_categories_groups = "#{Group::AUTO_GROUPS[:everyone]}"
|
||||||
|
user_actions
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
category_ids = response.parsed_body["categories"].map { |category| category["id"] }
|
||||||
|
expect(category_ids).to contain_exactly(post.topic.category.id)
|
||||||
|
end
|
||||||
|
|
||||||
context "when 'acting_username' is provided" do
|
context "when 'acting_username' is provided" do
|
||||||
let(:user) { Fabricate(:user) }
|
let(:user) { Fabricate(:user) }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user