##// END OF EJS Templates
Adds closed_on column that stores the time of the last closing (#824)....
Jean-Philippe Lang -
r11172:a45c0dc55057
parent child
Show More
@@ -0,0 +1,9
1 class AddIssuesClosedOn < ActiveRecord::Migration
2 def up
3 add_column :issues, :closed_on, :datetime, :default => nil
4 end
5
6 def down
7 remove_column :issues, :closed_on
8 end
9 end
@@ -0,0 +1,25
1 class PopulateIssuesClosedOn < ActiveRecord::Migration
2 def up
3 closed_status_ids = IssueStatus.where(:is_closed => true).pluck(:id)
4 if closed_status_ids.any?
5 # First set closed_on for issues that have been closed once
6 closed_status_values = closed_status_ids.map {|status_id| "'#{status_id}'"}.join(',')
7 subselect = "SELECT MAX(#{Journal.table_name}.created_on)" +
8 " FROM #{Journal.table_name}, #{JournalDetail.table_name}" +
9 " WHERE #{Journal.table_name}.id = #{JournalDetail.table_name}.journal_id" +
10 " AND #{Journal.table_name}.journalized_type = 'Issue' AND #{Journal.table_name}.journalized_id = #{Issue.table_name}.id" +
11 " AND #{JournalDetail.table_name}.property = 'attr' AND #{JournalDetail.table_name}.prop_key = 'status_id'" +
12 " AND #{JournalDetail.table_name}.old_value NOT IN (#{closed_status_values})" +
13 " AND #{JournalDetail.table_name}.value IN (#{closed_status_values})"
14 Issue.update_all "closed_on = (#{subselect})"
15
16 # Then set closed_on for closed issues that weren't up updated by the above UPDATE
17 # No journal was found so we assume that they were closed on creation
18 Issue.update_all "closed_on = created_on", {:status_id => closed_status_ids, :closed_on => nil}
19 end
20 end
21
22 def down
23 Issue.update_all :closed_on => nil
24 end
25 end
@@ -91,7 +91,7 class Issue < ActiveRecord::Base
91 }
91 }
92
92
93 before_create :default_assign
93 before_create :default_assign
94 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change
94 before_save :close_duplicates, :update_done_ratio_from_issue_status, :force_updated_on_change, :update_closed_on
95 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
95 after_save {|issue| issue.send :after_project_change if !issue.id_changed? && issue.project_id_changed?}
96 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
96 after_save :reschedule_following_issues, :update_nested_set_attributes, :update_parent_attributes, :create_journal
97 # Should be after_create but would be called before previous after_save callbacks
97 # Should be after_create but would be called before previous after_save callbacks
@@ -1307,10 +1307,23 class Issue < ActiveRecord::Base
1307 end
1307 end
1308 end
1308 end
1309
1309
1310 # Make sure updated_on is updated when adding a note
1310 # Make sure updated_on is updated when adding a note and set updated_on now
1311 # so we can set closed_on with the same value on closing
1311 def force_updated_on_change
1312 def force_updated_on_change
1312 if @current_journal
1313 if @current_journal || changed?
1313 self.updated_on = current_time_from_proper_timezone
1314 self.updated_on = current_time_from_proper_timezone
1315 if new_record?
1316 self.created_on = updated_on
1317 end
1318 end
1319 end
1320
1321 # Callback for setting closed_on when the issue is closed.
1322 # The closed_on attribute stores the time of the last closing
1323 # and is preserved when the issue is reopened.
1324 def update_closed_on
1325 if closing? || (new_record? && closed?)
1326 self.closed_on = updated_on
1314 end
1327 end
1315 end
1328 end
1316
1329
@@ -1320,7 +1333,7 class Issue < ActiveRecord::Base
1320 if @current_journal
1333 if @current_journal
1321 # attributes changes
1334 # attributes changes
1322 if @attributes_before_change
1335 if @attributes_before_change
1323 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on)).each {|c|
1336 (Issue.column_names - %w(id root_id lft rgt lock_version created_on updated_on closed_on)).each {|c|
1324 before = @attributes_before_change[c]
1337 before = @attributes_before_change[c]
1325 after = send(c)
1338 after = send(c)
1326 next if before == after || (before.blank? && after.blank?)
1339 next if before == after || (before.blank? && after.blank?)
@@ -152,6 +152,7 issues_008:
152 root_id: 8
152 root_id: 8
153 lft: 1
153 lft: 1
154 rgt: 2
154 rgt: 2
155 closed_on: <%= 3.days.ago.to_s(:db) %>
155 issues_009:
156 issues_009:
156 created_on: <%= 1.minute.ago.to_s(:db) %>
157 created_on: <%= 1.minute.ago.to_s(:db) %>
157 project_id: 5
158 project_id: 5
@@ -209,6 +210,7 issues_011:
209 root_id: 11
210 root_id: 11
210 lft: 1
211 lft: 1
211 rgt: 2
212 rgt: 2
213 closed_on: <%= 1.day.ago.to_s(:db) %>
212 issues_012:
214 issues_012:
213 created_on: <%= 3.days.ago.to_s(:db) %>
215 created_on: <%= 3.days.ago.to_s(:db) %>
214 project_id: 1
216 project_id: 1
@@ -228,6 +230,7 issues_012:
228 root_id: 12
230 root_id: 12
229 lft: 1
231 lft: 1
230 rgt: 2
232 rgt: 2
233 closed_on: <%= 1.day.ago.to_s(:db) %>
231 issues_013:
234 issues_013:
232 created_on: <%= 5.days.ago.to_s(:db) %>
235 created_on: <%= 5.days.ago.to_s(:db) %>
233 project_id: 3
236 project_id: 3
@@ -1917,4 +1917,58 class IssueTest < ActiveSupport::TestCase
1917 assert_equal 3, issue.reload.attachments.count
1917 assert_equal 3, issue.reload.attachments.count
1918 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
1918 assert_equal %w(upload foo bar), issue.attachments.map(&:filename)
1919 end
1919 end
1920
1921 def test_closed_on_should_be_nil_when_creating_an_open_issue
1922 issue = Issue.generate!(:status_id => 1).reload
1923 assert !issue.closed?
1924 assert_nil issue.closed_on
1925 end
1926
1927 def test_closed_on_should_be_set_when_creating_a_closed_issue
1928 issue = Issue.generate!(:status_id => 5).reload
1929 assert issue.closed?
1930 assert_not_nil issue.closed_on
1931 assert_equal issue.updated_on, issue.closed_on
1932 assert_equal issue.created_on, issue.closed_on
1933 end
1934
1935 def test_closed_on_should_be_nil_when_updating_an_open_issue
1936 issue = Issue.find(1)
1937 issue.subject = 'Not closed yet'
1938 issue.save!
1939 issue.reload
1940 assert_nil issue.closed_on
1941 end
1942
1943 def test_closed_on_should_be_set_when_closing_an_open_issue
1944 issue = Issue.find(1)
1945 issue.subject = 'Now closed'
1946 issue.status_id = 5
1947 issue.save!
1948 issue.reload
1949 assert_not_nil issue.closed_on
1950 assert_equal issue.updated_on, issue.closed_on
1951 end
1952
1953 def test_closed_on_should_not_be_updated_when_updating_a_closed_issue
1954 issue = Issue.open(false).first
1955 was_closed_on = issue.closed_on
1956 assert_not_nil was_closed_on
1957 issue.subject = 'Updating a closed issue'
1958 issue.save!
1959 issue.reload
1960 assert_equal was_closed_on, issue.closed_on
1961 end
1962
1963 def test_closed_on_should_be_preserved_when_reopening_a_closed_issue
1964 issue = Issue.open(false).first
1965 was_closed_on = issue.closed_on
1966 assert_not_nil was_closed_on
1967 issue.subject = 'Reopening a closed issue'
1968 issue.status_id = 1
1969 issue.save!
1970 issue.reload
1971 assert !issue.closed?
1972 assert_equal was_closed_on, issue.closed_on
1973 end
1920 end
1974 end
General Comments 0
You need to be logged in to leave comments. Login now