From f7973d311e2f29ca91eca2bc72df3a961eb6784a Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Wed, 25 Sep 2024 22:38:39 +0200 Subject: [PATCH 1/8] wip: add outlook event sync --- .gitignore | 1 + chrome-extension/js/view.js | 116 ++++++++++++++++++++++++++++++++++++ chrome-extension/popup.html | 1 + 3 files changed, 118 insertions(+) diff --git a/.gitignore b/.gitignore index 02345ef..91f876e 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json +.idea # Logs logs diff --git a/chrome-extension/js/view.js b/chrome-extension/js/view.js index ec5f6ae..083be21 100644 --- a/chrome-extension/js/view.js +++ b/chrome-extension/js/view.js @@ -4,6 +4,7 @@ window.View = window.View || {} window.View.Main = (function () { var worklogDateInput, getWorklogButton, + getOutlookEventsButton, worklogInput, addWorklogsButton, saveButton, @@ -16,6 +17,7 @@ window.View.Main = (function () { View.Table.init() getWorklogButton = document.getElementById('getWorklogButton') + getOutlookEventsButton = document.getElementById('getOutlookEventsButton') worklogInput = document.getElementById('worklog') addWorklogsButton = document.getElementById('addWorklogs') saveButton = document.getElementById('save') @@ -64,6 +66,120 @@ window.View.Main = (function () { }) }) + getOutlookEventsButton.addEventListener('click', () => { + let redirectUrl = chrome.identity.getRedirectURL() + + /*global chrome*/ + chrome.identity.launchWebAuthFlow( + { + url: 'https://login.microsoftonline.com/9514c5bb-f1dc-4ce8-a6a5-dc12d7d15702/oauth2/v2.0/authorize?' + + 'response_type=token' + + '&response_mode=fragment' + + `&client_id=1babeb8a-7ace-4e8d-8f40-a338da4eea29` + // Calender-Read Jira + `&redirect_uri=${redirectUrl}` + + '&scope=Calendars.ReadBasic', + interactive: true + }, + function (responseWithToken) { + console.log('Authorization response:', responseWithToken); + + if (chrome.runtime.lastError || !responseWithToken) { + console.error('Authorization failed:', chrome.runtime.lastError); + return; + } + + const token = extractAccessToken(responseWithToken); + if (token) { + fetchCalendarEntries(token); // Use the access token to fetch calendar entries + } else { + console.error('Failed to extract access token from response:', responseWithToken); + } + } + ); + }) + + // Utility function to extract the access token from the response + function extractAccessToken(responseUrl) { + const params = new URLSearchParams(responseUrl.split('#')[1]); + return params.get('access_token'); + } + + async function fetchAllPages(url, accessToken) { + let allResults = []; + + async function fetchPage(url) { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch calendar entries'); + } + + const data = await response.json(); + allResults = allResults.concat(data.value); + + if (data['@odata.nextLink']) { + await fetchPage(data['@odata.nextLink']); + } + } + + await fetchPage(url); + return allResults; + } + + function fetchCalendarEntries(accessToken) { + const dateStart = new Date(new Date(worklogDateInput.value).setHours(0, 0, 0, 0)); + const dateEnd = new Date(new Date(worklogDateInput.value).setHours(23, 59, 59, 999)); + const url = `https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${dateStart.toISOString()}&endDateTime=${dateEnd.toISOString()}`; + + fetchAllPages(url, accessToken) + .then(data => { + console.log('Calendar events:', data); + + // Sort events by start date + data.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime)); + + // Filter out events based on the specified criteria + const filteredData = data.filter(event => + !event.isAllDay && + !event.isCancelled && + event.sensitivity !== 'private' && + event.subject !== 'Mittagspause' && + event.subject !== 'Notizen' && + event.subject !== 'Notes' + ); + + const worklogItems = filteredData.map(event => { + const startTime = new Date(event.start.dateTime); + const endTime = new Date(event.end.dateTime); + const durationMinutes = (endTime - startTime) / (1000 * 60); // Convert milliseconds to minutes + const durationString = durationMinutes >= 60 + ? `${Math.floor(durationMinutes / 60)}h ${durationMinutes % 60}m` + : `${durationMinutes}m`; + const worklogString = `${event.subject} ${durationString} ${event.subject}`; + return worklogString; + }); + + console.log('Worklog items:', worklogItems); + + setLoadingStatus(true) + Controller.LogController.bulkInsert(worklogItems.join('\n')).then( + () => { + mediator.trigger('view.table.new-worklog.changed', {}) + setLoadingStatus(false) + } + ) + }) + .catch(error => { + console.error('Error fetching calendar entries:', error); + }); + } + addWorklogsButton.addEventListener('click', () => { setLoadingStatus(true) Controller.LogController.bulkInsert(worklogInput.value).then( diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html index 7ecbde8..b5ab551 100644 --- a/chrome-extension/popup.html +++ b/chrome-extension/popup.html @@ -40,6 +40,7 @@

