milestone.rb 3.8 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2 3 4 5 6 7 8 9
# == Schema Information
#
# Table name: milestones
#
#  id          :integer          not null, primary key
#  title       :string(255)      not null
#  project_id  :integer          not null
#  description :text
#  due_date    :date
D
Dmitriy Zaporozhets 已提交
10 11
#  created_at  :datetime
#  updated_at  :datetime
D
Dmitriy Zaporozhets 已提交
12
#  state       :string(255)
D
Dmitriy Zaporozhets 已提交
13
#  iid         :integer
D
Dmitriy Zaporozhets 已提交
14 15
#

D
Dmitriy Zaporozhets 已提交
16
class Milestone < ActiveRecord::Base
17 18
  # Represents a "No Milestone" state used for filtering Issues and Merge
  # Requests that have no milestone assigned.
19 20 21
  MilestoneStruct = Struct.new(:title, :name, :id)
  None = MilestoneStruct.new('No Milestone', 'No Milestone', 0)
  Any = MilestoneStruct.new('Any Milestone', '', -1)
T
tiagonbotelho 已提交
22
  Upcoming = MilestoneStruct.new('Upcoming', '#upcoming', -2)
23

24
  include InternalId
25
  include Sortable
D
Douwe Maan 已提交
26
  include Referable
27
  include StripAttribute
R
Rubén Dávila 已提交
28
  include Milestoneish
29

D
Dmitriy Zaporozhets 已提交
30 31
  belongs_to :project
  has_many :issues
32
  has_many :labels, -> { distinct.reorder('labels.title') },  through: :issues
33
  has_many :merge_requests
R
Rubén Dávila 已提交
34
  has_many :participants, -> { distinct.reorder('users.name') }, through: :issues, source: :assignee
D
Dmitriy Zaporozhets 已提交
35

A
Andrew8xx8 已提交
36 37
  scope :active, -> { with_state(:active) }
  scope :closed, -> { with_state(:closed) }
38
  scope :of_projects, ->(ids) { where(project_id: ids) }
D
Dmitriy Zaporozhets 已提交
39

40
  validates :title, presence: true, uniqueness: { scope: :project_id }
A
Andrey Kumanyaev 已提交
41
  validates :project, presence: true
A
Andrew8xx8 已提交
42

43 44
  strip_attributes :title

A
Andrew8xx8 已提交
45
  state_machine :state, initial: :active do
A
Andrew8xx8 已提交
46
    event :close do
A
Andrew8xx8 已提交
47
      transition active: :closed
A
Andrew8xx8 已提交
48 49 50
    end

    event :activate do
A
Andrew8xx8 已提交
51
      transition closed: :active
A
Andrew8xx8 已提交
52 53 54 55 56 57
    end

    state :closed

    state :active
  end
D
Dmitriy Zaporozhets 已提交
58

59 60
  alias_attribute :name, :title

V
Valery Sizov 已提交
61
  class << self
62 63 64 65 66 67 68
    # 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.
V
Valery Sizov 已提交
69
    def search(query)
70 71 72 73
      t = arel_table
      pattern = "%#{query}%"

      where(t[:title].matches(pattern).or(t[:description].matches(pattern)))
V
Valery Sizov 已提交
74 75 76
    end
  end

D
Douwe Maan 已提交
77 78 79 80 81
  def self.reference_pattern
    nil
  end

  def self.link_reference_pattern
82
    @link_reference_pattern ||= super("milestones", /(?<milestone>\d+)/)
D
Douwe Maan 已提交
83 84
  end

T
tiagonbotelho 已提交
85
  def self.upcoming
D
Douwe Maan 已提交
86
    self.where('due_date > ?', Time.now).reorder(due_date: :asc).first
T
tiagonbotelho 已提交
87 88
  end

D
Douwe Maan 已提交
89
  def to_reference(from_project = nil)
90 91
    escaped_title = self.title.gsub("]", "\\]")

92
    h = Gitlab::Routing.url_helpers
93 94 95
    url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)

    "[#{escaped_title}](#{url})"
D
Douwe Maan 已提交
96 97 98
  end

  def reference_link_text(from_project = nil)
99
    self.title
D
Douwe Maan 已提交
100 101
  end

D
Dmitriy Zaporozhets 已提交
102 103
  def expired?
    if due_date
104
      due_date.past?
D
Dmitriy Zaporozhets 已提交
105 106 107
    else
      false
    end
D
Dmitriy Zaporozhets 已提交
108
  end
109

D
Dmitriy Zaporozhets 已提交
110
  def expires_at
111 112
    if due_date
      if due_date.past?
113
        "expired on #{due_date.to_s(:medium)}"
114
      else
115
        "expires on #{due_date.to_s(:medium)}"
116
      end
A
Andrey Kumanyaev 已提交
117
    end
D
Dmitriy Zaporozhets 已提交
118
  end
D
Dmitriy Zaporozhets 已提交
119 120

  def can_be_closed?
A
Andrew8xx8 已提交
121
    active? && issues.opened.count.zero?
D
Dmitriy Zaporozhets 已提交
122 123
  end

124 125
  def is_empty?(user = nil)
    total_items_count(user).zero?
D
Dmitriy Zaporozhets 已提交
126 127
  end

128
  def author_id
129
    nil
130
  end
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162

  # 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
D
Dmitriy Zaporozhets 已提交
163
end