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

Commit cd0cfc0

Browse files
authored
DEV: Group PMs by date (#1287)
# Preview https://github.com/user-attachments/assets/3fe3ac8f-c938-4df4-9afe-11980046944d # Details - Group pms by `last_posted_at`. In this first iteration we are group by `7 days`, `30 days`, then by month beyond that. - I inject a sidebar section link with the relative (last_posted_at) date and then update a tracked value to ensure we don't do it again. Then for each month beyond the first 30days, I add a value to the `loadedMonthLabels` set and we reference that (plus the year) to see if we need to load a new month label. - I took the creative liberty to remove the `Conversations` section label - this had no purpose - I hid the _collapse all sidebar sections_ carrot. This had no purpose. - Swap `BasicTopicSerializer` to `ListableTopicSerializer` to get access to `last_posted_at`
1 parent 60ea590 commit cd0cfc0

File tree

6 files changed

+126
-22
lines changed

6 files changed

+126
-22
lines changed

app/controllers/discourse_ai/ai_bot/conversations_controller.rb

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ def index
1010
page = params[:page].to_i
1111
per_page = params[:per_page]&.to_i || 40
1212

13-
bot_user_ids = EntryPoint.all_bot_ids
1413
base_query =
1514
Topic
1615
.private_messages_for_user(current_user)
@@ -26,7 +25,7 @@ def index
2625
pms = base_query.order(last_posted_at: :desc).offset(page * per_page).limit(per_page)
2726

2827
render json: {
29-
conversations: serialize_data(pms, BasicTopicSerializer),
28+
conversations: serialize_data(pms, ListableTopicSerializer),
3029
meta: {
3130
total: total,
3231
page: page,

assets/javascripts/initializers/ai-conversations-sidebar.js

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { tracked } from "@glimmer/tracking";
2+
import { htmlSafe } from "@ember/template";
23
import { TrackedArray } from "@ember-compat/tracked-built-ins";
34
import { ajax } from "discourse/lib/ajax";
45
import { bind } from "discourse/lib/decorators";
6+
import { autoUpdatingRelativeAge } from "discourse/lib/formatter";
57
import { withPluginApi } from "discourse/lib/plugin-api";
68
import { i18n } from "discourse-i18n";
79
import AiBotSidebarNewConversation from "../discourse/components/ai-bot-sidebar-new-conversation";
@@ -85,6 +87,9 @@ export default {
8587
@tracked links = new TrackedArray();
8688
@tracked topics = [];
8789
@tracked hasMore = [];
90+
@tracked loadedSevenDayLabel = false;
91+
@tracked loadedThirtyDayLabel = false;
92+
@tracked loadedMonthLabels = new Set();
8893
page = 0;
8994
isFetching = false;
9095
totalTopicsCount = 0;
@@ -127,7 +132,14 @@ export default {
127132
}
128133

129134
addNewPMToSidebar(topic) {
130-
this.links = [new AiConversationLink(topic), ...this.links];
135+
// Reset category labels since we're adding a new topic
136+
this.loadedSevenDayLabel = false;
137+
this.loadedThirtyDayLabel = false;
138+
this.loadedMonthLabels.clear();
139+
140+
this.topics = [topic, ...this.topics];
141+
this.buildSidebarLinks();
142+
131143
this.watchForTitleUpdate(topic);
132144
}
133145

@@ -206,10 +218,71 @@ export default {
206218
this.fetchMessages(true);
207219
}
208220

209-
buildSidebarLinks() {
210-
this.links = this.topics.map(
211-
(topic) => new AiConversationLink(topic)
221+
groupByDate(topic) {
222+
const now = new Date();
223+
const lastPostedAt = new Date(topic.last_posted_at);
224+
const daysDiff = Math.round(
225+
(now - lastPostedAt) / (1000 * 60 * 60 * 24)
212226
);
227+
228+
// Last 7 days group
229+
if (daysDiff <= 7) {
230+
if (!this.loadedSevenDayLabel) {
231+
this.loadedSevenDayLabel = true;
232+
return {
233+
text: i18n("discourse_ai.ai_bot.conversations.last_7_days"),
234+
classNames: "date-heading",
235+
name: "date-heading-last-7-days",
236+
};
237+
}
238+
}
239+
// Last 30 days group
240+
else if (daysDiff <= 30) {
241+
if (!this.loadedThirtyDayLabel) {
242+
this.loadedThirtyDayLabel = true;
243+
return {
244+
text: i18n(
245+
"discourse_ai.ai_bot.conversations.last_30_days"
246+
),
247+
classNames: "date-heading",
248+
name: "date-heading-last-30-days",
249+
};
250+
}
251+
}
252+
// Group by month for older conversations
253+
else {
254+
const month = lastPostedAt.getMonth();
255+
const year = lastPostedAt.getFullYear();
256+
const monthKey = `${year}-${month}`;
257+
258+
if (!this.loadedMonthLabels.has(monthKey)) {
259+
this.loadedMonthLabels.add(monthKey);
260+
261+
const formattedDate = autoUpdatingRelativeAge(
262+
new Date(topic.last_posted_at)
263+
);
264+
265+
return {
266+
text: htmlSafe(formattedDate),
267+
classNames: "date-heading",
268+
name: `date-heading-${monthKey}`,
269+
};
270+
}
271+
}
272+
}
273+
274+
buildSidebarLinks() {
275+
// Reset date header tracking
276+
this.loadedSevenDayLabel = false;
277+
this.loadedThirtyDayLabel = false;
278+
this.loadedMonthLabels.clear();
279+
280+
this.links = [...this.topics].flatMap((topic) => {
281+
const dateLabel = this.groupByDate(topic);
282+
return dateLabel
283+
? [dateLabel, new AiConversationLink(topic)]
284+
: [new AiConversationLink(topic)];
285+
});
213286
}
214287

215288
watchForTitleUpdate(topic) {

assets/stylesheets/modules/ai-bot-conversations/common.scss

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ body.has-ai-conversations-sidebar {
1010
margin: 1.8em 1rem 0;
1111
}
1212

13+
.sidebar-toggle-all-sections {
14+
display: none;
15+
}
16+
1317
.sidebar-wrapper {
1418
.ai-conversations-panel {
1519
padding-top: 1em;
@@ -18,19 +22,23 @@ body.has-ai-conversations-sidebar {
1822
// ai related sidebar content
1923
[data-section-name="ai-conversations-history"] {
2024
.sidebar-section-header-wrapper {
21-
pointer-events: none;
22-
font-size: var(--font-down-1);
23-
24-
.sidebar-section-header-caret {
25-
display: none;
26-
}
27-
28-
.sidebar-section-header-text {
29-
letter-spacing: 0.5px;
30-
}
25+
display: none;
3126
}
3227

3328
.sidebar-section-link-wrapper {
29+
.sidebar-section-link.date-heading {
30+
pointer-events: none;
31+
cursor: default;
32+
color: var(--primary-medium);
33+
opacity: 0.8;
34+
font-weight: 700;
35+
margin-top: 1em;
36+
37+
&[data-link-name="date-heading-last-7-days"] {
38+
margin-top: 0;
39+
}
40+
}
41+
3442
.sidebar-section-link {
3543
height: unset;
3644
padding-block: 0.65em;

config/locales/client.en.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,6 +727,8 @@ en:
727727
new: "New Question"
728728
min_input_length_message: "Message must be longer than 10 characters"
729729
messages_sidebar_title: "Conversations"
730+
last_7_days: "Last 7 days"
731+
last_30_days: "Last 30 days"
730732
sentiments:
731733
dashboard:
732734
title: "Sentiment"

spec/system/ai_bot/homepage_spec.rb

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
:private_message_topic,
5050
title: "This is my special PM",
5151
user: user,
52+
last_posted_at: Time.zone.now,
5253
topic_allowed_users: [
5354
Fabricate.build(:topic_allowed_user, user: user),
5455
Fabricate.build(:topic_allowed_user, user: bot_user),
@@ -150,18 +151,35 @@
150151

151152
expect(ai_pm_homepage).to have_homepage
152153
expect(sidebar).to have_section("ai-conversations-history")
154+
expect(sidebar).to have_section_link("Last 7 days")
153155
expect(sidebar).to have_section_link(pm.title)
154156
expect(sidebar).to have_no_css("button.ai-new-question-button")
155157
end
156158

159+
it "displays last_30_days label in the sidebar" do
160+
pm.update!(last_posted_at: Time.zone.now - 28.days)
161+
visit "/"
162+
header.click_bot_button
163+
164+
expect(ai_pm_homepage).to have_homepage
165+
expect(sidebar).to have_section_link("Last 30 days")
166+
end
167+
168+
it "displays month and year label in the sidebar for older conversations" do
169+
pm.update!(last_posted_at: "2024-04-10 15:39:11.406192000 +00:00")
170+
visit "/"
171+
header.click_bot_button
172+
173+
expect(ai_pm_homepage).to have_homepage
174+
expect(sidebar).to have_section_link("Apr 2024")
175+
end
176+
157177
it "navigates to the bot conversation when clicked" do
158178
visit "/"
159179
header.click_bot_button
160180

161181
expect(ai_pm_homepage).to have_homepage
162-
sidebar.find(
163-
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link",
164-
).click
182+
ai_pm_homepage.click_fist_sidebar_conversation
165183
expect(topic_page).to have_topic_title(pm.title)
166184
end
167185

@@ -173,9 +191,7 @@
173191
expect(header).to have_icon_in_bot_button(icon: "shuffle")
174192

175193
# Go to a PM and assert that the icon is still shuffle
176-
sidebar.find(
177-
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link",
178-
).click
194+
ai_pm_homepage.click_fist_sidebar_conversation
179195
expect(header).to have_icon_in_bot_button(icon: "shuffle")
180196

181197
# Go back home and assert that the icon is now robot again

spec/system/page_objects/components/ai_pm_homepage.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,12 @@ def click_new_question_button
4040
page.find(".ai-new-question-button").click
4141
end
4242

43+
def click_fist_sidebar_conversation
44+
page.find(
45+
".sidebar-section[data-section-name='ai-conversations-history'] a.sidebar-section-link:not(.date-heading)",
46+
).click
47+
end
48+
4349
def persona_selector
4450
PageObjects::Components::SelectKit.new(".persona-llm-selector__persona-dropdown")
4551
end

0 commit comments

Comments
 (0)