issuables_helper.rb 12.4 KB
Newer Older
1 2
# frozen_string_literal: true

3
module IssuablesHelper
4 5
  include GitlabRoutingHelper

6
  def sidebar_gutter_toggle_icon
F
Filipa Lacerda 已提交
7
    sidebar_gutter_collapsed? ? icon('angle-double-left', { 'aria-hidden': 'true' }) : icon('angle-double-right', { 'aria-hidden': 'true' })
8 9 10 11 12 13
  end

  def sidebar_gutter_collapsed_class
    "right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
  end

14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
  def sidebar_gutter_tooltip_text
    sidebar_gutter_collapsed? ? _('Expand sidebar') : _('Collapse sidebar')
  end

  def sidebar_assignee_tooltip_label(issuable)
    if issuable.assignee
      issuable.assignee.name
    else
      issuable.allows_multiple_assignees? ? _('Assignee(s)') : _('Assignee')
    end
  end

  def sidebar_due_date_tooltip_label(issuable)
    if issuable.due_date
      "#{_('Due date')}<br />#{due_date_remaining_days(issuable)}"
    else
      _('Due date')
    end
  end

  def due_date_remaining_days(issuable)
    remaining_days_in_words = remaining_days_in_words(issuable)

    "#{issuable.due_date.to_s(:medium)} (#{remaining_days_in_words})"
  end

40
  def multi_label_name(current_labels, default_label)
41
    if current_labels && current_labels.any?
42
      title = current_labels.first.try(:title)
43 44
      if current_labels.size > 1
        "#{title} +#{current_labels.size - 1} more"
J
Jacob Schatz 已提交
45
      else
46
        title
J
Jacob Schatz 已提交
47
      end
A
Arinde Eniola 已提交
48
    else
49
      default_label
J
Jacob Schatz 已提交
50 51 52
    end
  end

J
Jacob Schatz 已提交
53 54 55
  def issuable_json_path(issuable)
    project = issuable.project

D
Douwe Maan 已提交
56
    if issuable.is_a?(MergeRequest)
57
      project_merge_request_path(project, issuable.iid, :json)
J
Jacob Schatz 已提交
58
    else
59
      project_issue_path(project, issuable.iid, :json)
J
Jacob Schatz 已提交
60 61 62
    end
  end

63
  def serialize_issuable(issuable, serializer: nil)
E
Eric Eastwood 已提交
64 65 66 67 68 69 70 71 72
    serializer_klass = case issuable
                       when Issue
                         IssueSerializer
                       when MergeRequest
                         MergeRequestSerializer
                       end

    serializer_klass
      .new(current_user: current_user, project: issuable.project)
73
      .represent(issuable, serializer: serializer)
E
Eric Eastwood 已提交
74
      .to_json
75 76
  end

77 78 79 80 81 82 83 84 85 86 87 88 89
  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,
90
        namespace_path: ref_project.namespace.full_path
91 92 93 94 95 96 97 98
      }
    }

    dropdown_tag(title, options: options) do
      capture(&block)
    end
  end

99
  def users_dropdown_label(selected_users)
100 101
    case selected_users.length
    when 0
102
      "Unassigned"
103
    when 1
104 105 106 107 108 109
      selected_users[0].name
    else
      "#{selected_users[0].name} + #{selected_users.length - 1} more"
    end
  end

110
  # rubocop: disable CodeReuse/ActiveRecord
111
  def user_dropdown_label(user_id, default_label)
P
Phil Hughes 已提交
112
    return default_label if user_id.nil?
113 114
    return "Unassigned" if user_id == "0"

P
Phil Hughes 已提交
115
    user = User.find_by(id: user_id)
116 117 118 119 120 121 122

    if user
      user.name
    else
      default_label
    end
  end
123
  # rubocop: enable CodeReuse/ActiveRecord
124

125
  # rubocop: disable CodeReuse/ActiveRecord
126 127 128 129 130 131 132
  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
133
      project.full_name
134 135 136 137
    else
      default_label
    end
  end
138
  # rubocop: enable CodeReuse/ActiveRecord
139

140
  # rubocop: disable CodeReuse/ActiveRecord
141 142 143 144 145 146 147 148 149 150 151 152
  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
153
  # rubocop: enable CodeReuse/ActiveRecord
154

P
Phil Hughes 已提交
155
  def milestone_dropdown_label(milestone_title, default_label = "Milestone")
156 157 158 159 160 161
    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
P
Phil Hughes 已提交
162

163
    h(title || default_label)
164 165
  end

166 167 168 169 170 171 172 173 174 175 176
  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

