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

Commit 606ae7b

Browse files
committed
DEV: First version with real data
1 parent 99ac3cf commit 606ae7b

File tree

7 files changed

+411
-68
lines changed

7 files changed

+411
-68
lines changed
Lines changed: 129 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,143 @@
11
import Component from "@glimmer/component";
2-
import Chart from "admin/components/chart";
2+
import { tracked } from "@glimmer/tracking";
3+
import { fn, hash } from "@ember/helper";
4+
import { on } from "@ember/modifier";
5+
import { action, get } from "@ember/object";
6+
import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
7+
import dIcon from "discourse-common/helpers/d-icon";
8+
import { i18n } from "discourse-i18n";
9+
import DoughnutChart from "./doughnut-chart";
10+
import PostList from "discourse/components/post-list";
311

412
export default class AdminReportSentimentAnalysis extends Component {
5-
get chartConfig() {
6-
return {
7-
type: "doughnut",
8-
data: {
9-
labels: ["Positive", "Neutral", "Negative"],
10-
datasets: [
11-
{
12-
data: [300, 50, 100],
13-
backgroundColor: ["#2ecc71", "#95a5a6", "#e74c3c"],
14-
},
13+
@tracked selectedChart = null;
14+
15+
get labels() {
16+
return ["Positive", "Neutral", "Negative"];
17+
}
18+
19+
get colors() {
20+
return ["#2ecc71", "#95a5a6", "#e74c3c"];
21+
}
22+
23+
get transformedData() {
24+
return this.args.model.data.map((data) => {
25+
return {
26+
category_name: data.category_name,
27+
scores: [
28+
data.overall_scores.positive,
29+
data.overall_scores.neutral,
30+
data.overall_scores.negative,
1531
],
16-
},
17-
options: {
18-
responsive: true,
19-
plugins: {
20-
legend: {
21-
position: "bottom",
22-
},
23-
},
24-
},
25-
plugins: [
26-
{
27-
id: "centerText",
28-
afterDraw: function (chart) {
29-
const cssVarColor =
30-
getComputedStyle(document.documentElement).getPropertyValue(
31-
"--primary"
32-
) || "#000";
33-
const cssFontSize =
34-
getComputedStyle(document.documentElement).getPropertyValue(
35-
"--font-down-2"
36-
) || "1.3em";
37-
const cssFontFamily =
38-
getComputedStyle(document.documentElement).getPropertyValue(
39-
"--font-family"
40-
) || "sans-serif";
32+
// TODO slicing 3 posts for now
33+
posts: data.posts,
34+
};
35+
});
36+
}
4137

42-
const { ctx, chartArea } = chart;
43-
const centerX = (chartArea.left + chartArea.right) / 2;
44-
const centerY = (chartArea.top + chartArea.bottom) / 2;
38+
@action
39+
showDetails(data) {
40+
console.log(data);
41+
this.selectedChart = data;
42+
}
4543

46-
ctx.restore();
47-
ctx.textAlign = "center";
48-
ctx.textBaseline = "middle";
49-
ctx.fillStyle = cssVarColor.trim();
50-
ctx.font = `${cssFontSize.trim()} ${cssFontFamily.trim()}`;
44+
sentimentTopScore(post) {
45+
const { positive_score, neutral_score, negative_score } = post;
46+
const maxScore = Math.max(positive_score, neutral_score, negative_score);
5147

52-
// TODO: populate with actual tag / category title
53-
ctx.fillText("member-experience", centerX, centerY);
54-
ctx.save();
55-
},
56-
},
57-
],
58-
};
48+
if (maxScore === positive_score) {
49+
return {
50+
id: "positive",
51+
text: i18n(
52+
"discourse_ai.sentiments.sentiment_analysis.score_types.positive"
53+
),
54+
icon: "face-smile",
55+
};
56+
} else if (maxScore === neutral_score) {
57+
return {
58+
id: "neutral",
59+
text: i18n(
60+
"discourse_ai.sentiments.sentiment_analysis.score_types.neutral"
61+
),
62+
icon: "face-meh",
63+
};
64+
} else {
65+
return {
66+
id: "negative",
67+
text: i18n(
68+
"discourse_ai.sentiments.sentiment_analysis.score_types.negative"
69+
),
70+
icon: "face-angry",
71+
};
72+
}
5973
}
6074

6175
<template>
62-
{{! TODO each-loop based on data, display doughnut component }}
76+
{{! TODO add more details about posts on click + tag data }}
6377
<div class="admin-report-sentiment-analysis">
64-
<Chart @chartConfig={{this.chartConfig}} class="admin-report-doughnut" />
65-
<Chart @chartConfig={{this.chartConfig}} class="admin-report-doughnut" />
66-
<Chart @chartConfig={{this.chartConfig}} class="admin-report-doughnut" />
67-
<Chart @chartConfig={{this.chartConfig}} class="admin-report-doughnut" />
68-
<Chart @chartConfig={{this.chartConfig}} class="admin-report-doughnut" />
78+
{{#each this.transformedData as |data|}}
79+
<div
80+
class="admin-report-sentiment-analysis__chart-wrapper"
81+
{{on "click" (fn this.showDetails data)}}
82+
{{closeOnClickOutside
83+
(fn (mut this.selectedChart) null)
84+
(hash
85+
targetSelector=".admin-report-sentiment-analysis-details"
86+
secondaryTargetSelector=".admin-report-sentiment-analysis"
87+
)
88+
}}
89+
>
90+
<DoughnutChart
91+
@labels={{this.labels}}
92+
@colors={{this.colors}}
93+
@data={{data.scores}}
94+
@doughnutTitle={{data.category_name}}
95+
/>
96+
</div>
97+
{{/each}}
6998
</div>
99+
100+
{{#if this.selectedChart}}
101+
<div class="admin-report-sentiment-analysis-details">
102+
<h3 class="admin-report-sentiment-analysis-details__title">
103+
{{this.selectedChart.category_name}}
104+
</h3>
105+
106+
<ul class="admin-report-sentiment-analysis-details__scores">
107+
<li>
108+
{{dIcon "face-smile" style="color: #2ecc71"}}
109+
{{i18n
110+
"discourse_ai.sentiments.sentiment_analysis.score_types.positive"
111+
}}:
112+
{{get this.selectedChart.scores 0}}</li>
113+
<li>
114+
{{dIcon "face-meh"}}
115+
{{i18n
116+
"discourse_ai.sentiments.sentiment_analysis.score_types.neutral"
117+
}}:
118+
{{get this.selectedChart.scores 1}}</li>
119+
<li>
120+
{{dIcon "face-angry"}}
121+
{{i18n
122+
"discourse_ai.sentiments.sentiment_analysis.score_types.negative"
123+
}}:
124+
{{get this.selectedChart.scores 2}}</li>
125+
</ul>
126+
127+
<PostList @posts={{this.selectedChart.posts}} @urlPath="postUrl">
128+
<:abovePostItemExcerpt as |post|>
129+
{{#let (this.sentimentTopScore post) as |score|}}
130+
<span
131+
class="admin-report-sentiment-analysis-details__post-score"
132+
data-sentiment-score={{score.id}}
133+
>
134+
{{dIcon score.icon}}
135+
{{score.text}}
136+
</span>
137+
{{/let}}
138+
</:abovePostItemExcerpt>
139+
</PostList>
140+
</div>
141+
{{/if}}
70142
</template>
71143
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import Component from "@glimmer/component";
2+
import Chart from "admin/components/chart";
3+
4+
export default class DoughnutChart extends Component {
5+
get config() {
6+
const doughnutTitle = this.args.doughnutTitle || "";
7+
8+
return {
9+
type: "doughnut",
10+
data: {
11+
labels: this.args.labels,
12+
datasets: [
13+
{
14+
data: this.args.data,
15+
backgroundColor: this.args.colors,
16+
},
17+
],
18+
},
19+
options: {
20+
responsive: true,
21+
plugins: {
22+
legend: {
23+
position: this.args.legendPosition || "bottom",
24+
},
25+
},
26+
},
27+
plugins: [
28+
{
29+
id: "centerText",
30+
afterDraw: function (chart) {
31+
const cssVarColor =
32+
getComputedStyle(document.documentElement).getPropertyValue(
33+
"--primary"
34+
) || "#000";
35+
const cssFontSize =
36+
getComputedStyle(document.documentElement).getPropertyValue(
37+
"--font-down-2"
38+
) || "1.3em";
39+
const cssFontFamily =
40+
getComputedStyle(document.documentElement).getPropertyValue(
41+
"--font-family"
42+
) || "sans-serif";
43+
44+
const { ctx, chartArea } = chart;
45+
const centerX = (chartArea.left + chartArea.right) / 2;
46+
const centerY = (chartArea.top + chartArea.bottom) / 2;
47+
48+
ctx.restore();
49+
ctx.textAlign = "center";
50+
ctx.textBaseline = "middle";
51+
ctx.fillStyle = cssVarColor.trim();
52+
ctx.font = `${cssFontSize.trim()} ${cssFontFamily.trim()}`;
53+
54+
ctx.fillText(doughnutTitle, centerX, centerY);
55+
ctx.save();
56+
},
57+
},
58+
],
59+
};
60+
}
61+
62+
<template>
63+
<Chart @chartConfig={{this.config}} class="admin-report-doughnut" />
64+
</template>
65+
}

assets/stylesheets/modules/sentiment/common/dashboard.scss

Lines changed: 102 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,120 @@
1010
}
1111
}
1212

13-
.admin-report.sentiment-analyis {
13+
@mixin report-container-box() {
14+
border: 1px solid var(--primary-low);
15+
border-radius: var(--d-border-radius);
16+
padding: 1rem;
17+
}
18+
19+
.admin-report.sentiment-analysis .body {
20+
display: flex;
21+
flex-flow: row wrap;
22+
gap: 1rem;
23+
1424
.filters {
1525
order: 1;
16-
width: 100%;
26+
width: 300px;
27+
@include report-container-box();
28+
margin-left: 0;
29+
// width: 300px;
1730
}
1831

1932
.main {
33+
flex: 100%;
34+
35+
display: flex;
2036
order: 2;
37+
gap: 1rem;
2138
}
2239
}
2340

2441
.admin-report-sentiment-analysis {
25-
margin-top: 1rem;
26-
display: grid;
27-
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
28-
gap: 2.5rem;
29-
justify-items: center;
42+
@include report-container-box();
43+
flex: 2;
44+
display: flex;
45+
flex-flow: row wrap;
46+
gap: 3rem;
3047

3148
.admin-report-doughnut {
32-
max-width: 300px; /* Adjust size */
49+
max-width: 300px;
3350
max-height: 300px;
51+
padding: 0.25rem;
52+
}
53+
54+
&__chart-wrapper {
55+
transition: transform 0.25s ease, box-shadow 0.25s ease;
56+
border-radius: var(--d-border-radius);
57+
58+
&:hover {
59+
@include transform(translateY(-1rem));
60+
box-shadow: var(--shadow-card);
61+
cursor: pointer;
62+
}
63+
}
64+
}
65+
66+
:root {
67+
--d-sentiment-report-positive-rgb: 46, 204, 112;
68+
--d-sentiment-report-neutral-rgb: 149, 166, 167;
69+
--d-sentiment-report-negative-rgb: 231, 77, 60;
70+
}
71+
72+
.admin-report-sentiment-analysis-details {
73+
@include report-container-box();
74+
flex: 1;
75+
display: flex;
76+
flex-flow: column nowrap;
77+
78+
&__title {
79+
font-size: var(--font-up-2);
80+
}
81+
82+
&__scores {
83+
display: flex;
84+
flex-flow: column wrap;
85+
align-items: flex-start;
86+
justify-content: flex-start;
87+
gap: 0.25rem;
88+
list-style: none;
89+
margin-left: 0;
90+
background: var(--primary-very-low);
91+
padding: 1rem;
92+
border-radius: var(--d-border-radius);
93+
94+
.d-icon-face-smile {
95+
color: rgb(var(--d-sentiment-report-positive-rgb));
96+
}
97+
98+
.d-icon-face-meh {
99+
color: rgb(var(--d-sentiment-report-neutral-rgb));
100+
}
101+
102+
.d-icon-face-angry {
103+
color: rgb(var(--d-sentiment-report-negative-rgb));
104+
}
105+
}
106+
107+
&__post-score {
108+
border-radius: var(--d-border-radius);
109+
background: var(--primary-very-low);
110+
margin-top: 0.5rem;
111+
padding: 0.25rem;
112+
font-size: var(--font-down-1);
113+
display: inline-block;
114+
&[data-sentiment-score="positive"] {
115+
color: rgb(var(--d-sentiment-report-positive-rgb));
116+
background: rgba(var(--d-sentiment-report-positive-rgb), 0.1);
117+
}
118+
119+
&[data-sentiment-score="neutral"] {
120+
color: rgb(var(--d-sentiment-report-neutral-rgb));
121+
background: rgba(var(--d-sentiment-report-neutral-rgb), 0.1);
122+
}
123+
124+
&[data-sentiment-score="negative"] {
125+
color: rgb(var(--d-sentiment-report-negative-rgb));
126+
background: rgba(var(--d-sentiment-report-negative-rgb), 0.1);
127+
}
34128
}
35129
}

0 commit comments

Comments
 (0)