diff options
author | Phil Hughes <me@iamphill.com> | 2017-05-10 17:15:19 +0000 |
---|---|---|
committer | Phil Hughes <me@iamphill.com> | 2017-05-10 17:15:19 +0000 |
commit | 09c4d27ae48ceb181f86657043af2a129c17dabf (patch) | |
tree | 1766eee1b65d69e27ae8e43342047f20d0c67957 | |
parent | 05a2ef690b11467241e16b380a50384cf541ad0e (diff) | |
parent | f328f38e5b1f013c3b21f6f572cc22c71aad8fa3 (diff) | |
download | gitlab-ce-09c4d27ae48ceb181f86657043af2a129c17dabf.tar.gz |
Merge branch '31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select' into 'master'
Resolve "Issue boards sidebar milestone dropdown should not be multi select"
Closes #31474
See merge request !11084
7 files changed, 97 insertions, 13 deletions
diff --git a/app/assets/javascripts/boards/components/board_sidebar.js b/app/assets/javascripts/boards/components/board_sidebar.js index 317cef9f227..9bcea302da2 100644 --- a/app/assets/javascripts/boards/components/board_sidebar.js +++ b/app/assets/javascripts/boards/components/board_sidebar.js @@ -36,6 +36,9 @@ gl.issueBoards.BoardSidebar = Vue.extend({ }, assigneeId() { return this.issue.assignee ? this.issue.assignee.id : 0; + }, + milestoneTitle() { + return this.issue.milestone ? this.issue.milestone.title : 'No Milestone'; } }, watch: { diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 11e68c0a3be..9d481d7c003 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -18,12 +18,11 @@ } $els.each(function(i, dropdown) { - var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; + var $block, $dropdown, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, collapsedSidebarLabelTemplate, defaultLabel, defaultNo, issuableId, issueUpdateURL, milestoneLinkNoneTemplate, milestoneLinkTemplate, milestonesUrl, projectId, selectedMilestone, selectedMilestoneDefault, showAny, showNo, showUpcoming, showStarted, useId, showMenuAbove; $dropdown = $(dropdown); projectId = $dropdown.data('project-id'); milestonesUrl = $dropdown.data('milestones'); issueUpdateURL = $dropdown.data('issueUpdate'); - selectedMilestone = $dropdown.data('selected'); showNo = $dropdown.data('show-no'); showAny = $dropdown.data('show-any'); showMenuAbove = $dropdown.data('showMenuAbove'); @@ -31,6 +30,7 @@ showStarted = $dropdown.data('show-started'); useId = $dropdown.data('use-id'); defaultLabel = $dropdown.data('default-label'); + defaultNo = $dropdown.data('default-no'); issuableId = $dropdown.data('issuable-id'); abilityName = $dropdown.data('ability-name'); $selectbox = $dropdown.closest('.selectbox'); @@ -38,6 +38,9 @@ $sidebarCollapsedValue = $block.find('.sidebar-collapsed-icon'); $value = $block.find('.value'); $loading = $block.find('.block-loading').fadeOut(); + selectedMilestoneDefault = (showAny ? '' : null); + selectedMilestoneDefault = (showNo && defaultNo ? 'No Milestone' : selectedMilestoneDefault); + selectedMilestone = $dropdown.data('selected') || selectedMilestoneDefault; if (issueUpdateURL) { milestoneLinkTemplate = _.template('<a href="/<%- full_path %>/milestones/<%- iid %>" class="bold has-tooltip" data-container="body" title="<%- remaining %>"><%- title %></a>'); milestoneLinkNoneTemplate = '<span class="no-value">None</span>'; @@ -86,8 +89,18 @@ if (showMenuAbove) { $dropdown.data('glDropdown').positionMenuAbove(); } + $(`[data-milestone-id="${selectedMilestone}"] > a`).addClass('is-active'); }); }, + renderRow: function(milestone) { + return ` + <li data-milestone-id="${milestone.name}"> + <a href='#' class='dropdown-menu-milestone-link'> + ${_.escape(milestone.title)} + </a> + </li> + `; + }, filterable: true, search: { fields: ['title'] @@ -120,15 +133,24 @@ // display:block overrides the hide-collapse rule return $value.css('display', ''); }, + opened: function(e) { + const $el = $(e.currentTarget); + if ($dropdown.hasClass('js-issue-board-sidebar')) { + selectedMilestone = $dropdown[0].dataset.selected || selectedMilestoneDefault; + } + $('a.is-active', $el).removeClass('is-active'); + $(`[data-milestone-id="${selectedMilestone}"] > a`, $el).addClass('is-active'); + }, vue: $dropdown.hasClass('js-issue-board-sidebar'), clicked: function(options) { const { $el, e } = options; let selected = options.selectedObj; - - var data, isIssueIndex, isMRIndex, page, boardsStore; + var data, isIssueIndex, isMRIndex, isSelecting, page, boardsStore; page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = (page === page && page === 'projects:merge_requests:index'); + isSelecting = (selected.name !== selectedMilestone); + selectedMilestone = isSelecting ? selected.name : selectedMilestoneDefault; if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) { e.preventDefault(); return; @@ -142,16 +164,11 @@ boardsStore[$dropdown.data('field-name')] = selected.name; e.preventDefault(); } else if ($dropdown.hasClass('js-filter-submit') && (isIssueIndex || isMRIndex)) { - if (selected.name != null) { - selectedMilestone = selected.name; - } else { - selectedMilestone = ''; - } return Issuable.filterResults($dropdown.closest('form')); } else if ($dropdown.hasClass('js-filter-submit')) { return $dropdown.closest('form').submit(); } else if ($dropdown.hasClass('js-issue-board-sidebar')) { - if (selected.id !== -1) { + if (selected.id !== -1 && isSelecting) { gl.issueBoards.boardStoreIssueSet('milestone', new ListMilestone({ id: selected.id, title: selected.name diff --git a/app/views/projects/boards/components/sidebar/_milestone.html.haml b/app/views/projects/boards/components/sidebar/_milestone.html.haml index 190e7290303..4e46351bf8a 100644 --- a/app/views/projects/boards/components/sidebar/_milestone.html.haml +++ b/app/views/projects/boards/components/sidebar/_milestone.html.haml @@ -16,7 +16,8 @@ name: "issue[milestone_id]", "v-if" => "issue.milestone" } .dropdown - %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true" }, + %button.dropdown-menu-toggle.js-milestone-select.js-issue-board-sidebar{ type: "button", data: { toggle: "dropdown", show_no: "true", field_name: "issue[milestone_id]", project_id: @project.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: "issue", use_id: "true", default_no: "true" }, + ":data-selected" => "milestoneTitle", ":data-issuable-id" => "issue.id", ":data-issue-update" => "'#{namespace_project_issues_path(@project.namespace, @project)}/' + issue.id + '.json'" } Milestone diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index f0d50828e2a..6750921338a 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -6,7 +6,7 @@ - if selected.present? || params[:milestone_title].present? = hidden_field_tag(name, name == :milestone_title ? selected_text : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", - placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do + placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, show_started: show_started, field_name: name, selected: selected_text, project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list - if can? current_user, :admin_milestone, project diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 3a66880e177..305d1c36a73 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -43,7 +43,7 @@ .selectbox.hide-collapsed = f.hidden_field 'milestone_id', value: issuable.milestone_id, id: nil - = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true }}) + = dropdown_tag('Milestone', options: { title: 'Assign milestone', toggle_class: 'js-milestone-select js-extra-options', filter: true, dropdown_class: 'dropdown-menu-selectable', placeholder: 'Search milestones', data: { show_no: true, field_name: "#{issuable.to_ability_name}[milestone_id]", project_id: @project.id, issuable_id: issuable.id, milestones: namespace_project_milestones_path(@project.namespace, @project, :json), ability_name: issuable.to_ability_name, issue_update: issuable_json_path(issuable), use_id: true, default_no: true, selected: (issuable.milestone.name if issuable.milestone), null_default: true }}) - if issuable.has_attribute?(:time_estimate) #issuable-time-tracker.block // Fallback while content is loading diff --git a/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml b/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml new file mode 100644 index 00000000000..88e79e3b6ea --- /dev/null +++ b/changelogs/unreleased/31474-issue-boards-sidebar-milestone-dropdown-should-not-be-multi-select.yml @@ -0,0 +1,4 @@ +--- +title: Disallow multiple selections for Milestone dropdown +merge_request: 11084 +author: diff --git a/spec/features/dashboard/milestone_filter_spec.rb b/spec/features/dashboard/milestone_filter_spec.rb new file mode 100644 index 00000000000..628627f70d4 --- /dev/null +++ b/spec/features/dashboard/milestone_filter_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe 'Dashboard > milestone filter', feature: true, js: true do + let(:user) { create(:user) } + let(:project) { create(:project, name: 'test', namespace: user.namespace) } + let(:milestone) { create(:milestone, title: "v1.0", project: project) } + let(:milestone2) { create(:milestone, title: "v2.0", project: project) } + let!(:issue) { create :issue, author: user, project: project, milestone: milestone } + let!(:issue2) { create :issue, author: user, project: project, milestone: milestone2 } + + before do + login_as(user) + visit issues_dashboard_path(author_id: user.id) + end + + context 'default state' do + it 'shows issues with Any Milestone' do + page.all('.issue-info').each do |issue_info| + expect(issue_info.text).to match(/v\d.0/) + end + end + end + + context 'filtering by milestone' do + milestone_select = '.js-milestone-select' + + before do + find(milestone_select).click + + page.within('.dropdown-content') do + click_link 'v1.0' + end + + find(milestone_select).click + end + + it 'shows issues with Milestone v1.0' do + expect(find('.issues-list')).to have_selector('.issue', count: 1) + + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + end + + it 'should not change active Milestone unless clicked' do + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + + # open & close dropdown + find('.dropdown-menu-close').click + expect(find('.milestone-filter')).not_to have_selector('.dropdown.open') + find(milestone_select).click + + expect(find('.dropdown-content')).to have_selector('a.is-active', count: 1) + expect(find('.dropdown-content a.is-active')).to have_content('v1.0') + end + end +end |