Commit ebd6e5f3 authored by Fatih Acet's avatar Fatih Acet

Merge branch '32916-browser-notifications-for-pipeline-running-in-a-mr-is-gone' into 'master'

Resolve "Browser notifications for pipeline running in a MR is gone"

Closes #32916

See merge request !11734
parents ffe52f01 13dd82b5
/* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */ /* eslint-disable func-names, space-before-function-paren, wrap-iife, no-var, one-var, one-var-declaration-per-line, consistent-return, prefer-arrow-callback, no-return-assign, object-shorthand, comma-dangle, no-param-reassign, max-len */
(function() { function notificationGranted(message, opts, onclick) {
(function(w) { var notification;
var notificationGranted, notifyMe, notifyPermissions; notification = new Notification(message, opts);
notificationGranted = function(message, opts, onclick) { setTimeout(function() {
var notification; // Hide the notification after X amount of seconds
notification = new Notification(message, opts); return notification.close();
setTimeout(function() { }, 8000);
return notification.close();
// Hide the notification after X amount of seconds return notification.onclick = onclick || notification.close;
}, 8000); }
if (onclick) {
return notification.onclick = onclick;
}
};
notifyPermissions = function() {
if ('Notification' in window) {
return Notification.requestPermission();
}
};
notifyMe = function(message, body, icon, onclick) {
var opts;
opts = {
body: body,
icon: icon
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
// do nothing function notifyPermissions() {
} else if (Notification.permission === 'granted') { if ('Notification' in window) {
// If it's okay let's create a notification return Notification.requestPermission();
}
}
function notifyMe(message, body, icon, onclick) {
var opts;
opts = {
body: body,
icon: icon
};
// Let's check if the browser supports notifications
if (!('Notification' in window)) {
// do nothing
} else if (Notification.permission === 'granted') {
// If it's okay let's create a notification
return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
return Notification.requestPermission(function(permission) {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick); return notificationGranted(message, opts, onclick);
} else if (Notification.permission !== 'denied') {
return Notification.requestPermission(function(permission) {
// If the user accepts, let's create a notification
if (permission === 'granted') {
return notificationGranted(message, opts, onclick);
}
});
} }
}; });
w.notify = notifyMe; }
return w.notifyPermissions = notifyPermissions; }
})(window);
}).call(window); const notify = {
notificationGranted,
notifyPermissions,
notifyMe,
};
export default notify;
...@@ -56,7 +56,6 @@ import './lib/utils/animate'; ...@@ -56,7 +56,6 @@ import './lib/utils/animate';
import './lib/utils/bootstrap_linked_tabs'; import './lib/utils/bootstrap_linked_tabs';
import './lib/utils/common_utils'; import './lib/utils/common_utils';
import './lib/utils/datetime_utility'; import './lib/utils/datetime_utility';
import './lib/utils/notify';
import './lib/utils/pretty_time'; import './lib/utils/pretty_time';
import './lib/utils/text_utility'; import './lib/utils/text_utility';
import './lib/utils/url_utility'; import './lib/utils/url_utility';
......
...@@ -41,3 +41,4 @@ export { default as getStateKey } from './stores/get_state_key'; ...@@ -41,3 +41,4 @@ export { default as getStateKey } from './stores/get_state_key';
export { default as mrWidgetOptions } from './mr_widget_options'; export { default as mrWidgetOptions } from './mr_widget_options';
export { default as stateMaps } from './stores/state_maps'; export { default as stateMaps } from './stores/state_maps';
export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge'; export { default as SquashBeforeMerge } from './components/states/mr_widget_squash_before_merge';
export { default as notify } from '../lib/utils/notify';
...@@ -4,6 +4,8 @@ import { ...@@ -4,6 +4,8 @@ import {
} from './dependencies'; } from './dependencies';
document.addEventListener('DOMContentLoaded', () => { document.addEventListener('DOMContentLoaded', () => {
gl.mrWidgetData.gitlabLogo = gon.gitlab_logo;
const vm = new Vue(mrWidgetOptions); const vm = new Vue(mrWidgetOptions);
window.gl.mrWidget = { window.gl.mrWidget = {
......
...@@ -29,6 +29,7 @@ import { ...@@ -29,6 +29,7 @@ import {
eventHub, eventHub,
stateMaps, stateMaps,
SquashBeforeMerge, SquashBeforeMerge,
notify,
} from './dependencies'; } from './dependencies';
export default { export default {
...@@ -77,8 +78,10 @@ export default { ...@@ -77,8 +78,10 @@ export default {
this.service.checkStatus() this.service.checkStatus()
.then(res => res.json()) .then(res => res.json())
.then((res) => { .then((res) => {
this.handleNotification(res);
this.mr.setData(res); this.mr.setData(res);
this.setFavicon(); this.setFavicon();
if (cb) { if (cb) {
cb.call(null, res); cb.call(null, res);
} }
...@@ -136,6 +139,15 @@ export default { ...@@ -136,6 +139,15 @@ export default {
new Flash('Something went wrong. Please try again.'); // eslint-disable-line new Flash('Something went wrong. Please try again.'); // eslint-disable-line
}); });
}, },
handleNotification(data) {
if (data.ci_status === this.mr.ciStatus) return;
const label = data.pipeline.details.status.label;
const title = `Pipeline ${label}`;
const message = `Pipeline ${label} for "${data.title}"`;
notify.notifyMe(title, message, this.mr.gitlabLogo);
},
resumePolling() { resumePolling() {
this.pollingInterval.resume(); this.pollingInterval.resume();
}, },
......
...@@ -5,6 +5,8 @@ export default class MergeRequestStore { ...@@ -5,6 +5,8 @@ export default class MergeRequestStore {
constructor(data) { constructor(data) {
this.sha = data.diff_head_sha; this.sha = data.diff_head_sha;
this.gitlabLogo = data.gitlabLogo;
this.setData(data); this.setData(data);
} }
......
# rubocop:disable Metrics/AbcSize
module Gitlab module Gitlab
module GonHelper module GonHelper
def add_gon_variables def add_gon_variables
...@@ -13,6 +15,7 @@ module Gitlab ...@@ -13,6 +15,7 @@ module Gitlab
gon.sentry_dsn = current_application_settings.clientside_sentry_dsn if current_application_settings.clientside_sentry_enabled gon.sentry_dsn = current_application_settings.clientside_sentry_dsn if current_application_settings.clientside_sentry_enabled
gon.gitlab_url = Gitlab.config.gitlab.url gon.gitlab_url = Gitlab.config.gitlab.url
gon.revision = Gitlab::REVISION gon.revision = Gitlab::REVISION
gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png')
if current_user if current_user
gon.current_user_id = current_user.id gon.current_user_id = current_user.id
......
...@@ -2,6 +2,7 @@ import Vue from 'vue'; ...@@ -2,6 +2,7 @@ import Vue from 'vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service'; import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options'; import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options';
import eventHub from '~/vue_merge_request_widget/event_hub'; import eventHub from '~/vue_merge_request_widget/event_hub';
import notify from '~/lib/utils/notify';
import mockData from './mock_data'; import mockData from './mock_data';
const createComponent = () => { const createComponent = () => {
...@@ -107,6 +108,8 @@ describe('mrWidgetOptions', () => { ...@@ -107,6 +108,8 @@ describe('mrWidgetOptions', () => {
it('should tell service to check status', (done) => { it('should tell service to check status', (done) => {
spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData)); spyOn(vm.service, 'checkStatus').and.returnValue(returnPromise(mockData));
spyOn(vm.mr, 'setData'); spyOn(vm.mr, 'setData');
spyOn(vm, 'handleNotification');
let isCbExecuted = false; let isCbExecuted = false;
const cb = () => { const cb = () => {
isCbExecuted = true; isCbExecuted = true;
...@@ -117,6 +120,7 @@ describe('mrWidgetOptions', () => { ...@@ -117,6 +120,7 @@ describe('mrWidgetOptions', () => {
setTimeout(() => { setTimeout(() => {
expect(vm.service.checkStatus).toHaveBeenCalled(); expect(vm.service.checkStatus).toHaveBeenCalled();
expect(vm.mr.setData).toHaveBeenCalled(); expect(vm.mr.setData).toHaveBeenCalled();
expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
expect(isCbExecuted).toBeTruthy(); expect(isCbExecuted).toBeTruthy();
done(); done();
}, 333); }, 333);
...@@ -254,6 +258,39 @@ describe('mrWidgetOptions', () => { ...@@ -254,6 +258,39 @@ describe('mrWidgetOptions', () => {
}); });
}); });
describe('handleNotification', () => {
const data = {
ci_status: 'running',
title: 'title',
pipeline: { details: { status: { label: 'running-label' } } },
};
beforeEach(() => {
spyOn(notify, 'notifyMe');
vm.mr.ciStatus = 'failed';
vm.mr.gitlabLogo = 'logo.png';
});
it('should call notifyMe', () => {
vm.handleNotification(data);
expect(notify.notifyMe).toHaveBeenCalledWith(
'Pipeline running-label',
'Pipeline running-label for "title"',
'logo.png',
);
});
it('should not call notifyMe if the status has not changed', () => {
vm.mr.ciStatus = data.ci_status;
vm.handleNotification(data);
expect(notify.notifyMe).not.toHaveBeenCalled();
});
});
describe('resumePolling', () => { describe('resumePolling', () => {
it('should call stopTimer on pollingInterval', () => { it('should call stopTimer on pollingInterval', () => {
spyOn(vm.pollingInterval, 'resume'); spyOn(vm.pollingInterval, 'resume');
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment