Skip to content

Commit 89e337a

Browse files
committed
Various bug fizes, added loading indicator, added search using jira's issue suggestion endpoint
1 parent 12bd946 commit 89e337a

14 files changed

+641
-353
lines changed

.prettierrc

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"tabWidth": 4,
3+
"useTabs": false
4+
}

app.js

+8
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,14 @@ var nunjucks = require("nunjucks");
88
const passport = require('passport');
99
const AtlassianOAuth2Strategy = require('passport-atlassian-oauth2');
1010
const session = require('express-session');
11+
12+
var fs = require('fs');
13+
14+
if (!fs.existsSync('.env')) {
15+
console.log("No .env file found. Please create one using the .env.example file as a template.");
16+
process.exit(1);
17+
}
18+
1119
const dotenv = require('dotenv').config();
1220

1321
const https = require('https');

controllers/jiraAPIController.js

+17
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,23 @@ exports.searchIssues = function(req, jql) {
124124
return withRetry(searchIssuesInternal, req, jql);
125125
}
126126

127+
exports.suggestIssues = function(req, query) {
128+
return withRetry(suggestIssuesInternal, req, query);
129+
}
130+
131+
function suggestIssuesInternal(req, query) {
132+
const url = getCallURL(req);
133+
if (process.env.JIRA_PROJECT_JQL) {
134+
query += '&currentJQL=' + process.env.JIRA_PROJECT_JQL
135+
}
136+
return fetch(url + '/rest/api/3/issue/picker?query=' + query , {
137+
method: 'GET',
138+
headers: getDefaultHeaders(req),
139+
agent:httpsAgent
140+
}).then(res => res.json());
141+
142+
}
143+
127144
function getIssueInternal(req, issueId) {
128145
const url = getCallURL(req);
129146
return fetch(url + '/rest/api/3/issue/' + issueId, {

controllers/jiraController.js

+62-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ exports.getUsersWorkLogsAsEvent = function(req, start, end) {
99
const formattedStart = filterStartTime.toLocaleDateString('en-CA');
1010
const formattedEnd = filterEndTime.toLocaleDateString('en-CA');
1111

12-
return jiraAPIController.searchIssues(req, 'worklogAuthor = currentUser() AND worklogDate >= ' + formattedStart + ' AND worklogDate <= '+ formattedEnd).then(result => {
12+
let issuesPromise = jiraAPIController.searchIssues(req, 'worklogAuthor = currentUser() AND worklogDate >= ' + formattedStart + ' AND worklogDate <= '+ formattedEnd);
13+
14+
return issuesPromise.then(result => {
1315
// create an array of issue IDs and keys from result.issues
1416
const issues = result.issues.map(issue => { return {issueId: issue.id, issueKey: issue.key, summary: issue.fields.summary} });
1517
const userWorkLogs = [];
@@ -87,6 +89,65 @@ exports.deleteWorkLog = function(req, issueId, worklogId) {
8789
return jiraAPIController.deleteWorkLog(req, issueId, worklogId);
8890
}
8991

92+
exports.suggestIssues = function(req, start, end, query) {
93+
var startDate = new Date(start).toISOString().split('T')[0];
94+
var endDate = new Date(end).toISOString().split('T')[0];
95+
var searchInJira = req.query.searchInJira;
96+
var query = req.query.query;
97+
98+
var promises = [];
99+
100+
var emptyJQL = 'worklogAuthor = currentUser() AND worklogDate >= ' + startDate + ' AND worklogDate <= '+endDate+' OR ((assignee = currentUser() OR reporter = currentUser()) AND ((statusCategory != '+ process.env.JIRA_DONE_STATUS +') OR (statusCategory = '+ process.env.JIRA_DONE_STATUS +' AND status CHANGED DURING (' + startDate + ', '+endDate+'))))';
101+
102+
var keyJQL = 'key = ' + query
103+
104+
if (searchInJira != 'true') {
105+
promises.push(jiraAPIController.searchIssues(req, emptyJQL));
106+
} else {
107+
promises.push(jiraAPIController.searchIssues(req, keyJQL));
108+
promises.push(jiraAPIController.suggestIssues(req, query));
109+
}
110+
111+
112+
113+
return Promise.all(promises).then(results => {
114+
// results[0] = base JQL search
115+
// if search in jira
116+
// results[0] = Key search
117+
// results[1] = Suggestion search
118+
119+
var issues = []
120+
if (searchInJira != 'true' && results[0].issues) {
121+
issues = results[0].issues.map(mapIssuesFunction);
122+
}
123+
124+
if (searchInJira == 'true') {
125+
if (results[1] && results[1].sections && results[1].sections.length > 0) {
126+
for (var i = 0; i < results[1].sections.length; i++) {
127+
if (results[1].sections[i].id == "cs") {
128+
//console.log(JSON.stringify(results[0].sections[i]));
129+
// add issues from the suggestion results to the issues array
130+
var mappedIssues = results[1].sections[i].issues.map(mapIssuesFunction);
131+
issues = issues.concat(mappedIssues);
132+
}
133+
}
134+
}
135+
if (results[0] && results[0].issues) {
136+
issues = issues.concat(results[0].issues.map(mapIssuesFunction));
137+
}
138+
}
139+
return issues;
140+
});
141+
}
142+
143+
function mapIssuesFunction(issue) {
144+
return {
145+
id: issue.id,
146+
key: issue.key,
147+
summary: issue.fields ? issue.fields.summary : issue.summary
148+
}
149+
}
150+
90151

91152
function formatDateToJira(toFormat) {
92153
const dayJsDate = dayjs(toFormat);

example.env

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ JIRA_OAUTH_CALLBACK_URL=http://locahost:3000/auth/callback
2929
JIRA_DONE_STATUS="Done"
3030
# Maximum number of issues to return at once.
3131
JIRA_MAX_SEARCH_RESULTS=200
32+
# JQL used to filter suggested issues. uswful for restricting to a project
33+
JIRA_PROJECT_JQL="project = PLY"
3234

3335
# Express app settings
3436
# The port to run the app on

public/images/icons8-dots-loading.gif

7.92 KB
Loading

public/stylesheets/style.css

+18-1
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,19 @@ a {
5959
margin-bottom: 15px;
6060
}
6161

62+
.modal-content .search-options {
63+
display: flex;
64+
width: fit-content;
65+
align-items: center;
66+
}
67+
.modal-content .search-options input {
68+
flex-grow: 0;
69+
}
70+
.modal-content .search-options label {
71+
word-break: keep-all;
72+
white-space: nowrap;
73+
}
74+
6275
.modal .destructive {
6376
border: 2px solid darkred;
6477
background: red;
@@ -139,12 +152,16 @@ input#include-weekends {
139152
}
140153

141154

142-
.about-section {
155+
#about-section {
143156
position: absolute;
144157
bottom: 5px;
145158
opacity: 35%;
146159
}
147160

161+
#loading-container {
162+
width: 50px;
163+
overflow: clip;
164+
}
148165

149166

150167
/********** Choices.js **********/

routes/index.js

+3-9
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,13 @@ router.get('/events', function(req, res, next) {
3030

3131
router.get('/issues/user', function(req, res, next) {
3232
try {
33-
var startDate = new Date(req.query.start).toISOString().split('T')[0];
34-
35-
var endDate = new Date(req.query.end).toISOString().split('T')[0];
36-
37-
jiraAPIController.searchIssues(req, 'worklogAuthor = currentUser() AND worklogDate >= ' + startDate + ' AND worklogDate <= '+endDate+' OR ((assignee = currentUser() OR reporter = currentUser()) AND ((statusCategory != '+ process.env.JIRA_DONE_STATUS +') OR (statusCategory = '+ process.env.JIRA_DONE_STATUS +' AND status CHANGED DURING (' + startDate + ', '+endDate+'))))').then(result => {
38-
if (!result.issues) {
39-
console.log(result);
40-
}
41-
res.json(result.issues);
33+
jiraController.suggestIssues(req, req.query.start, req.query.end, req.query.query).then(result => {
34+
res.json(result);
4235
});
4336
} catch (error) {
4437
console.log(error);
4538
}
39+
4640
});
4741

4842
router.get('/issues/:issueId', function(req, res, next) {

views/aboutModal.njk

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{% macro modal() %}
2+
<div class="modal unselectable modal-about" style="display:none;">
3+
<div class="modal-content">
4+
<h2>About Plywood</h2>
5+
<form class="modal-form" id="modal-form-update" method="PUT" action="{{ endpoint }}">
6+
<div class="about-ctn">
7+
<div class="icons-8">
8+
<p><a target="_blank" href="https://icons8.com/icon/4ONNYCMz9R42/plywood">Plywood</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a></p>
9+
<p><a target="_blank" href="https://icons8.com/icon/KnQ23R20ge4i/dots-loading">Dots Loading</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a></p>
10+
</div>
11+
<p>Software licensed under the <a target="_blank" href="https://opensource.org/license/MIT/">MIT License</a></p>
12+
<p>Version: 0.2.1</p>
13+
</div>
14+
<div class="buttons">
15+
<button id="close-btn-about" title="Close" type="button">✖️</button>
16+
</div>
17+
</form>
18+
</div>
19+
</div>
20+
21+
<script>
22+
23+
function showAboutModal() {
24+
const modal = document.querySelector('.modal-about');
25+
modal.style.display = 'block';
26+
}
27+
28+
// Hide the modal
29+
function hideAboutModal() {
30+
const modal = document.querySelector('.modal-about');
31+
modal.style.display = 'none';
32+
}
33+
34+
// Add event listeners to the buttons
35+
const closeBtnAbout = document.getElementById('close-btn-about');
36+
closeBtnAbout.addEventListener('click', hideAboutModal);
37+
38+
</script>
39+
{% endmacro %}

0 commit comments

Comments
 (0)