Commit 1ac15de9 authored by Filipa Lacerda's avatar Filipa Lacerda Committed by Phil Hughes

Resolve "Jobs dropdown in mini graph should close when we receive an error"

parent d71d09e8
...@@ -28,7 +28,9 @@ export default class MiniPipelineGraph { ...@@ -28,7 +28,9 @@ export default class MiniPipelineGraph {
* All dropdown events are fired at the .dropdown-menu's parent element. * All dropdown events are fired at the .dropdown-menu's parent element.
*/ */
bindEvents() { bindEvents() {
$(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList); $(document)
.off('shown.bs.dropdown', this.container)
.on('shown.bs.dropdown', this.container, this.getBuildsList);
} }
/** /**
...@@ -91,6 +93,9 @@ export default class MiniPipelineGraph { ...@@ -91,6 +93,9 @@ export default class MiniPipelineGraph {
}, },
error: () => { error: () => {
this.toggleLoading(button); this.toggleLoading(button);
if ($(button).parent().hasClass('open')) {
$(button).dropdown('toggle');
}
new Flash('An error occurred while fetching the builds.', 'alert'); new Flash('An error occurred while fetching the builds.', 'alert');
}, },
}); });
......
...@@ -2,13 +2,6 @@ ...@@ -2,13 +2,6 @@
import StatusIconEntityMap from '../../ci_status_icons'; import StatusIconEntityMap from '../../ci_status_icons';
export default { export default {
data() {
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
};
},
props: { props: {
stage: { stage: {
type: Object, type: Object,
...@@ -16,6 +9,13 @@ export default { ...@@ -16,6 +9,13 @@ export default {
}, },
}, },
data() {
return {
builds: '',
spinner: '<span class="fa fa-spinner fa-spin"></span>',
};
},
updated() { updated() {
if (this.builds) { if (this.builds) {
this.stopDropdownClickPropagation(); this.stopDropdownClickPropagation();
...@@ -31,7 +31,13 @@ export default { ...@@ -31,7 +31,13 @@ export default {
return this.$http.get(this.stage.dropdown_path) return this.$http.get(this.stage.dropdown_path)
.then((response) => { .then((response) => {
this.builds = JSON.parse(response.body).html; this.builds = JSON.parse(response.body).html;
}, () => { })
.catch(() => {
// If dropdown is opened we'll close it.
if (this.$el.classList.contains('open')) {
$(this.$refs.dropdown).dropdown('toggle');
}
const flash = new Flash('Something went wrong on our end.'); const flash = new Flash('Something went wrong on our end.');
return flash; return flash;
}); });
...@@ -46,9 +52,10 @@ export default { ...@@ -46,9 +52,10 @@ export default {
* target the click event of this component. * target the click event of this component.
*/ */
stopDropdownClickPropagation() { stopDropdownClickPropagation() {
$(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item')).on('click', (e) => { $(this.$el.querySelectorAll('.js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item'))
e.stopPropagation(); .on('click', (e) => {
}); e.stopPropagation();
});
}, },
}, },
computed: { computed: {
...@@ -81,12 +88,22 @@ export default { ...@@ -81,12 +88,22 @@ export default {
data-placement="top" data-placement="top"
data-toggle="dropdown" data-toggle="dropdown"
type="button" type="button"
:aria-label="stage.title"> :aria-label="stage.title"
<span v-html="svgHTML" aria-hidden="true"></span> ref="dropdown">
<i class="fa fa-caret-down" aria-hidden="true"></i> <span
v-html="svgHTML"
aria-hidden="true">
</span>
<i
class="fa fa-caret-down"
aria-hidden="true" />
</button> </button>
<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container"> <ul
<div class="arrow-up" aria-hidden="true"></div> ref="dropdown-content"
class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
<div
class="arrow-up"
aria-hidden="true"></div>
<div <div
:class="dropdownClass" :class="dropdownClass"
class="js-builds-dropdown-list scrollable-menu" class="js-builds-dropdown-list scrollable-menu"
......
...@@ -195,7 +195,6 @@ ...@@ -195,7 +195,6 @@
border: 1px solid $dropdown-border-color; border: 1px solid $dropdown-border-color;
border-radius: $border-radius-base; border-radius: $border-radius-base;
box-shadow: 0 2px 4px $dropdown-shadow-color; box-shadow: 0 2px 4px $dropdown-shadow-color;
overflow: hidden;
@include set-invisible; @include set-invisible;
@media (max-width: $screen-sm-min) { @media (max-width: $screen-sm-min) {
......
...@@ -3,70 +3,84 @@ ...@@ -3,70 +3,84 @@
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown'; import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
import '~/flash'; import '~/flash';
(() => { describe('Mini Pipeline Graph Dropdown', () => {
describe('Mini Pipeline Graph Dropdown', () => { preloadFixtures('static/mini_dropdown_graph.html.raw');
preloadFixtures('static/mini_dropdown_graph.html.raw');
beforeEach(() => { beforeEach(() => {
loadFixtures('static/mini_dropdown_graph.html.raw'); loadFixtures('static/mini_dropdown_graph.html.raw');
}); });
describe('When is initialized', () => { describe('When is initialized', () => {
it('should initialize without errors when no options are given', () => { it('should initialize without errors when no options are given', () => {
const miniPipelineGraph = new MiniPipelineGraph(); const miniPipelineGraph = new MiniPipelineGraph();
expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container'); expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
}); });
it('should set the container as the given prop', () => { it('should set the container as the given prop', () => {
const container = '.foo'; const container = '.foo';
const miniPipelineGraph = new MiniPipelineGraph({ container }); const miniPipelineGraph = new MiniPipelineGraph({ container });
expect(miniPipelineGraph.container).toEqual(container); expect(miniPipelineGraph.container).toEqual(container);
});
}); });
});
describe('When dropdown is clicked', () => { describe('When dropdown is clicked', () => {
it('should call getBuildsList', () => { it('should call getBuildsList', () => {
const getBuildsListSpy = spyOn(MiniPipelineGraph.prototype, 'getBuildsList').and.callFake(function () {}); const getBuildsListSpy = spyOn(
MiniPipelineGraph.prototype,
'getBuildsList',
).and.callFake(function () {});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click(); document.querySelector('.js-builds-dropdown-button').click();
expect(getBuildsListSpy).toHaveBeenCalled(); expect(getBuildsListSpy).toHaveBeenCalled();
}); });
it('should make a request to the endpoint provided in the html', () => { it('should make a request to the endpoint provided in the html', () => {
const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {}); const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click(); document.querySelector('.js-builds-dropdown-button').click();
expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar'); expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
}); });
it('should not close when user uses cmd/ctrl + click', () => { it('should not close when user uses cmd/ctrl + click', () => {
spyOn($, 'ajax').and.callFake(function (params) { spyOn($, 'ajax').and.callFake(function (params) {
params.success({ params.success({
html: `<li> html: `<li>
<a class="mini-pipeline-graph-dropdown-item" href="#"> <a class="mini-pipeline-graph-dropdown-item" href="#">
<span class="ci-status-icon ci-status-icon-failed"></span> <span class="ci-status-icon ci-status-icon-failed"></span>
<span class="ci-build-text">build</span> <span class="ci-build-text">build</span>
</a> </a>
<a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a> <a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
</li>`, </li>`,
});
}); });
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents(); });
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click(); document.querySelector('.js-builds-dropdown-button').click();
document.querySelector('a.mini-pipeline-graph-dropdown-item').click(); document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true); expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
});
}); });
}); });
})();
it('should close the dropdown when request returns an error', (done) => {
spyOn($, 'ajax').and.callFake(options => options.error());
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
document.querySelector('.js-builds-dropdown-button').click();
setTimeout(() => {
expect($('.js-builds-dropdown-tests .dropdown').hasClass('open')).toEqual(false);
done();
}, 0);
});
});
...@@ -63,4 +63,19 @@ describe('Pipelines Stage', () => { ...@@ -63,4 +63,19 @@ describe('Pipelines Stage', () => {
expect(minifiedComponent).toContain(expectedSVG); expect(minifiedComponent).toContain(expectedSVG);
}); });
}); });
describe('when request fails', () => {
it('closes dropdown', () => {
spyOn($, 'ajax').and.callFake(options => options.error());
const StageComponent = Vue.extend(Stage);
const component = new StageComponent({
propsData: { stage: { status: { icon: 'foo' } } },
}).$mount();
expect(
component.$el.classList.contains('open'),
).toEqual(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