Commit f315216d authored by Florie Guibert's avatar Florie Guibert Committed by Phil Hughes

WIP: Close blocked issue warning

- Close issue warning, top of issue page
parent 45183ce3
......@@ -12,6 +12,8 @@ export default class Issue {
constructor() {
if ($('a.btn-close').length) this.initIssueBtnEventListeners();
if ($('.js-close-blocked-issue-warning').length) this.initIssueWarningBtnEventListener();
Issue.$btnNewBranch = $('#new-branch');
Issue.createMrDropdownWrap = document.querySelector('.create-mr-dropdown-wrap');
......@@ -89,7 +91,7 @@ export default class Issue {
return $(document).on(
'click',
'.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen',
'.js-issuable-actions a.btn-close, .js-issuable-actions a.btn-reopen, a.btn-close-anyway',
e => {
e.preventDefault();
e.stopImmediatePropagation();
......@@ -99,19 +101,30 @@ export default class Issue {
Issue.submitNoteForm($button.closest('form'));
}
this.disableCloseReopenButton($button);
const url = $button.attr('href');
return axios
.put(url)
.then(({ data }) => {
const isClosed = $button.hasClass('btn-close');
this.updateTopState(isClosed, data);
})
.catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
const shouldDisplayBlockedWarning = $button.hasClass('btn-issue-blocked');
const warningBanner = $('.js-close-blocked-issue-warning');
if (shouldDisplayBlockedWarning) {
this.toggleWarningAndCloseButton();
} else {
this.disableCloseReopenButton($button);
const url = $button.attr('href');
return axios
.put(url)
.then(({ data }) => {
const isClosed = $button.is('.btn-close, .btn-close-anyway');
this.updateTopState(isClosed, data);
if ($button.hasClass('btn-close-anyway')) {
warningBanner.addClass('hidden');
if (this.closeReopenReportToggle)
$('.js-issuable-close-dropdown').removeClass('hidden');
}
})
.catch(() => flash(issueFailMessage))
.then(() => {
this.disableCloseReopenButton($button, false);
});
}
},
);
}
......@@ -137,6 +150,23 @@ export default class Issue {
this.reopenButtons.toggleClass('hidden', !isClosed);
}
toggleWarningAndCloseButton() {
const warningBanner = $('.js-close-blocked-issue-warning');
warningBanner.toggleClass('hidden');
$('.btn-close').toggleClass('hidden');
if (this.closeReopenReportToggle) {
$('.js-issuable-close-dropdown').toggleClass('hidden');
}
}
initIssueWarningBtnEventListener() {
return $(document).on('click', '.js-close-blocked-issue-warning button.btn-secondary', e => {
e.preventDefault();
e.stopImmediatePropagation();
this.toggleWarningAndCloseButton();
});
}
static submitNoteForm(form) {
const noteText = form.find('textarea.js-note-text').val();
if (noteText && noteText.trim().length > 0) {
......
......@@ -10,6 +10,8 @@
- can_report_spam = @issue.submittable_as_spam_by?(current_user)
- can_create_issue = show_new_issue_link?(@project)
= render_if_exists "projects/issues/alert_blocked", issue: @issue, current_user: current_user
.detail-page-header
.detail-page-header-body
.issuable-status-box.status-box.status-box-issue-closed{ class: issue_status_visibility(@issue, status_box: :closed) }
......@@ -50,7 +52,7 @@
%li.divider
%li= link_to 'New issue', new_project_issue_path(@project), id: 'new_issue_link'
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue
= render 'shared/issuable/close_reopen_button', issuable: @issue, can_update: can_update_issue, can_reopen: can_reopen_issue, warn_before_close: defined?(@issue.blocked?) && @issue.blocked?
- if can_report_spam
= link_to 'Submit as spam', mark_as_spam_project_issue_path(@project, @issue), method: :post, class: 'd-none d-sm-none d-md-block btn btn-grouped btn-spam', title: 'Submit as spam'
......
......@@ -2,17 +2,20 @@
- display_issuable_type = issuable_display_type(issuable)
- button_method = issuable_close_reopen_button_method(issuable)
- are_close_and_open_buttons_hidden = issuable_button_hidden?(issuable, true) && issuable_button_hidden?(issuable, false)
- add_blocked_class = false
- if defined? warn_before_close
- add_blocked_class = warn_before_close
- if is_current_user
- if can_update
= link_to "Close #{display_issuable_type}", close_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)}", title: "Close #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
class: "d-none d-sm-none d-md-block btn btn-grouped btn-close js-btn-issue-action #{issuable_button_visibility(issuable, true)} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "Close #{display_issuable_type}", data: { qa_selector: 'close_issue_button' }
- if can_reopen
= link_to "Reopen #{display_issuable_type}", reopen_issuable_path(issuable), method: button_method,
class: "d-none d-sm-none d-md-block btn btn-grouped btn-reopen js-btn-issue-action #{issuable_button_visibility(issuable, false)}", title: "Reopen #{display_issuable_type}", data: { qa_selector: 'reopen_issue_button' }
- else
- if can_update && !are_close_and_open_buttons_hidden
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable
= render 'shared/issuable/close_reopen_report_toggle', issuable: issuable, warn_before_close: add_blocked_class
- else
= link_to 'Report abuse', new_abuse_report_path(user_id: issuable.author.id, ref_url: issuable_url(issuable)),
class: 'd-none d-sm-none d-md-block btn btn-grouped btn-close-color', title: 'Report abuse'
......@@ -5,10 +5,13 @@
- button_class = "#{button_responsive_class} btn btn-grouped js-issuable-close-button js-btn-issue-action issuable-close-button"
- toggle_class = "#{button_responsive_class} btn btn-nr dropdown-toggle js-issuable-close-toggle"
- button_method = issuable_close_reopen_button_method(issuable)
- add_blocked_class = false
- if defined? warn_before_close
- add_blocked_class = !issuable.closed? && warn_before_close
.float-left.btn-group.prepend-left-10.issuable-close-dropdown.droplab-dropdown.js-issuable-close-dropdown
= link_to "#{display_button_action} #{display_issuable_type}", close_reopen_issuable_path(issuable),
method: button_method, class: "#{button_class} btn-#{button_action}", title: "#{display_button_action} #{display_issuable_type}"
method: button_method, class: "#{button_class} btn-#{button_action} #{(add_blocked_class ? 'btn-issue-blocked' : '')}", title: "#{display_button_action} #{display_issuable_type}"
= button_tag type: 'button', class: "#{toggle_class} btn-#{button_action}-color",
data: { 'dropdown-trigger' => '#issuable-close-menu' }, 'aria-label' => 'Toggle dropdown' do
......
......@@ -11,7 +11,7 @@ module EE
expose :blocked_by_issues do |issue|
issues = issue.blocked_by_issues(request.current_user)
serializer_options = options.merge(only: [:id, :web_url])
serializer_options = options.merge(only: [:iid, :web_url])
::IssueEntity.represent(issues, serializer_options)
end
......
- blocked_by_issues = @issue.blocked_by_issues(current_user)
- blocked_by_issues_links = blocked_by_issues.map { |blocking_issue| link_to "\##{blocking_issue.iid}", project_issue_path(blocking_issue.project, blocking_issue), class: 'gl-link' }.join(', ').html_safe
- if @issue.blocked? && @issue.blocked_by_issues(current_user).length > 0
.hidden.js-close-blocked-issue-warning.gl-alert.gl-alert-warning.prepend-top-16{ role: 'alert' }
= sprite_icon('warning', size: 16, css_class: 'gl-icon gl-alert-icon')
%h4.gl-alert-title
= _('Are you sure you want to close this blocked issue?')
.gl-alert-body
= _('This issue is currently blocked by the following issues: %{issues}.').html_safe % { issues: blocked_by_issues_links }
.gl-alert-actions
= link_to _("Yes, close issue"), close_issuable_path(issue), rel: 'nofollow', method: '',
class: "btn btn-close-anyway gl-alert-action btn-warning btn-md gl-button", title: _("Yes, close issue")
%button.btn.gl-alert-action.btn-warning.btn-md.gl-button.btn-secondary
= s_('Cancel')
# frozen_string_literal: true
require 'spec_helper'
describe Projects::IssuesController, '(JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
let(:user) { create(:user, feed_token: 'feedtoken:coldfeed') }
let(:group) { create(:group) }
let(:project) { create(:project_empty_repo, namespace: group, path: 'issues-project') }
render_views
before(:all) do
clean_frontend_fixtures('ee/issues/')
end
before do
project.add_developer(user)
sign_in(user)
end
after do
remove_repository(project)
end
it 'ee/issues/blocked-issue.html' do
issue = create(:issue, project: project)
related_issue = create(:issue, project: project)
create(:issue_link, source: related_issue, target: issue, link_type: IssueLink::TYPE_BLOCKS)
render_issue(issue)
end
private
def render_issue(issue)
get :show, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: issue.to_param
}
expect(response).to be_successful
end
end
/* eslint-disable one-var */
import $ from 'jquery';
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Issue from '~/issue';
import '~/lib/utils/text_utility';
describe('Issue', () => {
let testContext;
beforeEach(() => {
testContext = {};
});
let $btn, $dropdown, $alert, $boxOpen, $boxClosed;
preloadFixtures('ee/issues/blocked-issue.html');
describe('with blocked issue', () => {
let mock;
function setup() {
testContext.issue = new Issue();
testContext.$projectIssuesCounter = $('.issue_counter').first();
testContext.$projectIssuesCounter.text('1,001');
}
function mockCloseButtonResponseSuccess(url, response) {
mock.onPut(url).reply(() => [200, response]);
}
beforeEach(() => {
loadFixtures('ee/issues/blocked-issue.html');
mock = new MockAdapter(axios);
mock.onGet(/(.*)\/related_branches$/).reply(200, {});
jest.spyOn(axios, 'get');
});
afterEach(() => {
mock.restore();
});
it(`displays warning when attempting to close the issue`, done => {
setup();
$btn = $('.js-issuable-close-button');
$dropdown = $('.js-issuable-close-dropdown ');
$alert = $('.js-close-blocked-issue-warning');
expect($btn).toExist();
expect($btn).toHaveClass('btn-issue-blocked');
expect($dropdown).not.toHaveClass('hidden');
expect($alert).toHaveClass('hidden');
testContext.$triggeredButton = $btn;
testContext.$triggeredButton.trigger('click');
setImmediate(() => {
expect($alert).not.toHaveClass('hidden');
expect($dropdown).toHaveClass('hidden');
done();
});
});
it(`hides warning when cancelling closing the issue`, done => {
setup();
$btn = $('.js-issuable-close-button');
$alert = $('.js-close-blocked-issue-warning');
testContext.$triggeredButton = $btn;
testContext.$triggeredButton.trigger('click');
setImmediate(() => {
expect($alert).not.toHaveClass('hidden');
const $cancelbtn = $('.js-close-blocked-issue-warning .btn-secondary');
$cancelbtn.trigger('click');
expect($alert).toHaveClass('hidden');
done();
});
});
it('closes the issue when clicking alert close button', done => {
$btn = $('.js-issuable-close-button');
$boxOpen = $('div.status-box-open');
$boxClosed = $('div.status-box-issue-closed');
expect($boxOpen).not.toHaveClass('hidden');
expect($boxOpen).toHaveText('Open');
expect($boxClosed).toHaveClass('hidden');
testContext.$triggeredButton = $btn;
mockCloseButtonResponseSuccess(testContext.$triggeredButton.attr('href'), {
id: 34,
});
setup();
testContext.$triggeredButton.trigger('click');
const $btnCloseAnyway = $('.js-close-blocked-issue-warning .btn-close-anyway');
$btnCloseAnyway.trigger('click');
setImmediate(() => {
expect($btn).toHaveText('Reopen');
expect($boxOpen).toHaveClass('hidden');
expect($boxClosed).not.toHaveClass('hidden');
expect($boxClosed).toHaveText('Closed');
done();
});
});
});
});
......@@ -32,10 +32,10 @@ describe IssueEntity do
expect(subject).to include(:blocked_by_issues)
end
it 'exposes only id and web_path' do
it 'exposes only iid and web_url' do
response = described_class.new(blocked_issue, request: request, with_blocking_issues: true).as_json
expect(response[:blocked_by_issues].first.keys).to match_array([:id, :web_url])
expect(response[:blocked_by_issues].first.keys).to match_array([:iid, :web_url])
end
end
end
......@@ -2423,6 +2423,9 @@ msgstr ""
msgid "Are you sure you want to cancel editing this comment?"
msgstr ""
msgid "Are you sure you want to close this blocked issue?"
msgstr ""
msgid "Are you sure you want to delete %{name}?"
msgstr ""
......@@ -21074,6 +21077,9 @@ msgstr ""
msgid "This issue is confidential"
msgstr ""
msgid "This issue is currently blocked by the following issues: %{issues}."
msgstr ""
msgid "This issue is locked."
msgstr ""
......@@ -23640,6 +23646,9 @@ msgstr ""
msgid "Yes, add it"
msgstr ""
msgid "Yes, close issue"
msgstr ""
msgid "Yes, let me map Google Code users to full names or GitLab users."
msgstr ""
......
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