summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/dispatcher.js4
-rw-r--r--app/assets/javascripts/issuable.js1
-rw-r--r--app/assets/javascripts/labels_select.js73
-rw-r--r--app/assets/javascripts/milestone_select.js12
-rw-r--r--app/assets/javascripts/users_select.js12
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss4
-rw-r--r--app/controllers/projects/boards_controller.rb2
-rw-r--r--app/helpers/dropdowns_helper.rb3
-rw-r--r--app/helpers/issuables_helper.rb16
-rw-r--r--app/helpers/labels_helper.rb5
-rw-r--r--app/helpers/milestones_helper.rb5
-rw-r--r--app/views/shared/issuable/_filter.html.haml9
-rw-r--r--app/views/shared/issuable/_form.html.haml35
-rw-r--r--app/views/shared/issuable/_label_dropdown.html.haml19
-rw-r--r--app/views/shared/issuable/_milestone_dropdown.html.haml21
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml17
-rw-r--r--features/project/issues/issues.feature1
-rw-r--r--features/steps/project/forked_merge_requests.rb14
-rw-r--r--features/steps/project/issues/issues.rb3
-rw-r--r--spec/features/issues/filter_issues_spec.rb4
-rw-r--r--spec/features/issues/form_spec.rb119
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues_spec.rb5
-rw-r--r--spec/features/merge_requests/form_spec.rb273
25 files changed, 550 insertions, 115 deletions
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 38cdc7b9fba..a72beb42646 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -58,6 +58,8 @@
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.issue-form'));
new IssuableForm($('.issue-form'));
+ new LabelsSelect();
+ new MilestoneSelect();
new IssuableTemplateSelectors();
break;
case 'projects:merge_requests:new':
@@ -66,6 +68,8 @@
shortcut_handler = new ShortcutsNavigation();
new GLForm($('.merge-request-form'));
new IssuableForm($('.merge-request-form'));
+ new LabelsSelect();
+ new MilestoneSelect();
new IssuableTemplateSelectors();
break;
case 'projects:tags:new':
diff --git a/app/assets/javascripts/issuable.js b/app/assets/javascripts/issuable.js
index d0305c6c6a1..86ecad0b1fc 100644
--- a/app/assets/javascripts/issuable.js
+++ b/app/assets/javascripts/issuable.js
@@ -41,7 +41,6 @@
return this.value === $button.data('label');
}).remove();
Issuable.filterResults($('.filter-form'));
- return $('.js-label-select').trigger('update.label');
});
},
filterResults: (function(_this) {
diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js
index bab23ff5ac0..9d40a303a8f 100644
--- a/app/assets/javascripts/labels_select.js
+++ b/app/assets/javascripts/labels_select.js
@@ -4,8 +4,9 @@
var _this;
_this = this;
$('.js-label-select').each(function(i, dropdown) {
- var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip;
+ var $block, $colorPreview, $dropdown, $form, $loading, $selectbox, $sidebarCollapsedValue, $value, abilityName, defaultLabel, enableLabelCreateButton, issueURLSplit, issueUpdateURL, labelHTMLTemplate, labelNoneHTMLTemplate, labelUrl, projectId, saveLabelData, selectedLabel, showAny, showNo, $sidebarLabelTooltip, $toggleText, fieldName, useId, propertyName;
$dropdown = $(dropdown);
+ $toggleText = $dropdown.find('.dropdown-toggle-text');
projectId = $dropdown.data('project-id');
labelUrl = $dropdown.data('labels');
issueUpdateURL = $dropdown.data('issueUpdate');
@@ -24,6 +25,10 @@
$sidebarLabelTooltip = $block.find('.js-sidebar-labels-tooltip');
$value = $block.find('.value');
$loading = $block.find('.block-loading').fadeOut();
+ fieldName = $dropdown.data('field-name');
+ useId = $dropdown.is('.js-issuable-form-dropdown, .js-filter-bulk-update, .js-label-sidebar-dropdown');
+ propertyName = useId ? 'id' : 'title';
+
if (issueUpdateURL != null) {
issueURLSplit = issueUpdateURL.split('/');
}
@@ -40,7 +45,7 @@
saveLabelData = function() {
var data, selected;
- selected = $dropdown.closest('.selectbox').find("input[name='" + ($dropdown.data('field-name')) + "']").map(function() {
+ selected = $dropdown.closest('.selectbox').find("input[name='" + fieldName + "']").map(function() {
return this.value;
}).get();
data = {};
@@ -124,20 +129,22 @@
};
}).value();
if ($dropdown.hasClass('js-extra-options')) {
+ var extraData = [];
if (showNo) {
- data.unshift({
+ extraData.unshift({
id: 0,
title: 'No Label'
});
}
if (showAny) {
- data.unshift({
+ extraData.unshift({
isAny: true,
title: 'Any Label'
});
}
- if (data.length > 2) {
- data.splice(2, 0, 'divider');
+ if (extraData.length) {
+ extraData.push('divider');
+ data = extraData.concat(data);
}
}
return callback(data);
@@ -148,7 +155,7 @@
$li = $('<li>');
$a = $('<a href="#">');
selectedClass = [];
- removesAll = label.id === 0 || (label.id == null);
+ removesAll = label.id <= 0 || (label.id == null);
if ($dropdown.hasClass('js-filter-bulk-update')) {
indeterminate = instance.indeterminateIds;
active = instance.activeIds;
@@ -205,27 +212,36 @@
},
selectable: true,
filterable: true,
+ selected: $dropdown.data('selected') || [],
toggleLabel: function(selected, el) {
- var selected_labels;
- selected_labels = $('.js-label-select').siblings('.dropdown-menu-labels').find('.is-active');
- if (selected && (selected.title != null)) {
- if (selected_labels.length > 1) {
- return selected.title + " +" + (selected_labels.length - 1) + " more";
- } else {
- return selected.title;
- }
- } else if (!selected && selected_labels.length !== 0) {
- if (selected_labels.length > 1) {
- return ($(selected_labels[0]).text()) + " +" + (selected_labels.length - 1) + " more";
- } else if (selected_labels.length === 1) {
- return $(selected_labels).text();
- }
+ var isSelected = el !== null ? el.hasClass('is-active') : false,
+ title = selected.title;
+
+ if (isSelected) {
+ this.selected.push(title);
+ } else {
+ var index = this.selected.indexOf(title);
+ this.selected.splice(index, 1);
+ }
+
+ var selectedLabels = this.selected;
+
+ if (selectedLabels.length === 1) {
+ return selectedLabels;
+ } else if (selectedLabels.length > 1) {
+ return selectedLabels[0] + " +" + (selectedLabels.length - 1) + " more";
} else {
return defaultLabel;
}
},
fieldName: $dropdown.data('field-name'),
id: function(label) {
+ if (label.id <= 0) return;
+
+ if ($dropdown.hasClass('js-issuable-form-dropdown')) {
+ return label.id;
+ }
+
if ($dropdown.hasClass("js-filter-submit") && (label.isAny == null)) {
return label.title;
} else {
@@ -239,6 +255,11 @@
isMRIndex = page === 'projects:merge_requests:index';
$selectbox.hide();
$value.removeAttr('style');
+
+ if ($dropdown.hasClass('js-issuable-form-dropdown')) {
+ return;
+ }
+
if (page === 'projects:boards:show') {
return;
}
@@ -264,9 +285,17 @@
clicked: function(label, $el, e) {
var isIssueIndex, isMRIndex, page;
_this.enableBulkLabelDropdown();
- if ($dropdown.hasClass('js-filter-bulk-update')) {
+
+ if ($dropdown.parent().find('.is-active:not(.dropdown-clear-active)').length) {
+ $dropdown.parent()
+ .find('.dropdown-clear-active')
+ .removeClass('is-active')
+ }
+
+ if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
return;
}
+
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = page === 'projects:merge_requests:index';
diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js
index e897ebdf630..260db626c96 100644
--- a/app/assets/javascripts/milestone_select.js
+++ b/app/assets/javascripts/milestone_select.js
@@ -58,7 +58,7 @@
title: 'Upcoming'
});
}
- if (extraOptions.length > 2) {
+ if (extraOptions.length) {
extraOptions.push('divider');
}
return callback(extraOptions.concat(data));
@@ -69,19 +69,20 @@
fields: ['title']
},
selectable: true,
- toggleLabel: function(selected) {
- if (selected && 'id' in selected) {
+ toggleLabel: function(selected, el, e) {
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
return selected.title;
} else {
return defaultLabel;
}
},
+ defaultLabel: defaultLabel,
fieldName: $dropdown.data('field-name'),
text: function(milestone) {
return _.escape(milestone.title);
},
id: function(milestone) {
- if (!useId) {
+ if (!useId && !$dropdown.is('.js-issuable-form-dropdown')) {
return milestone.name;
} else {
return milestone.id;
@@ -99,7 +100,8 @@
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');
- if ($dropdown.hasClass('js-filter-bulk-update')) {
+ if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
+ e.preventDefault();
return;
}
if (page === 'projects:boards:show') {
diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js
index bad82868ab0..b76a0831cf9 100644
--- a/app/assets/javascripts/users_select.js
+++ b/app/assets/javascripts/users_select.js
@@ -125,8 +125,8 @@
},
selectable: true,
fieldName: $dropdown.data('field-name'),
- toggleLabel: function(selected) {
- if (selected && 'id' in selected) {
+ toggleLabel: function(selected, el) {
+ if (selected && 'id' in selected && $(el).hasClass('is-active')) {
if (selected.text) {
return selected.text;
} else {
@@ -136,6 +136,7 @@
return defaultLabel;
}
},
+ defaultLabel: defaultLabel,
inputId: 'issue_assignee_id',
hidden: function(e) {
$selectbox.hide();
@@ -146,7 +147,9 @@
page = $('body').data('page');
isIssueIndex = page === 'projects:issues:index';
isMRIndex = (page === page && page === 'projects:merge_requests:index');
- if ($dropdown.hasClass('js-filter-bulk-update')) {
+ if ($dropdown.hasClass('js-filter-bulk-update') || $dropdown.hasClass('js-issuable-form-dropdown')) {
+ e.preventDefault();
+ selectedId = user.id;
return;
}
if (page === 'projects:boards:show') {
@@ -164,6 +167,9 @@
return assignTo(selected);
}
},
+ id: function (user) {
+ return user.id;
+ },
renderRow: function(user) {
var avatar, img, listClosingTags, listWithName, listWithUserName, selected, username;
username = user.username ? "@" + user.username : "";
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index b0ba112476b..4a87a73a68a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -604,3 +604,9 @@
display: block;
color: $gl-placeholder-color;
}
+
+.dropdown-toggle-text {
+ &.is-default {
+ color: $gl-placeholder-color;
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 7fdd79fa8b9..ecfed2c32f1 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -353,6 +353,10 @@
.issuable-form-select-holder {
display: inline-block;
width: 250px;
+
+ .dropdown-menu-toggle {
+ width: 100%;
+ }
}
.table-holder {
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 33206717089..0035633b774 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,4 +1,6 @@
class Projects::BoardsController < Projects::ApplicationController
+ include IssuableCollections
+
respond_to :html
before_action :authorize_read_board!, only: [:show]
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index 4566f3782cc..81e0b6bb5ae 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -40,8 +40,9 @@ module DropdownsHelper
end
def dropdown_toggle(toggle_text, data_attr, options = {})
+ default_label = data_attr[:default_label]
content_tag(:button, class: "dropdown-menu-toggle #{options[:toggle_class] if options.has_key?(:toggle_class)}", id: (options[:id] if options.has_key?(:id)), type: "button", data: data_attr) do
- output = content_tag(:span, toggle_text, class: "dropdown-toggle-text")
+ output = content_tag(:span, toggle_text, class: "dropdown-toggle-text #{'is-default' if toggle_text == default_label}")
output << icon('chevron-down')
output.html_safe
end
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 5c04bba323f..d1e432e41ae 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -8,18 +8,12 @@ module IssuablesHelper
end
def multi_label_name(current_labels, default_label)
- # current_labels may be a string from before
- if current_labels.is_a?(Array)
- if current_labels.count > 1
- "#{current_labels[0]} +#{current_labels.count - 1} more"
+ if current_labels && current_labels.any?
+ title = current_labels.first.try(:title)
+ if current_labels.size > 1
+ "#{title} +#{current_labels.size - 1} more"
else
- current_labels[0]
- end
- elsif current_labels.is_a?(String)
- if current_labels.nil? || current_labels.empty?
- default_label
- else
- current_labels
+ title
end
else
default_label
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 5e9f5837101..b9f3d6c75c2 100644
--- a/app/helpers/labels_helper.rb
+++ b/app/helpers/labels_helper.rb
@@ -115,8 +115,9 @@ module LabelsHelper
end
def labels_filter_path
- if @project
- namespace_project_labels_path(@project.namespace, @project, :json)
+ project = @target_project || @project
+ if project
+ namespace_project_labels_path(project.namespace, project, :json)
else
dashboard_labels_path(:json)
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index b3e6e468ecd..e3d6fbd06ef 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -47,8 +47,9 @@ module MilestonesHelper
end
def milestones_filter_dropdown_path
- if @project
- namespace_project_milestones_path(@project.namespace, @project, :json)
+ project = @target_project || @project
+ if project
+ namespace_project_milestones_path(project.namespace, project, :json)
else
dashboard_milestones_path(:json)
end
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index 0f4f744a71f..a3bad0e8326 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -1,3 +1,4 @@
+- finder = controller.controller_name == 'issues' || controller.controller_name == 'boards' ? issues_finder : merge_requests_finder
.issues-filters
.issues-details-filters.row-content-block.second-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name, :issue_search]), method: :get, class: 'filter-form js-filter-form' do
@@ -12,19 +13,19 @@
- if params[:author_id].present?
= hidden_field_tag(:author_id, params[:author_id])
= dropdown_tag(user_dropdown_label(params[:author_id], "Author"), options: { toggle_class: "js-user-search js-filter-submit js-author-search", title: "Filter by author", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-author js-filter-submit",
- placeholder: "Search authors", data: { any_user: "Any Author", first_user: (current_user.username if current_user), current_user: true, project_id: (@project.id if @project), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
+ placeholder: "Search authors", data: { any_user: "Any Author", first_user: current_user.try(:username), current_user: true, project_id: @project.try(:id), selected: params[:author_id], field_name: "author_id", default_label: "Author" } })
.filter-item.inline
- if params[:assignee_id].present?
= hidden_field_tag(:assignee_id, params[:assignee_id])
= dropdown_tag(user_dropdown_label(params[:assignee_id], "Assignee"), options: { toggle_class: "js-user-search js-filter-submit js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
- placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: (current_user.username if current_user), null_user: true, current_user: true, project_id: (@project.id if @project), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
+ placeholder: "Search assignee", data: { any_user: "Any Assignee", first_user: current_user.try(:username), null_user: true, current_user: true, project_id: @project.try(:id), selected: params[:assignee_id], field_name: "assignee_id", default_label: "Assignee" } })
.filter-item.inline.milestone-filter
- = render "shared/issuable/milestone_dropdown"
+ = render "shared/issuable/milestone_dropdown", selected: finder.milestones.try(:first), name: :milestone_title, show_any: true, show_upcoming: true
.filter-item.inline.labels-filter
- = render "shared/issuable/label_dropdown"
+ = render "shared/issuable/label_dropdown", selected: finder.labels.select(:title).uniq, use_id: false, selected_toggle: params[:label_name], data_options: { field_name: "label_name[]" }
.pull-right
- if controller.controller_name == 'boards'
diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 3856a4917b4..ac593d8937a 100644
--- a/app/views/shared/issuable/_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -1,3 +1,4 @@
+- project = @target_project || @project
= form_errors(issuable)
- if @conflict
@@ -82,38 +83,22 @@
= f.label :assignee_id, "Assignee", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
.issuable-form-select-holder
- = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]",
- placeholder: 'Select assignee', class: 'custom-form-control', null_user: true,
- selected: issuable.assignee_id, project: @target_project || @project,
- first_user: true, current_user: true, include_blank: true)
- %div
- = link_to 'Assign to me', '#', class: 'assign-to-me-link prepend-top-5 inline'
+ - if issuable.assignee_id
+ = f.hidden_field :assignee_id
+ = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit",
+ placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee" } })
.form-group.issue-milestone
= f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}"
.col-sm-10{ class: ("col-lg-8" if has_due_date) }
- - if milestone_options(issuable).present?
- .issuable-form-select-holder
- = f.select(:milestone_id, milestone_options(issuable),
- { include_blank: true }, { class: 'select2', data: { placeholder: 'Select milestone' } })
- - else
- .prepend-top-10
- %span.light No open milestones available.
- - if can? current_user, :admin_milestone, issuable.project
- %div
- = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline"
+ .issuable-form-select-holder
+ = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input"
.form-group
- has_labels = issuable.project.labels.any?
= f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}"
+ = f.hidden_field :label_ids, multiple: true, value: ''
.col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" }
- - if has_labels
- .issuable-form-select-holder
- = f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
- { selected: issuable.label_ids }, multiple: true, class: 'select2', data: { placeholder: "Select labels" }
- - else
- %span.light No labels yet.
- - if can? current_user, :admin_label, issuable.project
- %div
- = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank, class: "prepend-top-5 inline"
+ .issuable-form-select-holder
+ = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false }
- if has_due_date
.col-lg-6
.form-group
diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml
index 24a1a616919..6d5bdbe435e 100644
--- a/app/views/shared/issuable/_label_dropdown.html.haml
+++ b/app/views/shared/issuable/_label_dropdown.html.haml
@@ -1,25 +1,28 @@
+- project = @target_project || @project
- show_create = local_assigns.fetch(:show_create, true)
- extra_options = local_assigns.fetch(:extra_options, true)
- filter_submit = local_assigns.fetch(:filter_submit, true)
- show_footer = local_assigns.fetch(:show_footer, true)
+- use_id = local_assigns.fetch(:use_id, true)
- data_options = local_assigns.fetch(:data_options, {})
- classes = local_assigns.fetch(:classes, [])
-- dropdown_data = {toggle: 'dropdown', field_name: 'label_name[]', show_no: "true", show_any: "true", selected: params[:label_name], project_id: @project.try(:id), labels: labels_filter_path, default_label: "Label"}
+- selected = local_assigns.fetch(:selected, nil)
+- selected_toggle = local_assigns.fetch(:selected_toggle, nil)
+- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", project_id: project.try(:id), labels: labels_filter_path, default_label: "Labels"}
- dropdown_data.merge!(data_options)
- classes << 'js-extra-options' if extra_options
- classes << 'js-filter-submit' if filter_submit
-- if params[:label_name].present?
- - if params[:label_name].respond_to?('any?')
- - params[:label_name].each do |label|
- = hidden_field_tag "label_name[]", u(label), id: nil
+- if selected
+ - selected.each do |label|
+ = hidden_field_tag data_options[:field_name], use_id ? label.try(:id) : u(label.try(:title)), id: nil
.dropdown
%button.dropdown-menu-toggle.js-label-select.js-multiselect{class: classes.join(' '), type: "button", data: dropdown_data}
- %span.dropdown-toggle-text
- = h(multi_label_name(params[:label_name], "Label"))
+ %span.dropdown-toggle-text{ class: ("is-default" if selected.nil? || selected.empty?) }
+ = multi_label_name(selected, "Labels")
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create }
- - if show_create and @project and can?(current_user, :admin_label, @project)
+ - if show_create && project && can?(current_user, :admin_label, project)
= render partial: "shared/issuable/label_page_create"
= dropdown_loading
diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml
index 2fcf40ece99..ab537dc0ed7 100644
--- a/app/views/shared/issuable/_milestone_dropdown.html.haml
+++ b/app/views/shared/issuable/_milestone_dropdown.html.haml
@@ -1,16 +1,19 @@
-- if params[:milestone_title].present?
- = hidden_field_tag(:milestone_title, params[:milestone_title])
-= dropdown_tag(milestone_dropdown_label(params[:milestone_title]), options: { title: "Filter by milestone", toggle_class: 'js-milestone-select js-filter-submit', filter: true, dropdown_class: "dropdown-menu-selectable",
- placeholder: "Search milestones", footer_content: @project.present?, data: { show_no: true, show_any: true, show_upcoming: true, field_name: "milestone_title", selected: params[:milestone_title], project_id: @project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do
- - if @project
+- project = @target_project || @project
+- extra_class = extra_class || ''
+- selected_text = selected.try(:title)
+- if selected.present?
+ = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id)
+= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable",
+ placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), 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
+ - if can? current_user, :admin_milestone, project
%li
- = link_to new_namespace_project_milestone_path(@project.namespace, @project), title: "New Milestone" do
+ = link_to new_namespace_project_milestone_path(project.namespace, project), title: "New Milestone" do
Create new
%li
- = link_to namespace_project_milestones_path(@project.namespace, @project) do
- - if can? current_user, :admin_milestone, @project
+ = link_to namespace_project_milestones_path(project.namespace, project) do
+ - if can? current_user, :admin_milestone, project
Manage milestones
- else
View milestones
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index b13daaf43c9..4e3bb8a8285 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -108,29 +108,30 @@
.js-due-date-calendar
- if issuable.project.labels.any?
+ - selected_labels = issuable.labels
.block.labels
.sidebar-collapsed-icon.js-sidebar-labels-tooltip{ title: issuable_labels_tooltip(issuable.labels_array), data: { placement: "left", container: "body" } }
= icon('tags')
%span
- = issuable.labels_array.size
+ = selected_labels.size
.title.hide-collapsed
Labels
= icon('spinner spin', class: 'block-loading')
- if can_edit_issuable
= link_to 'Edit', '#', class: 'edit-link pull-right'
- .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if issuable.labels_array.any?) }
- - if issuable.labels_array.any?
- - issuable.labels_array.each do |label|
+ .value.issuable-show-labels.hide-collapsed{ class: ("has-labels" if selected_labels.any?) }
+ - if selected_labels.any?
+ - selected_labels.each do |label|
= link_to_label(label, type: issuable.to_ability_name)
- else
%span.no-value None
.selectbox.hide-collapsed
- - issuable.labels_array.each do |label|
+ - selected_labels.each do |label|
= hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil
.dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect{type: "button", data: {toggle: "dropdown", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
- %span.dropdown-toggle-text
- Label
+ %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", project_id: (@project.id if @project), issue_update: issuable_json_path(issuable), labels: (namespace_project_labels_path(@project.namespace, @project, :json) if @project)}}
+ %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?)}
+ = multi_label_name(selected_labels, "Labels")
= icon('chevron-down')
.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
= render partial: "shared/issuable/label_page_default"
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index 358e622b736..80670063ea0 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -37,6 +37,7 @@ Feature: Project Issues
And I submit new issue "500 error on profile"
Then I should see issue "500 error on profile"
+ @javascript
Scenario: I submit new unassigned issue with labels
Given project "Shop" has labels: "bug", "feature", "enhancement"
And I click link "New Issue"
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index dacab6c7977..6c14d835004 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -138,19 +138,19 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
end
step 'I click "Assign to" dropdown"' do
- first('.ajax-users-select').click
+ click_button 'Assignee'
end
step 'I should see the target project ID in the input selector' do
- expect(page).to have_selector("input[data-project-id=\"#{@project.id}\"]")
+ expect(find('.js-assignee-search')["data-project-id"]).to eq "#{@project.id}"
end
step 'I should see the users from the target project ID' do
- expect(page).to have_selector('.user-result', visible: true, count: 3)
- users = page.all('.user-name')
- expect(users[0].text).to eq 'Unassigned'
- expect(users[1].text).to eq current_user.name
- expect(users[2].text).to eq @project.users.first.name
+ page.within '.dropdown-menu-user' do
+ expect(page).to have_content 'Unassigned'
+ expect(page).to have_content current_user.name
+ expect(page).to have_content @project.users.first.name
+ end
end
# Verify a link is generated against the correct project
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index e21f76d00d9..99bd2ac3bc0 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -84,7 +84,8 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
step 'I submit new issue "500 error on profile" with label \'bug\'' do
fill_in "issue_title", with: "500 error on profile"
- select 'bug', from: "Labels"
+ click_button "Label"
+ click_link "bug"
click_button "Submit issue"
end
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/issues/filter_issues_spec.rb
index 0e9f814044e..372ad39676d 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/issues/filter_issues_spec.rb
@@ -96,9 +96,9 @@ describe 'Filter issues', feature: true do
wait_for_ajax
page.within '.labels-filter' do
- expect(page).to have_content 'No Label'
+ expect(page).to have_content 'Labels'
end
- expect(find('.js-label-select .dropdown-toggle-text')).to have_content('No Label')
+ expect(find('.js-label-select .dropdown-toggle-text')).to have_content('Labels')
end
it 'filters by no label' do
diff --git a/spec/features/issues/form_spec.rb b/spec/features/issues/form_spec.rb
new file mode 100644
index 00000000000..8771cc8e157
--- /dev/null
+++ b/spec/features/issues/form_spec.rb
@@ -0,0 +1,119 @@
+require 'rails_helper'
+
+describe 'New/edit issue', feature: true, js: true do
+ let!(:project) { create(:project) }
+ let!(:user) { create(:user)}
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:label) { create(:label, project: project) }
+ let!(:label2) { create(:label, project: project) }
+ let!(:issue) { create(:issue, project: project, assignee: user, milestone: milestone) }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ end
+
+ context 'new issue' do
+ before do
+ visit new_namespace_project_issue_path(project.namespace, project)
+ end
+
+ it 'allows user to create new issue' do
+ fill_in 'issue_title', with: 'title'
+ fill_in 'issue_description', with: 'title'
+
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Submit issue'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+
+ context 'edit issue' do
+ before do
+ visit edit_namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it 'allows user to update issue' do
+ expect(find('input[name="issue[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ expect(find('input[name="issue[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+
+ page.within '.js-user-search' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="issue[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="issue[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+end
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 7773c486b4e..055210399a7 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -55,7 +55,7 @@ feature 'issue move to another project' do
first('.select2-choice').click
end
- fill_in('s2id_autogen2_search', with: new_project_search.name)
+ fill_in('s2id_autogen1_search', with: new_project_search.name)
page.within '.select2-drop' do
expect(page).to have_content(new_project_search.name)
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d744d0eb6af..36ef28ae992 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -51,9 +51,8 @@ describe 'Issues', feature: true do
expect(page).to have_content "Assignee #{@user.name}"
- first('#s2id_issue_assignee_id').click
- sleep 2 # wait for ajax stuff to complete
- first('.user-result').click
+ first('.js-user-search').click
+ click_link 'Unassigned'
click_button 'Save changes'
diff --git a/spec/features/merge_requests/form_spec.rb b/spec/features/merge_requests/form_spec.rb
new file mode 100644
index 00000000000..7594cbf54e8
--- /dev/null
+++ b/spec/features/merge_requests/form_spec.rb
@@ -0,0 +1,273 @@
+require 'rails_helper'
+
+describe 'New/edit merge request', feature: true, js: true do
+ let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
+ let(:fork_project) { create(:project, forked_from_project: project) }
+ let!(:user) { create(:user)}
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:label) { create(:label, project: project) }
+ let!(:label2) { create(:label, project: project) }
+
+ before do
+ project.team << [user, :master]
+ end
+
+ context 'owned projects' do
+ before do
+ login_as(user)
+ end
+
+ context 'new merge request' do
+ before do
+ visit new_namespace_project_merge_request_path(
+ project.namespace,
+ project,
+ merge_request: {
+ source_project_id: project.id,
+ target_project_id: project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ })
+ end
+
+ it 'creates new merge request' do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Submit merge request'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+
+ context 'edit merge request' do
+ before do
+ merge_request = create(:merge_request,
+ source_project: project,
+ target_project: project,
+ source_branch: 'fix',
+ target_branch: 'master'
+ )
+
+ visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'updates merge request' do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+ end
+
+ context 'forked project' do
+ before do
+ fork_project.team << [user, :master]
+ login_as(user)
+ end
+
+ context 'new merge request' do
+ before do
+ visit new_namespace_project_merge_request_path(
+ fork_project.namespace,
+ fork_project,
+ merge_request: {
+ source_project_id: fork_project.id,
+ target_project_id: project.id,
+ source_branch: 'fix',
+ target_branch: 'master'
+ })
+ end
+
+ it 'creates new merge request' do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+
+ click_button 'Submit merge request'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+
+ context 'edit merge request' do
+ before do
+ merge_request = create(:merge_request,
+ source_project: fork_project,
+ target_project: project,
+ source_branch: 'fix',
+ target_branch: 'master'
+ )
+
+ visit edit_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ end
+
+ it 'should update merge request' do
+ click_button 'Assignee'
+ page.within '.dropdown-menu-user' do
+ click_link user.name
+ end
+ expect(find('input[name="merge_request[assignee_id]"]', visible: false).value).to match(user.id.to_s)
+ page.within '.js-assignee-search' do
+ expect(page).to have_content user.name
+ end
+
+ click_button 'Milestone'
+ page.within '.issue-milestone' do
+ click_link milestone.title
+ end
+ expect(find('input[name="merge_request[milestone_id]"]', visible: false).value).to match(milestone.id.to_s)
+ page.within '.js-milestone-select' do
+ expect(page).to have_content milestone.title
+ end
+
+ click_button 'Labels'
+ page.within '.dropdown-menu-labels' do
+ click_link label.title
+ click_link label2.title
+ end
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[1].value).to match(label.id.to_s)
+ expect(page.all('input[name="merge_request[label_ids][]"]', visible: false)[2].value).to match(label2.id.to_s)
+ page.within '.js-label-select' do
+ expect(page).to have_content label.title
+ end
+
+ click_button 'Save changes'
+
+ page.within '.issuable-sidebar' do
+ page.within '.assignee' do
+ expect(page).to have_content user.name
+ end
+
+ page.within '.milestone' do
+ expect(page).to have_content milestone.title
+ end
+
+ page.within '.labels' do
+ expect(page).to have_content label.title
+ expect(page).to have_content label2.title
+ end
+ end
+ end
+ end
+ end
+end