Skip to content

Commit ff93bfa

Browse files
akshat-khosyaAkshat Khosya
andauthored
GH-367 Render tooltip for Enterprise installations (#949)
* GH-367 Render tooltip for Enterprise installations This adds support for rendering tooltips for links from Enterprise GitHub installations by checking the configured Enterprise Base URL. It also adds unit tests to verify the URL parsing logic for both SaaS and Enterprise links. * Implement useMemo for 'Opened by' link generation to prioritize html_urladded db schema creation in start command and Add comprehensive unit tests for Enterprise and Cloud scenarios --------- Co-authored-by: Akshat Khosya <[email protected]>
1 parent 5b995e7 commit ff93bfa

File tree

3 files changed

+273
-46
lines changed

3 files changed

+273
-46
lines changed

webapp/src/components/link_tooltip/index.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@ import manifest from '@/manifest';
88
import {LinkTooltip} from './link_tooltip';
99

1010
const mapStateToProps = (state) => {
11-
return {connected: state[`plugins-${manifest.id}`].connected};
11+
return {
12+
connected: state[`plugins-${manifest.id}`].connected,
13+
enterpriseURL: state[`plugins-${manifest.id}`].enterpriseURL,
14+
};
1215
};
1316

1417
export default connect(mapStateToProps, null)(LinkTooltip);

webapp/src/components/link_tooltip/link_tooltip.jsx

Lines changed: 76 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,55 @@
11
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
22
// See LICENSE.txt for license information.
33

4-
import React, {useEffect, useState} from 'react';
4+
import React, { useEffect, useMemo, useState } from 'react';
55
import PropTypes from 'prop-types';
66
import './tooltip.css';
7-
import {GitMergeIcon, GitPullRequestIcon, IssueClosedIcon, IssueOpenedIcon} from '@primer/octicons-react';
7+
import { GitMergeIcon, GitPullRequestIcon, IssueClosedIcon, IssueOpenedIcon } from '@primer/octicons-react';
88
import ReactMarkdown from 'react-markdown';
99

1010
import Client from '@/client';
1111

12-
import {getLabelFontColor, hexToRGB} from '../../utils/styles';
12+
import { getLabelFontColor, hexToRGB } from '../../utils/styles';
1313

1414
const maxTicketDescriptionLength = 160;
1515

16-
export const LinkTooltip = ({href, connected, show, theme}) => {
16+
export const LinkTooltip = ({ href, connected, show, theme, enterpriseURL }) => {
1717
const [data, setData] = useState(null);
1818
useEffect(() => {
1919
const initData = async () => {
20-
if (href.includes('github.com/')) {
21-
const [owner, repo, type, number] = href.split('github.com/')[1].split('/');
22-
if (!owner | !repo | !type | !number) {
23-
return;
20+
let owner;
21+
let repo;
22+
let type;
23+
let number;
24+
25+
if (enterpriseURL) {
26+
const entURL = enterpriseURL.endsWith('/') ? enterpriseURL : enterpriseURL + '/';
27+
if (href.startsWith(entURL)) {
28+
[owner, repo, type, number] = href.substring(entURL.length).split('/');
2429
}
30+
} else if (href.includes('github.com/')) {
31+
[owner, repo, type, number] = href.split('github.com/')[1].split('/');
32+
}
33+
34+
if (!owner || !repo || !type || !number) {
35+
return;
36+
}
2537

26-
let res;
27-
switch (type) {
38+
let res;
39+
switch (type) {
2840
case 'issues':
2941
res = await Client.getIssue(owner, repo, number);
3042
break;
3143
case 'pull':
3244
res = await Client.getPullRequest(owner, repo, number);
3345
break;
34-
}
35-
if (res) {
36-
res.owner = owner;
37-
res.repo = repo;
38-
res.type = type;
39-
}
40-
setData(res);
4146
}
47+
if (res) {
48+
res.owner = owner;
49+
res.repo = repo;
50+
res.type = type;
51+
}
52+
setData(res);
4253
};
4354

4455
// show is not provided for Mattermost Server < 5.28
@@ -47,7 +58,26 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
4758
}
4859

4960
initData();
50-
}, [connected, data, href, show]);
61+
}, [connected, data, href, show, enterpriseURL]);
62+
63+
const openedByLink = useMemo(() => {
64+
if (!data?.user?.login) {
65+
return null;
66+
}
67+
// Immediately map the html_url value when present (which should work for both Enterprise and Cloud)
68+
if (data.user.html_url) {
69+
return data.user.html_url;
70+
}
71+
72+
// Fallback to a generic enterprise URL when appropriate, handling possible trailing slashes
73+
if (enterpriseURL) {
74+
const entURL = enterpriseURL.endsWith('/') ? enterpriseURL : enterpriseURL + '/';
75+
return `${entURL}${data.user.login}`;
76+
}
77+
78+
// Assume it's GitHub cloud and fallback to the original path (unlikely to ever run unless there are breaking changes in GitHub's API
79+
return `https://github.com/${data.user.login}`;
80+
}, [data, enterpriseURL]);
5181

5282
const getIconElement = () => {
5383
const iconProps = {
@@ -58,32 +88,32 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
5888
let icon;
5989
let color;
6090
switch (data.type) {
61-
case 'pull':
62-
icon = <GitPullRequestIcon {...iconProps}/>;
63-
64-
color = '#28a745';
65-
if (data.state === 'closed') {
66-
if (data.merged) {
67-
color = '#6f42c1';
68-
icon = <GitMergeIcon {...iconProps}/>;
69-
} else {
70-
color = '#cb2431';
91+
case 'pull':
92+
icon = <GitPullRequestIcon {...iconProps} />;
93+
94+
color = '#28a745';
95+
if (data.state === 'closed') {
96+
if (data.merged) {
97+
color = '#6f42c1';
98+
icon = <GitMergeIcon {...iconProps} />;
99+
} else {
100+
color = '#cb2431';
101+
}
71102
}
72-
}
73103

74-
break;
75-
case 'issues':
76-
color = data.state === 'open' ? '#28a745' : '#cb2431';
104+
break;
105+
case 'issues':
106+
color = data.state === 'open' ? '#28a745' : '#cb2431';
77107

78-
if (data.state === 'open') {
79-
icon = <IssueOpenedIcon {...iconProps}/>;
80-
} else {
81-
icon = <IssueClosedIcon {...iconProps}/>;
82-
}
83-
break;
108+
if (data.state === 'open') {
109+
icon = <IssueOpenedIcon {...iconProps} />;
110+
} else {
111+
icon = <IssueClosedIcon {...iconProps} />;
112+
}
113+
break;
84114
}
85115
return (
86-
<span style={{color}}>
116+
<span style={{ color }}>
87117
{icon}
88118
</span>
89119
);
@@ -105,10 +135,10 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
105135
<div className='github-tooltip'>
106136
<div
107137
className='github-tooltip box github-tooltip--large github-tooltip--bottom-left p-4'
108-
style={{backgroundColor: theme.centerChannelBg, border: `1px solid ${hexToRGB(theme.centerChannelColor, '0.16')}`}}
138+
style={{ backgroundColor: theme.centerChannelBg, border: `1px solid ${hexToRGB(theme.centerChannelColor, '0.16')}` }}
109139
>
110140
<div className='header mb-1'>
111-
<span style={{color: theme.centerChannelColor}}>
141+
<span style={{ color: theme.centerChannelColor }}>
112142
{data.repo}
113143
</span>
114144
{' on '}
@@ -117,7 +147,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
117147

118148
<div className='body d-flex mt-2'>
119149
<span className='pt-1 pb-1 pr-2'>
120-
{ getIconElement() }
150+
{getIconElement()}
121151
</span>
122152

123153
{/* info */}
@@ -126,7 +156,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
126156
href={href}
127157
target='_blank'
128158
rel='noopener noreferrer'
129-
style={{color: theme.centerChannelColor}}
159+
style={{ color: theme.centerChannelColor }}
130160
>
131161
<h5 className='mr-1'>{data.title}</h5>
132162
<span>{'#' + data.number}</span>
@@ -135,7 +165,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
135165
<p className='opened-by'>
136166
{'Opened by '}
137167
<a
138-
href={`https://github.com/${data.user.login}`}
168+
href={openedByLink}
139169
target='_blank'
140170
rel='noopener noreferrer'
141171
>
@@ -172,7 +202,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
172202
key={idx}
173203
className='label mr-1'
174204
title={label.description}
175-
style={{backgroundColor: '#' + label.color, color: getLabelFontColor(label.color)}}
205+
style={{ backgroundColor: '#' + label.color, color: getLabelFontColor(label.color) }}
176206
>
177207
<span>{label.name}</span>
178208
</span>
@@ -193,4 +223,5 @@ LinkTooltip.propTypes = {
193223
connected: PropTypes.bool.isRequired,
194224
theme: PropTypes.object.isRequired,
195225
show: PropTypes.bool,
226+
enterpriseURL: PropTypes.string,
196227
};

0 commit comments

Comments
 (0)