From d7f0959d08a2447b52184bb63c0f04748cfc9f1e Mon Sep 17 00:00:00 2001 From: Akshat Khosya Date: Thu, 18 Dec 2025 20:50:31 +0530 Subject: [PATCH] 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. --- webapp/src/components/link_tooltip/index.jsx | 5 +- .../components/link_tooltip/link_tooltip.jsx | 58 ++++++----- .../link_tooltip/link_tooltip.test.jsx | 96 +++++++++++++++++++ 3 files changed, 135 insertions(+), 24 deletions(-) create mode 100644 webapp/src/components/link_tooltip/link_tooltip.test.jsx diff --git a/webapp/src/components/link_tooltip/index.jsx b/webapp/src/components/link_tooltip/index.jsx index 00bd19fa4..7f1448e18 100644 --- a/webapp/src/components/link_tooltip/index.jsx +++ b/webapp/src/components/link_tooltip/index.jsx @@ -8,7 +8,10 @@ import manifest from '@/manifest'; import {LinkTooltip} from './link_tooltip'; const mapStateToProps = (state) => { - return {connected: state[`plugins-${manifest.id}`].connected}; + return { + connected: state[`plugins-${manifest.id}`].connected, + enterpriseURL: state[`plugins-${manifest.id}`].enterpriseURL, + }; }; export default connect(mapStateToProps, null)(LinkTooltip); diff --git a/webapp/src/components/link_tooltip/link_tooltip.jsx b/webapp/src/components/link_tooltip/link_tooltip.jsx index feff6df51..88de4b63f 100644 --- a/webapp/src/components/link_tooltip/link_tooltip.jsx +++ b/webapp/src/components/link_tooltip/link_tooltip.jsx @@ -13,32 +13,43 @@ import {getLabelFontColor, hexToRGB} from '../../utils/styles'; const maxTicketDescriptionLength = 160; -export const LinkTooltip = ({href, connected, show, theme}) => { +export const LinkTooltip = ({href, connected, show, theme, enterpriseURL}) => { const [data, setData] = useState(null); useEffect(() => { const initData = async () => { - if (href.includes('github.com/')) { - const [owner, repo, type, number] = href.split('github.com/')[1].split('/'); - if (!owner | !repo | !type | !number) { - return; + let owner; + let repo; + let type; + let number; + + if (enterpriseURL) { + const entURL = enterpriseURL.endsWith('/') ? enterpriseURL : enterpriseURL + '/'; + if (href.startsWith(entURL)) { + [owner, repo, type, number] = href.substring(entURL.length).split('/'); } + } else if (href.includes('github.com/')) { + [owner, repo, type, number] = href.split('github.com/')[1].split('/'); + } - let res; - switch (type) { - case 'issues': - res = await Client.getIssue(owner, repo, number); - break; - case 'pull': - res = await Client.getPullRequest(owner, repo, number); - break; - } - if (res) { - res.owner = owner; - res.repo = repo; - res.type = type; - } - setData(res); + if (!owner || !repo || !type || !number) { + return; + } + + let res; + switch (type) { + case 'issues': + res = await Client.getIssue(owner, repo, number); + break; + case 'pull': + res = await Client.getPullRequest(owner, repo, number); + break; + } + if (res) { + res.owner = owner; + res.repo = repo; + res.type = type; } + setData(res); }; // show is not provided for Mattermost Server < 5.28 @@ -47,7 +58,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => { } initData(); - }, [connected, data, href, show]); + }, [connected, data, href, show, enterpriseURL]); const getIconElement = () => { const iconProps = { @@ -117,7 +128,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {
- { getIconElement() } + {getIconElement()} {/* info */} @@ -135,7 +146,7 @@ export const LinkTooltip = ({href, connected, show, theme}) => {

{'Opened by '} @@ -193,4 +204,5 @@ LinkTooltip.propTypes = { connected: PropTypes.bool.isRequired, theme: PropTypes.object.isRequired, show: PropTypes.bool, + enterpriseURL: PropTypes.string, }; diff --git a/webapp/src/components/link_tooltip/link_tooltip.test.jsx b/webapp/src/components/link_tooltip/link_tooltip.test.jsx new file mode 100644 index 000000000..a6ad64282 --- /dev/null +++ b/webapp/src/components/link_tooltip/link_tooltip.test.jsx @@ -0,0 +1,96 @@ +import React from 'react'; +import {mount} from 'enzyme'; + +import Client from '@/client'; + +import {LinkTooltip} from './link_tooltip'; + +jest.mock('@/client', () => ({ + getIssue: jest.fn(), + getPullRequest: jest.fn(), +})); + +jest.mock('react-markdown', () => () =>

); + +describe('LinkTooltip', () => { + const baseProps = { + href: 'https://github.com/mattermost/mattermost-plugin-github/issues/1', + connected: true, + show: true, + theme: { + centerChannelBg: '#ffffff', + centerChannelColor: '#333333', + }, + enterpriseURL: '', + }; + + let wrapper; + + beforeEach(() => { + jest.clearAllMocks(); + }); + + afterEach(() => { + if (wrapper && wrapper.length) { + wrapper.unmount(); + } + }); + + test('should fetch issue for github.com link', () => { + // We need to use mount or wait for useEffect? + // shallow renders the component, useEffect is a hook. + // Enzyme shallow supports hooks in newer versions, but let's check if we need to manually trigger logic. + // The component uses useEffect to call initData. + wrapper = mount(); + expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '1'); + }); + + test('should fetch pull request for github.com link', () => { + const props = { + ...baseProps, + href: 'https://github.com/mattermost/mattermost-plugin-github/pull/2', + }; + wrapper = mount(); + expect(Client.getPullRequest).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '2'); + }); + + test('should fetch issue for enterprise link', () => { + const props = { + ...baseProps, + href: 'https://github.example.com/mattermost/mattermost-plugin-github/issues/3', + enterpriseURL: 'https://github.example.com', + }; + wrapper = mount(); + expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '3'); + }); + + test('should fetch pull request for enterprise link', () => { + const props = { + ...baseProps, + href: 'https://github.example.com/mattermost/mattermost-plugin-github/pull/4', + enterpriseURL: 'https://github.example.com', + }; + wrapper = mount(); + expect(Client.getPullRequest).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '4'); + }); + + test('should handle enterprise URL with trailing slash', () => { + const props = { + ...baseProps, + href: 'https://github.example.com/mattermost/mattermost-plugin-github/issues/5', + enterpriseURL: 'https://github.example.com/', + }; + wrapper = mount(); + expect(Client.getIssue).toHaveBeenCalledWith('mattermost', 'mattermost-plugin-github', '5'); + }); + + test('should not fetch if enterprise URL does not match', () => { + const props = { + ...baseProps, + href: 'https://other-github.com/mattermost/mattermost-plugin-github/issues/6', + enterpriseURL: 'https://github.example.com', + }; + wrapper = mount(); + expect(Client.getIssue).not.toHaveBeenCalled(); + }); +});