From 448ac6721531754c5e4a39ae7c0f3ffb35d53bc1 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 21 Feb 2025 11:28:08 -0800 Subject: [PATCH 1/7] DEV: make include subcategories checkbox operational --- lib/sentiment/sentiment_analysis_report.rb | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/sentiment/sentiment_analysis_report.rb b/lib/sentiment/sentiment_analysis_report.rb index 17ad72cb8..8d1f73538 100644 --- a/lib/sentiment/sentiment_analysis_report.rb +++ b/lib/sentiment/sentiment_analysis_report.rb @@ -31,7 +31,8 @@ def self.register!(plugin) auto_insert_none_item: false, ) - report.add_category_filter(disabled: group_by_filter.to_sym == :tag) + category_id, include_subcategories = + report.add_category_filter(disabled: group_by_filter.to_sym == :tag) tag_filter = report.filters.dig(:tag) || "any" tag_choices = @@ -49,7 +50,8 @@ def self.register!(plugin) disabled: group_by_filter.to_sym == :category, ) - sentiment_data = DiscourseAi::Sentiment::SentimentAnalysisReport.fetch_data(report) + opts = { category_id: category_id, include_subcategories: include_subcategories } + sentiment_data = DiscourseAi::Sentiment::SentimentAnalysisReport.fetch_data(report, opts) report.data = sentiment_data report.labels = [ @@ -60,12 +62,13 @@ def self.register!(plugin) end end - def self.fetch_data(report) + def self.fetch_data(report, opts) threshold = SENTIMENT_THRESHOLD grouping = (report.filters.dig(:group_by) || GROUP_BY_FILTER_DEFAULT).to_sym sorting = (report.filters.dig(:sort_by) || SORT_BY_FILTER_DEFAULT).to_sym category_filter = report.filters.dig(:category) + pp "========================== category_filter ===================================== #{category_filter} include subcategories?: #{opts[:include_subcategories]}" tag_filter = report.filters.dig(:tag) sentiment_count_sql = Proc.new { |sentiment| <<~SQL } @@ -118,6 +121,10 @@ def self.fetch_data(report) when :category if category_filter.nil? "" + elsif opts[:include_subcategories] + <<~SQL + AND (c.id = :category_filter OR c.parent_category_id = :category_filter) + SQL else "AND c.id = :category_filter" end From 3066dff71dfc38f127ac1fc76cbb28c2e74e8a8d Mon Sep 17 00:00:00 2001 From: Keegan George Date: Fri, 21 Feb 2025 12:53:49 -0800 Subject: [PATCH 2/7] DEV: add pagination for post requests --- .../sentiment/sentiment_controller.rb | 33 +++++++++--- .../admin-report-sentiment-analysis.gjs | 50 +++++++++++++++---- lib/sentiment/sentiment_analysis_report.rb | 1 - 3 files changed, 64 insertions(+), 20 deletions(-) diff --git a/app/controllers/discourse_ai/sentiment/sentiment_controller.rb b/app/controllers/discourse_ai/sentiment/sentiment_controller.rb index 6fbe3fd2f..c45d2780a 100644 --- a/app/controllers/discourse_ai/sentiment/sentiment_controller.rb +++ b/app/controllers/discourse_ai/sentiment/sentiment_controller.rb @@ -6,6 +6,11 @@ class SentimentController < ::Admin::StaffController include Constants requires_plugin ::DiscourseAi::PLUGIN_NAME + # DEFAULT_POSTS_LIMIT = 50 + # MAX_POSTS_LIMIT = 100 + DEFAULT_POSTS_LIMIT = 3 + MAX_POSTS_LIMIT = 3 + def posts group_by = params.required(:group_by)&.to_sym group_value = params.required(:group_value).presence @@ -15,6 +20,9 @@ def posts raise Discourse::InvalidParameters if %i[category tag].exclude?(group_by) + limit = fetch_limit_from_params(default: DEFAULT_POSTS_LIMIT, max: MAX_POSTS_LIMIT) + offset = params[:offset].to_i || 0 + case group_by when :category grouping_clause = "c.name" @@ -56,22 +64,31 @@ def posts ((:start_date IS NULL OR p.created_at > :start_date) AND (:end_date IS NULL OR p.created_at < :end_date)) AND p.deleted_at IS NULL ORDER BY p.created_at DESC + LIMIT :limit OFFSET :offset SQL group_value: group_value, start_date: start_date, end_date: end_date, threshold: threshold, + limit: limit + 1, + offset: offset, ) + has_more = posts.length > limit + posts.pop if has_more + render_json_dump( - serialize_data( - posts, - AiSentimentPostSerializer, - scope: guardian, - add_raw: true, - add_excerpt: true, - add_title: true, - ), + posts: + serialize_data( + posts, + AiSentimentPostSerializer, + scope: guardian, + add_raw: true, + add_excerpt: true, + add_title: true, + ), + has_more: has_more, + next_offset: has_more ? offset + limit : nil, ) end end diff --git a/assets/javascripts/discourse/components/admin-report-sentiment-analysis.gjs b/assets/javascripts/discourse/components/admin-report-sentiment-analysis.gjs index 77e29a9b1..a3ebdc65f 100644 --- a/assets/javascripts/discourse/components/admin-report-sentiment-analysis.gjs +++ b/assets/javascripts/discourse/components/admin-report-sentiment-analysis.gjs @@ -15,6 +15,8 @@ import DoughnutChart from "discourse/plugins/discourse-ai/discourse/components/d export default class AdminReportSentimentAnalysis extends Component { @tracked selectedChart = null; @tracked posts = null; + @tracked hasMorePosts = false; + @tracked nextOffset = 0; get colors() { return ["#2ecc71", "#95a5a6", "#e74c3c"]; @@ -50,20 +52,45 @@ export default class AdminReportSentimentAnalysis extends Component { }); } + async postRequest() { + return await ajax("/discourse-ai/sentiment/posts", { + data: { + group_by: this.currentGroupFilter, + group_value: this.selectedChart?.title, + start_date: this.args.model.start_date, + end_date: this.args.model.end_date, + offset: this.nextOffset, + }, + }); + } + @action async showDetails(data) { this.selectedChart = data; + try { - const posts = await ajax(`/discourse-ai/sentiment/posts`, { - data: { - group_by: this.currentGroupFilter, - group_value: data.title, - start_date: this.args.model.start_date, - end_date: this.args.model.end_date, - }, - }); - - this.posts = posts.map((post) => Post.create(post)); + const response = await this.postRequest(); + + this.posts = response.posts.map((post) => Post.create(post)); + this.hasMorePosts = response.has_more; + this.nextOffset = response.next_offset; + } catch (e) { + popupAjaxError(e); + } + } + + @action + async fetchMorePosts() { + if (!this.hasMorePosts || this.selectedChart === null) { + return []; + } + + try { + const response = await this.postRequest(); + + this.hasMorePosts = response.has_more; + this.nextOffset = response.next_offset; + return response.posts.map((post) => Post.create(post)); } catch (e) { popupAjaxError(e); } @@ -139,7 +166,7 @@ export default class AdminReportSentimentAnalysis extends Component {