summaryrefslogtreecommitdiff
path: root/app/models/label.rb
blob: 49c352cc239b811a7f8e772c00b9047d4d75f0a6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
class Label < ActiveRecord::Base
  include Referable
  include Subscribable

  # Represents a "No Label" state used for filtering Issues and Merge
  # Requests that have no label assigned.
  LabelStruct = Struct.new(:title, :name)
  None = LabelStruct.new('No Label', 'No Label')
  Any = LabelStruct.new('Any Label', '')

  DEFAULT_COLOR = '#428BCA'

  default_value_for :color, DEFAULT_COLOR

  belongs_to :project
  has_many :label_links, dependent: :destroy
  has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
  has_many :merge_requests, through: :label_links, source: :target, source_type: 'MergeRequest'

  validates :color, color: true, allow_blank: false
  validates :project, presence: true, unless: Proc.new { |service| service.template? }

  # Don't allow '?', '&', and ',' for label titles
  validates :title,
            presence: true,
            format: { with: /\A[^&\?,]+\z/ },
            uniqueness: { scope: :project_id }

  before_save :nullify_priority

  default_scope { order(title: :asc) }

  scope :templates, ->  { where(template: true) }

  def self.prioritized
    where.not(priority: nil).reorder(:priority, :title)
  end

  def self.unprioritized
    where(priority: nil)
  end

  alias_attribute :name, :title

  def self.reference_prefix
    '~'
  end

  ##
  # Pattern used to extract label references from text
  #
  # This pattern supports cross-project references.
  #
  def self.reference_pattern
    @reference_pattern ||= %r{
      (#{Project.reference_pattern})?
      #{Regexp.escape(reference_prefix)}
      (?:
        (?<label_id>\d+) | # Integer-based label ID, or
        (?<label_name>
          [A-Za-z0-9_-]+ | # String-based single-word label title, or
          "[^&\?,]+"       # String-based multi-word label surrounded in quotes
        )
      )
    }x
  end

  def self.link_reference_pattern
    nil
  end

  ##
  # Returns the String necessary to reference this Label in Markdown
  #
  # format - Symbol format to use (default: :id, optional: :name)
  #
  # Examples:
  #
  #   Label.first.to_reference                # => "~1"
  #   Label.first.to_reference(format: :name) # => "~\"bug\""
  #   Label.first.to_reference(project)       # => "gitlab-org/gitlab-ce~1"
  #
  # Returns a String
  #
  def to_reference(from_project = nil, format: :id)
    format_reference = label_format_reference(format)
    reference = "#{self.class.reference_prefix}#{format_reference}"

    if cross_project_reference?(from_project)
      project.to_reference + reference
    else
      reference
    end
  end

  def open_issues_count(user = nil)
    issues.visible_to_user(user).opened.count
  end

  def closed_issues_count(user = nil)
    issues.visible_to_user(user).closed.count
  end

  def open_merge_requests_count
    merge_requests.opened.count
  end

  def template?
    template
  end

  def text_color
    LabelsHelper::text_color_for_bg(self.color)
  end

  def title=(value)
    write_attribute(:title, Sanitize.clean(value.to_s)) if value.present?
  end

  private

  def label_format_reference(format = :id)
    raise StandardError, 'Unknown format' unless [:id, :name].include?(format)

    if format == :name && !name.include?('"')
      %("#{name}")
    else
      id
    end
  end

  def nullify_priority
    self.priority = nil if priority.blank?
  end
end