Skip to content

Commit 3d4465e

Browse files
authored
feat(metrics): add reaction metric pill (#2580)
* feat(metrics): add reaction metric pill Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(metrics): add reaction metric pill Signed-off-by: Adam Setch <adam.setch@outlook.com> * feat(metrics): add reaction metric pill Signed-off-by: Adam Setch <adam.setch@outlook.com> * address sonar feedback Signed-off-by: Adam Setch <adam.setch@outlook.com> * address sonar feedback Signed-off-by: Adam Setch <adam.setch@outlook.com> --------- Signed-off-by: Adam Setch <adam.setch@outlook.com>
1 parent 3164149 commit 3d4465e

17 files changed

Lines changed: 2760 additions & 71 deletions

File tree

src/renderer/components/metrics/MetricGroup.test.tsx

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,102 @@ describe('renderer/components/metrics/MetricGroup.tsx', () => {
4848
});
4949
});
5050

51+
describe('reactions pills', () => {
52+
it('should render reaction pill when one reaction', async () => {
53+
const mockNotification = mockGitifyNotification;
54+
mockNotification.subject.reactionsCount = 1;
55+
mockNotification.subject.reactionGroups = [
56+
{
57+
content: 'ROCKET',
58+
reactors: {
59+
totalCount: 1,
60+
},
61+
},
62+
];
63+
64+
const props: MetricGroupProps = {
65+
notification: mockNotification,
66+
};
67+
68+
const tree = renderWithAppContext(<MetricGroup {...props} />);
69+
expect(tree).toMatchSnapshot();
70+
});
71+
72+
it('should render reaction pill when multiple reactions', async () => {
73+
const mockNotification = mockGitifyNotification;
74+
mockNotification.subject.reactionsCount = 53;
75+
mockNotification.subject.reactionGroups = [
76+
{
77+
content: 'THUMBS_UP',
78+
reactors: {
79+
totalCount: 1,
80+
},
81+
},
82+
{
83+
content: 'THUMBS_DOWN',
84+
reactors: {
85+
totalCount: 1,
86+
},
87+
},
88+
{
89+
content: 'LAUGH',
90+
reactors: {
91+
totalCount: 2,
92+
},
93+
},
94+
{
95+
content: 'HOORAY',
96+
reactors: {
97+
totalCount: 3,
98+
},
99+
},
100+
{
101+
content: 'CONFUSED',
102+
reactors: {
103+
totalCount: 5,
104+
},
105+
},
106+
{
107+
content: 'ROCKET',
108+
reactors: {
109+
totalCount: 8,
110+
},
111+
},
112+
{
113+
content: 'EYES',
114+
reactors: {
115+
totalCount: 13,
116+
},
117+
},
118+
{
119+
content: 'HEART',
120+
reactors: {
121+
totalCount: 21,
122+
},
123+
},
124+
];
125+
126+
const props: MetricGroupProps = {
127+
notification: mockNotification,
128+
};
129+
130+
const tree = renderWithAppContext(<MetricGroup {...props} />);
131+
expect(tree).toMatchSnapshot();
132+
});
133+
134+
it('should render issues pill when linked to multiple issues/prs', async () => {
135+
const mockNotification = mockGitifyNotification;
136+
mockNotification.subject.linkedIssues = ['#1', '#2'];
137+
138+
const props: MetricGroupProps = {
139+
notification: mockNotification,
140+
};
141+
142+
const tree = renderWithAppContext(<MetricGroup {...props} />);
143+
expect(tree).toMatchSnapshot();
144+
});
145+
});
146+
51147
describe('comment pills', () => {
52148
it('should render when no comments', async () => {
53149
const mockNotification = mockGitifyNotification;

src/renderer/components/metrics/MetricGroup.tsx

Lines changed: 73 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CommentIcon,
55
IssueOpenedIcon,
66
MilestoneIcon,
7+
SmileyIcon,
78
TagIcon,
89
} from '@primer/octicons-react';
910

@@ -12,6 +13,7 @@ import { useAppContext } from '../../hooks/useAppContext';
1213
import { type GitifyNotification, IconColor } from '../../types';
1314

1415
import { getPullRequestReviewIcon } from '../../utils/icons';
16+
import { formatMetricDescription } from '../../utils/notifications/formatters';
1517
import { MetricPill } from './MetricPill';
1618

1719
export interface MetricGroupProps {
@@ -25,25 +27,73 @@ export const MetricGroup: FC<MetricGroupProps> = ({
2527

2628
const linkedIssues = notification.subject.linkedIssues ?? [];
2729
const hasLinkedIssues = linkedIssues.length > 0;
28-
const linkedIssuesPillDescription = hasLinkedIssues
29-
? `Linked to ${
30-
linkedIssues.length > 1 ? 'issues' : 'issue'
31-
} ${linkedIssues.join(', ')}`
32-
: '';
30+
const linkedIssuesPillDescription = formatMetricDescription(
31+
linkedIssues.length,
32+
'issue',
33+
(count, noun) => {
34+
return `Linked to ${count} ${noun}: ${linkedIssues.join(', ')}`;
35+
},
36+
);
37+
38+
const reactionCount = notification.subject.reactionsCount ?? 0;
39+
const reactionGroups = notification.subject.reactionGroups ?? [];
40+
const hasReactions = reactionCount > 0;
41+
const hasMultipleReactions =
42+
reactionGroups.filter((rg) => rg.reactors.totalCount > 0).length > 1;
43+
const reactionEmojiMap: Record<string, string> = {
44+
THUMBS_UP: '👍',
45+
THUMBS_DOWN: '👎',
46+
LAUGH: '😆',
47+
HOORAY: '🎉',
48+
CONFUSED: '😕',
49+
ROCKET: '🚀',
50+
EYES: '👀',
51+
HEART: '❤️',
52+
};
53+
54+
const reactionPillDescription = formatMetricDescription(
55+
reactionCount,
56+
'reaction',
57+
(count, noun) => {
58+
const formatted = reactionGroups
59+
.map((rg) => {
60+
const emoji = reactionEmojiMap[rg.content];
61+
if (!emoji || !rg.reactors.totalCount) {
62+
return '';
63+
}
64+
65+
return `${emoji} ${hasMultipleReactions ? rg.reactors.totalCount : ''}`.trim();
66+
})
67+
.filter(Boolean)
68+
.join(' ');
69+
70+
return `${count} ${noun}: ${formatted}`;
71+
},
72+
);
3373

3474
const commentCount = notification.subject.commentCount ?? 0;
3575
const hasComments = commentCount > 0;
36-
const commentsPillDescription = hasComments
37-
? `${notification.subject.commentCount} ${
38-
notification.subject.commentCount > 1 ? 'comments' : 'comment'
39-
}`
40-
: '';
76+
const commentsPillDescription = formatMetricDescription(
77+
commentCount,
78+
'comment',
79+
);
4180

4281
const labels = notification.subject.labels ?? [];
43-
const hasLabels = labels.length > 0;
44-
const labelsPillDescription = hasLabels
45-
? labels.map((label) => `🏷️ ${label}`).join(', ')
46-
: '';
82+
const labelsCount = labels.length;
83+
const hasLabels = labelsCount > 0;
84+
const labelsPillDescription = formatMetricDescription(
85+
labelsCount,
86+
'label',
87+
(count, noun) => {
88+
const formatted = labels
89+
.map((label) => {
90+
return `🏷️ ${label}`.trim();
91+
})
92+
.join(', ');
93+
94+
return `${count} ${noun}: ${formatted}`;
95+
},
96+
);
4797

4898
const milestone = notification.subject.milestone;
4999

@@ -59,6 +109,15 @@ export const MetricGroup: FC<MetricGroupProps> = ({
59109
/>
60110
)}
61111

112+
{hasReactions && (
113+
<MetricPill
114+
color={IconColor.GRAY}
115+
icon={SmileyIcon}
116+
metric={notification.subject.reactionsCount}
117+
title={reactionPillDescription}
118+
/>
119+
)}
120+
62121
{notification.subject.reviews?.map((review) => {
63122
const icon = getPullRequestReviewIcon(review);
64123
if (!icon) {

0 commit comments

Comments
 (0)