Revert "DEV: Updates to sentiment analysis reports (#1161)"

This reverts commit 8863cf0c86140065978d33ee9dca421fc8670fb5.

The conditionals added around the report registrations are incompatible with multisite environment and skip_db=true
This commit is contained in:
David Taylor 2025-03-06 00:18:38 +00:00
parent e255c7a8f0
commit 86bdc7b517
No known key found for this signature in database
GPG Key ID: 46904C18B1D3F434
9 changed files with 75 additions and 201 deletions

View File

@ -37,7 +37,7 @@ module DiscourseAi
SELECT SELECT
p.id AS post_id, p.id AS post_id,
p.topic_id, p.topic_id,
t.fancy_title AS topic_title, t.title AS topic_title,
p.cooked as post_cooked, p.cooked as post_cooked,
p.user_id, p.user_id,
p.post_number, p.post_number,

View File

@ -2,10 +2,6 @@
class ClassificationResult < ActiveRecord::Base class ClassificationResult < ActiveRecord::Base
belongs_to :target, polymorphic: true belongs_to :target, polymorphic: true
def self.has_sentiment_classification?
where(classification_type: "sentiment").exists?
end
end end
# == Schema Information # == Schema Information

View File

@ -3,36 +3,26 @@ import { tracked } from "@glimmer/tracking";
import { fn, hash } from "@ember/helper"; import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier"; import { on } from "@ember/modifier";
import { action } from "@ember/object"; import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import { modifier } from "ember-modifier"; import { modifier } from "ember-modifier";
import { and } from "truth-helpers"; import { and } from "truth-helpers";
import DButton from "discourse/components/d-button"; import DButton from "discourse/components/d-button";
import HorizontalOverflowNav from "discourse/components/horizontal-overflow-nav"; import HorizontalOverflowNav from "discourse/components/horizontal-overflow-nav";
import PostList from "discourse/components/post-list"; import PostList from "discourse/components/post-list";
import dIcon from "discourse/helpers/d-icon"; import dIcon from "discourse/helpers/d-icon";
import replaceEmoji from "discourse/helpers/replace-emoji";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error"; import { popupAjaxError } from "discourse/lib/ajax-error";
import { getAbsoluteURL } from "discourse/lib/get-url";
import discourseLater from "discourse/lib/later";
import { clipboardCopy } from "discourse/lib/utilities";
import Post from "discourse/models/post"; import Post from "discourse/models/post";
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside"; import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
import { i18n } from "discourse-i18n"; import { i18n } from "discourse-i18n";
import DTooltip from "float-kit/components/d-tooltip";
import DoughnutChart from "discourse/plugins/discourse-ai/discourse/components/doughnut-chart"; import DoughnutChart from "discourse/plugins/discourse-ai/discourse/components/doughnut-chart";
export default class AdminReportSentimentAnalysis extends Component { export default class AdminReportSentimentAnalysis extends Component {
@service router;
@tracked selectedChart = null; @tracked selectedChart = null;
@tracked posts = []; @tracked posts = null;
@tracked hasMorePosts = false; @tracked hasMorePosts = false;
@tracked nextOffset = 0; @tracked nextOffset = 0;
@tracked showingSelectedChart = false; @tracked showingSelectedChart = false;
@tracked activeFilter = "all"; @tracked activeFilter = "all";
@tracked shareIcon = "link";
setActiveFilter = modifier((element) => { setActiveFilter = modifier((element) => {
this.clearActiveFilters(element); this.clearActiveFilters(element);
@ -81,6 +71,32 @@ export default class AdminReportSentimentAnalysis extends Component {
} }
} }
doughnutTitle(data) {
const MAX_TITLE_LENGTH = 18;
const title = data?.title || "";
const score = data?.total_score ? ` (${data.total_score})` : "";
if (title.length + score.length > MAX_TITLE_LENGTH) {
return (
title.substring(0, MAX_TITLE_LENGTH - score.length) + "..." + score
);
}
return title + score;
}
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,
},
});
}
get colors() { get colors() {
return ["#2ecc71", "#95a5a6", "#e74c3c"]; return ["#2ecc71", "#95a5a6", "#e74c3c"];
} }
@ -117,11 +133,10 @@ export default class AdminReportSentimentAnalysis extends Component {
} }
return this.posts.filter((post) => { return this.posts.filter((post) => {
post.topic_title = replaceEmoji(post.topic_title);
if (this.activeFilter === "all") { if (this.activeFilter === "all") {
return true; return true;
} }
return post.sentiment === this.activeFilter; return post.sentiment === this.activeFilter;
}); });
} }
@ -171,42 +186,6 @@ 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 openToChart() {
const queryParams = this.router.currentRoute.queryParams;
if (queryParams.selectedChart) {
this.selectedChart = this.transformedData.find(
(data) => data.title === queryParams.selectedChart
);
if (!this.selectedChart) {
return;
}
this.showingSelectedChart = true;
try {
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 @action
async showDetails(data) { async showDetails(data) {
if (this.selectedChart === data) { if (this.selectedChart === data) {
@ -214,14 +193,6 @@ export default class AdminReportSentimentAnalysis extends Component {
return; return;
} }
const currentQueryParams = this.router.currentRoute.queryParams;
this.router.transitionTo(this.router.currentRoute.name, {
queryParams: {
...currentQueryParams,
selectedChart: data.title,
},
});
this.selectedChart = data; this.selectedChart = data;
this.showingSelectedChart = true; this.showingSelectedChart = true;
@ -246,10 +217,7 @@ export default class AdminReportSentimentAnalysis extends Component {
this.hasMorePosts = response.has_more; this.hasMorePosts = response.has_more;
this.nextOffset = response.next_offset; this.nextOffset = response.next_offset;
return response.posts.map((post) => Post.create(post));
const mappedPosts = response.posts.map((post) => Post.create(post));
this.posts.pushObjects(mappedPosts);
return mappedPosts;
} catch (e) { } catch (e) {
popupAjaxError(e); popupAjaxError(e);
} }
@ -260,35 +228,9 @@ export default class AdminReportSentimentAnalysis extends Component {
this.showingSelectedChart = false; this.showingSelectedChart = false;
this.selectedChart = null; this.selectedChart = null;
this.activeFilter = "all"; this.activeFilter = "all";
this.posts = [];
const currentQueryParams = this.router.currentRoute.queryParams;
this.router.transitionTo(this.router.currentRoute.name, {
queryParams: {
...currentQueryParams,
selectedChart: null,
},
});
}
@action
shareChart() {
const url = this.router.currentURL;
if (!url) {
return;
}
clipboardCopy(getAbsoluteURL(url));
this.shareIcon = "check";
discourseLater(() => {
this.shareIcon = "link";
}, 2000);
} }
<template> <template>
<span {{didInsert this.openToChart}}></span>
{{#unless this.showingSelectedChart}} {{#unless this.showingSelectedChart}}
<div class="admin-report-sentiment-analysis"> <div class="admin-report-sentiment-analysis">
{{#each this.transformedData as |data|}} {{#each this.transformedData as |data|}}
@ -310,7 +252,6 @@ export default class AdminReportSentimentAnalysis extends Component {
@data={{data.scores}} @data={{data.scores}}
@totalScore={{data.total_score}} @totalScore={{data.total_score}}
@doughnutTitle={{data.title}} @doughnutTitle={{data.title}}
@displayLegend={{true}}
/> />
</div> </div>
{{/each}} {{/each}}
@ -319,23 +260,12 @@ export default class AdminReportSentimentAnalysis extends Component {
{{#if (and this.selectedChart this.showingSelectedChart)}} {{#if (and this.selectedChart this.showingSelectedChart)}}
<div class="admin-report-sentiment-analysis__selected-chart"> <div class="admin-report-sentiment-analysis__selected-chart">
<div class="admin-report-sentiment-analysis__selected-chart-actions"> <DButton
<DButton @label="back_button"
@label="back_button" @icon="chevron-left"
@icon="chevron-left" class="btn-flat"
class="btn-flat" @action={{this.backToAllCharts}}
@action={{this.backToAllCharts}} />
/>
<DTooltip
class="share btn-flat"
@icon={{this.shareIcon}}
{{on "click" this.shareChart}}
@content={{i18n
"discourse_ai.sentiments.sentiment_analysis.share_chart"
}}
/>
</div>
<DoughnutChart <DoughnutChart
@labels={{@model.labels}} @labels={{@model.labels}}
@ -343,9 +273,7 @@ export default class AdminReportSentimentAnalysis extends Component {
@data={{this.selectedChart.scores}} @data={{this.selectedChart.scores}}
@totalScore={{this.selectedChart.total_score}} @totalScore={{this.selectedChart.total_score}}
@doughnutTitle={{this.selectedChart.title}} @doughnutTitle={{this.selectedChart.title}}
@displayLegend={{true}}
/> />
</div> </div>
<div class="admin-report-sentiment-analysis-details"> <div class="admin-report-sentiment-analysis-details">
<HorizontalOverflowNav <HorizontalOverflowNav

View File

@ -1,10 +1,7 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import Chart from "admin/components/chart"; import Chart from "admin/components/chart";
export default class DoughnutChart extends Component { export default class DoughnutChart extends Component {
@tracked canvasSize = null;
get config() { get config() {
const totalScore = this.args.totalScore || ""; const totalScore = this.args.totalScore || "";
@ -16,18 +13,14 @@ export default class DoughnutChart extends Component {
{ {
data: this.args.data, data: this.args.data,
backgroundColor: this.args.colors, backgroundColor: this.args.colors,
cutout: "50%",
radius: 100,
}, },
], ],
}, },
options: { options: {
responsive: true, responsive: true,
maintainAspectRatio: false,
plugins: { plugins: {
legend: { legend: {
display: this.args.displayLegend || false, position: this.args.legendPosition || "bottom",
position: "bottom",
}, },
}, },
}, },

View File

@ -22,13 +22,6 @@ export default {
"sentiment_analysis", "sentiment_analysis",
AdminReportSentimentAnalysis AdminReportSentimentAnalysis
); );
api.registerValueTransformer(
"admin-reports-show-query-params",
({ value }) => {
return [...value, "selectedChart"];
}
);
}); });
}, },
}; };

View File

@ -1,27 +1,21 @@
import { apiInitializer } from "discourse/lib/api"; import { apiInitializer } from "discourse/lib/api";
export default apiInitializer("1.15.0", (api) => { export default apiInitializer("1.15.0", (api) => {
const currentUser = api.getCurrentUser(); const settings = api.container.lookup("service:site-settings");
if ( if (settings.ai_sentiment_enabled) {
!currentUser || api.addAdminSidebarSectionLink("reports", {
!currentUser.admin || name: "sentiment_overview",
!currentUser.can_see_sentiment_reports route: "admin.dashboardSentiment",
) { label: "discourse_ai.sentiments.sidebar.overview",
return; icon: "chart-column",
});
api.addAdminSidebarSectionLink("reports", {
name: "sentiment_analysis",
route: "adminReports.show",
routeModels: ["sentiment_analysis"],
label: "discourse_ai.sentiments.sidebar.analysis",
icon: "chart-pie",
});
} }
api.addAdminSidebarSectionLink("reports", {
name: "sentiment_overview",
route: "admin.dashboardSentiment",
label: "discourse_ai.sentiments.sidebar.overview",
icon: "chart-column",
});
api.addAdminSidebarSectionLink("reports", {
name: "sentiment_analysis",
route: "adminReports.show",
routeModels: ["sentiment_analysis"],
label: "discourse_ai.sentiments.sidebar.analysis",
icon: "chart-pie",
});
}); });

View File

@ -39,12 +39,13 @@
flex: 1; flex: 1;
} }
// Hides tag selector when showing subcategories selector .control:nth-of-type(n + 6) {
.control:nth-of-type(6):nth-last-of-type(3) { flex-basis: 49%;
display: none; align-self: flex-end;
} }
.control:has(.export-csv-btn) { // Hides tag selector when showing subcategories selector
.control:nth-of-type(6):nth-last-of-type(3) {
display: none; display: none;
} }
} }
@ -54,6 +55,7 @@
display: flex; display: flex;
flex-flow: row wrap; flex-flow: row wrap;
order: 2; order: 2;
gap: 1rem;
align-items: flex-start; align-items: flex-start;
max-height: 100vh; max-height: 100vh;
} }
@ -63,31 +65,30 @@
@include report-container-box(); @include report-container-box();
flex: 2; flex: 2;
display: flex; display: flex;
gap: 1rem;
justify-content: space-around;
align-items: center;
flex-flow: row wrap; flex-flow: row wrap;
gap: 3rem;
.admin-report-doughnut { .admin-report-doughnut {
max-width: 300px;
max-height: 300px;
padding: 0.25rem; padding: 0.25rem;
} }
&__chart-wrapper { &__chart-wrapper {
height: fit-content; width: auto;
position: relative;
transition: transform 0.25s ease, box-shadow 0.25s ease;
border-radius: var(--d-border-radius);
.doughnut-chart-title { .doughnut-chart-title {
@include ellipsis; @include ellipsis;
margin: 0 auto; margin: 0 auto;
margin-top: 1rem;
text-align: center; text-align: center;
margin-bottom: 1rem;
max-width: 300px;
} }
transition: transform 0.25s ease, box-shadow 0.25s ease;
border-radius: var(--d-border-radius);
&:hover { &:hover {
box-shadow: var(--shadow-card);
transform: translateY(-1rem); transform: translateY(-1rem);
box-shadow: var(--shadow-card);
cursor: pointer; cursor: pointer;
} }
} }
@ -101,23 +102,11 @@
font-size: var(--font-up-2); font-size: var(--font-up-2);
margin: 0 auto; margin: 0 auto;
text-align: center; text-align: center;
margin-bottom: 1rem;
margin-top: 0.3rem; margin-top: 0.3rem;
padding-top: 2rem; padding-top: 2rem;
} padding-bottom: 1rem;
} border-top: 1px solid var(--primary-low);
&__selected-chart-actions {
display: flex;
align-items: center;
padding-bottom: 0.35rem;
border-bottom: 1px solid var(--primary-low);
.share {
margin-left: auto;
.d-icon-check {
color: var(--success);
}
} }
} }
} }
@ -132,12 +121,11 @@
@include report-container-box(); @include report-container-box();
flex: 1 1 300px; flex: 1 1 300px;
min-width: 300px; min-width: 300px;
margin-left: 1rem;
display: flex; display: flex;
flex-flow: column nowrap; flex-flow: column nowrap;
overflow-y: auto; overflow-y: auto;
height: 100%; height: 100%;
padding-top: 0;
&__filters { &__filters {
border-bottom: 1px solid var(--primary-low); border-bottom: 1px solid var(--primary-low);
@ -212,13 +200,3 @@
} }
} }
} }
.admin-reports.admin-contents .sentiment-analysis {
.horizontal-overflow-nav {
background: var(--secondary);
position: sticky;
top: 0;
padding-top: 1rem;
z-index: z("header");
}
}

