Skip to content

Commit d7f0959

Browse files
author
Akshat Khosya
committed
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.
1 parent 69b9162 commit d7f0959

File tree

3 files changed

+135
-24
lines changed

3 files changed

+135
-24
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: 35 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,43 @@ 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+
}
2533

26-
let res;
27-
switch (type) {
28-
case 'issues':
29-
res = await Client.getIssue(owner, repo, number);
30-
break;
31-
case 'pull':
32-
res = await Client.getPullRequest(owner, repo, number);
33-
break;
34-
}
35-
if (res) {
36-
res.owner = owner;
37-
res.repo = repo;
38-
res.type = type;
39-
}
40-
setData(res);
34+
if (!owner || !repo || !type || !number) {
35+
return;
36+
}
37+
38+
let res;
39+
switch (type) {
40+
case 'issues':
41+
res = await Client.getIssue(owner, repo, number);
42+
break;
43+
case 'pull':
44+
res = await Client.getPullRequest(owner, repo, number);
45+
break;
46+
}
47+
if (res) {
48+
res.owner = owner;
49+
res.repo = repo;
50+
res.type = type;
4151
}
52+
setData(res);
4253
};
4354

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

4960
initData();
50-
}, [connected, data, href, show]);
61+
}, [connected, data, href, show, enterpriseURL]);
5162

5263
const getIconElement = () => {
5364
const iconProps = {
@@ -117,7 +128,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
117128

118129
<div className='body d-flex mt-2'>
119130
<span className='pt-1 pb-1 pr-2'>
120-
{ getIconElement() }
131+
{getIconElement()}
121132
</span>
122133

123134
{/* info */}
@@ -135,7 +146,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
135146
<p className='opened-by'>
136147
{'Opened by '}
137148
<a
138-
href={`https://github.com/${data.user.login}`}
149+
href={`${enterpriseURL && enterpriseURL !== '' ? enterpriseURL : 'https://github.com'}/${data.user.login}`}
139150
target='_blank'
140151
rel='noopener noreferrer'
141152
>
@@ -193,4 +204,5 @@ LinkTooltip.propTypes = {
193204
connected: PropTypes.bool.isRequired,
194205
theme: PropTypes.object.isRequired,
195206
show: PropTypes.bool,
207+
enterpriseURL: PropTypes.string,
196208
};
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React from 'react';
2+
import {mount} from 'enzyme';
3+
4+
import Client from '@/client';
5+
6+
import {LinkTooltip} from './link_tooltip';
7+
8+
jest.mock('@/client', () => ({
9+
getIssue: jest.fn(),
10+
getPullRequest: jest.fn(),
11+
}));
12+
13+
jest.mock('react-markdown', () => () => <div/>);
14+
15+
describe('LinkTooltip', () => {
16+
const baseProps = {
17+
href: 'https://github.com/mattermost/mattermost-plugin-github/issues/1',
18+
connected: true,
19+
show: true,
20+
theme: {
21+
centerChannelBg: '#ffffff',
22+
centerChannelColor: '#333333',
23+
},
24+
enterpriseURL: '',
25+
};
26+
27+
let wrapper;
28+
29+
beforeEach(() => {
30+
jest.clearAllMocks();
31+
});
32+
33+
afterEach(() => {
34+
if (wrapper && wrapper.length) {
35+
wrapper.unmount();
36+
}
37+
});
38+
39+
test('should fetch issue for github.com link', () => {
40+
// We need to use mount or wait for useEffect?
41+
// shallow renders the component, useEffect is a hook.
42+
// Enzyme shallow supports hooks in newer versions, but let's check if we need to manually trigger logic.
43+
// The component uses useEffect to call initData.
44+
wrapper = mount(<LinkTooltip {...baseProps}/>);
45+
expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '1');
46+
});
47+
48+
test('should fetch pull request for github.com link', () => {
49+
const props = {
50+
...baseProps,
51+
href: 'https://github.com/mattermost/mattermost-plugin-github/pull/2',
52+
};
53+
wrapper = mount(<LinkTooltip {...props}/>);
54+
expect(Client.getPullRequest).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '2');
55+
});
56+
57+
test('should fetch issue for enterprise link', () => {
58+
const props = {
59+
...baseProps,
60+
href: 'https://github.example.com/mattermost/mattermost-plugin-github/issues/3',
61+
enterpriseURL: 'https://github.example.com',
62+
};
63+
wrapper = mount(<LinkTooltip {...props}/>);
64+
expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '3');
65+
});
66+
67+
test('should fetch pull request for enterprise link', () => {
68+
const props = {
69+
...baseProps,
70+
href: 'https://github.example.com/mattermost/mattermost-plugin-github/pull/4',
71+
enterpriseURL: 'https://github.example.com',
72+
};
73+
wrapper = mount(<LinkTooltip {...props}/>);
74+
expect(Client.getPullRequest).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '4');
75+
});
76+
77+
test('should handle enterprise URL with trailing slash', () => {
78+
const props = {
79+
...baseProps,
80+
href: 'https://github.example.com/mattermost/mattermost-plugin-github/issues/5',
81+
enterpriseURL: 'https://github.example.com/',
82+
};
83+
wrapper = mount(<LinkTooltip {...props}/>);
84+
expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '5');
85+
});
86+
87+
test('should not fetch if enterprise URL does not match', () => {
88+
const props = {
89+
...baseProps,
90+
href: 'https://other-github.com/mattermost/mattermost-plugin-github/issues/6',
91+
enterpriseURL: 'https://github.example.com',
92+
};
93+
wrapper = mount(<LinkTooltip {...props}/>);
94+
expect(Client.getIssue).not.toHaveBeenCalled();
95+
});
96+
});

0 commit comments

Comments
 (0)