@@ -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