issuables_helper.rb 11.6 KB
Newer Older
1
module IssuablesHelper
2 3
  prepend EE::IssuablesHelper

4 5
  include GitlabRoutingHelper

6
  def sidebar_gutter_toggle_icon
Filipa Lacerda's avatar
Filipa Lacerda committed
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"
45
      else
46
        title
47
      end
Arinde Eniola's avatar
Arinde Eniola committed
48
    else
49
      default_label
50 51 52
    end
  end

Jacob Schatz's avatar
Jacob Schatz committed
53 54 55
  def issuable_json_path(issuable)
    project = issuable.project

Douwe Maan's avatar
Douwe Maan committed
56
    if issuable.is_a?(MergeRequest)
57
      project_merge_request_path(project, issuable.iid, :json)
Jacob Schatz's avatar
Jacob Schatz committed
58
    else
59
      project_issue_path(project, issuable.iid, :json)
Jacob Schatz's avatar
Jacob Schatz committed
60 61 62
    end
  end

63
  def serialize_issuable(issuable, serializer: nil)
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)
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
  def user_dropdown_label(user_id, default_label)
Phil Hughes's avatar
Phil Hughes committed
111
    return default_label if user_id.nil?
112 113
    return "Unassigned" if user_id == "0"

Phil Hughes's avatar
Phil Hughes committed
114
    user = User.find_by(id: user_id)
115 116 117 118 119 120 121 122

    if user
      user.name
    else
      default_label
    end
  end

123 124 125 126 127 128 129
  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
130
      project.full_name
131 132 133 134 135
    else
      default_label
    end
  end

136
  def milestone_dropdown_label(milestone_title, default_label = "Milestone")
137 138 139 140 141 142
    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
Phil Hughes's avatar
Phil Hughes committed
143

144
    h(title || default_label)
145 146
  end

147 148 149 150 151 152 153 154 155 156 157
  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

158
  def issuable_meta(issuable, project, text)
159
    output = ""
160
    output << "Opened #{time_ago_with_tooltip(issuable.created_at)} by ".html_safe
161
    output << content_tag(:strong) do
Clement Ho's avatar
Clement Ho committed
162 163
      author_output = link_to_member(project, issuable.author, size: 24, mobile_classes: "d-none d-sm-inline-block", tooltip: true)
      author_output << link_to_member(project, issuable.author, size: 24, by_username: true, avatar: false, mobile_classes: "d-block d-sm-none")
164
    end
165

166
    output << "&ensp;".html_safe
167 168
    output << content_tag(:span, (issuable_first_contribution_icon if issuable.first_contribution?), class: 'has-tooltip', title: _('1st contribution!'))

Clement Ho's avatar
Clement Ho committed
169
    output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block")
Clement Ho's avatar
Clement Ho committed
170
    output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
171

172
    output.html_safe
173 174
  end

Phil Hughes's avatar
Phil Hughes committed
175
  def issuable_todo(issuable)
176
    if current_user
Phil Hughes's avatar
Phil Hughes committed
177
      current_user.todos.find_by(target: issuable, state: :pending)
178
    end
179 180
  end

Phil Hughes's avatar
Phil Hughes committed
181
  def issuable_labels_tooltip(labels, limit: 5)
182
    first, last = labels.partition.with_index { |_, i| i < limit  }
183

184 185 186
    if labels && labels.any?
      label_names = first.collect(&:name)
      label_names << "and #{last.size} more" unless last.empty?
187

188 189 190 191
      label_names.join(', ')
    else
      _("Labels")
    end
192 193
  end

194
  def issuables_state_counter_text(issuable_type, state, display_count)
195 196 197 198 199 200
    titles = {
      opened: "Open"
    }

    state_title = titles[state] || state.to_s.humanize
    html = content_tag(:span, state_title)
201 202 203

    if display_count
      count = issuables_count_for_state(issuable_type, state)
Clement Ho's avatar
Clement Ho committed
204
      html << " " << content_tag(:span, number_with_delimiter(count), class: 'badge badge-pill')
205
    end
206 207 208

    html.html_safe
  end
209

210 211 212 213 214 215 216
  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

217
  def assigned_issuables_count(issuable_type)
218 219 220 221 222 223 224 225
    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
226 227
  end

228 229 230 231
  def issuable_reference(issuable)
    @show_full_reference ? issuable.to_reference(full: true) : issuable.to_reference(@group || @project)
  end

232
  def issuable_initial_data(issuable)
233
    data = {
Clement Ho's avatar
Clement Ho committed
234
      endpoint: issuable_path(issuable),
235
      updateEndpoint: "#{issuable_path(issuable)}.json",
Clement Ho's avatar
Clement Ho committed
236 237
      canUpdate: can?(current_user, :"update_#{issuable.to_ability_name}", issuable),
      canDestroy: can?(current_user, :"destroy_#{issuable.to_ability_name}", issuable),
Phil Hughes's avatar
Phil Hughes committed
238
      canAdmin: can?(current_user, :"admin_#{issuable.to_ability_name}", issuable),
239
      issuableRef: issuable.to_reference,
Clement Ho's avatar
Clement Ho committed
240
      markdownPreviewPath: preview_markdown_path(parent),
241
      markdownDocsPath: help_page_path('user/markdown'),
242 243 244 245
      issuableTemplates: issuable_templates(issuable),
      initialTitleHtml: markdown_field(issuable, :title),
      initialTitleText: issuable.title,
      initialDescriptionHtml: markdown_field(issuable, :description),
246 247
      initialDescriptionText: issuable.description,
      initialTaskStatus: issuable.task_status
248 249
    }

