5dc253f138
Previously the task status would be visible even if there where no tasks in the description. This fixes that by changing the regex to check for tasks without 0. This also fixes a bug where update the description inline would not update the task status text on screen. Closes #33880
314 lines
9 KiB
Ruby
314 lines
9 KiB
Ruby
module IssuablesHelper
|
|
include GitlabRoutingHelper
|
|
|
|
def sidebar_gutter_toggle_icon
|
|
sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
|
|
end
|
|
|
|
def sidebar_gutter_collapsed_class
|
|
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
|
|
end
|
|
|
|
def multi_label_name(current_labels, default_label)
|
|
if current_labels && current_labels.any?
|
|
title = current_labels.first.try(:title)
|
|
if current_labels.size > 1
|
|
"#{title} +#{current_labels.size - 1} more"
|
|
else
|
|
title
|
|
end
|
|
else
|
|
default_label
|
|
end
|
|
end
|
|
|
|
def issuable_json_path(issuable)
|
|
project = issuable.project
|
|
|
|
if issuable.is_a?(MergeRequest)
|
|
namespace_project_merge_request_path(project.namespace, project, issuable.iid, :json)
|
|
else
|
|
namespace_project_issue_path(project.namespace, project, issuable.iid, :json)
|
|
end
|
|
end
|
|
|
|
def serialize_issuable(issuable)
|
|
case issuable
|
|
when Issue
|
|
IssueSerializer.new.represent(issuable).to_json
|
|
when MergeRequest
|
|
MergeRequestSerializer
|
|
.new(current_user: current_user, project: issuable.project)
|
|
.represent(issuable)
|
|
.to_json
|
|
end
|
|
end
|
|
|
|
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,
|
|
namespace_path: ref_project.namespace.full_path
|
|
}
|
|
}
|
|
|
|
dropdown_tag(title, options: options) do
|
|
capture(&block)
|
|
end
|
|
end
|
|
|
|
def users_dropdown_label(selected_users)
|
|
case selected_users.length
|
|
when 0
|
|
"Unassigned"
|
|
when 1
|
|
selected_users[0].name
|
|
else
|
|
"#{selected_users[0].name} + #{selected_users.length - 1} more"
|
|
end
|
|
end
|
|
|
|
def user_dropdown_label(user_id, default_label)
|
|
return default_label if user_id.nil?
|
|
return "Unassigned" if user_id == "0"
|
|
|
|
user = User.find_by(id: user_id)
|
|
|
|
if user
|
|
user.name
|
|
else
|
|
default_label
|
|
end
|
|
end
|
|
|
|
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
|
|
project.name_with_namespace
|
|
else
|
|
default_label
|
|
end
|
|
end
|
|
|
|
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
|
|
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
|
|
|
|
h(title || default_label)
|
|
end
|
|
|
|
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
|
|
|
|
def issuable_meta(issuable, project, text)
|
|
output = content_tag(:strong, class: "identifier") do
|
|
concat("#{text} ")
|
|
concat(to_url_reference(issuable))
|
|
end
|
|
|
|
output << " opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
|
|
output << content_tag(:strong) do
|
|
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "hidden-xs", tooltip: true)
|
|
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "hidden-sm hidden-md hidden-lg")
|
|
end
|
|
|
|
output << " ".html_safe
|
|
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")
|
|
|
|
output
|
|
end
|
|
|
|
def issuable_todo(issuable)
|
|
if current_user
|
|
current_user.todos.find_by(target: issuable, state: :pending)
|
|
end
|
|
end
|
|
|
|
def issuable_labels_tooltip(labels, limit: 5)
|
|
first, last = labels.partition.with_index{ |_, i| i < limit }
|
|
|
|
label_names = first.collect(&:name)
|
|
label_names << "and #{last.size} more" unless last.empty?
|
|
|
|
label_names.join(', ')
|
|
end
|
|
|
|
def issuables_state_counter_text(issuable_type, state)
|
|
titles = {
|
|
opened: "Open"
|
|
}
|
|
|
|
state_title = titles[state] || state.to_s.humanize
|
|
|
|
count =
|
|
Rails.cache.fetch(issuables_state_counter_cache_key(issuable_type, state), expires_in: 2.minutes) do
|
|
issuables_count_for_state(issuable_type, state)
|
|
end
|
|
|
|
html = content_tag(:span, state_title)
|
|
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge')
|
|
|
|
html.html_safe
|
|
end
|
|
|
|
def assigned_issuables_count(issuable_type)
|
|
current_user.public_send("assigned_open_#{issuable_type}_count")
|
|
end
|
|
|
|
def issuable_filter_params
|
|
[
|
|
:search,
|
|
:author_id,
|
|
:assignee_id,
|
|
:milestone_title,
|
|
:label_name
|
|
]
|
|
end
|
|
|
|
def issuable_reference(issuable)
|
|
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
|
|
end
|
|
|
|
def issuable_filter_present?
|
|
issuable_filter_params.any? { |k| params.key?(k) }
|
|
end
|
|
|
|
def issuable_initial_data(issuable)
|
|
data = {
|
|
endpoint: namespace_project_issue_path(@project.namespace, @project, issuable),
|
|
canUpdate: can?(current_user, :update_issue, issuable),
|
|
canDestroy: can?(current_user, :destroy_issue, issuable),
|
|
canMove: current_user ? issuable.can_move?(current_user) : false,
|
|
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),
|
|
issuableTemplates: issuable_templates(issuable),
|
|
projectPath: ref_project.path,
|
|
projectNamespace: ref_project.namespace.full_path,
|
|
initialTitleHtml: markdown_field(issuable, :title),
|
|
initialTitleText: issuable.title,
|
|
initialDescriptionHtml: markdown_field(issuable, :description),
|
|
initialDescriptionText: issuable.description,
|
|
initialTaskStatus: issuable.task_status
|
|
}
|
|
|
|
data.merge!(updated_at_by(issuable))
|
|
|
|
data.to_json
|
|
end
|
|
|
|
def updated_at_by(issuable)
|
|
return {} unless issuable.is_edited?
|
|
|
|
{
|
|
updatedAt: issuable.updated_at.to_time.iso8601,
|
|
updatedBy: {
|
|
name: issuable.last_edited_by.name,
|
|
path: user_path(issuable.last_edited_by)
|
|
}
|
|
}
|
|
end
|
|
|
|
private
|
|
|
|
def sidebar_gutter_collapsed?
|
|
cookies[:collapsed_gutter] == 'true'
|
|
end
|
|
|
|
def base_issuable_scope(issuable)
|
|
issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
|
|
end
|
|
|
|
def issuable_state_scope(issuable)
|
|
if issuable.respond_to?(:merged?) && issuable.merged?
|
|
:merged
|
|
else
|
|
issuable.open? ? :opened : :closed
|
|
end
|
|
end
|
|
|
|
def issuables_count_for_state(issuable_type, state)
|
|
@counts ||= {}
|
|
@counts[issuable_type] ||= public_send("#{issuable_type}_finder").count_by_state
|
|
@counts[issuable_type][state]
|
|
end
|
|
|
|
IRRELEVANT_PARAMS_FOR_CACHE_KEY = %i[utf8 sort page].freeze
|
|
private_constant :IRRELEVANT_PARAMS_FOR_CACHE_KEY
|
|
|
|
def issuables_state_counter_cache_key(issuable_type, state)
|
|
opts = params.with_indifferent_access
|
|
opts[:state] = state
|
|
opts.except!(*IRRELEVANT_PARAMS_FOR_CACHE_KEY)
|
|
opts.delete_if { |_, value| value.blank? }
|
|
|
|
hexdigest(['issuables_count', issuable_type, opts.sort].flatten.join('-'))
|
|
end
|
|
|
|
def issuable_templates(issuable)
|
|
@issuable_templates ||=
|
|
case issuable
|
|
when Issue
|
|
issue_template_names
|
|
when MergeRequest
|
|
merge_request_template_names
|
|
else
|
|
raise 'Unknown issuable type!'
|
|
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)
|
|
params[:issuable_template] if issuable_templates(issuable).any?{ |template| template[:name] == params[:issuable_template] }
|
|
end
|
|
|
|
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,
|
|
url: namespace_project_todos_path(@project.namespace, @project),
|
|
delete_path: (dashboard_todo_path(todo) if todo),
|
|
placement: (is_collapsed ? 'left' : nil),
|
|
container: (is_collapsed ? 'body' : nil)
|
|
}
|
|
end
|
|
end
|