Merge branch '24166-close-builds-dropdown' into 'master'
Prevent dropdown from closing when user clicks in a build. Closes #24166 See merge request !9834
This commit is contained in:
commit
065825b795
5 changed files with 127 additions and 85 deletions
|
@ -39,6 +39,7 @@ import Issue from './issue';
|
|||
import BindInOut from './behaviors/bind_in_out';
|
||||
import GroupsList from './groups_list';
|
||||
import ProjectsList from './projects_list';
|
||||
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
|
||||
|
||||
const ShortcutsBlob = require('./shortcuts_blob');
|
||||
const UserCallout = require('./user_callout');
|
||||
|
@ -181,7 +182,7 @@ const UserCallout = require('./user_callout');
|
|||
shortcut_handler = new ShortcutsNavigation();
|
||||
break;
|
||||
case 'projects:commit:pipelines':
|
||||
new gl.MiniPipelineGraph({
|
||||
new MiniPipelineGraph({
|
||||
container: '.js-pipeline-table',
|
||||
}).bindEvents();
|
||||
break;
|
||||
|
|
|
@ -3,7 +3,8 @@
|
|||
/* global notifyPermissions */
|
||||
/* global merge_request_widget */
|
||||
|
||||
require('./smart_interval');
|
||||
import './smart_interval';
|
||||
import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
|
||||
|
||||
((global) => {
|
||||
var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i += 1) { if (i in this && this[i] === item) return i; } return -1; };
|
||||
|
@ -285,7 +286,7 @@ require('./smart_interval');
|
|||
};
|
||||
|
||||
MergeRequestWidget.prototype.initMiniPipelineGraph = function() {
|
||||
new gl.MiniPipelineGraph({
|
||||
new MiniPipelineGraph({
|
||||
container: '.js-pipeline-inline-mr-widget-graph:visible',
|
||||
}).bindEvents();
|
||||
};
|
||||
|
|
|
@ -15,81 +15,96 @@
|
|||
* <div class="js-builds-dropdown-container dropdown-menu"></div>
|
||||
* </div>
|
||||
*/
|
||||
(() => {
|
||||
class MiniPipelineGraph {
|
||||
constructor(opts = {}) {
|
||||
this.container = opts.container || '';
|
||||
this.dropdownListSelector = '.js-builds-dropdown-container';
|
||||
this.getBuildsList = this.getBuildsList.bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the event listener when the dropdown is opened.
|
||||
* All dropdown events are fired at the .dropdown-menu's parent element.
|
||||
*/
|
||||
bindEvents() {
|
||||
$(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the clicked stage, renders the given data in the dropdown list.
|
||||
*
|
||||
* @param {HTMLElement} stageContainer
|
||||
* @param {Object} data
|
||||
*/
|
||||
renderBuildsList(stageContainer, data) {
|
||||
const dropdownContainer = stageContainer.parentElement.querySelector(
|
||||
`${this.dropdownListSelector} .js-builds-dropdown-list`,
|
||||
);
|
||||
|
||||
dropdownContainer.innerHTML = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the clicked stage, gets the list of builds.
|
||||
*
|
||||
* All dropdown events have a relatedTarget property,
|
||||
* whose value is the toggling anchor element.
|
||||
*
|
||||
* @param {Object} e bootstrap dropdown event
|
||||
* @return {Promise}
|
||||
*/
|
||||
getBuildsList(e) {
|
||||
const button = e.relatedTarget;
|
||||
const endpoint = button.dataset.stageEndpoint;
|
||||
|
||||
return $.ajax({
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
url: endpoint,
|
||||
beforeSend: () => {
|
||||
this.renderBuildsList(button, '');
|
||||
this.toggleLoading(button);
|
||||
},
|
||||
success: (data) => {
|
||||
this.toggleLoading(button);
|
||||
this.renderBuildsList(button, data.html);
|
||||
},
|
||||
error: () => {
|
||||
this.toggleLoading(button);
|
||||
new Flash('An error occurred while fetching the builds.', 'alert');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the loading icon.
|
||||
*
|
||||
* @param {HTMLElement} stageContainer
|
||||
* @return {type}
|
||||
*/
|
||||
toggleLoading(stageContainer) {
|
||||
stageContainer.parentElement.querySelector(
|
||||
`${this.dropdownListSelector} .js-builds-dropdown-loading`,
|
||||
).classList.toggle('hidden');
|
||||
}
|
||||
export default class MiniPipelineGraph {
|
||||
constructor(opts = {}) {
|
||||
this.container = opts.container || '';
|
||||
this.dropdownListSelector = '.js-builds-dropdown-container';
|
||||
this.getBuildsList = this.getBuildsList.bind(this);
|
||||
}
|
||||
|
||||
window.gl = window.gl || {};
|
||||
window.gl.MiniPipelineGraph = MiniPipelineGraph;
|
||||
})();
|
||||
/**
|
||||
* Adds the event listener when the dropdown is opened.
|
||||
* All dropdown events are fired at the .dropdown-menu's parent element.
|
||||
*/
|
||||
bindEvents() {
|
||||
$(document).off('shown.bs.dropdown', this.container).on('shown.bs.dropdown', this.container, this.getBuildsList);
|
||||
}
|
||||
|
||||
/**
|
||||
* When the user right clicks or cmd/ctrl + click in the job name
|
||||
* the dropdown should not be closed and the link should open in another tab,
|
||||
* so we stop propagation of the click event inside the dropdown.
|
||||
*
|
||||
* Since this component is rendered multiple times per page we need to guarantee we only
|
||||
* target the click event of this component.
|
||||
*/
|
||||
stopDropdownClickPropagation() {
|
||||
$(document).on(
|
||||
'click',
|
||||
`${this.container} .js-builds-dropdown-list a.mini-pipeline-graph-dropdown-item`,
|
||||
(e) => {
|
||||
e.stopPropagation();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* For the clicked stage, renders the given data in the dropdown list.
|
||||
*
|
||||
* @param {HTMLElement} stageContainer
|
||||
* @param {Object} data
|
||||
*/
|
||||
renderBuildsList(stageContainer, data) {
|
||||
const dropdownContainer = stageContainer.parentElement.querySelector(
|
||||
`${this.dropdownListSelector} .js-builds-dropdown-list`,
|
||||
);
|
||||
|
||||
dropdownContainer.innerHTML = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* For the clicked stage, gets the list of builds.
|
||||
*
|
||||
* All dropdown events have a relatedTarget property,
|
||||
* whose value is the toggling anchor element.
|
||||
*
|
||||
* @param {Object} e bootstrap dropdown event
|
||||
* @return {Promise}
|
||||
*/
|
||||
getBuildsList(e) {
|
||||
const button = e.relatedTarget;
|
||||
const endpoint = button.dataset.stageEndpoint;
|
||||
|
||||
return $.ajax({
|
||||
dataType: 'json',
|
||||
type: 'GET',
|
||||
url: endpoint,
|
||||
beforeSend: () => {
|
||||
this.renderBuildsList(button, '');
|
||||
this.toggleLoading(button);
|
||||
},
|
||||
success: (data) => {
|
||||
this.toggleLoading(button);
|
||||
this.renderBuildsList(button, data.html);
|
||||
this.stopDropdownClickPropagation();
|
||||
},
|
||||
error: () => {
|
||||
this.toggleLoading(button);
|
||||
new Flash('An error occurred while fetching the builds.', 'alert');
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the visibility of the loading icon.
|
||||
*
|
||||
* @param {HTMLElement} stageContainer
|
||||
* @return {type}
|
||||
*/
|
||||
toggleLoading(stageContainer) {
|
||||
stageContainer.parentElement.querySelector(
|
||||
`${this.dropdownListSelector} .js-builds-dropdown-loading`,
|
||||
).classList.toggle('hidden');
|
||||
}
|
||||
}
|
||||
|
|
4
changelogs/unreleased/24166-close-builds-dropdown.yml
Normal file
4
changelogs/unreleased/24166-close-builds-dropdown.yml
Normal file
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Prevent builds dropdown to close when the user clicks in a build
|
||||
merge_request:
|
||||
author:
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable no-new */
|
||||
|
||||
require('~/flash');
|
||||
require('~/mini_pipeline_graph_dropdown');
|
||||
import MiniPipelineGraph from '~/mini_pipeline_graph_dropdown';
|
||||
import '~/flash';
|
||||
|
||||
(() => {
|
||||
describe('Mini Pipeline Graph Dropdown', () => {
|
||||
|
@ -13,7 +13,7 @@ require('~/mini_pipeline_graph_dropdown');
|
|||
|
||||
describe('When is initialized', () => {
|
||||
it('should initialize without errors when no options are given', () => {
|
||||
const miniPipelineGraph = new window.gl.MiniPipelineGraph();
|
||||
const miniPipelineGraph = new MiniPipelineGraph();
|
||||
|
||||
expect(miniPipelineGraph.dropdownListSelector).toEqual('.js-builds-dropdown-container');
|
||||
});
|
||||
|
@ -21,7 +21,7 @@ require('~/mini_pipeline_graph_dropdown');
|
|||
it('should set the container as the given prop', () => {
|
||||
const container = '.foo';
|
||||
|
||||
const miniPipelineGraph = new window.gl.MiniPipelineGraph({ container });
|
||||
const miniPipelineGraph = new MiniPipelineGraph({ container });
|
||||
|
||||
expect(miniPipelineGraph.container).toEqual(container);
|
||||
});
|
||||
|
@ -29,9 +29,9 @@ require('~/mini_pipeline_graph_dropdown');
|
|||
|
||||
describe('When dropdown is clicked', () => {
|
||||
it('should call getBuildsList', () => {
|
||||
const getBuildsListSpy = spyOn(gl.MiniPipelineGraph.prototype, 'getBuildsList').and.callFake(function () {});
|
||||
const getBuildsListSpy = spyOn(MiniPipelineGraph.prototype, 'getBuildsList').and.callFake(function () {});
|
||||
|
||||
new gl.MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
|
||||
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
|
||||
|
||||
document.querySelector('.js-builds-dropdown-button').click();
|
||||
|
||||
|
@ -41,11 +41,32 @@ require('~/mini_pipeline_graph_dropdown');
|
|||
it('should make a request to the endpoint provided in the html', () => {
|
||||
const ajaxSpy = spyOn($, 'ajax').and.callFake(function () {});
|
||||
|
||||
new gl.MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
|
||||
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
|
||||
|
||||
document.querySelector('.js-builds-dropdown-button').click();
|
||||
expect(ajaxSpy.calls.allArgs()[0][0].url).toEqual('foobar');
|
||||
});
|
||||
|
||||
it('should not close when user uses cmd/ctrl + click', () => {
|
||||
spyOn($, 'ajax').and.callFake(function (params) {
|
||||
params.success({
|
||||
html: `<li>
|
||||
<a class="mini-pipeline-graph-dropdown-item" href="#">
|
||||
<span class="ci-status-icon ci-status-icon-failed"></span>
|
||||
<span class="ci-build-text">build</span>
|
||||
</a>
|
||||
<a class="ci-action-icon-wrapper js-ci-action-icon" href="#"></a>
|
||||
</li>`,
|
||||
});
|
||||
});
|
||||
new MiniPipelineGraph({ container: '.js-builds-dropdown-tests' }).bindEvents();
|
||||
|
||||
document.querySelector('.js-builds-dropdown-button').click();
|
||||
|
||||
document.querySelector('a.mini-pipeline-graph-dropdown-item').click();
|
||||
|
||||
expect($('.js-builds-dropdown-list').is(':visible')).toEqual(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
Loading…
Reference in a new issue