Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GH-774] Add tooltip to Jira ticket links #887

Merged
merged 106 commits into from
Dec 5, 2023
Merged
Show file tree
Hide file tree
Changes from 102 commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
622547d
[GH-774] Add Jira Ticket Tolltip
xantabdoc Jan 7, 2022
e2da4c6
[GH-774] Add Jira Ticket Tooltip
xantabdoc Jan 7, 2022
2686e5d
[GH-774] Add Jira Ticket Tooltip
xantabdoc Jan 7, 2022
f79f69c
[GH-774] Add Jira Ticket Tolltip
xantabdoc Jan 12, 2022
52a5fd4
[GH-774] Draft Update Styling With Fake Data Link PopOver
xantabdoc Jan 26, 2022
5b453cb
tooltip for jira link comment
Feb 10, 2022
e08af33
tooltip for jira link comment
Feb 10, 2022
068407d
fix lint issue
Feb 10, 2022
16bd17d
fix ci issue
Feb 10, 2022
f46808e
fix ci issue
Feb 10, 2022
e402699
fix ci issue
Feb 10, 2022
858ba86
fix ci issue
Feb 10, 2022
689d96a
fix ci issue
Feb 10, 2022
36b589a
fix ci issue
Feb 10, 2022
c1e6c07
fix ci issue
Feb 10, 2022
4f9b03c
fix ci issue
Feb 10, 2022
62bb43f
fix ci issue
Feb 10, 2022
f99e423
fix lint isssue
Feb 10, 2022
e23ebd1
lint issue fix
Feb 10, 2022
d5d4222
fix lint issue
Feb 11, 2022
3a76de2
fix lint issue
Feb 11, 2022
8a79953
fix lint issue
Feb 11, 2022
d3a583b
fix lint issue
Feb 11, 2022
f13eccc
fix lint issue
Feb 11, 2022
4a67ed8
fix lint issue
Feb 11, 2022
088f420
fix lint issue
Feb 11, 2022
1a8b35b
fix lint issue
Feb 11, 2022
06c99ac
added some debug statement
Feb 12, 2022
9f216c4
added some debug statement
Feb 12, 2022
68fa388
fix overlap of state
Feb 13, 2022
2df14eb
fix overlap of state
Feb 13, 2022
5a9f63e
fix overlap of state
Feb 13, 2022
41c745e
fix lint issue
Feb 13, 2022
b07f1d5
lint fix
Feb 13, 2022
8c2c689
fixed lint issue
Feb 13, 2022
074a44d
Merge branch 'GH-774' of github.com:demansoltech/mattermost-plugin-ji…
Feb 21, 2022
f96e431
resolved comments
Mar 14, 2022
9bccf9b
resolved comments
Mar 15, 2022
f1d9d4d
resloved lint
Mar 24, 2022
420ed2b
temp push
Mar 27, 2022
23e521c
temp push
Mar 27, 2022
27aedb4
fix logic
Apr 2, 2022
c2c9beb
solved lint issue
Apr 2, 2022
ff66d25
solved lint issue
Apr 2, 2022
3b05620
resolved comments
Apr 5, 2022
a284bd1
removed reducer flow to fetch jira ticket details
Apr 17, 2022
3195ef8
removed reducer flow to fetch jira ticket details
Apr 17, 2022
deee44f
temp push
Apr 26, 2022
0ea1126
added props redux logic to handle state
Apr 30, 2022
8dd826d
fixed lint issue
Apr 30, 2022
4c3eaec
Merge branch 'GH-774' of github.com:demansoltech/mattermost-plugin-ji…
raghavaggarwal2308 Oct 17, 2022
c3837d1
[MI-2260] Add tooltip on jira ticket links
raghavaggarwal2308 Oct 25, 2022
596ad0c
[MI-2260] Self review fixes
raghavaggarwal2308 Oct 25, 2022
a36fd62
[MI-2260] Review fixes
raghavaggarwal2308 Nov 1, 2022
f8f2346
[MI-2260] Review fixes
raghavaggarwal2308 Nov 4, 2022
add58bc
[MI-2260] Review fixes
raghavaggarwal2308 Nov 7, 2022
5126a91
[MI-2260] Review fixes
raghavaggarwal2308 Nov 7, 2022
c62872f
[MI-2260] Updated babel config to use optional chaining
raghavaggarwal2308 Nov 7, 2022
c690e78
[MI-2260] Review fixes
raghavaggarwal2308 Nov 7, 2022
3deeeb8
[MI-2260] Review fixes
raghavaggarwal2308 Nov 7, 2022
e48ecba
Merge pull request #12 from Brightscout/MI-2260
raghavaggarwal2308 Nov 7, 2022
e1badec
[MI-2385] Fix review fixes for PR GH-774
ayusht2810 Nov 23, 2022
647cce5
[MI-2385] Fix declaration of variables
ayusht2810 Nov 23, 2022
df5e000
[MI-2385] Fix lint error
ayusht2810 Nov 23, 2022
e6feae6
Merge pull request #19 from Brightscout/MI-2385
ayusht2810 Nov 24, 2022
9ce43e4
[GH-774] Revert package-lock.json file changes.
raghavaggarwal2308 Dec 13, 2022
5f24d70
[MI-2471] Review fixes on PR #887 by javaguirre
raghavaggarwal2308 Dec 19, 2022
83a6ee9
[MI-2471] Review fix on jira PR #887 by mickmister
raghavaggarwal2308 Dec 19, 2022
4f6253f
[MI-2471] Review fix on PR #887 by mickmister
raghavaggarwal2308 Dec 19, 2022
0f4f660
[MI-2471] Fix lint errors
raghavaggarwal2308 Dec 19, 2022
d867374
[MI-2471] Replaced id with class
raghavaggarwal2308 Dec 21, 2022
1192484
[MI-2471] Review fixes
raghavaggarwal2308 Dec 29, 2022
d79df51
[MI-2471] Review fixes
raghavaggarwal2308 Jan 2, 2023
3566f69
Merge pull request #26 from Brightscout/MI-2471
raghavaggarwal2308 Jan 2, 2023
98db4e8
Merge branch 'master' of github.com:mattermost/mattermost-plugin-jira…
raghavaggarwal2308 Jan 2, 2023
27068c8
[MI-2543] Review fixes on Jira PR #887 (Jira ticket link tooltip)
raghavaggarwal2308 Jan 10, 2023
3f20f27
[MI-2543] Changed reducer name
raghavaggarwal2308 Jan 10, 2023
a444df8
Merge pull request #29 from Brightscout/MI-2543
raghavaggarwal2308 Jan 10, 2023
6647a2f
[GH-774] Review fixes
raghavaggarwal2308 Jan 11, 2023
99201ab
[MI-2612] Review fixes on Jira PR #887
raghavaggarwal2308 Jan 13, 2023
ebe9f77
[MI-2612] Review fix
raghavaggarwal2308 Jan 13, 2023
121ce85
Merge pull request #32 from Brightscout/MI-2612
raghavaggarwal2308 Jan 13, 2023
95f1f72
Done the review fixes of PR #887 (#37)
Nityanand13 Feb 28, 2023
426a9ba
Merge branch 'master' of github.com:mattermost/mattermost-plugin-jira…
raghavaggarwal2308 Mar 29, 2023
568e779
[MI-2944] Review fixes on Jira PR #887 (Add link tooltip)
raghavaggarwal2308 Mar 30, 2023
b2cb2bf
[MI-2944] Fixed few testcases related comments
raghavaggarwal2308 Apr 4, 2023
07e2456
[MI-2944] Updated logic to store the actual ticket data in redux
raghavaggarwal2308 Apr 4, 2023
694a59d
[MI-2944] Review fixes
raghavaggarwal2308 Apr 7, 2023
ca5ae47
Merge pull request #44 from Brightscout/MI-2944
raghavaggarwal2308 Apr 9, 2023
54ff519
Merge branch 'master' of github.com:mattermost/mattermost-plugin-jira…
raghavaggarwal2308 Apr 9, 2023
3c7e155
[MI-2988] Review fixes on Jira PR #887(Add link tooltip)
raghavaggarwal2308 Apr 19, 2023
9046d13
[MI-2988] Review fix
raghavaggarwal2308 Apr 20, 2023
56da25b
Merge pull request #47 from Brightscout/MI-2988
raghavaggarwal2308 Apr 20, 2023
7489ebf
[MI-3053] Review fixes on Jira PR #887 (Add link tooltip)
raghavaggarwal2308 May 3, 2023
fda8063
[MI-3053] Removed unused css variable
raghavaggarwal2308 May 3, 2023
b075115
[MI-3053] Fix spinner opacity and size
raghavaggarwal2308 May 4, 2023
0fac5bc
Merge pull request #49 from Brightscout/MI-3053
raghavaggarwal2308 May 5, 2023
5ab1a72
[MI-3064] Review fixes on jira PR #887(Add link tooltip)
raghavaggarwal2308 May 9, 2023
31fef49
Merge pull request #50 from Brightscout/MI-3064
raghavaggarwal2308 May 9, 2023
1c1653b
[MI-3077] Review fixes on Jira PR #887 (Add link tooltip)
raghavaggarwal2308 May 15, 2023
76c9a7e
[MI-3077] Review fixes
raghavaggarwal2308 May 17, 2023
3d7f2c0
Merge pull request #51 from Brightscout/MI-3077
raghavaggarwal2308 May 19, 2023
ed7bab1
[MI-3103] Review fixes on Jira PR #887 (Add link tooltip)
raghavaggarwal2308 May 25, 2023
1c809d7
Merge pull request #52 from Brightscout/MI-3103
raghavaggarwal2308 May 30, 2023
00bdb7e
Merge branch 'master' of github.com:mattermost/mattermost-plugin-jira…
raghavaggarwal2308 Jun 6, 2023
330bb61
Merge branch 'master' of github.com:mattermost/mattermost-plugin-jira…
raghavaggarwal2308 Nov 30, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions server/constants.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package main