Jira Worklog Tool

+ From f0bf54f7fb7976f6353d7f64eb7fef6382cac24a Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Thu, 26 Sep 2024 01:00:11 +0200 Subject: [PATCH 2/8] wip: add promises, refactor structure, add options --- chrome-extension/js/controller.js | 6 +- chrome-extension/js/options.js | 63 +++++++--- chrome-extension/js/outlook-helper.js | 164 ++++++++++++++++++++++++++ chrome-extension/js/update-script.js | 14 ++- chrome-extension/js/view.js | 125 ++------------------ chrome-extension/manifest.json | 11 +- chrome-extension/options.html | 17 ++- chrome-extension/popup.html | 3 +- 8 files changed, 260 insertions(+), 143 deletions(-) create mode 100644 chrome-extension/js/outlook-helper.js diff --git a/chrome-extension/js/controller.js b/chrome-extension/js/controller.js index 5bf2a25..ffe41ab 100644 --- a/chrome-extension/js/controller.js +++ b/chrome-extension/js/controller.js @@ -1,8 +1,8 @@ window.Controller = window.Controller || {} -window.Controller.LogController = (function (JiraHelper, Model, JiraParser) { +window.Controller.LogController = (function (JiraHelper, OutlookHelper, Model, JiraParser) { 'use strict' function init () { - return JiraHelper.init() + return Promise.all([JiraHelper.init(), OutlookHelper.init()]) } function getWorklogsByDay (worklogDate) { @@ -144,4 +144,4 @@ window.Controller.LogController = (function (JiraHelper, Model, JiraParser) { init: init, getInvalidFields: getInvalidFields } -})(window.JiraHelper, window.Model, window.JiraParser) +})(window.JiraHelper, window.OutlookHelper, window.Model, window.JiraParser) diff --git a/chrome-extension/js/options.js b/chrome-extension/js/options.js index da1dc89..2866c14 100644 --- a/chrome-extension/js/options.js +++ b/chrome-extension/js/options.js @@ -2,15 +2,16 @@ var chrome = window.chrome || {} var JiraHelper = window.JiraHelper || {} // Saves options to chrome.storage -function saveOptions (options) { +function saveOptions (jiraOptions, outlookOptions) { // make sure to not save user password, as chrome storage is not encrypted (https://developer.chrome.com/apps/storage#using-sync). // The JESSIONID authentication cookie will be remembered by the browser once User clicks 'Test Connection' anyway, // and Jira will consider the JESSIONID cookie and ignore the basic auth settings for the requests. - options.password = '' + jiraOptions.password = '' chrome.storage.sync.set( { - jiraOptions: options + jiraOptions: jiraOptions, + outlookOptions: outlookOptions }, function () { // Update status to let user know options were saved. @@ -28,24 +29,32 @@ function saveOptions (options) { function restoreOptions () { chrome.storage.sync.get( { - jiraOptions: {} + jiraOptions: {}, + outlookOptions: {} }, function (items) { - restoreOptionsToInput(items.jiraOptions) + restoreJiraOptionsToInput(items.jiraOptions) + restoreOutlookOptionsToInput(items.outlookOptions) } ) } -var jiraUrlInput, userInput, passwordInput, tokenInput +var jiraUrlInput, userInput, passwordInput, tokenInput, outlookSyncEnabledInput, tenantIDInput, clientIDInput -function restoreOptionsToInput (options) { +function restoreJiraOptionsToInput (options) { jiraUrlInput.value = options.jiraUrl || '' userInput.value = options.user || '' passwordInput.value = options.password || '' tokenInput.value = options.token || '' } -function getOptionsFromInput () { +function restoreOutlookOptionsToInput (options) { + outlookSyncEnabledInput.checked = options.outlookSyncEnabled || false + tenantIDInput.value = options.tenantID || '' + clientIDInput.value = options.clientID || '' +} + +function getJiraOptionsFromInput () { return { jiraUrl: jiraUrlInput.value, user: userInput.value, @@ -54,6 +63,14 @@ function getOptionsFromInput () { } } +function getOutlookOptionsFromInput () { + return { + outlookSyncEnabled: outlookSyncEnabledInput.checked, + tenantID: tenantIDInput.value, + clientID: clientIDInput.value + } +} + document.addEventListener('DOMContentLoaded', () => { restoreOptions() jiraUrlInput = document.getElementById('jiraUrl') @@ -61,21 +78,39 @@ document.addEventListener('DOMContentLoaded', () => { passwordInput = document.getElementById('password') tokenInput = document.getElementById('token') + outlookSyncEnabledInput = document.getElementById('outlookSyncEnabled') + tenantIDInput = document.getElementById('tenantID') + clientIDInput = document.getElementById('clientID') + document.getElementById('save').addEventListener('click', () => { - saveOptions(getOptionsFromInput()) + saveOptions(getJiraOptionsFromInput(), getOutlookOptionsFromInput()) }) - document.getElementById('testConnection').addEventListener('click', () => { - var jiraOptions = getOptionsFromInput() + document.getElementById('testConnectionJira').addEventListener('click', () => { + var jiraOptions = getJiraOptionsFromInput() console.log(jiraOptions) JiraHelper.testConnection(jiraOptions) .then(result => { console.info('connection successful', result) - saveOptions(getOptionsFromInput()) - alert('Connection [OK]') + saveOptions(getJiraOptionsFromInput(), getOutlookOptionsFromInput()) + alert('Jira Connection [OK]') + }) + .catch(error => { + console.error('connection failed', error) + alert('Jira Connection [FAILED]. Please double-check the options.') + }) + }) + document.getElementById('testConnectionOutlook').addEventListener('click', () => { + var outlookOptions = getOutlookOptionsFromInput() + console.log(outlookOptions) + OutlookHelper.testConnection(outlookOptions) + .then(result => { + console.info('connection successful', result) + saveOptions(getJiraOptionsFromInput(), getOutlookOptionsFromInput()) + alert('Outlook Connection [OK]') }) .catch(error => { console.error('connection failed', error) - alert('Connection [FAILED]. Please double-check the options.') + alert('Outlook Connection [FAILED]. Please double-check the options.') }) }) }) diff --git a/chrome-extension/js/outlook-helper.js b/chrome-extension/js/outlook-helper.js new file mode 100644 index 0000000..038902c --- /dev/null +++ b/chrome-extension/js/outlook-helper.js @@ -0,0 +1,164 @@ +(function (chrome) { + var outlookOptions = {} + + function setOutlookOptions (options) { + outlookOptions = options + } + + function testConnection () { + if (!outlookOptions.outlookSyncEnabled) { + console.log('Outlook sync is disabled') + return Promise.resolve() + } + + return getToken(); + } + + function init () { + return new Promise((resolve, reject) => { + chrome.storage.sync.get( + { + outlookOptions: {} + }, + function (items) { + setOutlookOptions(items.outlookOptions) + testConnection() + .then(resolve) + .catch(reject) + } + ) + }) + } + + function getToken() { + return new Promise((resolve, reject) => { + if (!outlookOptions.tenantID || !outlookOptions.clientID) { + console.log('Outlook options are not set'); + reject('Outlook options are not set'); + } + + chrome.identity.launchWebAuthFlow( + { + url: `https://login.microsoftonline.com/${outlookOptions.tenantID}/oauth2/v2.0/authorize?` + + 'response_type=token' + + '&response_mode=fragment' + + '&client_id=' + outlookOptions.clientID + + '&redirect_uri=' + chrome.identity.getRedirectURL() + + '&scope=Calendars.ReadBasic', + interactive: true + }, + function (responseWithToken) { + console.log('Authorization response:', responseWithToken); + if (chrome.runtime.lastError || !responseWithToken) { + reject('Authorization failed: ' + chrome.runtime.lastError); + } + + const token = extractAccessToken(responseWithToken); + if (token) { + resolve(token); + } else { + reject('Failed to extract access token from response: ' + responseWithToken); + } + } + ); + + chrome.identity.getAuthToken({ 'interactive': true }, function(token) { + if (chrome.runtime.lastError) { + reject(chrome.runtime.lastError) + } else { + resolve(token) + } + }) + }) + } + + function extractAccessToken(responseUrl) { + const params = new URLSearchParams(responseUrl.split('#')[1]); + return params.get('access_token'); + } + + async function fetchAllPages(url, accessToken) { + let allResults = []; + + async function fetchPage(url) { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Authorization': `Bearer ${accessToken}`, + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + throw new Error('Failed to fetch calendar entries'); + } + + const data = await response.json(); + allResults = allResults.concat(data.value); + + if (data['@odata.nextLink']) { + await fetchPage(data['@odata.nextLink']); + } + } + + await fetchPage(url); + return allResults; + } + + function fetchCalendarEntries(accessToken) { + return new Promise((resolve, reject) => { + const dateStart = new Date(new Date(worklogDateInput.value).setHours(0, 0, 0, 0)); + const dateEnd = new Date(new Date(worklogDateInput.value).setHours(23, 59, 59, 999)); + const url = `https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${dateStart.toISOString()}&endDateTime=${dateEnd.toISOString()}`; + + fetchAllPages(url, accessToken) + .then(data => { + console.log('Calendar events:', data); + + // Sort events by start date + data.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime)); + + // Filter out events based on the specified criteria + const filteredData = data.filter(event => + !event.isAllDay && + !event.isCancelled && + event.sensitivity !== 'private' && + event.subject !== 'Mittagspause' && + event.subject !== 'Notizen' && + event.subject !== 'Notes' + ); + + const worklogItems = filteredData.map(event => { + const startTime = new Date(event.start.dateTime); + const endTime = new Date(event.end.dateTime); + const durationMinutes = (endTime - startTime) / (1000 * 60); // Convert milliseconds to minutes + const durationString = durationMinutes >= 60 + ? `${Math.floor(durationMinutes / 60)}h ${durationMinutes % 60}m` + : `${durationMinutes}m`; + const worklogString = `${event.subject} ${durationString} ${event.subject}`; + return worklogString; + }); + + console.log('Worklog items:', worklogItems); + + Controller.LogController.bulkInsert(worklogItems.join('\n')) + .then(() => { + mediator.trigger('view.table.new-worklog.changed', {}) + resolve() + }); + }) + .catch(error => { + reject('Error fetching calendar entries: ' + error); + }); + }); + } + + window.OutlookHelper = { + init: init, + testConnection: testConnection, + getToken: getToken, + fetchCalendarEntries: fetchCalendarEntries, + } +})(window.chrome) + +if (typeof module !== 'undefined') { module.exports = window.OutlookHelper } diff --git a/chrome-extension/js/update-script.js b/chrome-extension/js/update-script.js index 98aa98d..8e6e292 100644 --- a/chrome-extension/js/update-script.js +++ b/chrome-extension/js/update-script.js @@ -1,10 +1,11 @@ /* global chrome */ var updateScript = (function () { - function saveOptions (jiraOptions) { + function saveOptions (jiraOptions, outlookOptions) { return new Promise(resolve => { chrome.storage.sync.set( { - jiraOptions: jiraOptions + jiraOptions: jiraOptions, + outlookOptions: outlookOptions }, function () { resolve() @@ -16,19 +17,20 @@ var updateScript = (function () { return new Promise(resolve => { chrome.storage.sync.get( { - jiraOptions: {} + jiraOptions: {}, + outlookOptions: {} }, function (options) { - resolve(options.jiraOptions) + resolve(options.jiraOptions, options.outlookOptions) } ) }) } function removePassword () { var getPromise = getOptions() - var savePromise = getPromise.then((jiraOptions) => { + var savePromise = getPromise.then((jiraOptions, outlookOptions) => { jiraOptions.password = '' - return saveOptions(jiraOptions) + return saveOptions(jiraOptions, outlookOptions) }) return savePromise } diff --git a/chrome-extension/js/view.js b/chrome-extension/js/view.js index 083be21..e2bfa2b 100644 --- a/chrome-extension/js/view.js +++ b/chrome-extension/js/view.js @@ -1,4 +1,4 @@ -/* global Controller View mediator JiraHelper */ +/* global Controller View mediator JiraHelper OutlookHelper */ window.View = window.View || {} window.View.Main = (function () { @@ -67,118 +67,17 @@ window.View.Main = (function () { }) getOutlookEventsButton.addEventListener('click', () => { - let redirectUrl = chrome.identity.getRedirectURL() - - /*global chrome*/ - chrome.identity.launchWebAuthFlow( - { - url: 'https://login.microsoftonline.com/9514c5bb-f1dc-4ce8-a6a5-dc12d7d15702/oauth2/v2.0/authorize?' + - 'response_type=token' + - '&response_mode=fragment' + - `&client_id=1babeb8a-7ace-4e8d-8f40-a338da4eea29` + // Calender-Read Jira - `&redirect_uri=${redirectUrl}` + - '&scope=Calendars.ReadBasic', - interactive: true - }, - function (responseWithToken) { - console.log('Authorization response:', responseWithToken); - - if (chrome.runtime.lastError || !responseWithToken) { - console.error('Authorization failed:', chrome.runtime.lastError); - return; - } - - const token = extractAccessToken(responseWithToken); - if (token) { - fetchCalendarEntries(token); // Use the access token to fetch calendar entries - } else { - console.error('Failed to extract access token from response:', responseWithToken); - } - } - ); - }) - - // Utility function to extract the access token from the response - function extractAccessToken(responseUrl) { - const params = new URLSearchParams(responseUrl.split('#')[1]); - return params.get('access_token'); - } - - async function fetchAllPages(url, accessToken) { - let allResults = []; - - async function fetchPage(url) { - const response = await fetch(url, { - method: 'GET', - headers: { - 'Authorization': `Bearer ${accessToken}`, - 'Content-Type': 'application/json', - }, - }); - - if (!response.ok) { - throw new Error('Failed to fetch calendar entries'); - } - - const data = await response.json(); - allResults = allResults.concat(data.value); - - if (data['@odata.nextLink']) { - await fetchPage(data['@odata.nextLink']); - } - } - - await fetchPage(url); - return allResults; - } - - function fetchCalendarEntries(accessToken) { - const dateStart = new Date(new Date(worklogDateInput.value).setHours(0, 0, 0, 0)); - const dateEnd = new Date(new Date(worklogDateInput.value).setHours(23, 59, 59, 999)); - const url = `https://graph.microsoft.com/v1.0/me/calendar/calendarView?startDateTime=${dateStart.toISOString()}&endDateTime=${dateEnd.toISOString()}`; - - fetchAllPages(url, accessToken) - .then(data => { - console.log('Calendar events:', data); - - // Sort events by start date - data.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime)); - - // Filter out events based on the specified criteria - const filteredData = data.filter(event => - !event.isAllDay && - !event.isCancelled && - event.sensitivity !== 'private' && - event.subject !== 'Mittagspause' && - event.subject !== 'Notizen' && - event.subject !== 'Notes' - ); - - const worklogItems = filteredData.map(event => { - const startTime = new Date(event.start.dateTime); - const endTime = new Date(event.end.dateTime); - const durationMinutes = (endTime - startTime) / (1000 * 60); // Convert milliseconds to minutes - const durationString = durationMinutes >= 60 - ? `${Math.floor(durationMinutes / 60)}h ${durationMinutes % 60}m` - : `${durationMinutes}m`; - const worklogString = `${event.subject} ${durationString} ${event.subject}`; - return worklogString; - }); - - console.log('Worklog items:', worklogItems); - - setLoadingStatus(true) - Controller.LogController.bulkInsert(worklogItems.join('\n')).then( - () => { - mediator.trigger('view.table.new-worklog.changed', {}) - setLoadingStatus(false) - } - ) + setLoadingStatus(true) + OutlookHelper.getToken().then(accessToken => { + return OutlookHelper.fetchCalendarEntries(accessToken) + }).then(() => { + setLoadingStatus(false) + }).catch(error => { + console.error(error) + alert('Error fetching calendar entries: ' + error) + setLoadingStatus(false) }) - .catch(error => { - console.error('Error fetching calendar entries:', error); - }); - } + }) addWorklogsButton.addEventListener('click', () => { setLoadingStatus(true) @@ -233,7 +132,7 @@ window.View.Main = (function () { .catch(() => { document.getElementsByClassName('container')[0].classList.add('hidden') document.getElementsByClassName('error_status')[0].classList.remove('hidden') - alert('Something went wrong. Please go to \'Options\' and make sure you are logged in Jira, and the Jira URL is correct.') + alert('Something went wrong. Please go to \'Options\' and make sure you are logged in Jira, and the Jira URL is correct. If you have enabled Outlook sync, make sure you have provided valid credentials.') setLoadingStatus(false) }) } diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 1a7e27d..b81f05b 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -1,10 +1,10 @@ { "manifest_version": 3, "name": "Jira Worklog Tool", - "version": "0.5.1", - "author": "alfeugds", + "version": "0.6.0", + "author": "alfeugds & mashb1t", "description": "This extension allows the user to log the work in Jira easily.", - "homepage_url": "https://github.com/alfeugds/jiraworklogtool", + "homepage_url": "https://github.com/mashb1t/jiraworklogtool", "icons": { "128": "img/icon-time-task-512x512.png" }, @@ -15,7 +15,8 @@ "default_title": "Log Work" }, "permissions": [ - "storage" + "storage", + "identity" ], "host_permissions": [ "*://*/*" @@ -23,4 +24,4 @@ "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" } -} \ No newline at end of file +} diff --git a/chrome-extension/options.html b/chrome-extension/options.html index 211bfe0..bd8a9b5 100644 --- a/chrome-extension/options.html +++ b/chrome-extension/options.html @@ -37,7 +37,7 @@

Configure Jira Connection


- +

Basic Authentication*

@@ -52,6 +52,20 @@

App Token*

*Fill only if required by your Jira API. Ask your IT department for assistance.

+
+

Outlook Calendar Sync

+

This will allow you to read events from your calendar. To do so, create an Entra app with implicit grant (token flow) and permissions Calendars.ReadBasic, then fill the fields below.

+ + +
+ + +
+ + +
+ +
@@ -59,6 +73,7 @@

App Token*

+ diff --git a/chrome-extension/popup.html b/chrome-extension/popup.html index b5ab551..4b6fcfa 100644 --- a/chrome-extension/popup.html +++ b/chrome-extension/popup.html @@ -20,6 +20,7 @@ --> + @@ -111,7 +112,7 @@

Total: 2h

From 61f60fa494c8dfb09266679cc27e2c25ed731e58 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Thu, 26 Sep 2024 22:24:15 +0200 Subject: [PATCH 4/8] feat: add filter private events checkbox and filter subjects text area --- chrome-extension/js/options.js | 12 +++++++++--- chrome-extension/js/outlook-helper.js | 10 +++++----- chrome-extension/manifest.json | 2 +- chrome-extension/options.html | 7 +++++++ package.json | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/chrome-extension/js/options.js b/chrome-extension/js/options.js index 70a362a..7506bf5 100644 --- a/chrome-extension/js/options.js +++ b/chrome-extension/js/options.js @@ -39,7 +39,7 @@ function restoreOptions () { ) } -var jiraUrlInput, userInput, passwordInput, tokenInput, outlookSyncEnabledInput, tenantIDInput, clientIDInput +var jiraUrlInput, userInput, passwordInput, tokenInput, outlookSyncEnabledInput, tenantIDInput, clientIDInput, filterPrivateEventsInput, filterSubjectsInput function restoreJiraOptionsToInput (options) { jiraUrlInput.value = options.jiraUrl || '' @@ -49,9 +49,11 @@ function restoreJiraOptionsToInput (options) { } function restoreOutlookOptionsToInput (options) { - outlookSyncEnabledInput.checked = options.outlookSyncEnabled || false + outlookSyncEnabledInput.checked = options.outlookSyncEnabled tenantIDInput.value = options.tenantID || '' clientIDInput.value = options.clientID || '' + filterPrivateEventsInput.checked = options.filterPrivateEvents + filterSubjectsInput.value = options.filterSubjects || 'Lunch Break\nNotes' } function getJiraOptionsFromInput () { @@ -67,7 +69,9 @@ function getOutlookOptionsFromInput () { return { outlookSyncEnabled: outlookSyncEnabledInput.checked, tenantID: tenantIDInput.value, - clientID: clientIDInput.value + clientID: clientIDInput.value, + filterSubjects: filterSubjectsInput.value, + filterPrivateEvents: filterPrivateEventsInput.checked } } @@ -81,6 +85,8 @@ document.addEventListener('DOMContentLoaded', () => { outlookSyncEnabledInput = document.getElementById('outlookSyncEnabled') tenantIDInput = document.getElementById('tenantID') clientIDInput = document.getElementById('clientID') + filterPrivateEventsInput = document.getElementById('filterPrivateEvents') + filterSubjectsInput = document.getElementById('filterSubjects') document.getElementById('save').addEventListener('click', () => { saveOptions(getJiraOptionsFromInput(), getOutlookOptionsFromInput()) diff --git a/chrome-extension/js/outlook-helper.js b/chrome-extension/js/outlook-helper.js index 146ebca..10d2544 100644 --- a/chrome-extension/js/outlook-helper.js +++ b/chrome-extension/js/outlook-helper.js @@ -103,7 +103,7 @@ return allResults; } - function fetchCalendarEntries(accessToken, worklogDate) { + function fetchCalendarEntries(accessToken, worklogDate, options = outlookOptions) { return new Promise((resolve, reject) => { const dateStart = new Date(new Date(worklogDate).setHours(0, 0, 0, 0)); const dateEnd = new Date(new Date(worklogDate).setHours(23, 59, 59, 999)); @@ -116,14 +116,14 @@ // Sort events by start date data.sort((a, b) => new Date(a.start.dateTime) - new Date(b.start.dateTime)); + const filterSubjects = options.filterSubjects.split('\n'); + // Filter out events based on the specified criteria const filteredData = data.filter(event => !event.isAllDay && !event.isCancelled && - event.sensitivity !== 'private' && - event.subject !== 'Mittagspause' && - event.subject !== 'Notizen' && - event.subject !== 'Notes' + (!options.filterPrivateEvents || event.sensitivity !== 'private') && + !filterSubjects.includes(event.subject) ); const worklogItems = filteredData.map(event => { diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index b81f05b..42df5ad 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -24,4 +24,4 @@ "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" } -} +} \ No newline at end of file diff --git a/chrome-extension/options.html b/chrome-extension/options.html index bd8a9b5..946018c 100644 --- a/chrome-extension/options.html +++ b/chrome-extension/options.html @@ -66,6 +66,13 @@

Outlook Calendar Sync



+ + +
+ +
+ +
diff --git a/package.json b/package.json index 9e23637..094fbd3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jira-log", - "version": "0.5.1", + "version": "0.6.0", "description": "", "main": "jira-parser.js", "directories": { From af54fb85c22ac91ef232665876dab9c46b9d7473 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Thu, 26 Sep 2024 23:53:47 +0200 Subject: [PATCH 5/8] docs: revert homepage url and author to alfeugds reason: MR to upstream on master --- chrome-extension/manifest.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/chrome-extension/manifest.json b/chrome-extension/manifest.json index 42df5ad..3eadccd 100644 --- a/chrome-extension/manifest.json +++ b/chrome-extension/manifest.json @@ -2,9 +2,9 @@ "manifest_version": 3, "name": "Jira Worklog Tool", "version": "0.6.0", - "author": "alfeugds & mashb1t", + "author": "alfeugds", "description": "This extension allows the user to log the work in Jira easily.", - "homepage_url": "https://github.com/mashb1t/jiraworklogtool", + "homepage_url": "https://github.com/alfeugds/jiraworklogtool", "icons": { "128": "img/icon-time-task-512x512.png" }, @@ -24,4 +24,4 @@ "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'self'" } -} \ No newline at end of file +} From ef3868b61df72439f5993559a0df231d8c2a858d Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Fri, 27 Sep 2024 00:17:04 +0200 Subject: [PATCH 6/8] docs: update readme, add instructions how to set up Outlook integration --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 873b4e0..d8375fc 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ You can also omit the Jira # and time spent and add it later. * Add, edit and delete worklogs directly from the Chrome Extension; * Keep track of how many hours you already spent in the tasks; * Supports SAML and Basic Authentication with Jira app token. +* Also allows to sync events from Outlook Calendar to worklog list for easy logging. ## Getting Started Before using it, you need to do two things: @@ -39,6 +40,17 @@ If by only providing the Jira Hostname the connection fails, you'll need to conf *_The extension uses the **Jira Hostname** to build the URL and API calls to the Jira instance like this: **`https://jira.atlassian.com/`**`rest/api/2/search`._ +### Outlook integration +To sync events from Outlook Calendar to the worklog list, you need to check **Enable Outlook Sync** and provide both the **Tenant ID** and **Client ID**. +You can get these values from the Azure Portal. If you don't have them, please consult your IT department. + +For setting up the app in Azure, you can follow the steps below: +1. Go to the Azure Portal and create a new app registration +2. In the app registration, go to the **Authentication** section and add: + - Redirect URI: https://pekbjnkonfmgjfnbpmindidammhgmjji.chromiumapp.org/ + - Implicit grant: Access tokens +3. Open **API permissions** and add the Microsoft Graph permission **Calendars.ReadBasic**. Your IT department may need to approve this permission depending on your global account settings, even if it does not require admin consent. + ## Some Images ![Main popup screen](/screenshots/popup.png "Main Screen") From 801a570951cb953a3bd28da8212daf9a43618f5d Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Fri, 27 Sep 2024 00:31:25 +0200 Subject: [PATCH 7/8] docs: update readme, add link to Azure portal --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8375fc..c74bbd6 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ To sync events from Outlook Calendar to the worklog list, you need to check **En You can get these values from the Azure Portal. If you don't have them, please consult your IT department. For setting up the app in Azure, you can follow the steps below: -1. Go to the Azure Portal and create a new app registration +1. Go to the [Azure Portal](https://entra.microsoft.com/) and create a new app registration 2. In the app registration, go to the **Authentication** section and add: - Redirect URI: https://pekbjnkonfmgjfnbpmindidammhgmjji.chromiumapp.org/ - Implicit grant: Access tokens From af911495aa16d1c4b49ecf5238cec44350150f05 Mon Sep 17 00:00:00 2001 From: Manuel Schmid Date: Fri, 27 Sep 2024 00:38:05 +0200 Subject: [PATCH 8/8] docs: update readme, add instructions where to find tenant and client id --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c74bbd6..9b0644b 100644 --- a/README.md +++ b/README.md @@ -46,10 +46,11 @@ You can get these values from the Azure Portal. If you don't have them, please c For setting up the app in Azure, you can follow the steps below: 1. Go to the [Azure Portal](https://entra.microsoft.com/) and create a new app registration -2. In the app registration, go to the **Authentication** section and add: +2. Copy the **Tenant ID** and **Client ID** from the app overview to the extension's [options page](chrome-extension://pekbjnkonfmgjfnbpmindidammhgmjji/options.html) +3. In the app registration, go to the **Authentication** section and add: - Redirect URI: https://pekbjnkonfmgjfnbpmindidammhgmjji.chromiumapp.org/ - Implicit grant: Access tokens -3. Open **API permissions** and add the Microsoft Graph permission **Calendars.ReadBasic**. Your IT department may need to approve this permission depending on your global account settings, even if it does not require admin consent. +4. Open **API permissions** and add the Microsoft Graph permission **Calendars.ReadBasic**. Your IT department may need to approve this permission depending on your global account settings, even if it does not require admin consent. ## Some Images