Commit 9c2979ec authored by Jacob Schatz's avatar Jacob Schatz

Merge branch '12818-ci-status-as-favicon' into 'master'

Show CI status as Favicon on Pipelines, Job and MR pages

Closes #12818

See merge request !10144
parents 32db15b2 28a4e9d8
...@@ -88,6 +88,7 @@ window.Build = (function() { ...@@ -88,6 +88,7 @@ window.Build = (function() {
dataType: 'json', dataType: 'json',
success: function(buildData) { success: function(buildData) {
$('.js-build-output').html(buildData.trace_html); $('.js-build-output').html(buildData.trace_html);
gl.utils.setCiStatusFavicon(`${this.pageUrl}/status.json`);
if (window.location.hash === DOWN_BUILD_TRACE) { if (window.location.hash === DOWN_BUILD_TRACE) {
$("html,body").scrollTop(this.$buildTrace.height()); $("html,body").scrollTop(this.$buildTrace.height());
} }
......
...@@ -226,9 +226,11 @@ const ShortcutsBlob = require('./shortcuts_blob'); ...@@ -226,9 +226,11 @@ const ShortcutsBlob = require('./shortcuts_blob');
case 'projects:pipelines:builds': case 'projects:pipelines:builds':
case 'projects:pipelines:show': case 'projects:pipelines:show':
const { controllerAction } = document.querySelector('.js-pipeline-container').dataset; const { controllerAction } = document.querySelector('.js-pipeline-container').dataset;
const pipelineStatusUrl = `${document.querySelector('.js-pipeline-tab-link a').getAttribute('href')}/status.json`;
new gl.Pipelines({ new gl.Pipelines({
initTabs: true, initTabs: true,
pipelineStatusUrl,
tabsOptions: { tabsOptions: {
action: controllerAction, action: controllerAction,
defaultAction: 'pipelines', defaultAction: 'pipelines',
......
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
(function() { (function() {
(function(w) { (function(w) {
var base; var base;
const faviconEl = document.getElementById('favicon');
const originalFavicon = faviconEl ? faviconEl.getAttribute('href') : null;
w.gl || (w.gl = {}); w.gl || (w.gl = {});
(base = w.gl).utils || (base.utils = {}); (base = w.gl).utils || (base.utils = {});
w.gl.utils.isInGroupsPage = function() { w.gl.utils.isInGroupsPage = function() {
...@@ -361,5 +363,34 @@ ...@@ -361,5 +363,34 @@
fn(next, stop); fn(next, stop);
}); });
}; };
w.gl.utils.setFavicon = (iconName) => {
if (faviconEl && iconName) {
faviconEl.setAttribute('href', `/assets/${iconName}.ico`);
}
};
w.gl.utils.resetFavicon = () => {
if (faviconEl) {
faviconEl.setAttribute('href', originalFavicon);
}
};
w.gl.utils.setCiStatusFavicon = (pageUrl) => {
$.ajax({
url: pageUrl,
dataType: 'json',
success: function(data) {
if (data && data.icon) {
gl.utils.setFavicon(`ci_favicons/${data.icon}`);
} else {
gl.utils.resetFavicon();
}
},
error: function() {
gl.utils.resetFavicon();
}
});
};
})(window); })(window);
}).call(window); }).call(window);
...@@ -41,8 +41,10 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -41,8 +41,10 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
// check_enable - Boolean, whether to check automerge status // check_enable - Boolean, whether to check automerge status
// merge_check_url - String, URL to use to check automerge status // merge_check_url - String, URL to use to check automerge status
// ci_status_url - String, URL to use to check CI status // ci_status_url - String, URL to use to check CI status
// pipeline_status_url - String, URL to use to get CI status for Favicon
// //
this.opts = opts; this.opts = opts;
this.opts.pipeline_status_url = `${this.opts.pipeline_status_url}.json`;
this.$widgetBody = $('.mr-widget-body'); this.$widgetBody = $('.mr-widget-body');
$('#modal_merge_info').modal({ $('#modal_merge_info').modal({
show: false show: false
...@@ -159,6 +161,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown'; ...@@ -159,6 +161,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
_this.status = data.status; _this.status = data.status;
_this.hasCi = data.has_ci; _this.hasCi = data.has_ci;
_this.updateMergeButton(_this.status, _this.hasCi); _this.updateMergeButton(_this.status, _this.hasCi);
gl.utils.setCiStatusFavicon(_this.opts.pipeline_status_url);
if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); if (data.environments && data.environments.length) _this.renderEnvironments(data.environments);
if (data.status !== _this.opts.ci_status || if (data.status !== _this.opts.ci_status ||
data.sha !== _this.opts.ci_sha || data.sha !== _this.opts.ci_sha ||
......
...@@ -9,6 +9,10 @@ require('./lib/utils/bootstrap_linked_tabs'); ...@@ -9,6 +9,10 @@ require('./lib/utils/bootstrap_linked_tabs');
new global.LinkedTabs(options.tabsOptions); new global.LinkedTabs(options.tabsOptions);
} }
if (options.pipelineStatusUrl) {
gl.utils.setCiStatusFavicon(options.pipelineStatusUrl);
}
this.addMarginToBuildColumns(); this.addMarginToBuildColumns();
} }
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
%title= page_title(site_name) %title= page_title(site_name)
%meta{ name: "description", content: page_description } %meta{ name: "description", content: page_description }
= favicon_link_tag favicon = favicon_link_tag favicon, id: 'favicon'
= stylesheet_link_tag "application", media: "all" = stylesheet_link_tag "application", media: "all"
= stylesheet_link_tag "print", media: "print" = stylesheet_link_tag "print", media: "print"
......
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
check_enable: #{@merge_request.unchecked? ? "true" : "false"}, check_enable: #{@merge_request.unchecked? ? "true" : "false"},
ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
pipeline_status_url: "#{pipeline_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}",
gitlab_icon: "#{asset_path 'gitlab_logo.png'}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}",
ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}", ci_status: "#{@merge_request.head_pipeline ? @merge_request.head_pipeline.status : ''}",
......
---
title: Show CI status as Favicon on Pipelines, Job and MR pages
merge_request: 10144
author:
...@@ -75,6 +75,7 @@ describe('Build', () => { ...@@ -75,6 +75,7 @@ describe('Build', () => {
expect(url).toBe(`${BUILD_URL}.json`); expect(url).toBe(`${BUILD_URL}.json`);
expect(dataType).toBe('json'); expect(dataType).toBe('json');
expect(success).toEqual(jasmine.any(Function)); expect(success).toEqual(jasmine.any(Function));
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
success.call(context, { trace_html: '<span>Example</span>', status: 'running' }); success.call(context, { trace_html: '<span>Example</span>', status: 'running' });
...@@ -83,6 +84,7 @@ describe('Build', () => { ...@@ -83,6 +84,7 @@ describe('Build', () => {
it('removes the spinner', () => { it('removes the spinner', () => {
const [{ success, context }] = $.ajax.calls.argsFor(0); const [{ success, context }] = $.ajax.calls.argsFor(0);
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
success.call(context, { trace_html: '<span>Example</span>', status: 'success' }); success.call(context, { trace_html: '<span>Example</span>', status: 'success' });
expect($('.js-build-refresh').length).toBe(0); expect($('.js-build-refresh').length).toBe(0);
......
...@@ -310,5 +310,56 @@ require('~/lib/utils/common_utils'); ...@@ -310,5 +310,56 @@ require('~/lib/utils/common_utils');
}); });
}, 10000); }, 10000);
}); });
describe('gl.utils.setFavicon', () => {
it('should set page favicon to provided favicon', () => {
const faviconName = 'custom_favicon';
const fakeLink = {
setAttribute() {},
};
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val.indexOf('/assets/custom_favicon.ico') > -1).toBe(true);
});
gl.utils.setFavicon(faviconName);
});
});
describe('gl.utils.resetFavicon', () => {
it('should reset page favicon to tanuki', () => {
const fakeLink = {
setAttribute() {},
};
spyOn(window.document, 'getElementById').and.callFake(() => fakeLink);
spyOn(fakeLink, 'setAttribute').and.callFake((attr, val) => {
expect(attr).toEqual('href');
expect(val).toMatch(/favicon/);
});
gl.utils.resetFavicon();
});
});
describe('gl.utils.setCiStatusFavicon', () => {
it('should set page favicon to CI status favicon based on provided status', () => {
const BUILD_URL = `${gl.TEST_HOST}/frontend-fixtures/builds-project/builds/1/status.json`;
const FAVICON_PATH = 'ci_favicons/';
const FAVICON = 'icon_status_success';
const spySetFavicon = spyOn(gl.utils, 'setFavicon').and.stub();
const spyResetFavicon = spyOn(gl.utils, 'resetFavicon').and.stub();
spyOn($, 'ajax').and.callFake(function (options) {
options.success({ icon: FAVICON });
expect(spySetFavicon).toHaveBeenCalledWith(FAVICON_PATH + FAVICON);
options.success();
expect(spyResetFavicon).toHaveBeenCalled();
options.error();
expect(spyResetFavicon).toHaveBeenCalled();
});
gl.utils.setCiStatusFavicon(BUILD_URL);
});
});
}); });
})(); })();
...@@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility'); ...@@ -142,18 +142,21 @@ require('~/lib/utils/datetime_utility');
it('should call showCIStatus even if a notification should not be displayed', function() { it('should call showCIStatus even if a notification should not be displayed', function() {
var spy; var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub(); spy = spyOn(this["class"], 'showCIStatus').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
}); });
it('should call showCIStatus when a notification should be displayed', function() { it('should call showCIStatus when a notification should be displayed', function() {
var spy; var spy;
spy = spyOn(this["class"], 'showCIStatus').and.stub(); spy = spyOn(this["class"], 'showCIStatus').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(true); this["class"].getCIStatus(true);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.status);
}); });
it('should call showCICoverage when the coverage rate is set', function() { it('should call showCICoverage when the coverage rate is set', function() {
var spy; var spy;
spy = spyOn(this["class"], 'showCICoverage').and.stub(); spy = spyOn(this["class"], 'showCICoverage').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage); return expect(spy).toHaveBeenCalledWith(this.ciStatusData.coverage);
}); });
...@@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility'); ...@@ -161,12 +164,14 @@ require('~/lib/utils/datetime_utility');
var spy; var spy;
this.ciStatusData.coverage = null; this.ciStatusData.coverage = null;
spy = spyOn(this["class"], 'showCICoverage').and.stub(); spy = spyOn(this["class"], 'showCICoverage').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
return expect(spy).not.toHaveBeenCalled(); return expect(spy).not.toHaveBeenCalled();
}); });
it('should not display a notification on the first check after the widget has been created', function() { it('should not display a notification on the first check after the widget has been created', function() {
var spy; var spy;
spy = spyOn(window, 'notify'); spy = spyOn(window, 'notify');
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"] = new window.gl.MergeRequestWidget(this.opts);
this["class"].getCIStatus(true); this["class"].getCIStatus(true);
return expect(spy).not.toHaveBeenCalled(); return expect(spy).not.toHaveBeenCalled();
...@@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility'); ...@@ -174,6 +179,7 @@ require('~/lib/utils/datetime_utility');
it('should update the pipeline URL when the pipeline changes', function() { it('should update the pipeline URL when the pipeline changes', function() {
var spy; var spy;
spy = spyOn(this["class"], 'updatePipelineUrls').and.stub(); spy = spyOn(this["class"], 'updatePipelineUrls').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
this.ciStatusData.pipeline += 1; this.ciStatusData.pipeline += 1;
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
...@@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility'); ...@@ -182,6 +188,7 @@ require('~/lib/utils/datetime_utility');
it('should update the commit URL when the sha changes', function() { it('should update the commit URL when the sha changes', function() {
var spy; var spy;
spy = spyOn(this["class"], 'updateCommitUrls').and.stub(); spy = spyOn(this["class"], 'updateCommitUrls').and.stub();
spyOn(gl.utils, 'setCiStatusFavicon').and.callFake(() => {});
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
this.ciStatusData.sha = "9b50b99a"; this.ciStatusData.sha = "9b50b99a";
this["class"].getCIStatus(false); this["class"].getCIStatus(false);
......
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