const (
HeaderMattermostUserID = "Mattermost-User-Id"

ParamInstanceID = "instance_id"
ParamIssueKey = "issue_key"
)
3 changes: 3 additions & 0 deletions server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ const (
routeUserStart = "/user/start"
routeUserConnect = "/user/connect"
routeUserDisconnect = "/user/disconnect"
routeGetIssueByKey = "/get-issue-by-key"
routeSharePublicly = "/share-issue-publicly"
)

Expand Down Expand Up @@ -93,6 +94,7 @@ func (p *Plugin) initializeRouter() {

apiRouter := p.router.PathPrefix(routeAPI).Subrouter()

// Issue APIs
apiRouter.HandleFunc(routeAPIGetAutoCompleteFields, p.checkAuth(p.handleResponse(p.httpGetAutoCompleteFields))).Methods(http.MethodGet)
apiRouter.HandleFunc(routeAPICreateIssue, p.checkAuth(p.handleResponse(p.httpCreateIssue))).Methods(http.MethodPost)
apiRouter.HandleFunc(routeAPIGetCreateIssueMetadata, p.checkAuth(p.handleResponse(p.httpGetCreateIssueMetadataForProjects))).Methods(http.MethodGet)
Expand All @@ -102,6 +104,7 @@ func (p *Plugin) initializeRouter() {
apiRouter.HandleFunc(routeAPIAttachCommentToIssue, p.checkAuth(p.handleResponse(p.httpAttachCommentToIssue))).Methods(http.MethodPost)
apiRouter.HandleFunc(routeIssueTransition, p.handleResponse(p.httpTransitionIssuePostAction)).Methods(http.MethodPost)
apiRouter.HandleFunc(routeSharePublicly, p.handleResponse(p.httpShareIssuePublicly)).Methods(http.MethodPost)
apiRouter.HandleFunc(routeGetIssueByKey, p.handleResponse(p.httpGetIssueByKey)).Methods(http.MethodGet)

// User APIs
apiRouter.HandleFunc(routeAPIUserInfo, p.checkAuth(p.handleResponse(p.httpGetUserInfo))).Methods(http.MethodGet)
Expand Down
38 changes: 38 additions & 0 deletions server/issue.go
Original file line number Diff line number Diff line change
Expand Up @@ -1029,3 +1029,41 @@ func (p *Plugin) getClient(instanceID, mattermostUserID types.ID) (Client, Insta
}
return client, instance, connection, nil
}

func (p *Plugin) httpGetIssueByKey(w http.ResponseWriter, r *http.Request) (int, error) {
if r.Method != http.MethodGet {
return respondErr(w, http.StatusMethodNotAllowed, fmt.Errorf("request: %s is not allowed, must be GET", r.Method))
}

mattermostUserID := r.Header.Get(HeaderMattermostUserID)
if mattermostUserID == "" {
return respondErr(w, http.StatusUnauthorized, errors.New("not authorized"))
}

instanceID := r.FormValue(ParamInstanceID)
issueKey := r.FormValue(ParamIssueKey)
issue, err := p.GetIssueByKey(types.ID(instanceID), types.ID(mattermostUserID), issueKey)
if err != nil {
return respondErr(w, http.StatusInternalServerError, err)
}

return respondJSON(w, issue)
}

func (p *Plugin) GetIssueByKey(instanceID, mattermostUserID types.ID, issueKey string) (*jira.Issue, error) {
client, _, _, err := p.getClient(instanceID, mattermostUserID)
if err != nil {
return nil, err
}

issue, err := client.GetIssue(issueKey, nil)
if err != nil {
switch StatusCode(err) {
case http.StatusNotFound:
return nil, errors.New("we couldn't find the issue key, or you do not have the appropriate permissions to view the issue. Please try again or contact your Jira administrator")
default:
return nil, errors.WithMessage(err, "request to Jira failed")
}
}
return issue, nil
}
2 changes: 2 additions & 0 deletions webapp/src/action_types/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ export default {

RECEIVED_CHANNEL_SUBSCRIPTIONS: `${PluginId}_recevied_channel_subscriptions`,
DELETED_CHANNEL_SUBSCRIPTION: `${PluginId}_deleted_channel_subscription`,

RECEIVED_JIRA_TICKET: `${PluginId}_received_jira_ticket`,
};
30 changes: 29 additions & 1 deletion webapp/src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@ import ActionTypes from 'action_types';
import {doFetch, doFetchWithResponse, buildQueryString} from 'client';
import {getPluginServerRoute, getInstalledInstances, getUserConnectedInstances} from 'selectors';
import {isDesktopApp, isMinimumDesktopAppVersion} from 'utils/user_agent';
import {ChannelSubscription, CreateIssueRequest, SearchIssueParams, InstanceType, ProjectMetadata, APIResponse} from 'types/model';
import {
APIResponse,
ChannelSubscription,
CreateIssueRequest,
InstanceType,
ProjectMetadata,
SearchIssueParams,
} from 'types/model';

export const openConnectModal = () => {
return {
Expand Down Expand Up @@ -509,3 +516,24 @@ export function sendEphemeralPost(message: string, channelId?: string) {
});
};
}

