debian-mirror-gitlab/app/helpers/issuables_helper.rb

484 lines
14 KiB
Ruby
Raw Normal View History

2018-12-05 23:21:45 +05:30
# frozen_string_literal: true
2016-04-02 18:10:28 +05:30
module IssuablesHelper
2017-08-17 22:00:37 +05:30
include GitlabRoutingHelper
2016-04-02 18:10:28 +05:30
def sidebar_gutter_toggle_icon
2017-08-17 22:00:37 +05:30
sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
2016-04-02 18:10:28 +05:30
end
def sidebar_gutter_collapsed_class
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
end
2018-10-15 14:42:47 +05:30
def sidebar_gutter_tooltip_text
sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
end
2019-07-31 22:56:46 +05:30
def assignees_label(issuable, include_value: true)
label = 'Assignee'.pluralize(issuable.assignees.count)
if include_value
sanitized_list = sanitize_name(issuable.assignee_list)
"#{label}: #{sanitized_list}"
2018-10-15 14:42:47 +05:30
else
2019-07-31 22:56:46 +05:30
label
2018-10-15 14:42:47 +05:30
end
end
2019-02-15 15:39:39 +05:30
def sidebar_milestone_tooltip_label(milestone)
return _('Milestone') unless milestone.present?
[milestone[:title], sidebar_milestone_remaining_days(milestone) || _('Milestone')].join('<br/>')
end
def sidebar_milestone_remaining_days(milestone)
due_date_with_remaining_days(milestone[:due_date], milestone[:start_date])
end
def sidebar_due_date_tooltip_label(due_date)
[_('Due date'), due_date_with_remaining_days(due_date)].compact.join('<br/>')
end
def due_date_with_remaining_days(due_date, start_date = nil)
return unless due_date
"#{due_date.to_s(:medium)} (#{remaining_days_in_words(due_date, start_date)})"
2018-10-15 14:42:47 +05:30
end
2019-02-15 15:39:39 +05:30
def sidebar_label_filter_path(base_path, label_name)
query_params = { label_name: [label_name] }.to_query
2018-10-15 14:42:47 +05:30
2019-02-15 15:39:39 +05:30
"#{base_path}?#{query_params}"
2018-10-15 14:42:47 +05:30
end
2016-06-02 11:05:42 +05:30
def multi_label_name(current_labels, default_label)
2019-02-15 15:39:39 +05:30
return default_label if current_labels.blank?
title = current_labels.first.try(:title) || current_labels.first[:title]
if current_labels.size > 1
"#{title} +#{current_labels.size - 1} more"
2016-06-02 11:05:42 +05:30
else
2019-02-15 15:39:39 +05:30
title
2016-06-02 11:05:42 +05:30
end
end
def issuable_json_path(issuable)
project = issuable.project
2017-08-17 22:00:37 +05:30
if issuable.is_a?(MergeRequest)
2017-09-10 17:25:29 +05:30
project_merge_request_path(project, issuable.iid, :json)
2016-06-02 11:05:42 +05:30
else
2017-09-10 17:25:29 +05:30
project_issue_path(project, issuable.iid, :json)
2016-06-02 11:05:42 +05:30
end
end
2019-09-04 21:01:54 +05:30
def serialize_issuable(issuable, opts = {})
2018-03-17 18:26:18 +05:30
serializer_klass = case issuable
when Issue
IssueSerializer
when MergeRequest
MergeRequestSerializer
end
serializer_klass
.new(current_user: current_user, project: issuable.project)
2019-09-04 21:01:54 +05:30
.represent(issuable, opts)
2018-03-17 18:26:18 +05:30
.to_json
2017-08-17 22:00:37 +05:30
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
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2016-06-02 11:05:42 +05:30
def user_dropdown_label(user_id, default_label)
return default_label if user_id.nil?
2016-06-02 11:05:42 +05:30
return "Unassigned" if user_id == "0"
user = User.find_by(id: user_id)
2016-06-02 11:05:42 +05:30
if user
user.name
else
default_label
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2016-06-02 11:05:42 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2016-09-29 09:46:39 +05:30
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
2018-03-27 19:54:05 +05:30
project.full_name
2016-09-29 09:46:39 +05:30
else
default_label
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2016-09-29 09:46:39 +05:30
2018-12-05 23:21:45 +05:30
# rubocop: disable CodeReuse/ActiveRecord
2018-11-18 11:00:15 +05:30
def group_dropdown_label(group_id, default_label)
return default_label if group_id.nil?
return "Any group" if group_id == "0"
group = ::Group.find_by(id: group_id)
if group
group.full_name
else
default_label
end
end
2018-12-05 23:21:45 +05:30
# rubocop: enable CodeReuse/ActiveRecord
2018-11-18 11:00:15 +05:30
2016-06-02 11:05:42 +05:30
def milestone_dropdown_label(milestone_title, default_label = "Milestone")
2017-08-17 22:00:37 +05:30
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-06-02 11:05:42 +05:30
2017-08-17 22:00:37 +05:30
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
2016-06-02 11:05:42 +05:30
end
def issuable_meta(issuable, project, text)
2018-12-05 23:21:45 +05:30
output = []
2018-03-17 18:26:18 +05:30
output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
2018-12-05 23:21:45 +05:30
2016-06-02 11:05:42 +05:30
output << content_tag(:strong) do
2019-02-15 15:39:39 +05:30
author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline")
2019-07-07 11:18:12 +05:30
author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-inline d-sm-none")
2018-11-18 11:00:15 +05:30
2020-05-24 23:13:21 +05:30
author_output << issuable_meta_author_slot(issuable.author, css_class: 'ml-1')
2020-04-22 19:07:51 +05:30
2018-11-18 11:00:15 +05:30
if status = user_status(issuable.author)
2018-11-20 20:47:30 +05:30
author_output << "#{status}".html_safe
2018-11-18 11:00:15 +05:30
end
author_output
2016-06-02 11:05:42 +05:30
end
2017-08-17 22:00:37 +05:30
2020-06-23 00:09:42 +05:30
output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!'))
2018-03-17 18:26:18 +05:30
2020-06-23 00:09:42 +05:30
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block gl-ml-3")
2018-11-08 19:23:39 +05:30
output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
2017-08-17 22:00:37 +05:30
2018-12-05 23:21:45 +05:30
output.join.html_safe
2016-06-02 11:05:42 +05:30
end
2020-05-24 23:13:21 +05:30
# This is a dummy method, and has an override defined in ee
def issuable_meta_author_slot(author, css_class: nil)
nil
end
2016-09-13 17:45:13 +05:30
def issuable_labels_tooltip(labels, limit: 5)
2019-03-02 22:35:43 +05:30
first, last = labels.partition.with_index { |_, i| i < limit }
2016-09-13 17:45:13 +05:30
2018-10-15 14:42:47 +05:30
if labels && labels.any?
2019-02-15 15:39:39 +05:30
label_names = first.collect { |label| label.fetch(:title) }
2018-10-15 14:42:47 +05:30
label_names << "and #{last.size} more" unless last.empty?
2016-09-13 17:45:13 +05:30
2018-10-15 14:42:47 +05:30
label_names.join(', ')
else
_("Labels")
end
2016-09-13 17:45:13 +05:30
end
2018-05-09 12:01:36 +05:30
def issuables_state_counter_text(issuable_type, state, display_count)
2016-11-03 12:29:30 +05:30
titles = {
opened: "Open"
}
state_title = titles[state] || state.to_s.humanize
html = content_tag(:span, state_title)
2018-05-09 12:01:36 +05:30
if display_count
count = issuables_count_for_state(issuable_type, state)
2018-11-08 19:23:39 +05:30
html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge badge-pill')
2018-05-09 12:01:36 +05:30
end
2016-11-03 12:29:30 +05:30
html.html_safe
end
2018-03-17 18:26:18 +05:30
def issuable_first_contribution_icon
content_tag(:span, class: 'fa-stack') do
concat(icon('certificate', class: "fa-stack-2x"))
concat(content_tag(:strong, '1', class: 'fa-inverse fa-stack-1x'))
end
end
2017-08-17 22:00:37 +05:30
def assigned_issuables_count(issuable_type)
2018-03-17 18:26:18 +05:30
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
2017-08-17 22:00:37 +05:30
end
def issuable_reference(issuable)
@show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
end
2017-09-10 17:25:29 +05:30
def issuable_initial_data(issuable)
data = {
2018-03-17 18:26:18 +05:30
endpoint: issuable_path(issuable),
updateEndpoint: "#{issuable_path(issuable)}.json",
canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
2017-09-10 17:25:29 +05:30
issuableRef: issuable.to_reference,
2020-06-23 00:09:42 +05:30
issuableStatus: issuable.state,
2018-03-17 18:26:18 +05:30
markdownPreviewPath: preview_markdown_path(parent),
markdownDocsPath: help_page_path('user/markdown'),
2019-03-02 22:35:43 +05:30
lockVersion: issuable.lock_version,
2019-12-21 20:55:43 +05:30
issuableTemplateNamesPath: template_names_path(parent, issuable),
2017-09-10 17:25:29 +05:30
initialTitleHtml: markdown_field(issuable, :title),
initialTitleText: issuable.title,
initialDescriptionHtml: markdown_field(issuable, :description),
initialDescriptionText: issuable.description,
initialTaskStatus: issuable.task_status
}
2020-01-01 13:55:28 +05:30
data.merge!(issue_only_initial_data(issuable))
data.merge!(path_data(parent))
data.merge!(updated_at_by(issuable))
2017-09-10 17:25:29 +05:30
2020-01-01 13:55:28 +05:30
data
end
2019-09-30 21:07:59 +05:30
2020-01-01 13:55:28 +05:30
def issue_only_initial_data(issuable)
return {} unless issuable.is_a?(Issue)
2018-03-17 18:26:18 +05:30
2020-01-01 13:55:28 +05:30
{
hasClosingMergeRequest: issuable.merge_requests_count(current_user) != 0,
zoomMeetingUrl: ZoomMeeting.canonical_meeting_url(issuable),
sentryIssueIdentifier: SentryIssue.find_by(issue: issuable)&.sentry_issue_identifier # rubocop:disable CodeReuse/ActiveRecord
}
end
2017-09-10 17:25:29 +05:30
2020-01-01 13:55:28 +05:30
def path_data(parent)
return { groupPath: parent.path } if parent.is_a?(Group)
{
projectPath: ref_project.path,
projectNamespace: ref_project.namespace.full_path
}
2017-09-10 17:25:29 +05:30
end
def updated_at_by(issuable)
2018-03-17 18:26:18 +05:30
return {} unless issuable.edited?
2017-09-10 17:25:29 +05:30
{
2018-03-17 18:26:18 +05:30
updatedAt: issuable.last_edited_at.to_time.iso8601,
2017-09-10 17:25:29 +05:30
updatedBy: {
name: issuable.last_edited_by.name,
path: user_path(issuable.last_edited_by)
}
}
end
2018-03-17 18:26:18 +05:30
def issuables_count_for_state(issuable_type, state)
Gitlab::IssuablesCountForState.new(finder)[state]
end
2017-09-10 17:25:29 +05:30
2018-03-17 18:26:18 +05:30
def close_issuable_path(issuable)
issuable_path(issuable, close_reopen_params(issuable, :close))
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
def reopen_issuable_path(issuable)
issuable_path(issuable, close_reopen_params(issuable, :reopen))
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
def close_reopen_issuable_path(issuable, should_inverse = false)
issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
2017-09-10 17:25:29 +05:30
end
2018-03-17 18:26:18 +05:30
def issuable_path(issuable, *options)
polymorphic_path(issuable, *options)
2017-09-10 17:25:29 +05:30
end
def issuable_url(issuable, *options)
case issuable
when Issue
issue_url(issuable, *options)
when MergeRequest
merge_request_url(issuable, *options)
end
end
def issuable_button_visibility(issuable, closed)
2018-12-05 23:21:45 +05:30
return 'hidden' if issuable_button_hidden?(issuable, closed)
end
def issuable_button_hidden?(issuable, closed)
2017-09-10 17:25:29 +05:30
case issuable
when Issue
2018-12-05 23:21:45 +05:30
issue_button_hidden?(issuable, closed)
2017-09-10 17:25:29 +05:30
when MergeRequest
2018-12-05 23:21:45 +05:30
merge_request_button_hidden?(issuable, closed)
2017-09-10 17:25:29 +05:30
end
end
def issuable_author_is_current_user(issuable)
issuable.author == current_user
end
def issuable_display_type(issuable)
issuable.model_name.human.downcase
end
2019-02-15 15:39:39 +05:30
def has_filter_bar_param?
finder.class.scalar_params.any? { |p| params[p].present? }
2018-03-17 18:26:18 +05:30
end
2019-12-04 20:38:33 +05:30
def assignee_sidebar_data(assignee, merge_request: nil)
{ avatar_url: assignee.avatar_url, name: assignee.name, username: assignee.username }.tap do |data|
data[:can_merge] = merge_request.can_be_merged_by?(assignee) if merge_request
end
end
2020-07-28 23:09:34 +05:30
def issuable_squash_option?(issuable, project)
if issuable.persisted?
issuable.squash
else
project.squash_enabled_by_default?
end
end
2016-04-02 18:10:28 +05:30
private
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
2017-08-17 22:00:37 +05:30
def issuable_templates(issuable)
@issuable_templates ||=
case issuable
when Issue
2018-03-17 18:26:18 +05:30
ref_project.repository.issue_template_names
2017-08-17 22:00:37 +05:30
when MergeRequest
2018-03-17 18:26:18 +05:30
ref_project.repository.merge_request_template_names
2017-08-17 22:00:37 +05:30
end
end
2020-01-01 13:55:28 +05:30
def issuable_templates_names(issuable)
issuable_templates(issuable).map { |template| template[:name] }
end
2017-08-17 22:00:37 +05:30
def selected_template(issuable)
2018-03-17 18:26:18 +05:30
params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
2017-08-17 22:00:37 +05:30
end
2019-02-15 15:39:39 +05:30
def issuable_todo_button_data(issuable, is_collapsed)
2017-08-17 22:00:37 +05:30
{
2019-09-30 21:07:59 +05:30
todo_text: _('Add a To Do'),
mark_text: _('Mark as done'),
2019-02-15 15:39:39 +05:30
todo_icon: sprite_icon('todo-add'),
mark_icon: sprite_icon('todo-done', css_class: 'todo-undone'),
issuable_id: issuable[:id],
issuable_type: issuable[:type],
create_path: issuable[:create_todo_path],
delete_path: issuable.dig(:current_user, :todo, :delete_path),
placement: is_collapsed ? 'left' : nil,
container: is_collapsed ? 'body' : nil,
boundary: 'viewport',
2019-12-04 20:38:33 +05:30
is_collapsed: is_collapsed,
track_label: "right_sidebar",
track_property: "update_todo",
track_event: "click_button",
track_value: ""
2017-08-17 22:00:37 +05:30
}
end
2017-09-10 17:25:29 +05:30
def close_reopen_params(issuable, action)
{
issuable.model_name.to_s.underscore => { state_event: action }
}.tap do |params|
params[:format] = :json if issuable.is_a?(Issue)
end
end
2018-03-17 18:26:18 +05:30
def labels_path
if @project
project_labels_path(@project)
elsif @group
group_labels_path(@group)
end
end
2019-12-21 20:55:43 +05:30
def template_names_path(parent, issuable)
return '' unless parent.is_a?(Project)
project_template_names_path(parent, template_type: issuable.class.name.underscore)
end
2019-02-15 15:39:39 +05:30
def issuable_sidebar_options(issuable)
2017-09-10 17:25:29 +05:30
{
2019-02-15 15:39:39 +05:30
endpoint: "#{issuable[:issuable_json_path]}?serializer=sidebar_extras",
toggleSubscriptionEndpoint: issuable[:toggle_subscription_path],
moveIssueEndpoint: issuable[:move_issue_path],
projectsAutocompleteEndpoint: issuable[:projects_autocomplete_path],
editable: issuable.dig(:current_user, :can_edit),
currentUser: issuable[:current_user],
2017-09-10 17:25:29 +05:30
rootPath: root_path,
2019-09-30 21:07:59 +05:30
fullPath: issuable[:project_full_path],
2020-04-08 14:13:33 +05:30
iid: issuable[:iid],
2019-09-30 21:07:59 +05:30
timeTrackingLimitToHours: Gitlab::CurrentSettings.time_tracking_limit_to_hours
2017-09-10 17:25:29 +05:30
}
end
2018-03-17 18:26:18 +05:30
def parent
@project || @group
end
2016-04-02 18:10:28 +05:30
end
2019-12-04 20:38:33 +05:30
IssuablesHelper.prepend_if_ee('EE::IssuablesHelper')