177
  def issuable_meta(issuable, project, text)
178
    output = []
P
Phil Hughes 已提交
179
    output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
180

181
    output << content_tag(:strong) do
182
      author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline", tooltip: true)
C
Clement Ho 已提交
183
      author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
184 185

      if status = user_status(issuable.author)
186
        author_output << "#{status}".html_safe
187 188 189
      end

      author_output
190
    end
191

M
micael.bergeron 已提交
192
    output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))
193

194
    output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block prepend-left-8")
C
Clement Ho 已提交
195
    output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
196

197
    output.join.html_safe
198 199
  end

200
  # rubocop: disable CodeReuse/ActiveRecord
P
Phil Hughes 已提交
201
  def issuable_todo(issuable)
202
    if current_user
P
Phil Hughes 已提交
203
      current_user.todos.find_by(target: issuable, state: :pending)
204
    end
P
Phil Hughes 已提交
205
  end
206
  # rubocop: enable CodeReuse/ActiveRecord
P
Phil Hughes 已提交
207

P
Phil Hughes 已提交
208
  def issuable_labels_tooltip(labels, limit: 5)
209
    first, last = labels.partition.with_index { |_, i| i < limit  }
210

211 212 213
    if labels && labels.any?
      label_names = first.collect(&:name)
      label_names << "and #{last.size} more" unless last.empty?
214

215 216 217 218
      label_names.join(', ')
    else
      _("Labels")
    end
219 220
  end

221
  def issuables_state_counter_text(issuable_type, state, display_count)
222 223 224 225 226 227
    titles = {
      opened: "Open"
    }

    state_title = titles[state] || state.to_s.humanize
    html = content_tag(:span, state_title)
228 229 230

    if display_count
      count = issuables_count_for_state(issuable_type, state)
C
Clement Ho 已提交
231
      html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge badge-pill')
232
    end
233 234 235 236

    html.html_safe
  end

237
  def issuable_first_contribution_icon
M
micael.bergeron 已提交
238
    content_tag(:span, class: 'fa-stack') do
239 240
      concat(icon('certificate', class: "fa-stack-2x"))
      concat(content_tag(:strong, '1', class: 'fa-inverse fa-stack-1x'))
M
micael.bergeron 已提交
241
    end
242 243
  end

244
  def assigned_issuables_count(issuable_type)
245 246 247 248 249 250 251 252
    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
253 254
  end

255 256 257 258
  def issuable_reference(issuable)
    @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
  end

259
  def issuable_initial_data(issuable)
260
    data = {
261
      endpoint: issuable_path(issuable),
C
Clement Ho 已提交
262
      updateEndpoint: "#{issuable_path(issuable)}.json",
263 264
      canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
      canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
265
      issuableRef: issuable.to_reference,
266
      markdownPreviewPath: preview_markdown_path(parent),
267
      markdownDocsPath: help_page_path('user/markdown'),
268
      markdownVersion: issuable.cached_markdown_version,
269
      issuableTemplates: issuable_templates(issuable),
270 271 272
      initialTitleHtml: markdown_field(issuable, :title),
      initialTitleText: issuable.title,
      initialDescriptionHtml: markdown_field(issuable, :description),
273 274
      initialDescriptionText: issuable.description,
      initialTaskStatus: issuable.task_status
275 276
    }

277 278 279 280 281 282
    if parent.is_a?(Group)
      data[:groupPath] = parent.path
    else
      data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path)
    end

L
Luke "Jared" Bennett 已提交
283 284
    data.merge!(updated_at_by(issuable))

285
    data
286 287 288
  end

  def updated_at_by(issuable)
289
    return {} unless issuable.edited?
290 291

    {
292
      updatedAt: issuable.last_edited_at.to_time.iso8601,
293
      updatedBy: {
294 295 296 297 298 299
        name: issuable.last_edited_by.name,
        path: user_path(issuable.last_edited_by)
      }
    }
  end

300
  def issuables_count_for_state(issuable_type, state)
301
    Gitlab::IssuablesCountForState.new(finder)[state]
302 303
  end

304 305
  def close_issuable_path(issuable)
    issuable_path(issuable, close_reopen_params(issuable, :close))
306 307
  end

308 309
  def reopen_issuable_path(issuable)
    issuable_path(issuable, close_reopen_params(issuable, :reopen))
310 311
  end

312 313 314 315 316
  def close_reopen_issuable_path(issuable, should_inverse = false)
    issuable.closed? ^ should_inverse ? reopen_issuable_path(issuable) : close_issuable_path(issuable)
  end

  def issuable_path(issuable, *options)