export const fetchIssueByKey = (issueKey: string, instanceID: string) => {
return async (dispatch, getState) => {
const baseUrl = getPluginServerRoute(getState());
let data = null;
const params = `issue_key=${issueKey}&instance_id=${instanceID}`;
try {
data = await doFetch(`${baseUrl}/api/v2/get-issue-by-key?${params}`, {
method: 'get',
});

dispatch({
type: ActionTypes.RECEIVED_JIRA_TICKET,
data,
});
return {data};
} catch (error) {
return {error};
}
};
};
12 changes: 12 additions & 0 deletions webapp/src/components/default_avatar/defaultAvatar.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.jira-issue-tooltip {
.default-avatar {
background-color: #708090;
border-radius: 50%;
margin-right: 5px;
display: flex;
justify-content: center;
align-items: center;
width: 22px;
height: 22px;
}
}
31 changes: 31 additions & 0 deletions webapp/src/components/default_avatar/default_avatar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import React from 'react';
import './defaultAvatar.scss';

function DefaultAvatar() {
return (
<span className='default-avatar'>
<svg
width='18'
height='18'
viewBox='0 0 18 18'
role='presentation'
>
<g
fill='white'
fillRule='evenodd'
>
<path
d='M3.5 14c0-1.105.902-2 2.009-2h7.982c1.11 0 2.009.894 2.009 2.006v4.44c0 3.405-12 3.405-12 0V14z'
/>
<circle
cx='9'
cy='6'
r='3.5'
/>
</g>
</svg>
</span>
);
}

