Skip to content
This repository was archived by the owner on Jul 22, 2025. It is now read-only.

Commit 85a350e

Browse files
committed
FEATURE: Make hot topic gists opt-in.
This change restricts gists to members of specific groups. It also fixes a bug where other lists could display the gist if available.
1 parent e768fa8 commit 85a350e

File tree

8 files changed

+88
-30
lines changed

8 files changed

+88
-30
lines changed

app/controllers/discourse_ai/summarization/summary_controller.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ def show
99
topic = Topic.find(params[:topic_id])
1010
guardian.ensure_can_see!(topic)
1111

12-
if !guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])
13-
raise Discourse::NotFound
14-
end
12+
raise Discourse::NotFound if !guardian.can_see_summary?(topic)
1513

1614
RateLimiter.new(current_user, "summary", 6, 5.minutes).performed! if current_user
1715

app/jobs/regular/stream_topic_ai_summary.rb

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,7 @@ def execute(args)
99
return unless user = User.find_by(id: args[:user_id])
1010

1111
strategy = DiscourseAi::Summarization.topic_summary(topic)
12-
if strategy.nil? ||
13-
!Guardian.new(user).can_see_summary?(topic, AiSummary.summary_types[:complete])
14-
return
15-
end
12+
return if strategy.nil? || !Guardian.new(user).can_see_summary?(topic)
1613

1714
guardian = Guardian.new(user)
1815
return unless guardian.can_see?(topic)

config/locales/server.en.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ en:
8585
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
8686
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
8787
ai_summarize_max_hot_topics_gists_per_batch: "After updating topics in the hot list, we'll generate brief summaries of the first N ones. (Disabled when 0)"
88+
ai_hot_topic_gists_allowed_groups: "Groups allowed to see gists in the hot topics list."
8889

8990
ai_bot_enabled: "Enable the AI Bot module."
9091
ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning"

config/settings.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -380,6 +380,10 @@ discourse_ai:
380380
default: 0
381381
min: 0
382382
max: 1000
383+
ai_hot_topic_gists_allowed_groups:
384+
type: group_list
385+
list_type: compact
386+
default: ""
383387
ai_summarization_strategy: # TODO(roman): Deprecated. Remove by Sept 2024
384388
type: enum
385389
default: ""

lib/guardian_extensions.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
module DiscourseAi
44
module GuardianExtensions
5-
def can_see_summary?(target, summary_type)
5+
def can_see_summary?(target)
66
return false if !SiteSetting.ai_summarization_enabled
77

88
if target.class == Topic && target.private_message?
@@ -14,12 +14,24 @@ def can_see_summary?(target, summary_type)
1414
return false if !allowed
1515
end
1616

17-
has_cached_summary = AiSummary.exists?(target: target, summary_type: summary_type)
17+
has_cached_summary =
18+
AiSummary.exists?(target: target, summary_type: AiSummary.summary_types[:complete])
1819
return has_cached_summary if user.nil?
1920

2021
has_cached_summary || can_request_summary?
2122
end
2223

24+
def can_see_gists?
25+
return false if !SiteSetting.ai_summarization_enabled
26+
return false if SiteSetting.ai_summarize_max_hot_topics_gists_per_batch.zero?
27+
return false if anonymous?
28+
return false if SiteSetting.ai_hot_topic_gists_allowed_groups_map.empty?
29+
30+
SiteSetting.ai_hot_topic_gists_allowed_groups_map.any? do |group_id|
31+
user.group_ids.include?(group_id)
32+
end
33+
end
34+
2335
def can_request_summary?
2436
return false if anonymous?
2537

lib/summarization/entry_point.rb

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ def inject_into(plugin)
1010
end
1111

1212
plugin.add_to_serializer(:topic_view, :summarizable) do
13-
scope.can_see_summary?(object.topic, AiSummary.summary_types[:complete])
13+
scope.can_see_summary?(object.topic)
1414
end
1515

1616
plugin.add_to_serializer(:web_hook_topic_view, :summarizable) do
17-
scope.can_see_summary?(object.topic, AiSummary.summary_types[:complete])
17+
scope.can_see_summary?(object.topic)
1818
end
1919

2020
plugin.register_modifier(:topic_query_create_list_topics) do |topics, options|
@@ -32,12 +32,11 @@ def inject_into(plugin)
3232
plugin.add_to_serializer(
3333
:topic_list_item,
3434
:ai_topic_gist,
35-
include_condition: -> do
36-
SiteSetting.ai_summarization_enabled &&
37-
SiteSetting.ai_summarize_max_hot_topics_gists_per_batch > 0 &&
38-
options[:filter] == :hot
39-
end,
35+
include_condition: -> { scope.can_see_gists? },
4036
) do
37+
# Options is defined at the instance level so we cannot run this check inside "include_condition".
38+
return if options[:filter] != :hot
39+
4140
summaries = object.ai_summaries.to_a
4241

4342
# Summaries should always have one or zero elements here.

spec/lib/guardian_extensions_spec.rb

Lines changed: 36 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,20 @@
99
group.add(user)
1010
assign_fake_provider_to(:ai_summarization_model)
1111
SiteSetting.ai_summarization_enabled = true
12+
SiteSetting.ai_summarize_max_hot_topics_gists_per_batch = 1
1213
end
1314

