Skip to content
This repository was archived by the owner on Nov 14, 2019. It is now read-only.

Commit 1bc4c0c

Browse files
committedAug 20, 2017
Initial
0 parents  commit 1bc4c0c

11 files changed

+2096
-0
lines changed
 

‎.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
.history/**
2+
node_modules/**

‎README.md

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
## jira-quick-helper
2+
Quickly deal with your JIRA tasks from the system tray.
3+
4+
## Current Version
5+
1.0.0
6+
7+
## Author
8+
Faizaan
9+
10+
## License
11+
MIT
12+
13+

‎build/icon.icns

1.46 KB
Binary file not shown.

‎build/icon.ico

5.3 KB
Binary file not shown.

‎build/icons/32x32.png

1.64 KB
Loading

‎configure.html

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<html>
2+
3+
<head>
4+
<title>Configure helper</title>
5+
<style>
6+
* {
7+
font-family: Helvetica, sans-serif;
8+
}
9+
</style>
10+
</head>
11+
12+
<body>
13+
<div>
14+
<label>JIRA URL</label>
15+
<input type="text" id="hostname" required />
16+
<label>Username</label>
17+
<input type="text" id="username" required />
18+
<label>Password</label>
19+
<input type="text" id="pass" required />
20+
<input type="submit" id="store" />
21+
<input type="button" value="Cancel" />
22+
</div>
23+
<script src="script.js">
24+
</script>
25+
</body>
26+
27+
</html>

‎main.js

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
const { app, Tray, Menu, BrowserWindow, shell } = require("electron");
2+
const path = require("path");
3+
const Store = require("electron-store");
4+
const platformInfo = require("electron-platform");
5+
const Notification = require("electron-notifications");
6+
const JIRAService = require("./service");
7+
8+
const iconPath = platformInfo.isWin32
9+
? path.join(__dirname, "build", "icon.ico")
10+
: path.join(__dirname, "build", "icons", "32x32.png");
11+
12+
const store = new Store();
13+
const service = new JIRAService(store.store);
14+
let appTray = null;
15+
let win = null;
16+
17+
const configureHelper = () => {
18+
let configureWin = new BrowserWindow({
19+
width: 200,
20+
height: 200,
21+
resizable: false,
22+
movable: false,
23+
autoHideMenuBar: true,
24+
webPreferences: {
25+
devTools: false
26+
},
27+
alwaysOnTop: true,
28+
frame: true
29+
});
30+
configureWin.loadURL("file://" + path.join(__dirname, "configure.html"));
31+
configureWin.on("closed", () => {
32+
console.log("Configuration updated. Refreshing list");
33+
store.set("first_time", false);
34+
service.opts = store.store;
35+
getAllIssues();
36+
});
37+
};
38+
39+
const commonMenu = [
40+
{
41+
label: "Configure",
42+
click: configureHelper
43+
},
44+
{
45+
label: "Refresh",
46+
click: getAllIssues
47+
},
48+
{
49+
role: "quit"
50+
}
51+
];
52+
53+
function getAllIssues() {
54+
appTray.setToolTip("Loading your issues...");
55+
service
56+
.fetchMyJIRAIssues()
57+
.then((issues = []) =>
58+
issues.map(({ summary, key, transitions = [] }) => ({
59+
label: `[${key}] ${summary}`,
60+
submenu: [
61+
...transitions.map(({ name, id }) => ({
62+
label: `Mark as '${name}'`,
63+
click: () => service.doTransition(key, id).then(getAllIssues)
64+
})),
65+
{ type: "separator" },
66+
{
67+
label: "Open in browser",
68+
click: () => {
69+
shell.openExternal(
70+
`https://${store.get("hostname")}/browse/${key}`
71+
);
72+
}
73+
}
74+
]
75+
}))
76+
)
77+
.then(issuesMenu => [...issuesMenu, { type: "separator" }, ...commonMenu])
78+
.then(Menu.buildFromTemplate)
79+
.then(contextMenu => appTray.setContextMenu(contextMenu))
80+
.then(() => {
81+
console.info("Loaded issues succesfully.");
82+
appTray.setToolTip("Right-click to view your issues.");
83+
})
84+
.catch(error => {
85+
console.error(error.message);
86+
appTray.setToolTip("Error loading issues.");
87+
appTray.setContextMenu(Menu.buildFromTemplate(commonMenu));
88+
});
89+
}
90+
91+
app.on("ready", function() {
92+
win = new BrowserWindow({
93+
autoHideMenuBar: true,
94+
title: "Configure the JIRA Helper",
95+
webPreferences: {
96+
devTools: false
97+
},
98+
show: false
99+
});
100+
appTray = new Tray(iconPath);
101+
if (store.get("first_time")) {
102+
configureHelper();
103+
} else {
104+
getAllIssues();
105+
}
106+
});

‎package.json

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
{
2+
"name": "jira-tray-helper",
3+
"version": "1.0.0",
4+
"description": "Quickly deal with your JIRA tasks from the system tray.",
5+
"main": "main.js",
6+
"scripts": {
7+
"start": "electron ."
8+
},
9+
"author": "Faizaan <aulisius7@gmail.com>",
10+
"license": "MIT",
11+
"devDependencies": {
12+
"electron": "1.7.5",
13+
"electron-builder": "^19.22.1"
14+
},
15+
"dependencies": {
16+
"electron-platform": "^1.2.0",
17+
"electron-store": "^1.2.0",
18+
"request": "2.81.0",
19+
"request-promise-native": "1.0.4"
20+
}
21+
}

‎script.js

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
const Store = require("electron-store");
2+
let store = new Store({
3+
defaults: {
4+
hostname: "",
5+
password: "",
6+
username: "",
7+
first_time: true
8+
}
9+
});
10+
11+
let hostname = document.getElementById("hostname");
12+
let username = document.getElementById("username");
13+
let password = document.getElementById("pass");
14+
15+
hostname.value = store.get("hostname");
16+
username.value = store.get("username");
17+
password.value = store.get("password");
18+
19+
document.getElementById("store").addEventListener("click", e => {
20+
store.set("hostname", hostname.value);
21+
store.set("username", username.value);
22+
store.set("password", password.value);
23+
window.close();
24+
});

‎service.js

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
const request = require("request-promise-native");
2+
3+
class JIRAService {
4+
constructor({ hostname, username, password }) {
5+
this.jiraAPI = request.defaults({
6+
baseUrl: `https://${username}:${password}@${hostname}/rest/api/latest`,
7+
json: true
8+
});
9+
}
10+
11+
set opts({ hostname, username, password }) {
12+
this.jiraAPI = request.defaults({
13+
baseUrl: `https://${username}:${password}@${hostname}/rest/api/latest`,
14+
json: true
15+
});
16+
}
17+
18+
async fetchMyJIRAIssues() {
19+
const { issues = [] } = await this.jiraAPI({
20+
url: "/search",
21+
qs: {
22+
jql: "assignee=currentuser()",
23+
fields: "status,summary"
24+
}
25+
});
26+
27+
return Promise.all(
28+
issues
29+
.filter(issue => issue.fields.status.statusCategory.id !== 3)
30+
.map(async ({ fields: { summary, status }, key }) => ({
31+
summary,
32+
key,
33+
status: status.statusCategory,
34+
transitions: await this.fetchTransitions(key)
35+
}))
36+
);
37+
}
38+
39+
async fetchTransitions(id) {
40+
const { transitions = [] } = await this.jiraAPI(`/issue/${id}/transitions`);
41+
return transitions.map(({ id, name }) => ({ id, name }));
42+
}
43+
44+
async doTransition(id, toState) {
45+
let success;
46+
try {
47+
await this.jiraAPI.post({
48+
url: `/issue/${id}/transitions`,
49+
json: true,
50+
body: {
51+
transition: {
52+
id: toState.toString()
53+
}
54+
}
55+
});
56+
success = true;
57+
} catch (error) {
58+
success = false;
59+
}
60+
return success;
61+
}
62+
}
63+
64+
module.exports = JIRAService;

‎yarn.lock

+1,839
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
This repository has been archived.