export default DefaultAvatar;
24 changes: 24 additions & 0 deletions webapp/src/components/jira_ticket_tooltip/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {connect} from 'react-redux';
import {bindActionCreators, Dispatch} from 'redux';
import {GlobalState} from 'mattermost-redux/types/store';

import {jiraIssueToReducer} from 'utils/jira_issue_metadata';

import {isUserConnected, getStoredLinkTooltipIssue, getUserConnectedInstances, getDefaultUserInstanceID} from 'selectors';
import {fetchIssueByKey} from 'actions';

import TicketPopover from './jira_ticket_tooltip';

const mapStateToProps = (state: GlobalState) => {
return {
connected: isUserConnected(state),
ticketDetails: jiraIssueToReducer(getStoredLinkTooltipIssue(state).ticket),
connectedInstances: getUserConnectedInstances(state),
};
};

const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
fetchIssueByKey,
}, dispatch);

export default connect(mapStateToProps, mapDispatchToProps)(TicketPopover);
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import React from 'react';
import {shallow} from 'enzyme';

import {Instance, InstanceType} from 'types/model';

import TicketPopover, {Props} from './jira_ticket_tooltip';

describe('components/jira_ticket_tooltip', () => {
describe('getIssueKey', () => {
const mockConnectedInstances: Instance[] = [
{
instance_id: 'https://something-1.atlassian.net',
type: InstanceType.CLOUD,
},
{
instance_id: 'https://something-2.atlassian.net',
type: InstanceType.SERVER,
},
];

const mockProps1: Props = {
href: '',
show: false,
connected: false,
connectedInstances: mockConnectedInstances,
fetchIssueByKey: jest.fn(),
};

const mockProps2: Props = {
href: '',
show: false,
connected: false,
connectedInstances: [],
fetchIssueByKey: jest.fn(),
};

test('should return the expected output when URL matches the first regex pattern', () => {
const wrapper = shallow(
<TicketPopover
{...mockProps1}
href='https://something-1.atlassian.net/browse/TICKET-1234'
/>
);
const instance = wrapper.instance() as TicketPopover;
const expectedOutput = {ticketID: 'TICKET-1234', instanceID: 'https://something-1.atlassian.net'};
expect(instance.getIssueKey()).toEqual(expectedOutput);
});

test('should return the expected output when URL matches the second regex pattern', () => {
const wrapper = shallow(
<TicketPopover
{...mockProps1}
href='https://something-2.atlassian.net/jira/issues/?selectedIssue=TICKET-1234'
/>
);
const instance = wrapper.instance() as TicketPopover;
const expectedOutput = {ticketID: 'TICKET-1234', instanceID: 'https://something-2.atlassian.net'};
expect(instance.getIssueKey()).toEqual(expectedOutput);
});

test('should return null when URL does not match any pattern', () => {
const wrapper = shallow(
<TicketPopover
{...mockProps1}
href='https://something-invalid.atlassian.net/not-a-ticket'
/>
);
const instance = wrapper.instance() as TicketPopover;
expect(instance.getIssueKey()).toEqual(null);
});

test('should return null when the URL does not contain the ticket ID', () => {
const wrapper = shallow(
<TicketPopover
{...mockProps1}
href='https://something-2.atlassian.net/jira/issues/?selectedIssue='
/>
);
const instance = wrapper.instance() as TicketPopover;
expect(instance.getIssueKey()).toEqual(null);
});

test('should return null when no instance is connected', () => {
const wrapper = shallow(
<TicketPopover
{...mockProps2}
href='https://something-2.atlassian.net/jira/issues/?selectedIssue='
/>
);
const instance = wrapper.instance() as TicketPopover;
expect(instance.getIssueKey()).toEqual(null);
});
});
});
Loading