2016-02-15 09:19:23 -05:00
|
|
|
module IssuablesHelper
|
2017-03-08 19:14:16 -05:00
|
|
|
include GitlabRoutingHelper
|
|
|
|
|
2016-02-15 09:19:23 -05:00
|
|
|
def sidebar_gutter_toggle_icon
|
2017-03-01 05:36:15 -05:00
|
|
|
sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
|
2016-02-15 09:19:23 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
def sidebar_gutter_collapsed_class
|
|
|
|
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
|
|
|
|
end
|
|
|
|
|
2016-07-19 05:26:49 -04:00
|
|
|
def multi_label_name(current_labels, default_label)
|
2016-07-20 04:45:53 -04:00
|
|
|
if current_labels && current_labels.any?
|
2016-07-19 05:05:38 -04:00
|
|
|
title = current_labels.first.try(:title)
|
2016-07-19 05:26:49 -04:00
|
|
|
if current_labels.size > 1
|
|
|
|
"#{title} +#{current_labels.size - 1} more"
|
2016-04-07 14:57:21 -04:00
|
|
|
else
|
2016-07-16 04:32:08 -04:00
|
|
|
title
|
2016-04-07 14:57:21 -04:00
|
|
|
end
|
2016-04-19 12:22:55 -04:00
|
|
|
else
|
2016-07-19 05:26:49 -04:00
|
|
|
default_label
|
2016-04-07 14:57:21 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-25 08:00:16 -04:00
|
|
|
def issuable_json_path(issuable)
|
|
|
|
project = issuable.project
|
|
|
|
|
2017-02-22 12:25:50 -05:00
|
|
|
if issuable.is_a?(MergeRequest)
|
2017-06-29 13:06:35 -04:00
|
|
|
project_merge_request_path(project, issuable.iid, :json)
|
2016-03-25 08:00:16 -04:00
|
|
|
else
|
2017-06-29 13:06:35 -04:00
|
|
|
project_issue_path(project, issuable.iid, :json)
|
2016-03-25 08:00:16 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-12-23 00:44:02 -05:00
|
|
|
def serialize_issuable(issuable)
|
|
|
|
case issuable
|
|
|
|
when Issue
|
|
|
|
IssueSerializer.new.represent(issuable).to_json
|
|
|
|
when MergeRequest
|
2017-05-09 00:15:34 -04:00
|
|
|
MergeRequestSerializer
|
|
|
|
.new(current_user: current_user, project: issuable.project)
|
|
|
|
.represent(issuable)
|
|
|
|
.to_json
|
2016-12-23 00:44:02 -05:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-11-02 13:52:04 -04:00
|
|
|
def template_dropdown_tag(issuable, &block)
|
|
|
|
title = selected_template(issuable) || "Choose a template"
|
|
|
|
options = {
|
|
|
|
toggle_class: 'js-issuable-selector',
|
|
|
|
title: title,
|
|
|
|
filter: true,
|
|
|
|
placeholder: 'Filter',
|
|
|
|
footer_content: true,
|
|
|
|
data: {
|
|
|
|
data: issuable_templates(issuable),
|
|
|
|
field_name: 'issuable_template',
|
|
|
|
selected: selected_template(issuable),
|
|
|
|
project_path: ref_project.path,
|
2017-02-23 18:55:01 -05:00
|
|
|
namespace_path: ref_project.namespace.full_path
|
2016-11-02 13:52:04 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
dropdown_tag(title, options: options) do
|
|
|
|
capture(&block)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-05-04 08:11:15 -04:00
|
|
|
def users_dropdown_label(selected_users)
|
2017-05-08 10:58:42 -04:00
|
|
|
case selected_users.length
|
|
|
|
when 0
|
2017-05-04 08:11:15 -04:00
|
|
|
"Unassigned"
|
2017-05-08 10:58:42 -04:00
|
|
|
when 1
|
2017-05-04 08:11:15 -04:00
|
|
|
selected_users[0].name
|
|
|
|
else
|
|
|
|
"#{selected_users[0].name} + #{selected_users.length - 1} more"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-15 13:17:05 -04:00
|
|
|
def user_dropdown_label(user_id, default_label)
|
2016-04-19 10:58:20 -04:00
|
|
|
return default_label if user_id.nil?
|
2016-03-15 13:17:05 -04:00
|
|
|
return "Unassigned" if user_id == "0"
|
|
|
|
|
2016-04-19 10:58:20 -04:00
|
|
|
user = User.find_by(id: user_id)
|
2016-03-15 13:17:05 -04:00
|
|
|
|
|
|
|
if user
|
|
|
|
user.name
|
|
|
|
else
|
|
|
|
default_label
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-08-27 18:45:01 -04:00
|
|
|
def project_dropdown_label(project_id, default_label)
|
|
|
|
return default_label if project_id.nil?
|
|
|
|
return "Any project" if project_id == "0"
|
|
|
|
|
|
|
|
project = Project.find_by(id: project_id)
|
|
|
|
|
|
|
|
if project
|
2016-08-27 19:09:21 -04:00
|
|
|
project.name_with_namespace
|
2016-08-27 18:45:01 -04:00
|
|
|
else
|
|
|
|
default_label
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-03-29 10:26:12 -04:00
|
|
|
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
|
2017-03-10 14:01:05 -05:00
|
|
|
title =
|
|
|
|
case milestone_title
|
|
|
|
when Milestone::Upcoming.name then Milestone::Upcoming.title
|
|
|
|
when Milestone::Started.name then Milestone::Started.title
|
|
|
|
else milestone_title.presence
|
|
|
|
end
|
2016-03-29 07:19:09 -04:00
|
|
|
|
2017-03-10 14:01:05 -05:00
|
|
|
h(title || default_label)
|
2016-03-24 11:20:35 -04:00
|
|
|
end
|
|
|
|
|
2017-03-08 19:14:16 -05:00
|
|
|
def to_url_reference(issuable)
|
|
|
|
case issuable
|
|
|
|
when Issue
|
|
|
|
link_to issuable.to_reference, issue_url(issuable)
|
|
|
|
when MergeRequest
|
|
|
|
link_to issuable.to_reference, merge_request_url(issuable)
|
|
|
|
else
|
|
|
|
issuable.to_reference
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2016-04-01 07:20:54 -04:00
|
|
|
def issuable_meta(issuable, project, text)
|
2017-03-08 19:14:16 -05:00
|
|
|
output = content_tag(:strong, class: "identifier") do
|
|
|
|
concat("#{text} ")
|
|
|
|
concat(to_url_reference(issuable))
|
|
|
|
end
|
|
|
|
|
2016-05-27 06:44:28 -04:00
|
|
|
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
|
2016-04-01 07:20:54 -04:00
|
|
|
output << content_tag(:strong) do
|
2016-07-12 10:46:28 -04:00
|
|
|
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
|
2016-04-01 07:20:54 -04:00
|
|
|
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
|
|
|
|
end
|
2016-09-26 17:36:11 -04:00
|
|
|
|
2017-05-11 07:25:22 -04:00
|
|
|
output << " ".html_safe
|
2017-06-20 05:56:18 -04:00
|
|
|
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "hidden-xs hidden-sm")
|
|
|
|
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "hidden-md hidden-lg")
|
2016-09-26 17:36:11 -04:00
|
|
|
|
|
|
|
output
|
2016-04-01 07:20:54 -04:00
|
|
|
end
|
|
|
|
|
2016-06-17 04:01:03 -04:00
|
|
|
def issuable_todo(issuable)
|
2016-06-17 04:10:23 -04:00
|
|
|
if current_user
|
2016-06-17 04:01:03 -04:00
|
|
|
current_user.todos.find_by(target: issuable, state: :pending)
|
2016-06-07 05:54:02 -04:00
|
|
|
end
|
2016-06-07 04:44:01 -04:00
|
|
|
end
|
|
|
|
|
2016-08-18 06:38:12 -04:00
|
|
|
def issuable_labels_tooltip(labels, limit: 5)
|
2017-08-09 05:52:22 -04:00
|
|
|
first, last = labels.partition.with_index { |_, i| i < limit }
|
2016-07-13 06:53:27 -04:00
|
|
|
|
2016-08-18 06:38:12 -04:00
|
|
|
label_names = first.collect(&:name)
|
|
|
|
label_names << "and #{last.size} more" unless last.empty?
|
2016-07-13 06:53:27 -04:00
|
|
|
|
2016-08-18 06:38:12 -04:00
|
|
|
label_names.join(', ')
|
2016-07-13 06:53:27 -04:00
|
|
|
end
|
|
|
|
|
2016-09-26 13:12:16 -04:00
|
|
|
def issuables_state_counter_text(issuable_type, state)
|
|
|
|
titles = {
|
|
|
|
opened: "Open"
|
|
|
|
}
|
|
|
|
|
|
|
|
state_title = titles[state] || state.to_s.humanize
|
2017-06-22 15:58:20 -04:00
|
|
|
count = issuables_count_for_state(issuable_type, state)
|
2016-09-26 13:12:16 -04:00
|
|
|
|
|
|
|
html = content_tag(:span, state_title)
|
|
|
|
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
|
|
|
|
|
|
|
|
html.html_safe
|
|
|
|
end
|
|
|
|
|
2017-04-19 11:17:46 -04:00
|
|
|
def assigned_issuables_count(issuable_type)
|
2017-08-10 12:39:26 -04:00
|
|
|
case issuable_type
|
|
|
|
when :issues
|
|
|
|
current_user.assigned_open_issues_count
|
|
|
|
when :merge_requests
|
|
|
|
current_user.assigned_open_merge_requests_count
|
|
|
|
else
|
|
|
|
raise ArgumentError, "invalid issuable `#{issuable_type}`"
|
|
|
|
end
|
2016-11-09 01:59:51 -05:00
|
|
|
end
|
|
|
|
|
2016-11-27 12:35:44 -05:00
|
|
|
def issuable_filter_params
|
|
|
|
[
|
|
|
|
:search,
|
|
|
|
:author_id,
|
|
|
|
:assignee_id,
|
|
|
|
:milestone_title,
|
|
|
|
:label_name
|
|
|
|
]
|
|
|
|
end
|
|
|
|
|
2017-01-12 07:28:37 -05:00
|
|
|
def issuable_reference(issuable)
|
|
|
|
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
|
|
|
|
end
|
|
|
|
|
2016-11-27 12:35:44 -05:00
|
|
|
def issuable_filter_present?
|
|
|
|
issuable_filter_params.any? { |k| params.key?(k) }
|
|
|
|
end
|
|
|
|
|
2017-05-15 09:51:40 -04:00
|
|
|
def issuable_initial_data(issuable)
|
2017-05-31 09:03:32 -04:00
|
|
|
data = {
|
2017-06-29 13:06:35 -04:00
|
|
|
endpoint: project_issue_path(@project, issuable),
|
2017-05-24 07:25:53 -04:00
|
|
|
canUpdate: can?(current_user, :update_issue, issuable),
|
|
|
|
canDestroy: can?(current_user, :destroy_issue, issuable),
|
2017-05-24 07:56:05 -04:00
|
|
|
canMove: current_user ? issuable.can_move?(current_user) : false,
|
2017-05-24 07:25:53 -04:00
|
|
|
issuableRef: issuable.to_reference,
|
|
|
|
isConfidential: issuable.confidential,
|
|
|
|
markdownPreviewUrl: preview_markdown_path(@project),
|
|
|
|
markdownDocs: help_page_path('user/markdown'),
|
|
|
|
projectsAutocompleteUrl: autocomplete_projects_path(project_id: @project.id),
|
2017-05-25 13:46:51 -04:00
|
|
|
issuableTemplates: issuable_templates(issuable),
|
2017-05-24 07:25:53 -04:00
|
|
|
projectPath: ref_project.path,
|
|
|
|
projectNamespace: ref_project.namespace.full_path,
|
|
|
|
initialTitleHtml: markdown_field(issuable, :title),
|
|
|
|
initialTitleText: issuable.title,
|
|
|
|
initialDescriptionHtml: markdown_field(issuable, :description),
|
2017-06-20 05:56:18 -04:00
|
|
|
initialDescriptionText: issuable.description,
|
|
|
|
initialTaskStatus: issuable.task_status
|
2017-05-31 09:03:32 -04:00
|
|
|
}
|
|
|
|
|
2017-06-02 12:30:58 -04:00
|
|
|
data.merge!(updated_at_by(issuable))
|
|
|
|
|
|
|
|
data.to_json
|
2017-05-31 09:03:32 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def updated_at_by(issuable)
|
|
|
|
return {} unless issuable.is_edited?
|
|
|
|
|
|
|
|
{
|
2017-06-02 12:17:54 -04:00
|
|
|
updatedAt: issuable.updated_at.to_time.iso8601,
|
|
|
|
updatedBy: {
|
2017-05-31 09:03:32 -04:00
|
|
|
name: issuable.last_edited_by.name,
|
|
|
|
path: user_path(issuable.last_edited_by)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2017-06-30 06:55:27 -04:00
|
|
|
def issuables_count_for_state(issuable_type, state, finder: nil)
|
2017-08-03 22:20:34 -04:00
|
|
|
finder ||= public_send("#{issuable_type}_finder") # rubocop:disable GitlabSecurity/PublicSend
|
2017-07-11 08:19:43 -04:00
|
|
|
cache_key = finder.state_counter_cache_key
|
2017-06-30 06:55:27 -04:00
|
|
|
|
|
|
|
@counts ||= {}
|
|
|
|
@counts[cache_key] ||= Rails.cache.fetch(cache_key, expires_in: 2.minutes) do
|
|
|
|
finder.count_by_state
|
|
|
|
end
|
|
|
|
|
|
|
|
@counts[cache_key][state]
|
|
|
|
end
|
|
|
|
|
2017-07-01 10:23:43 -04:00
|
|
|
def close_issuable_url(issuable)
|
2017-07-04 09:41:26 -04:00
|
|
|
issuable_url(issuable, close_reopen_params(issuable, :close))
|
2017-06-07 06:18:35 -04:00
|
|
|
end
|
|
|
|
|
2017-07-01 10:23:43 -04:00
|
|
|
def reopen_issuable_url(issuable)
|
2017-07-04 09:41:26 -04:00
|
|
|
issuable_url(issuable, close_reopen_params(issuable, :reopen))
|
2017-06-07 06:18:35 -04:00
|
|
|
end
|
|
|
|
|
2017-07-03 12:00:59 -04:00
|
|
|
def close_reopen_issuable_url(issuable, should_inverse = false)
|
2017-07-06 10:35:07 -04:00
|
|
|
issuable.closed? ^ should_inverse ? reopen_issuable_url(issuable) : close_issuable_url(issuable)
|
2017-06-07 06:18:35 -04:00
|
|
|
end
|
|
|
|
|
2017-07-01 10:23:43 -04:00
|
|
|
def issuable_url(issuable, *options)
|
2017-06-07 06:18:35 -04:00
|
|
|
case issuable
|
2017-07-06 10:35:07 -04:00
|
|
|
when Issue
|
|
|
|
issue_url(issuable, *options)
|
|
|
|
when MergeRequest
|
|
|
|
merge_request_url(issuable, *options)
|
2017-06-07 06:18:35 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-04 09:41:26 -04:00
|
|
|
def issuable_button_visibility(issuable, closed)
|
|
|
|
case issuable
|
2017-07-06 10:35:07 -04:00
|
|
|
when Issue
|
|
|
|
issue_button_visibility(issuable, closed)
|
|
|
|
when MergeRequest
|
|
|
|
merge_request_button_visibility(issuable, closed)
|
2017-07-04 09:41:26 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def issuable_close_reopen_button_method(issuable)
|
|
|
|
case issuable
|
2017-07-06 10:35:07 -04:00
|
|
|
when Issue
|
|
|
|
''
|
|
|
|
when MergeRequest
|
|
|
|
'put'
|
2017-07-04 09:41:26 -04:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2017-07-06 10:35:07 -04:00
|
|
|
def issuable_author_is_current_user(issuable)
|
|
|
|
issuable.author == current_user
|
|
|
|
end
|
|
|
|
|
|
|
|
def issuable_display_type(issuable)
|
|
|
|
issuable.model_name.human.downcase
|
|
|
|
end
|
|
|
|
|
2016-02-15 09:19:23 -05:00
|
|
|
private
|
|
|
|
|
|
|
|
def sidebar_gutter_collapsed?
|
|
|
|
cookies[:collapsed_gutter] == 'true'
|
|
|
|
end
|
|
|
|
|
|
|
|
def issuable_state_scope(issuable)
|
2016-03-01 06:32:20 -05:00
|
|
|
if issuable.respond_to?(:merged?) && issuable.merged?
|
|
|
|
:merged
|
|
|
|
else
|
|
|
|
issuable.open? ? :opened : :closed
|
|
|
|
end
|
2016-02-15 09:19:23 -05:00
|
|
|
end
|
2016-09-26 13:12:16 -04:00
|
|
|
|
2016-11-02 13:52:04 -04:00
|
|
|
def issuable_templates(issuable)
|
|
|
|
@issuable_templates ||=
|
|
|
|
case issuable
|
|
|
|
when Issue
|
|
|
|
issue_template_names
|
|
|
|
when MergeRequest
|
|
|
|
merge_request_template_names
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def merge_request_template_names
|
|
|
|
@merge_request_templates ||= Gitlab::Template::MergeRequestTemplate.dropdown_names(ref_project)
|
|
|
|
end
|
|
|
|
|
|
|
|
def issue_template_names
|
|
|
|
@issue_templates ||= Gitlab::Template::IssueTemplate.dropdown_names(ref_project)
|
|
|
|
end
|
|
|
|
|
|
|
|
def selected_template(issuable)
|
2017-08-09 05:52:22 -04:00
|
|
|
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
|
2016-11-02 13:52:04 -04:00
|
|
|
end
|
2017-03-24 12:17:38 -04:00
|
|
|
|
|
|
|
def issuable_todo_button_data(issuable, todo, is_collapsed)
|
|
|
|
{
|
|
|
|
todo_text: "Add todo",
|
|
|
|
mark_text: "Mark done",
|
|
|
|
todo_icon: (is_collapsed ? icon('plus-square') : nil),
|
|
|
|
mark_icon: (is_collapsed ? icon('check-square', class: 'todo-undone') : nil),
|
|
|
|
issuable_id: issuable.id,
|
|
|
|
issuable_type: issuable.class.name.underscore,
|
2017-06-29 13:06:35 -04:00
|
|
|
url: project_todos_path(@project),
|
2017-03-24 12:17:38 -04:00
|
|
|
delete_path: (dashboard_todo_path(todo) if todo),
|
|
|
|
placement: (is_collapsed ? 'left' : nil),
|
|
|
|
container: (is_collapsed ? 'body' : nil)
|
|
|
|
}
|
|
|
|
end
|
2017-07-04 09:41:26 -04:00
|
|
|
|
|
|
|
def close_reopen_params(issuable, action)
|
2017-07-06 10:35:07 -04:00
|
|
|
{
|
|
|
|
issuable.model_name.to_s.underscore => { state_event: action }
|
|
|
|
}.tap do |params|
|
|
|
|
params[:format] = :json if issuable.is_a?(Issue)
|
|
|
|
end
|
2017-07-04 09:41:26 -04:00
|
|
|
end
|
2017-07-25 11:46:15 -04:00
|
|
|
|
|
|
|
def issuable_sidebar_options(issuable, can_edit_issuable)
|
|
|
|
{
|
|
|
|
endpoint: "#{issuable_json_path(issuable)}?basic=true",
|
|
|
|
editable: can_edit_issuable,
|
|
|
|
currentUser: current_user.as_json(only: [:username, :id, :name], methods: :avatar_url),
|
|
|
|
rootPath: root_path,
|
|
|
|
fullPath: @project.full_path
|
|
|
|
}
|
|
|
|
end
|
2016-02-15 09:19:23 -05:00
|
|
|
end
|