Clement Ho's avatar
Clement Ho committed
250 251
    if parent.is_a?(Group)
      data[:groupPath] = parent.path
252
      data[:issueLinksEndpoint] = group_epic_issues_path(parent, issuable)
Clement Ho's avatar
Clement Ho committed
253 254 255 256
    else
      data.merge!(projectPath: ref_project.path, projectNamespace: ref_project.namespace.full_path)
    end

Luke "Jared" Bennett's avatar
Luke "Jared" Bennett committed
257 258
    data.merge!(updated_at_by(issuable))

259
    data
260 261 262
  end

  def updated_at_by(issuable)
263
    return {} unless issuable.edited?
264 265

    {
266
      updatedAt: issuable.last_edited_at.to_time.iso8601,
267
      updatedBy: {
268 269 270 271
        name: issuable.last_edited_by.name,
        path: user_path(issuable.last_edited_by)
      }
    }
272 273
  end

274
  def issuables_count_for_state(issuable_type, state)
275
    Gitlab::IssuablesCountForState.new(finder)[state]
276 277
  end

278 279
  def close_issuable_path(issuable)
    issuable_path(issuable, close_reopen_params(issuable, :close))
280 281
  end

282 283
  def reopen_issuable_path(issuable)
    issuable_path(issuable, close_reopen_params(issuable, :reopen))
284 285
  end

286 287 288 289 290
  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)
Clement Ho's avatar
Clement Ho committed
291
    polymorphic_path(issuable, *options)
292 293
  end

294
  def issuable_url(issuable, *options)
295
    case issuable
296 297 298 299
    when Issue
      issue_url(issuable, *options)
    when MergeRequest
      merge_request_url(issuable, *options)
300 301 302
    end
  end

303 304
  def issuable_button_visibility(issuable, closed)
    case issuable
305 306 307 308
    when Issue
      issue_button_visibility(issuable, closed)
    when MergeRequest
      merge_request_button_visibility(issuable, closed)
309 310 311 312 313
    end
  end

  def issuable_close_reopen_button_method(issuable)
    case issuable
314 315 316 317
    when Issue
      ''
    when MergeRequest
      'put'
318 319 320
    end
  end

321 322 323 324 325 326 327 328
  def issuable_author_is_current_user(issuable)
    issuable.author == current_user
  end

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

329 330 331 332 333 334
  def selected_labels
    Array(params[:label_name]).map do |label_name|
      Label.new(title: label_name)
    end
  end

335 336 337 338 339 340
  private

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

341 342 343 344
  def issuable_templates(issuable)
    @issuable_templates ||=
      case issuable
      when Issue
345
        ref_project.repository.issue_template_names
346
      when MergeRequest
347
        ref_project.repository.merge_request_template_names
348 349 350 351
      end
  end

  def selected_template(issuable)
352
    params[:issuable_template] if issuable_templates(issuable).any? { |template| template[:name] == params[:issuable_template] }
353
  end
Phil Hughes's avatar
Phil Hughes committed
354 355 356 357

  def issuable_todo_button_data(issuable, todo, is_collapsed)
    {
      todo_text: "Add todo",
358
      mark_text: "Mark todo as done",
Phil Hughes's avatar
Phil Hughes committed
359 360 361 362
      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,
363
      url: project_todos_path(@project),
Phil Hughes's avatar
Phil Hughes committed
364 365
      delete_path: (dashboard_todo_path(todo) if todo),
      placement: (is_collapsed ? 'left' : nil),
Clement Ho's avatar
Clement Ho committed
366 367
      container: (is_collapsed ? 'body' : nil),
      boundary: 'viewport'
Phil Hughes's avatar
Phil Hughes committed
368
    }
369
  end
370 371

  def close_reopen_params(issuable, action)
372 373 374 375 376
    {
      issuable.model_name.to_s.underscore => { state_event: action }
    }.tap do |params|
      params[:format] = :json if issuable.is_a?(Issue)
    end
377
  end
378 379 380 381 382 383 384 385

  def labels_path
    if @project
      project_labels_path(@project)
    elsif @group
      group_labels_path(@group)
    end
  end
Felipe Artur's avatar
Felipe Artur committed
386

Phil Hughes's avatar
Phil Hughes committed
387 388
  def issuable_sidebar_options(issuable, can_edit_issuable)
    {
389 390
      endpoint: "#{issuable_json_path(issuable)}?serializer=sidebar",
      toggleSubscriptionEndpoint: toggle_subscription_path(issuable),
391 392
      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),
Phil Hughes's avatar
Phil Hughes committed
393
      editable: can_edit_issuable,
394
      currentUser: UserSerializer.new.represent(current_user),
Phil Hughes's avatar
Phil Hughes committed
395
      rootPath: root_path,
396
      fullPath: @project.full_path
Phil Hughes's avatar
Phil Hughes committed
397 398
    }
  end
Clement Ho's avatar
Clement Ho committed
399 400 401 402

  def parent
    @project || @group
  end
403 404 405 406 407 408 409

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