diff --git a/app/client/components/Task/Task.js b/app/client/components/Task/Task.js index bccc725..b28d700 100644 --- a/app/client/components/Task/Task.js +++ b/app/client/components/Task/Task.js @@ -6,6 +6,7 @@ import { relativeTime } from 'helpers/time' import { highlightLog } from 'helpers/log' import EnterFullScreen from '../svg/EnterFullScreen' import LeaveFullScreen from '../svg/LeaveFullScreen' +import { tryNotifyTaskStatus } from 'helpers/notification' const log = console.log @@ -171,7 +172,7 @@ export default class Task extends Component { this.setState({task: result}) var pipelineStatus = '' - result.runs.forEach(function(runObj){ + result.runs.forEach(function(runObj, index){ if (that.state.intervals[runObj.id] != undefined && runObj.status != 'running'){ // Run finished, clear the timeout clearTimeout(that.state.intervals[runObj.id]) @@ -182,6 +183,20 @@ export default class Task extends Component { if (runObj.status == 'running'){ pipelineStatus = 'running' } + + // this is the frist task in the array + // it's previous status is "running" + // but it's current status is not "running" + if ( + index === 0 && + that.state.status === 'running' && + runObj.status !== 'running' + ) { + const { + definition: { name } + } = result; + tryNotifyTaskStatus(name, runObj.status); + } }) that.setState({ status: pipelineStatus, @@ -475,4 +490,4 @@ export default class Task extends Component { ); } -}; \ No newline at end of file +}; diff --git a/app/client/helpers/notification.js b/app/client/helpers/notification.js new file mode 100644 index 0000000..15cf315 --- /dev/null +++ b/app/client/helpers/notification.js @@ -0,0 +1,43 @@ +// TODO we better: +// a) try retrieve a icon file from the current site meta head +// b) fallback to use a pipelie logo +const icon = 'https://wiredcraft.com/assets/favicons/apple-touch-icon.png'; +const hasNotificationFeature = typeof window.Notification === 'function'; +const successEmoji = '🚀'; +const failureEmoji = '🚨'; + +function tryAskForPermission(grantedCallback) { + Notification.requestPermission().then(permission => { + if (permission === 'granted') grantedCallback(); + }); +} + +function notifyTaskStatus(pipelineName, status = 'success') { + let title; + switch (status) { + case 'success': + title = `${successEmoji} ${status} ${pipelineName}`; + break; + case 'failure': + title = `${failureEmoji} ${status} ${pipelineName}`; + break; + default: + title = `${status} ${pipelineName}`; + } + return spawnNotification(title); +} + +function spawnNotification(title, body) { + const options = { icon }; + if (body) options.body = body; + return new Notification(title, options); +} + +export function tryNotifyTaskStatus(pipelineName, type = 'success') { + const notify = () => notifyTaskStatus(pipelineName, type); + if (hasNotificationFeature === false) return; + if (Notification.permission === 'granted') return notify(); + if (Notification.permission !== 'denied') { + tryAskForPermission(notify); + } +}