Module: Taskable

Included in:
Issuable
Defined in:
app/models/concerns/taskable.rb

Overview

Contains functionality for objects that can have task lists in their descriptions. Task list items can be added with Markdown like “* [x] Fix bugs”.

Used by MergeRequest and Issue

Constant Summary collapse

COMPLETED =
'completed'
INCOMPLETE =
'incomplete'
COMPLETE_PATTERN =
/\[[xX]\]/
INCOMPLETE_PATTERN =
/\[[[:space:]]\]/
ITEM_PATTERN =
%r{
  ^
  (?:(?:>\s{0,4})*)               # optional blockquote characters
  ((?:\s*(?:[-+*]|(?:\d+[.)])))+) # list prefix (one or more) required - task item has to be always in a list
  \s+                             # whitespace prefix has to be always presented for a list item
  (                               # checkbox
    #{COMPLETE_PATTERN}|#{INCOMPLETE_PATTERN}
  )
  (\s.+)                          # followed by whitespace and some text.
}x
ITEM_PATTERN_UNTRUSTED =
'^' \
'(?:(?:>\s{0,4})*)' \
'(?P<prefix>(?:\s*(?:[-+*]|(?:\d+[.)])))+)' \
'\s+' \
'(?P<checkbox>' \
"#{COMPLETE_PATTERN.source}|#{INCOMPLETE_PATTERN.source}" \
')' \
'(?P<label>\s.+)'.freeze
REGEX =

ignore tasks in code or html comment blocks. HTML blocks are ok as we allow tasks inside <detail> blocks

"#{::Gitlab::Regex.markdown_code_or_html_comments_untrusted}" \
"|" \
"(?P<task_item>" \
"#{ITEM_PATTERN_UNTRUSTED}" \
")".freeze

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.get_tasks(content) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# File 'app/models/concerns/taskable.rb', line 46

def self.get_tasks(content)
  items = []

  regex = Gitlab::UntrustedRegexp.new(REGEX, multiline: true)
  regex.scan(content.to_s).each do |match|
    next unless regex.extract_named_group(:task_item, match)

    prefix = regex.extract_named_group(:prefix, match)
    checkbox = regex.extract_named_group(:checkbox, match)
    label = regex.extract_named_group(:label, match)

    items << TaskList::Item.new("#{prefix.strip} #{checkbox}", label.strip)
  end

  items
end

.get_updated_tasks(old_content:, new_content:) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
# File 'app/models/concerns/taskable.rb', line 63

def self.get_updated_tasks(old_content:, new_content:)
  old_tasks = get_tasks(old_content)
  new_tasks = get_tasks(new_content)

  new_tasks.select.with_index do |new_task, i|
    old_task = old_tasks[i]
    next unless old_task

    new_task.source == old_task.source && new_task.complete? != old_task.complete?
  end
end

Instance Method Details

#task_completion_statusObject



113
114
115
116
117
118
# File 'app/models/concerns/taskable.rb', line 113

def task_completion_status
  @task_completion_status ||= {
      count: tasks.summary.item_count,
      completed_count: tasks.summary.complete_count
  }
end

#task_list_itemsObject

Called by ‘TaskList::Summary`



76
77
78
79
80
# File 'app/models/concerns/taskable.rb', line 76

def task_list_items
  return [] if description.blank?

  @task_list_items ||= Taskable.get_tasks(description) # rubocop:disable Gitlab/ModuleWithInstanceVariables
end

#task_status(short: false) ⇒ Object

Return a string that describes the current state of this Taskable’s task list items, e.g. “12 of 20 checklist items completed”



93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'app/models/concerns/taskable.rb', line 93

def task_status(short: false)
  return '' if description.blank?

  sum = tasks.summary
  checklist_item_noun = n_('checklist item', 'checklist items', sum.item_count)
  if short
    format(s_('Tasks|%{complete_count}/%{total_count} %{checklist_item_noun}'),
      checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
  else
    format(s_('Tasks|%{complete_count} of %{total_count} %{checklist_item_noun} completed'),
      checklist_item_noun: checklist_item_noun, complete_count: sum.complete_count, total_count: sum.item_count)
  end
end

#task_status_shortObject

Return a short string that describes the current state of this Taskable’s task list items – for small screens



109
110
111
# File 'app/models/concerns/taskable.rb', line 109

def task_status_short
  task_status(short: true)
end

#tasksObject



82
83
84
# File 'app/models/concerns/taskable.rb', line 82

def tasks
  @tasks ||= TaskList.new(self)
end

#tasks?Boolean

Return true if this object’s description has any task list items.

Returns:

  • (Boolean)


87
88
89
# File 'app/models/concerns/taskable.rb', line 87

def tasks?
  tasks.summary.items?
end