diff --git a/app-src/img/icon_128x128-with-pad.png b/app-src/img/icon_128x128-with-pad.png new file mode 100644 index 0000000000..e25a689b2c Binary files /dev/null and b/app-src/img/icon_128x128-with-pad.png differ diff --git a/app-src/img/icon_128x128.png b/app-src/img/icon_128x128.png new file mode 100644 index 0000000000..a4c579872f Binary files /dev/null and b/app-src/img/icon_128x128.png differ diff --git a/app-src/scripts/_app.js b/app-src/scripts/_app.js index 32b6b24999..0eae9f6bc3 100644 --- a/app-src/scripts/_app.js +++ b/app-src/scripts/_app.js @@ -39,6 +39,7 @@ .config(fixUnhandledRejectionError) .run(initGlobalModels) .run(initPollJiraTaskUpdates) + .run(initPollGitTaskUpdates) .run(initPollForSimpleTimeTracking) .run(initMousewheelZoomForElectron) .run(initGlobalShortcuts) @@ -149,19 +150,32 @@ /* @ngInject */ function initPollJiraTaskUpdates($localStorage, Jira, IS_ELECTRON, $interval, JIRA_UPDATE_POLL_INTERVAL) { - // handle updates that need to be made locally if (IS_ELECTRON) { - // one initial - Jira.checkForUpdates($localStorage.tasks); + // one initial call + if (Jira.isSufficientJiraSettings()) { + Jira.checkForUpdates($localStorage.tasks); + } $interval(() => { - if ($localStorage.currentTask) { + if (Jira.isSufficientJiraSettings()) { Jira.checkForUpdates($localStorage.tasks); } }, JIRA_UPDATE_POLL_INTERVAL); } } + /* @ngInject */ + function initPollGitTaskUpdates($localStorage, Git, $interval, GIT_UPDATE_POLL_INTERVAL) { + // one initial + Git.checkAndUpdateTasks($localStorage.tasks); + + $interval(() => { + if ($localStorage.git.projectDir && $localStorage.git.repo) { + Git.checkAndUpdateTasks($localStorage.tasks); + } + }, GIT_UPDATE_POLL_INTERVAL); + } + /* @ngInject */ function showWelcomeDialog($localStorage, Dialogs) { if ($localStorage.isShowWelcomeDialog) { diff --git a/app-src/scripts/constants.js b/app-src/scripts/constants.js index d836a0dea2..a414aae7f4 100644 --- a/app-src/scripts/constants.js +++ b/app-src/scripts/constants.js @@ -125,6 +125,7 @@ .constant('REQUEST_TIMEOUT', 15000) .constant('WORKLOG_DATE_STR_FORMAT', 'YYYY-MM-DD') .constant('JIRA_UPDATE_POLL_INTERVAL', 60 * 1000 * 5) + .constant('GIT_UPDATE_POLL_INTERVAL', 60 * 1000 * 0.25) .constant('SIMPLE_TIME_TRACKING_INTERVAL', 1000 * 5) .constant('IS_ELECTRON', (typeof window.ipcRenderer !== 'undefined')) .constant('THEMES', [ diff --git a/app-src/scripts/main/global-services/git-s.js b/app-src/scripts/main/global-services/git-s.js index 1462dc6c18..f2458cbcb8 100644 --- a/app-src/scripts/main/global-services/git-s.js +++ b/app-src/scripts/main/global-services/git-s.js @@ -14,14 +14,20 @@ .service('Git', Git); /* @ngInject */ - function Git($http, $localStorage) { + function Git($http, $localStorage, TasksUtil, $injector, $q, Notifier, SimpleToast) { const TYPE = 'GITHUB'; const BASE_URL = 'https://api.github.com/'; const settings = $localStorage.git; - function mapIssue(issue) { + // PRIVATE HELPER FUNCTIONS + // ------------------------ + function transformIssue(issue) { let title; + if (typeof issue === 'string') { + issue = angular.fromJson(issue); + } + if (issue.pull_request) { title = `${settings.prPrefix} #${issue.number}: ${issue.title}`; } else { @@ -45,17 +51,11 @@ } const newIssues = []; issues.forEach((issue) => { - newIssues.push(mapIssue(issue)); + newIssues.push(transformIssue(issue)); }); return newIssues; } - this.getIssueList = () => { - return $http.get(BASE_URL + 'repos/' + settings.repo + '/issues', { - transformResponse: [transformIssueList] - }); - }; - function transformComments(comments) { if (typeof comments === 'string') { comments = angular.fromJson(comments); @@ -71,12 +71,81 @@ return newComments; } + function taskIsUpdatedHandler(updatedTask) { + if (updatedTask) { + Notifier({ + title: 'Git Update', + message: '"' + updatedTask.title + '" => has been updated as it was updated on Git.', + sound: true, + wait: true + }); + SimpleToast('"' + updatedTask.title + '" => has been updated as it was updated on Git.'); + } + } + + // HELPER METHODS + // -------------- + this.isGitTask = (task) => { + return task && task.originalId && task.originalType === TYPE; + }; + + // API METHODS + // ----------- + this.getIssueList = () => { + return $http.get(BASE_URL + 'repos/' + settings.repo + '/issues', { + transformResponse: [transformIssueList] + }); + }; + this.getCommentListForIssue = (issueNumber) => { return $http.get(BASE_URL + 'repos/' + settings.repo + '/issues/' + issueNumber + '/comments', { transformResponse: [transformComments] }); }; + this.getIssueById = (issueNumber) => { + return $http.get(BASE_URL + 'repos/' + settings.repo + '/issues/' + issueNumber, { + transformResponse: [transformIssue] + }); + }; + + // COMPLEXER WRAPPER METHODS + // ------------------------- + this.checkAndUpdateTasks = (tasks) => { + const TasksUtil = $injector.get('TasksUtil'); + const defer = $q.defer(); + + const tasksToPoll = TasksUtil.flattenTasks(tasks, this.isGitTask, this.isGitTask); + + // execute requests sequentially to have a little more time + const pollPromise = tasksToPoll.reduce((promise, task) => + promise.then(() => + this.getIssueById(task.originalId) + .then((res) => { + const issue = res.data; + const lastUpdate = moment(task.originalUpdated); + if (lastUpdate && moment(issue.originalUpdated).isAfter(lastUpdate)) { + // extend task with new values + angular.extend(task, issue); + taskIsUpdatedHandler(issue, task); + + // also update comment list in the next step + this.getCommentListForIssue(task.originalId) + .then((res) => { + task.originalComments = res.data; + }); + } + }, defer.reject) + ), Promise.resolve() + ); + + pollPromise + .then(() => { + defer.resolve(); + }); + + return defer.promise; + }; } })(); diff --git a/app-src/scripts/main/global-services/notifier-s.js b/app-src/scripts/main/global-services/notifier-s.js index db4d8b6f8b..47df351c4a 100644 --- a/app-src/scripts/main/global-services/notifier-s.js +++ b/app-src/scripts/main/global-services/notifier-s.js @@ -17,10 +17,19 @@ function Notifier(IS_ELECTRON) { const IPC_NOTIFIER_EV = 'NOTIFY'; + if (!IS_ELECTRON && Notification && Notification.permission !== 'granted') { + Notification.requestPermission(); + } + return function (notification) { if (IS_ELECTRON) { // send to electron window.ipcRenderer.send(IPC_NOTIFIER_EV, notification); + } else if (Notification && Notification.permission === 'granted') { + new Notification(notification.title, { + icon: notification.icon || 'img/icon_128x128-with-pad.png', + body: notification.message + }); } }; diff --git a/app-src/scripts/task-list/task-list-d.html b/app-src/scripts/task-list/task-list-d.html index ca8bd78904..da16717681 100644 --- a/app-src/scripts/task-list/task-list-d.html +++ b/app-src/scripts/task-list/task-list-d.html @@ -145,7 +145,7 @@
-
Notes
+
{{ task.originalId ? 'Description': 'Notes' }}