Skip to content
This repository was archived by the owner on Aug 31, 2023. It is now read-only.

Commit 1d40f0d

Browse files
authored
Merge pull request #4 from EpocDotFr/direct-jira-ticket-link
Direct Jira ticket link
2 parents 391e6ed + 344de57 commit 1d40f0d

File tree

7 files changed

+281
-32
lines changed

7 files changed

+281
-32
lines changed

README.md

+11-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ A browser extension that enhance all Merge Requests lists on any instance of Git
1313
- Button allowing to copy Merge Request information (useful when sharing the Merge Request on e.g instant messaging softwares)
1414
- Can be disabled in the extension preferences
1515
- Text format is customizable (with support of placeholders)
16+
- Direct Jira ticket link
17+
- Can be enabled/disabled in the extension preferences
18+
- Ticket ID is automatically detected in source branch name or Merge Request title
19+
- Base Jira URL is configured in extension preferences
20+
- The ticket ID or an icon can be displayed as the link label (configured in extension preferences)
1621
- Compatible with all GitLab editions (GitLab CE, GitLab EE, GitLab.com) (look at the prerequisites, though)
1722
- No configuration needed
1823

@@ -37,11 +42,14 @@ You can also install this add-on manually by using one of the ZIP files on the [
3742

3843
👉 = current version
3944

40-
- **1.0** - Initial release (display Merge Request source and target branches)
45+
- **1.0** - Initial release (display Merge Request source and target branches name)
4146
- **1.1** - Copy source and target branches name
42-
- 👉 **1.2** - Copy Merge Request information (intended for sharing on e.g instant messaging softwares)
43-
- **1.3** - Direct Jira ticket link (automatic detection of ticket ID in branch name or Merge Request title)
47+
- **1.2** - Copy Merge Request information (intended for sharing on e.g instant messaging softwares)
48+
- 👉 **1.3** - Direct Jira ticket link (automatic detection of ticket ID in source branch name or Merge Request title)
4449
- **1.4** - WIP / unWIP toggle button
50+
- **1.5**
51+
- New option: enable display Merge Request source and target branches
52+
- New options: enable copy source and target branches name button (one option for each branches)
4553

4654
## License
4755

css/options.css

+80-11
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,48 @@
11
/************************************************************************
2-
* Global styles */
2+
* Browser-specific styles */
3+
4+
/* Firefox *************************************************************/
5+
6+
/* Dark theme styles override */
7+
@media (prefers-color-scheme: dark) {
8+
body.is-firefox {
9+
background-color: #202023;
10+
color: rgb(249, 249, 250);
11+
}
12+
13+
.is-firefox input[type="url"],
14+
.is-firefox textarea {
15+
background-color: #2a2a2e;
16+
color: #fff;
17+
border-color: rgba(249, 249, 250, 0.2);
18+
border-width: 1px;
19+
}
20+
}
21+
22+
/* Chrome **************************************************************/
23+
24+
body.is-chrome {
25+
width: 660px;
26+
}
27+
28+
/* Dark theme styles override */
29+
@media (prefers-color-scheme: dark) {
30+
body.is-chrome {
31+
background-color: #292a2d;
32+
color: rgb(232, 234, 237);
33+
}
334

4-
/*
5-
* Firefox for Mac: use a dark theme if the OS/browser also uses one.
6-
* This doesn't affects Firefox for Windows (as for now), as dark/light theme detection isn't implemented for this OS.
7-
*/
8-
@supports (-moz-appearance:none) {
9-
@media (prefers-color-scheme: dark) {
10-
body {
11-
background-color: #202023;
12-
color: rgb(249, 249, 250);
13-
}
35+
.is-chrome input[type="url"],
36+
.is-chrome textarea {
37+
background-color: #202023;
38+
color: rgb(232, 234, 237);
39+
border-color: rgba(255, 255, 255, 0.1);
1440
}
1541
}
1642

43+
/************************************************************************
44+
* Global styles */
45+
1746
.is-hidden {
1847
display: none;
1948
}
@@ -29,17 +58,29 @@
2958
font-family: monospace;
3059
}
3160

61+
.txt-muted {
62+
opacity: 0.8;
63+
}
64+
3265
/************************************************************************
3366
* Sizing styles */
3467

3568
.w100 {
3669
width: 100%;
3770
}
3871

72+
.w50 {
73+
width: 50%;
74+
}
75+
3976
.w40p {
4077
width: 40px;
4178
}
4279

80+
.w300p {
81+
width: 300px;
82+
}
83+
4384
/************************************************************************
4485
* Layout styles */
4586

@@ -63,14 +104,42 @@
63104
padding-top: 5px;
64105
}
65106

107+
.ptm {
108+
padding-top: 10px;
109+
}
110+
66111
.pbs {
67112
padding-bottom: 5px;
68113
}
69114

115+
.pbm {
116+
padding-bottom: 10px;
117+
}
118+
119+
.prm {
120+
padding-right: 10px;
121+
}
122+
70123
.pll {
71124
padding-left: 40px;
72125
}
73126

127+
.mlm {
128+
margin-left: 10px;
129+
}
130+
74131
.man {
75132
margin: 0;
133+
}
134+
135+
.mtn {
136+
margin-top: 0;
137+
}
138+
139+
.mbn {
140+
margin-bottom: 0;
141+
}
142+
143+
.mrn {
144+
margin-right: 0;
76145
}

html/options.html

+24-6
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@
66
</head>
77
<body>
88
<form>
9-
<div class="pts pbs man row">
9+
<div class="pts pbs row">
1010
<div class="w40p txt-center browser-style">
1111
<input type="checkbox" id="enable_buttons_to_copy_source_and_target_branches_name">
1212
</div>
1313
<div>
1414
<label for="enable_buttons_to_copy_source_and_target_branches_name">Enable buttons allowing to copy source and target branches name</label>
1515
</div>
1616
</div>
17-
<div class="pts man row">
17+
<div class="pts row">
1818
<div class="w40p txt-center browser-style">
1919
<input type="checkbox" id="enable_button_to_copy_mr_info">
2020
</div>
@@ -23,14 +23,32 @@
2323
</div>
2424
</div>
2525
<div class="pbs pll">
26-
<small>Useful when sharing the Merge Request on e.g instant messaging softwares</small>
26+
<small class="txt-muted">Useful when sharing the Merge Request on e.g instant messaging softwares</small>
2727
</div>
28-
<div class="pll pts pbs man" id="copy-mr-info-options">
28+
<div class="pll pts pbs" id="copy-mr-info-options">
2929
<div class="pbs"><label for="copy_mr_info_format">Text format:</label></div>
3030
<div><textarea class="browser-style w100 monospaced man pas" id="copy_mr_info_format" rows="6" required></textarea></div>
31-
<div class="pts"><small>Available placeholders: <code>{MR_TITLE}</code>, <code>{MR_ID}</code>, <code>{MR_URL}</code>, <code>{MR_DIFFS_URL}</code>, <code>{MR_AUTHOR_NAME}</code>, <code>{MR_STATUS}</code>, <code>{MR_SOURCE_BRANCH_NAME}</code>, <code>{MR_TARGET_BRANCH_NAME}</code></small></div>
31+
<div class="pts"><small class="txt-muted">Available placeholders: <code>{MR_TITLE}</code>, <code>{MR_ID}</code>, <code>{MR_URL}</code>, <code>{MR_DIFFS_URL}</code>, <code>{MR_AUTHOR_NAME}</code>, <code>{MR_STATUS}</code>, <code>{MR_SOURCE_BRANCH_NAME}</code>, <code>{MR_TARGET_BRANCH_NAME}</code>, <code>{MR_JIRA_TICKET_ID}</code>, <code>{MR_JIRA_TICKET_URL}</code></small></div>
3232
</div>
33-
<div class="txt-center pts pbs man"><button type="submit" class="browser-style">Save preferences</button></div>
33+
<div class="pts row">
34+
<div class="w40p txt-center browser-style">
35+
<input type="checkbox" id="enable_jira_ticket_link">
36+
</div>
37+
<div>
38+
<label for="enable_jira_ticket_link">Enable Jira ticket link</label>
39+
</div>
40+
</div>
41+
<div class="pll pbs" id="jira-ticket-link-options">
42+
<div class="pbs"><small class="txt-muted">The ticket ID is automatically searched for in the source branch name and in the Merge Request title. The position of the ticket ID in these locations doesn't matter. Only the first uppercase ticket ID occurence will be matched.</small></div>
43+
<div class="browser-style pbs man"><label for="base_jira_url">Base Jira URL:</label></div>
44+
<div class="browser-style man pbm"><input type="url" id="base_jira_url" class="w100 man pas" required></div>
45+
<div class="pbs">What should be displayed as the label of the Jira ticket link?</div>
46+
<div class="pll">
47+
<div class="browser-style man pbs"><input type="radio" required name="jira_ticket_link_label_type" value="ticket_id" id="jira_ticket_link_label_type_ticket_id" class="man"> <label for="jira_ticket_link_label_type_ticket_id">The ticket ID</label></div>
48+
<div class="browser-style man"><input type="radio" required name="jira_ticket_link_label_type" value="icon" id="jira_ticket_link_label_type_icon" class="man"> <label for="jira_ticket_link_label_type_icon">An icon</label></div>
49+
</div>
50+
</div>
51+
<div class="txt-center pts pbs"><button type="submit" class="browser-style">Save preferences</button></div>
3452
</form>
3553

3654
<script src="../js/preferences.js"></script>

js/content.js

+93-9
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,39 @@
212212

213213
this.setDataAttributesToMergeRequestContainer(mergeRequestContainer, mergeRequest);
214214

215+
// -----------------------------------------------
216+
// Jira ticket link (data attributes are set in setDataAttributesToMergeRequestContainer, above)
217+
218+
if (('jiraTicketId' in mergeRequestContainer.dataset) && ('jiraTicketUrl' in mergeRequestContainer.dataset)) {
219+
let jiraTicketLinkLabel = null;
220+
221+
switch (this.preferences.jira_ticket_link_label_type) {
222+
case 'ticket_id':
223+
jiraTicketLinkLabel = mergeRequestContainer.dataset.jiraTicketId;
224+
225+
break;
226+
case 'icon':
227+
jiraTicketLinkLabel = '<button class="btn btn-secondary btn-md btn-default btn-transparent btn-clipboard has-tooltip" title="Jira ticket ' + mergeRequestContainer.dataset.jiraTicketId + '">' +
228+
'<i class="fa fa-ticket" aria-hidden="true"></i>' +
229+
'</button>';
230+
231+
break;
232+
default:
233+
console.error('Invalid link label type ' + this.preferences.jira_ticket_link_label_type);
234+
}
235+
236+
if (jiraTicketLinkLabel) {
237+
let jiraTicketLink = '<a href="' + mergeRequestContainer.dataset.jiraTicketUrl + '" class="issuable-milestone">' +
238+
jiraTicketLinkLabel +
239+
'</a> ';
240+
241+
this.parseHtmlAndPrepend(
242+
mergeRequestContainer.querySelector('.merge-request-title'),
243+
jiraTicketLink
244+
);
245+
}
246+
}
247+
215248
// -----------------------------------------------
216249
// Copy MR info button
217250

@@ -276,6 +309,55 @@
276309
mergeRequestContainer.dataset.status = mergeRequest.state;
277310
mergeRequestContainer.dataset.sourceBranchName = mergeRequest.source_branch;
278311
mergeRequestContainer.dataset.targetBranchName = mergeRequest.target_branch;
312+
313+
if (this.preferences.enable_jira_ticket_link) {
314+
let jiraTicketId = this.findFirstJiraTicketId(mergeRequest);
315+
316+
if (jiraTicketId) {
317+
mergeRequestContainer.dataset.jiraTicketId = jiraTicketId;
318+
mergeRequestContainer.dataset.jiraTicketUrl = this.createJiraTicketUrl(jiraTicketId);
319+
}
320+
}
321+
}
322+
323+
/**
324+
* Finds a Jira ticket ID in the given Merge Request object. It first tris in the source branch name, then
325+
* fallbacks to the Merge Request title.
326+
*/
327+
findFirstJiraTicketId(mergeRequest) {
328+
let jiraTicketIdRegex = new RegExp('[A-Z]{1,10}-\\d+');
329+
330+
// First try in the source branch name
331+
let results = jiraTicketIdRegex.exec(mergeRequest.source_branch);
332+
333+
if (results) {
334+
return results[0];
335+
}
336+
337+
// Fallback to the Merge Request title if none found in the source branch name
338+
results = jiraTicketIdRegex.exec(mergeRequest.title);
339+
340+
if (results) {
341+
return results[0];
342+
}
343+
344+
return null;
345+
}
346+
347+
/**
348+
* Creates an URL to a given Jira ticket ID, pointing to the Jira base URL the user has defined in its
349+
* preferences.
350+
*/
351+
createJiraTicketUrl(jiraTicketId) {
352+
let baseJiraUrl = new URL(this.preferences.base_jira_url);
353+
354+
if (!baseJiraUrl.pathname.endsWith('/')) {
355+
baseJiraUrl.pathname += '/';
356+
}
357+
358+
baseJiraUrl.pathname += 'browse/' + jiraTicketId;
359+
360+
return baseJiraUrl.toString();
279361
}
280362

