Skip to content

Commit 97d45b0

Browse files
committed
implemented mutex for config colors
1 parent 47c0f5e commit 97d45b0

File tree

5 files changed

+100
-39
lines changed

5 files changed

+100
-39
lines changed

controllers/configController.js

+45-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const fs = require('fs');
22
const path = require('path');
3+
const { Mutex } = require('async-mutex');
34
const jiraAPIController = require('../controllers/jiraAPIController');
45
const { JSDOM } = require('jsdom');
56
const fetch = (...args) => import('node-fetch').then(({default: fetch}) => fetch(...args));
@@ -14,19 +15,58 @@ const defaultConfig = {
1415
issueColors: {}
1516
};
1617

17-
exports.loadConfig = function () {
18+
// Create a mutex
19+
const mutex = new Mutex();
20+
21+
const issueColorsToUpdate = {};
22+
23+
exports.loadConfig = async function () {
24+
return await mutex.runExclusive( async () => {
25+
return readSettingsInternal();
26+
});
27+
}
28+
29+
function readSettingsInternal() {
1830
if (!fs.existsSync(configPath)) {
1931
fs.writeFileSync(configPath, JSON.stringify(defaultConfig, null, 2), 'utf8');
2032
}
2133
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
2234
}
2335

24-
exports.setSetting = function (key, value) {
25-
const config = exports.loadConfig();
26-
config[key] = value;
27-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
36+
exports.setSetting = async function (key, value) {
37+
return await mutex.runExclusive(async () => {
38+
const config = readSettingsInternal();
39+
config[key] = value;
40+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
41+
});
2842
}
2943

44+
/**
45+
* Accumulate issue colors to update.
46+
* @param {string} issueKey - The issue key.
47+
* @param {string} color - The color to update.
48+
*/
49+
exports.accumulateIssueColor = async function(issueKey, color) {
50+
await mutex.runExclusive(async () => {
51+
issueColorsToUpdate[issueKey.toLowerCase()] = color;
52+
});
53+
};
54+
55+
/**
56+
* Save accumulated issue colors.
57+
*/
58+
exports.saveAccumulatedIssueColors = async function() {
59+
await mutex.runExclusive(async () => {
60+
const config = readSettingsInternal();
61+
config.issueColors = { ...config.issueColors, ...issueColorsToUpdate };
62+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
63+
// Clear the accumulated colors after saving
64+
for (const key in issueColorsToUpdate) {
65+
delete issueColorsToUpdate[key];
66+
}
67+
});
68+
};
69+
3070
exports.ensureConfigDirExists = async function(req) {
3171
if (!fs.existsSync(configDir)) {
3272
fs.mkdirSync(configDir);

controllers/jiraController.js

+37-31
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ const configController = require('./configController');
33
const dayjs = require('dayjs');
44
const path = require('path');
55

6-
exports.getParentIssueColor = async function(settings, req, issue) {
6+
exports.getParentIssueColor = async function(settings, req, issue, newIssueColorKeys) {
77
if (issue.fields.parent) {
88
const parentIssue = await jiraAPIController.getIssue(req, issue.fields.parent.id);
99
if (parentIssue && parentIssue.fields) {
@@ -13,46 +13,50 @@ exports.getParentIssueColor = async function(settings, req, issue) {
1313
}
1414
if (settings.issueColors[parentIssue.key.toLowerCase()]) {
1515
return settings.issueColors[parentIssue.key.toLowerCase()];
16-
}
17-
if (parentIssue.fields[configController.loadConfig().issueColorField]) {
18-
const jiraColor = parentIssue.fields[configController.loadConfig().issueColorField];
19-
switch (jiraColor) {
20-
case 'purple': return '#8777D9';
21-
case 'blue': return '#2684FF';
22-
case 'green': return '#57D9A3';
23-
case 'teal': return '#00C7E6';
24-
case 'yellow': return '#FFC400';
25-
case 'orange': return '#FF7452';
26-
case 'grey': return '#6B778C';
27-
case 'dark_purple': return '#5243AA';
28-
case 'dark_blue': return '#0052CC';
29-
case 'dark_green': return '#00875A';
30-
case 'dark_teal': return '#00A3BF';
31-
case 'dark_yellow': return '#FF991F';
32-
case 'dark_orange': return '#DE350B';
33-
case 'dark_grey': return '#253858';
34-
default: return jiraColor;
16+
}else {
17+
let color = process.env.DEFAULT_ISSUE_COLOR || '#2a75fe';
18+
if (parentIssue.fields[settings.issueColorField]) {
19+
const jiraColor = parentIssue.fields[settings.issueColorField];
20+
switch (jiraColor) {
21+
case 'purple': color = '#8777D9'; break;
22+
case 'blue': color = '#2684FF'; break;
23+
case 'green': color = '#57D9A3'; break;
24+
case 'teal': color = '#00C7E6'; break;
25+
case 'yellow': color = '#FFC400'; break;
26+
case 'orange': color = '#FF7452'; break;
27+
case 'grey': color = '#6B778C'; break;
28+
case 'dark_purple': color = '#5243AA'; break;
29+
case 'dark_blue': color = '#0052CC'; break;
30+
case 'dark_green': color = '#00875A'; break;
31+
case 'dark_teal': color = '#00A3BF'; break;
32+
case 'dark_yellow': color = '#FF991F'; break;
33+
case 'dark_orange': color = '#DE350B'; break;
34+
case 'dark_grey': color = '#253858'; break;
35+
default: color = jiraColor; break;
36+
}
37+
await configController.accumulateIssueColor(parentIssue.key, color);
38+
settings.issueColors[parentIssue.key.toLowerCase()] = color;
39+
return color;
3540
}
41+
3642
}
43+
3744
}
3845
}
3946
return null;
4047
}
4148

42-
exports.determineIssueColor = async function(req, issue) {
43-
const settings = configController.loadConfig();
49+
exports.determineIssueColor = async function(settings, req, issue) {
4450
const defaultColor = process.env.DEFAULT_ISSUE_COLOR || '#2a75fe';
4551

4652
let color = settings.issueColors[issue.issueKey.toLowerCase()];
4753
if (!color) {
48-
// get the issue details
4954
const issueDetails = await jiraAPIController.getIssue(req, issue.issueId);
5055
color = await exports.getParentIssueColor(settings, req, issueDetails);
5156
if (!color && issue.issueType) {
5257
const issueTypeLower = issue.issueType.toLowerCase();
5358
color = settings.issueColors[issueTypeLower] || defaultColor;
5459
}
55-
configController.setSetting('issueColors', { ...settings.issueColors, [issue.issueKey.toLowerCase()]: color });
5660
}
5761
return color;
5862
}
@@ -73,8 +77,10 @@ exports.getUsersWorkLogsAsEvent = async function(req, start, end) {
7377
const worklogs = await getFilteredWorklogs(req, issue, filterStartTime, filterEndTime);
7478
return Promise.all(worklogs);
7579
});
76-
const worklogs = await Promise.all(worklogPromises);
77-
return worklogs.flat();
80+
const worklogs = await Promise.all(worklogPromises)
81+
const flatLogs = worklogs.flat();
82+
await configController.saveAccumulatedIssueColors();
83+
return flatLogs;
7884
});
7985
};
8086

@@ -88,12 +94,13 @@ function extractIssues(issues) {
8894
}));
8995
}
9096