View File

@ -678,7 +678,6 @@ en:
overview: "Sentiment overview" overview: "Sentiment overview"
analysis: "Sentiment analysis" analysis: "Sentiment analysis"
sentiment_analysis: sentiment_analysis:
share_chart: "Copy link to chart"
filter_types: filter_types:
all: "All" all: "All"
positive: "Positive" positive: "Positive"

View File

@ -14,17 +14,10 @@ module DiscourseAi
plugin.on(:post_created, &sentiment_analysis_cb) plugin.on(:post_created, &sentiment_analysis_cb)
plugin.on(:post_edited, &sentiment_analysis_cb) plugin.on(:post_edited, &sentiment_analysis_cb)
plugin.add_to_serializer(:current_user, :can_see_sentiment_reports) do EmotionFilterOrder.register!(plugin)
ClassificationResult.has_sentiment_classification? && SiteSetting.ai_sentiment_enabled EmotionDashboardReport.register!(plugin)
end SentimentDashboardReport.register!(plugin)
SentimentAnalysisReport.register!(plugin)
if Rails.env.test? ||
ClassificationResult.has_sentiment_classification? && SiteSetting.ai_sentiment_enabled
EmotionFilterOrder.register!(plugin)
EmotionDashboardReport.register!(plugin)
SentimentDashboardReport.register!(plugin)
SentimentAnalysisReport.register!(plugin)
end
end end
end end
end end