14-
describe "#can_see_summary?" do
15-
let(:guardian) { Guardian.new(user) }
15+
let(:anon_guardian) { Guardian.new }
16+
let(:guardian) { Guardian.new(user) }
1617

18+
describe "#can_see_summary?" do
1719
context "when the user cannot generate a summary" do
1820
before { SiteSetting.ai_custom_summarization_allowed_groups = "" }
1921

2022
it "returns false" do
2123
SiteSetting.ai_custom_summarization_allowed_groups = ""
2224

23-
expect(guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])).to eq(false)
25+
expect(guardian.can_see_summary?(topic)).to eq(false)
2426
end
2527

2628
it "returns true if there is a cached summary" do
@@ -32,15 +34,15 @@
3234
summary_type: AiSummary.summary_types[:complete],
3335
)
3436

35-
expect(guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])).to eq(true)
37+
expect(guardian.can_see_summary?(topic)).to eq(true)
3638
end
3739
end
3840

3941
context "when the user can generate a summary" do
4042
before { SiteSetting.ai_custom_summarization_allowed_groups = group.id }
4143

4244
it "returns true if the user group is present in the ai_custom_summarization_allowed_groups_map setting" do
43-
expect(guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])).to eq(true)
45+
expect(guardian.can_see_summary?(topic)).to eq(true)
4446
end
4547
end
4648

@@ -49,20 +51,18 @@
4951
let(:pm) { Fabricate(:private_message_topic) }
5052

5153
it "returns false" do
52-
expect(guardian.can_see_summary?(pm, AiSummary.summary_types[:complete])).to eq(false)
54+
expect(guardian.can_see_summary?(pm)).to eq(false)
5355
end
5456

5557
it "returns true if user is in a group that is allowed summaries" do
5658
SiteSetting.ai_pm_summarization_allowed_groups = group.id
57-
expect(guardian.can_see_summary?(pm, AiSummary.summary_types[:complete])).to eq(true)
59+
expect(guardian.can_see_summary?(pm)).to eq(true)
5860
end
5961
end
6062

6163
context "when there is no user" do
62-
let(:guardian) { Guardian.new }
63-
6464
it "returns false for anons" do
65-
expect(guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])).to eq(false)
65+
expect(anon_guardian.can_see_summary?(topic)).to eq(false)
6666
end
6767

6868
it "returns true for anons when there is a cached summary" do
@@ -74,7 +74,32 @@
7474
summary_type: AiSummary.summary_types[:complete],
7575
)
7676

77-
expect(guardian.can_see_summary?(topic, AiSummary.summary_types[:complete])).to eq(true)
77+
expect(guardian.can_see_summary?(topic)).to eq(true)
78+
end
79+
end
80+
end
81+
82+
describe "#can_see_gists?" do
83+
before { SiteSetting.ai_hot_topic_gists_allowed_groups = group.id }
84+
let(:guardian) { Guardian.new(user) }
85+
86+
context "when there is no user" do
87+
it "returns false for anons" do
88+
expect(anon_guardian.can_see_gists?).to eq(false)
89+
end
90+
end
91+
92+
context "when there is a user but it's not a member of the allowed groups" do
93+
before { SiteSetting.ai_hot_topic_gists_allowed_groups = "" }
94+
95+
it "returns false" do
96+
expect(guardian.can_see_gists?).to eq(false)
97+
end
98+
end
99+
100+
context "when there is a user who is a member of an allowed group" do
101+
it "returns false" do
102+
expect(guardian.can_see_gists?).to eq(true)
78103
end
79104
end
80105
end

spec/lib/modules/summarization/entry_point_spec.rb

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,21 @@
4949
end
5050

5151
context "when hot topics summarization is enabled" do
52-
before { SiteSetting.ai_summarize_max_hot_topics_gists_per_batch = 100 }
52+
fab!(:group)
53+
54+
before do
55+
group.add(user)
56+
SiteSetting.ai_hot_topic_gists_allowed_groups = group.id
57+
SiteSetting.ai_summarize_max_hot_topics_gists_per_batch = 100
58+
end
5359

5460
it "includes the summary" do
5561
gist_topic = topic_query.list_hot.topics.find { |t| t.id == topic_ai_gist.target_id }
5662

5763
serialized =
5864
TopicListItemSerializer.new(
5965
gist_topic,
60-
scope: Guardian.new,
66+
scope: Guardian.new(user),
6167
root: false,
6268
filter: :hot,
6369
).as_json
@@ -71,13 +77,29 @@
7177
serialized =
7278
TopicListItemSerializer.new(
7379
gist_topic,
74-
scope: Guardian.new,
80+
scope: Guardian.new(user),
7581
root: false,
7682
filter: :latest,
7783
).as_json
7884

7985
expect(serialized[:ai_topic_gist]).to be_nil
8086
end
87+
88+
it "doesn't include the summary when the user is not a member of the opt-in group" do
89+
SiteSetting.ai_hot_topic_gists_allowed_groups = ""
90+
91+
gist_topic = topic_query.list_hot.topics.find { |t| t.id == topic_ai_gist.target_id }
92+
93+
serialized =
94+
TopicListItemSerializer.new(
95+
gist_topic,
96+
scope: Guardian.new(user),
97+
root: false,
98+
filter: :hot,
99+
).as_json
100+
101+
expect(serialized[:ai_topic_gist]).to be_nil
102+
end
81103
end
82104
end
83105
end

0 commit comments

Comments
 (0)