BigW Consortium Gitlab

milestone.rb 3.61 KB
Newer Older
1
class Milestone < ActiveRecord::Base
2 3
  # Represents a "No Milestone" state used for filtering Issues and Merge
  # Requests that have no milestone assigned.
4 5 6
  MilestoneStruct = Struct.new(:title, :name, :id)
  None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
  Any = MilestoneStruct.new('Any Milestone', '', -1)
7
  Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
8

9
  include InternalId
10
  include Sortable
11
  include Referable
12
  include StripAttribute
13
  include Milestoneish
14

15 16
  belongs_to :project
  has_many :issues
17
  has_many :labels, -> { distinct.reorder('labels.title') },  through: :issues
18
  has_many :merge_requests
19
  has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
20

21 22
  scope :active, -> { with_state(:active) }
  scope :closed, -> { with_state(:closed) }
23
  scope :of_projects, ->(ids) { where(project_id: ids) }
24

25
  validates :title, presence: true, uniqueness: { scope: :project_id }
Andrey Kumanyaev committed
26
  validates :project, presence: true
27

28 29
  strip_attributes :title

Andrew8xx8 committed
30
  state_machine :state, initial: :active do
31
    event :close do
Andrew8xx8 committed
32
      transition active: :closed
33 34 35
    end

    event :activate do
Andrew8xx8 committed
36
      transition closed: :active
37 38 39 40 41 42
    end

    state :closed

    state :active
  end
43

44 45
  alias_attribute :name, :title

46
  class << self
47 48 49 50 51 52 53
    # Searches for milestones matching the given query.
    #
    # This method uses ILIKE on PostgreSQL and LIKE on MySQL.
    #
    # query - The search query as a String
    #
    # Returns an ActiveRecord::Relation.
54
    def search(query)
55 56 57 58
      t = arel_table
      pattern = "%#{query}%"

      where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
59 60 61
    end
  end

62 63 64 65 66
  def self.reference_pattern
    nil
  end

  def self.link_reference_pattern
67
    @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
68 69
  end

tiagonbotelho committed
70
  def self.upcoming
Douwe Maan committed
71
    self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
72 73
  end

74
  def to_reference(from_project = nil)
75 76
    escaped_title = self.title.gsub("]", "\\]")

77
    h = Gitlab::Routing.url_helpers
78 79 80
    url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)

    "[#{escaped_title}](#{url})"
81 82 83
  end

  def reference_link_text(from_project = nil)
84
    self.title
85 86
  end

87 88
  def expired?
    if due_date
89
      due_date.past?
90 91 92
    else
      false
    end
93
  end
94

95
  def expires_at
96 97
    if due_date
      if due_date.past?
98
        "expired on #{due_date.to_s(:medium)}"
99
      else
100
        "expires on #{due_date.to_s(:medium)}"
101
      end
102
    end
103
  end
104 105

  def can_be_closed?
106
    active? && issues.opened.count.zero?
107 108
  end

109 110
  def is_empty?(user = nil)
    total_items_count(user).zero?
111 112
  end

113
  def author_id
114
    nil
115
  end
116

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

121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  # Sorts the issues for the given IDs.
  #
  # This method runs a single SQL query using a CASE statement to update the
  # position of all issues in the current milestone (scoped to the list of IDs).
  #
  # Given the ids [10, 20, 30] this method produces a SQL query something like
  # the following:
  #
  #     UPDATE issues
  #     SET position = CASE
  #       WHEN id = 10 THEN 1
  #       WHEN id = 20 THEN 2
  #       WHEN id = 30 THEN 3
  #       ELSE position
  #     END
  #     WHERE id IN (10, 20, 30);
  #
  # This method expects that the IDs given in `ids` are already Fixnums.
  def sort_issues(ids)
    pairs = []

    ids.each_with_index do |id, index|
      pairs << id
      pairs << index + 1
    end

    conditions = 'WHEN id = ? THEN ? ' * ids.length

    issues.where(id: ids).
      update_all(["position = CASE #{conditions} ELSE position END", *pairs])
  end
152
end