317
    polymorphic_path(issuable, *options)
318 319
  end

320
  def issuable_url(issuable, *options)
321
    case issuable
322 323 324 325
    when Issue
      issue_url(issuable, *options)
    when MergeRequest
      merge_request_url(issuable, *options)
326 327 328
    end
  end

329
  def issuable_button_visibility(issuable, closed)
J
Jacopo 已提交
330 331 332 333
    return 'hidden' if issuable_button_hidden?(issuable, closed)
  end

  def issuable_button_hidden?(issuable, closed)
334
    case issuable
335
    when Issue
J
Jacopo 已提交
336
      issue_button_hidden?(issuable, closed)
337
    when MergeRequest
J
Jacopo 已提交
338
      merge_request_button_hidden?(issuable, closed)
339 340 341 342 343
    end
  end

  def issuable_close_reopen_button_method(issuable)
    case issuable
344 345 346 347
    when Issue
      ''
    when MergeRequest
      'put'
348 349 350
    end
  end

351 352 353 354 355 356 357 358
  def issuable_author_is_current_user(issuable)
    issuable.author == current_user
  end

  def issuable_display_type(issuable)
    issuable.model_name.human.downcase
  end

359 360 361 362 363 364
  def selected_labels
    Array(params[:label_name]).map do |label_name|
      Label.new(title: label_name)
    end
  end

365 366 367 368
  def has_filter_bar_param?
    finder.class.scalar_params.any? { |p| params[p].present? }
  end

369 370 371 372 373 374
  private

  def sidebar_gutter_collapsed?
    cookies[:collapsed_gutter] == 'true'
  end

375 376 377 378
  def issuable_templates(issuable)
    @issuable_templates ||=
      case issuable
      when Issue
S
Sean McGivern 已提交
379
        ref_project.repository.issue_template_names
380
      when MergeRequest
S
Sean McGivern 已提交
381
        ref_project.repository.merge_request_template_names
382 383 384 385
      end
  end

  def selected_template(issuable)
386
    params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
387
  end
P
Phil Hughes 已提交
388 389 390 391

  def issuable_todo_button_data(issuable, todo, is_collapsed)
    {
      todo_text: "Add todo",
392
      mark_text: "Mark todo as done",
393 394
      todo_icon: (is_collapsed ? sprite_icon('todo-add') : nil),
      mark_icon: (is_collapsed ? sprite_icon('todo-done', css_class: 'todo-undone') : nil),
P
Phil Hughes 已提交
395 396
      issuable_id: issuable.id,
      issuable_type: issuable.class.name.underscore,
397
      url: project_todos_path(@project),
P
Phil Hughes 已提交
398 399
      delete_path: (dashboard_todo_path(todo) if todo),
      placement: (is_collapsed ? 'left' : nil),
C
Clement Ho 已提交
400 401
      container: (is_collapsed ? 'body' : nil),
      boundary: 'viewport'
P
Phil Hughes 已提交
402 403
    }
  end
404 405

  def close_reopen_params(issuable, action)
406 407 408 409 410
    {
      issuable.model_name.to_s.underscore => { state_event: action }
    }.tap do |params|
      params[:format] = :json if issuable.is_a?(Issue)
    end
411
  end
P
Phil Hughes 已提交
412

F
Felipe Artur 已提交
413 414 415 416 417 418 419 420
  def labels_path
    if @project
      project_labels_path(@project)
    elsif @group
      group_labels_path(@group)
    end
  end

P
Phil Hughes 已提交
421 422
  def issuable_sidebar_options(issuable, can_edit_issuable)
    {
E
Eric Eastwood 已提交
423 424
      endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar",
      toggleSubscriptionEndpoint: toggle_subscription_path(issuable),
425 426
      moveIssueEndpoint: move_namespace_project_issue_path(namespace_id: issuable.project.namespace.to_param, project_id: issuable.project, id: issuable),
      projectsAutocompleteEndpoint: autocomplete_projects_path(project_id: @project.id),
P
Phil Hughes 已提交
427
      editable: can_edit_issuable,
428
      currentUser: UserSerializer.new.represent(current_user),
P
Phil Hughes 已提交
429 430 431 432
      rootPath: root_path,
      fullPath: @project.full_path
    }
  end
433 434 435 436

  def parent
    @project || @group
  end
437 438 439 440 441 442 443

  def issuable_milestone_tooltip_title(issuable)
    if issuable.milestone
      milestone_tooltip = milestone_tooltip_title(issuable.milestone)
      _('Milestone') + (milestone_tooltip ? ': ' + milestone_tooltip : '')
    end
  end
444
end