281363
/**
@@ -324,21 +406,23 @@
324406
*/
325407
buildMergeRequestInfoText(mergeRequestContainer) {
326408
let placeholders = {
327-
'MR_TITLE': mergeRequestContainer.dataset.title,
328-
'MR_ID': mergeRequestContainer.dataset.iid,
329-
'MR_URL': mergeRequestContainer.dataset.url,
330-
'MR_DIFFS_URL': mergeRequestContainer.dataset.diffsUrl,
331-
'MR_AUTHOR_NAME': mergeRequestContainer.dataset.authorName,
332-
'MR_STATUS': mergeRequestContainer.dataset.status,
333-
'MR_SOURCE_BRANCH_NAME': mergeRequestContainer.dataset.sourceBranchName,
334-
'MR_TARGET_BRANCH_NAME': mergeRequestContainer.dataset.targetBranchName
409+
MR_TITLE: mergeRequestContainer.dataset.title,
410+
MR_ID: mergeRequestContainer.dataset.iid,
411+
MR_URL: mergeRequestContainer.dataset.url,
412+
MR_DIFFS_URL: mergeRequestContainer.dataset.diffsUrl,
413+
MR_AUTHOR_NAME: mergeRequestContainer.dataset.authorName,
414+
MR_STATUS: mergeRequestContainer.dataset.status,
415+
MR_SOURCE_BRANCH_NAME: mergeRequestContainer.dataset.sourceBranchName,
416+
MR_TARGET_BRANCH_NAME: mergeRequestContainer.dataset.targetBranchName,
417+
MR_JIRA_TICKET_ID: ('jiraTicketId' in mergeRequestContainer.dataset) ? mergeRequestContainer.dataset.jiraTicketId : '',
418+
MR_JIRA_TICKET_URL: ('jiraTicketUrl' in mergeRequestContainer.dataset) ? mergeRequestContainer.dataset.jiraTicketUrl : ''
335419
};
336420

337421
let placeholdersReplaceRegex = new RegExp('{(' + Object.keys(placeholders).join('|') + ')}', 'g');
338422

339423
return this.preferences.copy_mr_info_format.replace(placeholdersReplaceRegex, function(_, placeholder) {
340424
return placeholders[placeholder];
341-
});
425+
}).trim();
342426
}
343427
}
344428

0 commit comments

Comments
 (0)