91-
function getFilteredWorklogs(req, issue, filterStartTime, filterEndTime) {
97+
async function getFilteredWorklogs(req, issue, filterStartTime, filterEndTime) {
98+
const settings = await configController.loadConfig();
9299
return jiraAPIController.getIssueWorklogs(req, issue.issueId, filterEndTime.getTime(), filterStartTime.getTime())
93100
.then(result => {
94101
const worklogs = result.worklogs;
95102
const filteredLogs = worklogs.filter(worklog => filterWorklog(req, worklog, filterStartTime, filterEndTime));
96-
const promises = filteredLogs.map(worklog => exports.formatWorklog(req, worklog, issue));
103+
const promises = filteredLogs.map(worklog => exports.formatWorklog(settings, req, worklog, issue));
97104
return promises;
98105
});
99106
}
@@ -111,9 +118,8 @@ function filterWorklog(req, worklog, filterStartTime, filterEndTime) {
111118
return condition;
112119
}
113120

114-
exports.formatWorklog = async function (req, worklog, issue) {
115-
const color = await exports.determineIssueColor(req, issue);
116-
const settings = configController.loadConfig();
121+
exports.formatWorklog = async function (settings, req, worklog, issue) {
122+
const color = await exports.determineIssueColor(settings, req, issue);
117123
const showIssueTypeIcons = settings.showIssueTypeIcons || false;
118124

119125
let title = `<b class="plywood-event-title">${issue.issueKey} - ${issue.summary}</b> <span class="comment">${worklog.comment || ''}</span>`;

package-lock.json

+14
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
"start": "node ./bin/www"
77
},
88
"dependencies": {
9+
"async-mutex": "^0.5.0",
910
"cookie-parser": "~1.4.6",
1011
"dayjs": "^1.11.13",
1112
"dotenv": "^16.4.5",

routes/config.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,16 @@ router.post('/saveConfig', async (req, res) => {
3939
await configController.ensureConfigDirExists(req);
4040
const config = req.body;
4141
for ( key in config ) {
42-
configController.setSetting(key, config[key]);
42+
await configController.setSetting(key, config[key]);
4343
}
4444
res.json({ message: 'Configuration saved successfully' }).status(200);
4545
});
4646

4747
router.post('/saveIssueColor', async (req, res) => {
4848
const { issueKey, color } = req.body;
49-
const config = configController.loadConfig();
49+
const config = await configController.loadConfig();
5050
config.issueColors[issueKey.toLowerCase()] = color;
51-
configController.setSetting('issueColors', config.issueColors);
51+
await configController.setSetting('issueColors', config.issueColors);
5252
res.json({ message: 'Issue color saved successfully' }).status(200);
5353

5454
});

0 commit comments

Comments
 (0)