rally_workspace_utils 0.0.4 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -12,7 +12,12 @@ lib/translate/copy/rest_object.rb
12
12
  lib/translate/copy/story_copy.rb
13
13
  lib/translate/copy/test_case_result_copy.rb
14
14
  lib/translate/copy/defect_copy.rb
15
+ lib/translate/copy/feature_copy.rb
16
+ lib/translate/copy/use_case_copy.rb
17
+ lib/translate/copy/supplemental_requirement_copy.rb
18
+ lib/translate/copy/card_copy.rb
15
19
  lib/translate/copy/object_copy.rb
20
+ lib/translate/copy/requirement_copy.rb
16
21
  lib/translate/copy/test_case_copy.rb
17
22
  lib/translate/translation_store.rb
18
23
  test/translation_store_spec.rb
data/Rakefile CHANGED
@@ -43,7 +43,7 @@ hoe = Hoe.new(GEM_NAME, VERS) do |p|
43
43
  p.clean_globs = CLEAN #An array of file patterns to delete on clean.
44
44
 
45
45
  p.extra_deps = []
46
- p.extra_deps << ["rally_rest_api", ">= 0.6.3"]
46
+ p.extra_deps << ["rally_rest_api", ">= 0.6.4"]
47
47
  p.extra_deps << ["activesupport"]
48
48
 
49
49
 
@@ -6,8 +6,17 @@ require 'getoptlong'
6
6
  require 'logger'
7
7
  require 'rubygems'
8
8
  require 'rally_rest_api'
9
-
10
9
  require 'rally_workspace_utils'
10
+ require 'active_support'
11
+
12
+ # Undo the logger stuff thatr active_support puts in.
13
+ class Logger
14
+ private
15
+ def format_message(severity, datetime, progname, msg)
16
+ (@formatter || @default_formatter).call(severity, datetime, progname, msg)
17
+ end
18
+ end
19
+
11
20
 
12
21
  opts = GetoptLong.new(
13
22
  ["--base_url", "-b", GetoptLong::REQUIRED_ARGUMENT],
@@ -3,7 +3,13 @@ require 'rally_rest_api'
3
3
 
4
4
  require 'translate/copy/rest_object'
5
5
  require 'translate/copy/object_copy'
6
+ require 'translate/copy/scheduled_story_as_work_product'
6
7
  require 'translate/copy/defect_copy'
8
+ require 'translate/copy/card_copy'
9
+ require 'translate/copy/requirement_copy'
10
+ require 'translate/copy/feature_copy'
11
+ require 'translate/copy/use_case_copy'
12
+ require 'translate/copy/supplemental_requirement_copy'
7
13
  require 'translate/copy/story_copy'
8
14
  require 'translate/copy/test_case_copy'
9
15
  require 'translate/copy/test_case_result_copy'
@@ -0,0 +1,36 @@
1
+ class CardCopy < ObjectCopy
2
+ def shallow_copy(object)
3
+ # We are here because we have an artifact with multiple cards. We need to create
4
+ # a user story for each artifact:card pair, and we are going to do it from the perspective
5
+ # of the card. The "parent" card has already been created
6
+ #
7
+ # so, let's shallow copy the workproduct values first
8
+ values = super(@object.work_product)
9
+
10
+ # then get the interesting card attrributes
11
+ values.merge! card_values(@object)
12
+
13
+ # append to the name attribute
14
+ values[:name] = "Scheduled child of #{values[:name]}"
15
+
16
+ # Capture the schedule note
17
+ values[:notes] = "#{values[:notes]} <br> Scheduled Note: #{@object.schedule_note}"
18
+
19
+ values
20
+ end
21
+
22
+ def create_object(values)
23
+ rally_rest.create(:hierarchical_requirement, values)
24
+ end
25
+
26
+ def copy_children
27
+ @object.tasks.each do |task|
28
+ task.copy(new_workspace, :work_product => @new_object)
29
+ end if @object.tasks
30
+ end
31
+
32
+ def type_as_symbol
33
+ :hierarchical_requirement
34
+ end
35
+
36
+ end
@@ -1,75 +1,57 @@
1
1
  class DefectCopy < ObjectCopy
2
-
3
- def shallow_copy(object, new_workspace)
2
+ def shallow_copy(object)
4
3
  values = super
5
- values.delete(:submitted_by) unless user_exists? @object.submitted_by
4
+ values.delete(:submitted_by) unless user_exists? object.submitted_by
6
5
  values
7
6
  end
8
7
 
9
- def tangle(new_workspace)
10
- values = {}
11
- new_defect = fetch_object(:defect, @object.oid, new_workspace)
12
-
13
- # If the original defect is in a different project from the stories' card,
14
- # then create a new user story, as the parent of this new user story, and make
15
- # the requirement on the defect this new story
16
- if @object.requirement != nil && @object.requirement.cards != nil && # The defect has a requirement, that is scheduled
17
- @object.project != nil && # The defect in not in the parent project
18
- @object.requirement.cards.first.project != @object.project # The card in in a different project from the defect
8
+ def tangle
9
+ super
19
10
 
20
- story_name = @object.requirement.name
21
- new_requirement = fetch_object(:artifact, @object.requirement.oid, new_workspace)
22
- if new_requirement.parent && new_requirement.parent.project.nil?
23
- parent_story = new_requirement.parent
24
- else
25
- parent_story = new_workspace.rally_rest.create(:hierarchical_requirement,
26
- :workspace => new_workspace,
27
- :name => "Parent story for UserStory '#{story_name}' to hold defects in project #{@object.project.name}")
28
- @object.rally_rest.logger.info "During the tangle of defect '#{@object.name}' we needed to create a new UserStory and make it the parent of this defect's requirement. This is because the original Story was scheduled into a project that was diffetent than the project this defect is in."
29
- end
30
- new_requirement.update(:parent => parent_story)
31
- values[:requirement] = parent_story
32
- else
33
-
34
- # if the old defect has a test case
35
- unless @object.test_case.nil?
36
- # get the new test_case
37
- new_test_case = fetch_object(:test_case, @object.test_case.oid, new_workspace)
38
- # update
39
- values[:test_case] = new_test_case
11
+ if @new_object.nil?
12
+ logger.debug "Could not find new object for object #{@object.oid} -- #{@object.name}"
13
+ return
14
+ end
40
15
 
41
- # if the new defect is in the Parent Project and
42
- # the its related test_case is not in the same project as the defect,
43
- # then put the defect in the same project as the test_case
44
- if new_defect.project.nil? && new_defect.project != new_test_case.project
45
- values[:project] = new_test_case.project
46
- end
47
- end
16
+ values = {}
48
17
 
49
- # if the old defect has a requirement
50
- if !@object.requirement.nil? && @object.requirement.type == "Story"
51
- # get the requiement, copied from this defect's requirement
52
- new_story = fetch_object(:artifact, @object.requirement.oid, new_workspace)
53
- # update the new defect
54
- values[:requirement] = new_story
18
+ if !@object.requirement.nil?
19
+ new_requirement = fetch_object(:artifact, @object.requirement.oid)
20
+ values[:requirement] = new_requirement
21
+ end
55
22
 
56
- # if the new defect is in the Parent Project and
57
- # the new requirement is not in the same project as the defect,
58
- # then put the defect in the same project as the requirement
59
- if new_defect.project.nil? && new_defect.project != new_story.project
60
- values[:project] = new_story.project
61
- end
62
- end
23
+ if has_test_case? && has_requirement? && test_cases_work_product != work_product
24
+ # no-op. Don't tangle the test_case. This exposes a bug in the system where we can be in an invalid state
25
+ elsif has_test_case?
26
+ new_test_case = fetch_object(:test_case, @object.test_case.oid)
27
+ values[:test_case] = new_test_case
63
28
  end
64
29
 
65
30
  # tangle the duplicate defects
66
31
  unless @object.duplicates.nil?
67
32
  dups = []
68
33
  @object.duplicates.each do |dup|
69
- dups << fetch_object(:defect, dup.oid, new_workspace)
34
+ dups << fetch_object(:defect, dup.oid)
70
35
  end
71
36
  values[:duplicates] = dups
72
37
  end
73
- new_defect.update(values) unless values.length == 0
38
+ @new_object.update(values) unless values.length == 0
39
+ end
40
+
41
+ def work_product
42
+ @object.requirement
43
+ end
44
+
45
+ def has_test_case?
46
+ not @object.test_case.nil?
47
+ end
48
+
49
+ def has_requirement?
50
+ not work_product.nil?
51
+ end
52
+
53
+ def test_cases_work_product
54
+ return nil unless has_test_case?
55
+ @object.test_case.work_product
74
56
  end
75
57
  end
@@ -0,0 +1,2 @@
1
+ class FeatureCopy < RequirementCopy
2
+ end
@@ -1,63 +1,81 @@
1
1
  class ObjectCopy
2
- def initialize(object)
2
+ def initialize(object, workspace, card = nil)
3
3
  @object = object
4
+ @card = card
5
+ @new_workspace = workspace
4
6
  end
5
7
 
6
- def copy(new_workspace, additional_values = {})
8
+ def copy(additional_values = {})
7
9
  unless TranslationStore[@object.oid]
8
- values = shallow_copy(@object, new_workspace)
10
+ logger.info "Copying #{@object.type} -- #{@object.name} -- #{@object.oid}"
11
+ values = shallow_copy @object
9
12
  values.merge! additional_values
10
13
 
11
14
  values[:workspace] = new_workspace
12
- values[:project] = find_project_in_new_workspace(new_workspace)
13
-
14
- @object.rally_rest.logger.info "Copying #{@object.type} -- #{@object.name}"
15
- @new_object = create_object(new_workspace.rally_rest, values)
15
+ values[:project] = find_project_in_new_workspace unless values[:project]
16
+
17
+ @new_object = create_object values
16
18
  remember @object, @new_object
17
19
  else
18
- @new_object = fetch_object(@object.type_as_symbol, @object.oid, new_workspace)
20
+ @new_object = fetch_object(type_as_symbol, @object.oid)
19
21
  end
20
- copy_children(new_workspace)
22
+ copy_children
21
23
  @new_object
22
24
  end
23
25
 
26
+ def tangle
27
+ logger.info "Tangle #{@object.type} -- #{@object.name}"
28
+ @new_object = fetch_object(:artifact, @object.oid)
29
+ end
30
+
24
31
  def remember old, new
32
+ logger.debug "rembering #{old.type} : #{old.oid} => #{new.type} => #{new.oid}"
25
33
  TranslationStore[old.oid] = new.oid
26
34
  end
27
35
 
28
- def create_object(rally_rest, values)
36
+ def create_object(values)
29
37
  rally_rest.create(@object.type_as_symbol, values)
30
38
  end
31
39
 
32
- def find_project_in_new_workspace(new_workspace)
40
+ def type_as_symbol
41
+ @object.type_as_symbol
42
+ end
43
+
44
+ def find_project_in_new_workspace
33
45
  # I'm assuming (after talking to nate) that if the project is nil, there there is more then one project in the
34
46
  # workspace. At least for single project workspaces that were migrated for project scoping, this is true.
35
47
  if @object.project.nil?
36
48
  return nil
37
49
  else
38
- fetch_project(@object.project.name, new_workspace)
50
+ fetch_project @object.project.name
39
51
  end
40
52
  end
41
53
 
42
- def fetch_project(project_name, workspace)
54
+ def fetch_project(project_name)
43
55
  cached_projects = TranslationStore.instance.projects
44
56
  return cached_projects[project_name] if cached_projects.key? project_name
45
- project = workspace.rally_rest.find(:project, :workspace => workspace) { equal :name, project_name }.first
57
+ project = rally_rest.find(:project, :workspace => new_workspace) { equal :name, project_name }.first
46
58
  cached_projects[project_name] = project
47
59
  end
48
60
 
49
- def shallow_copy(object, new_workspace)
61
+ def shallow_copy(object)
50
62
  keys_to_exclude = excluded_attributes(object)
51
63
  values = object.elements.reject { |key, value| keys_to_exclude.include? key }
52
- values.delete(:owner) unless user_exists? @object.owner
64
+ if !user_exists? object.owner
65
+ logger.warn "Removing unknown user '#{object.owner}' from a '#{@object.type}' with name '#{@object.name}'. The user no longer exists."
66
+ values.delete(:owner)
67
+ end
53
68
 
54
69
  # Here we need to fix any deleted custom dropdown values that have been deleted
55
70
  values.each do |attribute, value|
56
- if @object.typedef.custom_dropdown_attributes.key? attribute
57
- attrdef = @object.typedef.custom_dropdown_attributes[attribute]
71
+ logger.debug "Check values #{attribute} = #{value}"
72
+ if object.typedef.custom_dropdown_attributes.key? attribute
73
+ attrdef = object.typedef.custom_dropdown_attributes[attribute]
74
+ logger.debug "Check custom dropdown values #{attribute} = #{value}"
75
+ logger.debug "Check custom dropdown allowed_values = #{attrdef.allowed_values.inspect}"
58
76
  unless attrdef.allowed_values.include? value
59
77
  values[attribute] = nil
60
- @object.rally_rest.logger.info "Deleteing #{attribute} = #{value} from @{object.name} because it no longer exists"
78
+ logger.warn "Deleting Custom Dropdown value '#{attribute}' = '#{value}' from a '#{@object.type}' with name '#{object.name}' because it no longer exists."
61
79
  end
62
80
  end
63
81
  end
@@ -65,11 +83,36 @@ class ObjectCopy
65
83
  values
66
84
  end
67
85
 
86
+ def card_values(card)
87
+ values = {}
88
+ project = nil
89
+ values[:rank] = card.rank
90
+ values[:schedule_state] = card.state
91
+
92
+ unless card.iteration.nil?
93
+ iteration = fetch_object(:iteration, card.iteration.oid)
94
+ if iteration
95
+ values[:iteration] = iteration
96
+ project = iteration.project
97
+ end
98
+ end
99
+
100
+ unless card.release.nil?
101
+ release = fetch_object(:release, card.release.oid)
102
+ if release
103
+ values[:release] = release
104
+ project = release.project
105
+ end
106
+ end
107
+ values[:project] = project if project
108
+ values
109
+ end
110
+
68
111
  def excluded_attributes(object)
69
112
  object.typedef.reference_attributes(true).keys
70
113
  end
71
114
 
72
- def copy_children(new_workspace)
115
+ def copy_children
73
116
  end
74
117
 
75
118
  def user_exists?(username)
@@ -86,13 +129,28 @@ class ObjectCopy
86
129
  end
87
130
  end
88
131
 
89
- def fetch_object(type, oid, workspace)
90
- cached_objects = TranslationStore.instance.objects
132
+ def fetch_object(type, oid)
133
+ # post-copy, there should always be a new_oid. The new_oid is the oid of the object in the
134
+ # new workspace
91
135
  new_oid = TranslationStore[oid]
92
- return cached_objects[new_oid] if cached_objects.key? oid
93
-
94
- object = @object.rally_rest.find(type, :workspace => workspace) { equal :object_i_d, new_oid }.first
95
- cached_objects[new_oid] = object
136
+
137
+ return TranslationStore.instance.objects[new_oid] if TranslationStore.instance.objects.key? new_oid
138
+ logger.debug "Fetch object: Cache hit failed for new_oid = #{new_oid}"
139
+
140
+ object = @object.rally_rest.find(type, :workspace => new_workspace) { equal :object_i_d, new_oid }.first
141
+ TranslationStore.instance.objects[new_oid] = object
142
+ end
143
+
144
+ def logger
145
+ rally_rest.logger
146
+ end
147
+
148
+ def rally_rest
149
+ @object.rally_rest
150
+ end
151
+
152
+ def new_workspace
153
+ @new_workspace
96
154
  end
97
155
 
98
156
  end
@@ -0,0 +1,50 @@
1
+ class RequirementCopy < ObjectCopy
2
+
3
+ def create_object(values)
4
+ new_object = rally_rest.create(:hierarchical_requirement, values)
5
+
6
+ if @object.cards
7
+ @object.cards.each do |card|
8
+ card.copy(new_workspace, :parent => new_object)
9
+ end
10
+ end
11
+
12
+ if @object.dependents
13
+ @object.dependents.each do |dependent|
14
+ if dependent_in_correct_project? dependent
15
+ dependent.copy(new_workspace, :parent => new_object)
16
+ else
17
+ logger.warn "For '#{@object.type}' with name '#{@object.name}' in project '#{project_name @object.project}' not creating child association to dependent of type '#{dependent.type}' with name '#{dependent.name}' in project '#{project_name dependent.project}' because dependent it is in the wrong project."
18
+ end
19
+ end
20
+ end
21
+
22
+ new_object
23
+ end
24
+
25
+ def project_name(project)
26
+ return "Parent Project" if project.nil?
27
+ project.name
28
+ end
29
+
30
+ def dependent_in_correct_project?(dependent)
31
+ (in_same_project?(dependent) || in_child_project?(dependent)) && !in_different_child_projects?(dependent)
32
+ end
33
+
34
+ def in_same_project?(dependent)
35
+ @object.project == dependent.project
36
+ end
37
+
38
+ def in_child_project?(dependent)
39
+ @object.in_parent_project? && !dependent.in_parent_project?
40
+ end
41
+
42
+ def in_different_child_projects?(dependent)
43
+ !@object.in_parent_project? && !dependent.in_parent_project? && !in_same_project?(dependent)
44
+ end
45
+
46
+ def type_as_symbol
47
+ :hierarchical_requirement
48
+ end
49
+
50
+ end
@@ -3,11 +3,11 @@ require 'rally_rest_api'
3
3
 
4
4
  class RestObject
5
5
  def copy(new_workspace, additional_values = {})
6
- copy_class.new(self).copy(new_workspace, additional_values)
6
+ copy_class.new(self.dup, new_workspace).copy(additional_values)
7
7
  end
8
8
 
9
9
  def tangle(new_workspace)
10
- copy_class.new(self).tangle(new_workspace)
10
+ copy_class.new(self, new_workspace).tangle
11
11
  end
12
12
 
13
13
  def excepted_attributes
@@ -21,9 +21,17 @@ class RestObject
21
21
  when :test_case : TestCaseCopy
22
22
  when :defect : DefectCopy
23
23
  when :test_case_result : TestCaseResultCopy
24
+ when :card : CardCopy
25
+ when :feature : FeatureCopy
26
+ when :use_case : UseCaseCopy
27
+ when :supplemental_requirement : SupplementalRequirementCopy
24
28
  else
25
29
  ObjectCopy
26
30
  end
27
31
  end
32
+
33
+ def in_parent_project?
34
+ self.project.nil?
35
+ end
28
36
  end
29
37
 
@@ -1,44 +1,81 @@
1
- class StoryCopy < ObjectCopy
2
- def shallow_copy(object, new_workspace)
3
- values = super
4
-
5
- unless object.cards.nil?
6
- values[:rank] = object.cards.first.rank
7
- values[:schedule_state] = object.cards.first.state
8
-
9
- unless object.cards.first.iteration.nil?
10
- iteration_name = object.cards.first.iteration.name
11
- iteration = fetch_object(:iteration, object.cards.first.iteration.oid, new_workspace)
12
- values[:iteration] = iteration if iteration
13
- end
14
-
15
- unless object.cards.first.release.nil?
16
- release_name = object.cards.first.release.name
17
- release = fetch_object(:release, object.cards.first.release.oid, new_workspace)
18
- values[:release] = release if release
19
- end
1
+ require 'set'
2
+ require 'active_support/breakpoint'
20
3
 
4
+ class StoryCopy < RequirementCopy
5
+ def shallow_copy(object)
6
+ values = super
7
+ if @object.cards && !create_user_story_from_card?
8
+ logger.debug "Requirement copy only have one card"
9
+ card_values = card_values(object.cards.first)
10
+ logger.debug card_values.inspect
11
+ values.merge! card_values
12
+ logger.debug values.inspect
21
13
  end
22
14
  values
23
15
  end
24
16
 
25
- def find_project_in_new_workspace(new_workspace)
17
+ def create_object(values)
18
+ new_object = rally_rest.create(:hierarchical_requirement, values)
19
+
20
+ if @object.cards && create_user_story_from_card?
21
+ logger.warn "For '#{@object.type}' with name '#{@object.name}' we have created two User Stories in the new workspace because this Story has Test Cases and/or Defects attached to that are in different projects than the project the card was schedule in."
22
+ @object.cards.first.copy(new_workspace, :parent => new_object)
23
+ end
24
+ new_object
25
+ end
26
+
27
+ def find_project_in_new_workspace
26
28
  if @object.cards.nil?
27
29
  return super
30
+ elsif create_user_story_from_card?
31
+ return super
28
32
  elsif @object.cards.first.project.nil?
29
33
  return super
30
34
  else
31
- fetch_project(@object.cards.first.project.name, new_workspace)
35
+ fetch_project @object.cards.first.project.name
32
36
  end
33
37
  end
34
38
 
35
- def create_object(rally_rest, values)
36
- rally_rest.create(:hierarchical_requirement, values)
39
+
40
+ def copy_children
41
+ @object.cards.first.tasks.each do |task|
42
+ task.copy(new_workspace, :work_product => @new_object)
43
+ end if !@object.cards.nil? && !@object.cards.first.tasks.nil?
44
+ end
45
+
46
+ def create_user_story_from_card?
47
+ unless defined? @create_user_story_from_card
48
+ @create_user_story_from_card = any_test_cases_or_defects_in_different_project_than_card?
49
+ end
50
+ @create_user_story_from_card
51
+ end
52
+
53
+ def any_test_cases_or_defects_in_different_project_than_card?
54
+ return false unless @object.cards
55
+ projects = collect_test_case_and_defect_projects
56
+ return false if projects.length == 0
57
+ card_project = @object.cards.first.project
58
+
59
+ not projects.include? card_project
60
+ end
61
+
62
+ def collect_test_case_and_defect_projects
63
+ projects = Set.new
64
+ projects.merge collect_projects_from_test_case
65
+ projects.merge collect_projects_from_defects
66
+ projects
67
+ end
68
+
69
+ def collect_projects_from_test_case
70
+ object = @object
71
+ results = rally_rest.find(:test_case, :workspace => @object.workspace) { equal :work_product, object }
72
+ results.map { |tc| tc.project }
73
+ end
74
+
75
+ def collect_projects_from_defects
76
+ object = @object
77
+ results = rally_rest.find(:defect, :workspace => @object.workspace) { equal :requirement, object }
78
+ results.map { |tc| tc.project }
37
79
  end
38
80
 
39
- def copy_children(new_workspace)
40
- @object.cards.first.tasks.each do |task|
41
- task.copy(new_workspace, :work_product => @new_object)
42
- end if !@object.cards.nil? && !@object.cards.first.tasks.nil?
43
- end
44
81
  end
@@ -0,0 +1,2 @@
1
+ class SupplementalRequirementCopy < RequirementCopy
2
+ end
@@ -1,6 +1,6 @@
1
-
1
+ require 'active_support/breakpoint'
2
2
  class TestCaseCopy < ObjectCopy
3
- def copy_children(new_workspace)
3
+ def copy_children
4
4
  @object.results.each do |result|
5
5
  result.copy(new_workspace, :test_case => @new_object)
6
6
  end unless @object.results.nil?
@@ -10,44 +10,23 @@ class TestCaseCopy < ObjectCopy
10
10
  end unless @object.steps.nil?
11
11
  end
12
12
 
13
- def tangle(new_workspace)
14
-
15
- values = {}
16
- if !@object.work_product.nil? && @object.work_product.type == "Story"
17
- # if the old test case has a work product
18
- new_test_case = fetch_object(:test_case, @object.oid, new_workspace)
19
- # get the new test case copied from this testcase
20
- new_work_product = fetch_object(:artifact, @object.work_product.oid, new_workspace)
21
-
22
- values[:work_product] = new_work_product
23
-
24
-
25
-
26
- # If the original test_case is in a different project from the stories' card,
27
- # then create a new user story, as the parent of this new user story, and make
28
- # the work_product on the test case the new parent
29
- if @object.work_product != nil && @object.work_product.cards != nil && # The TC has a work_product, that is scheduled
30
- @object.project != nil && # The TC in not in the parent project
31
- @object.work_product.cards.first.project != @object.project # The card in in a different project from the test_case
13
+ def tangle
14
+ super
32
15
 
33
- story_name = @object.work_product.name
34
- parent_story = new_workspace.rally_rest.create(:hierarchical_requirement,
35
- :workspace => new_workspace,
36
- :project => nil,
37
- :name => "Parent story for #{story_name} to hold test cases in project #{@object.project.name}")
38
- new_work_product.update(:parent => parent_story)
39
- values[:work_product] = parent_story
16
+ if @new_object.nil?
17
+ logger.debug "Could not find new object for object #{@object.oid} -- #{@object.name}"
18
+ return
19
+ end
40
20
 
41
- # if the new test case is in the Parent Project and
42
- # the new work_product is not in the same project as the test case,
43
- # then put the test case in the same project as the work_product
44
- elsif new_test_case.project.nil? && new_test_case.project != new_work_product.project
45
- values[:project] = new_work_product.project
46
- end
47
-
48
-
21
+ values = {}
22
+ if !@object.work_product.nil?
23
+ # get the new work product copied from this testcase
24
+ new_work_product = fetch_object(:artifact, @object.work_product.oid)
25
+ values[:work_product] = new_work_product
49
26
  # update the new test case
50
- new_test_case.update(values) unless values.length == 0
27
+ @new_object.update(values) unless values.length == 0
51
28
  end
52
29
  end
53
30
  end
31
+
32
+
@@ -1,5 +1,5 @@
1
1
  class TestCaseResultCopy < ObjectCopy
2
- def shallow_copy(object, new_workspace)
2
+ def shallow_copy(object)
3
3
  values = super
4
4
  values.delete(:tester) unless user_exists? @object.tester
5
5
  values
@@ -0,0 +1,2 @@
1
+ class UseCaseCopy < RequirementCopy
2
+ end
@@ -14,10 +14,12 @@ class Translate
14
14
  @from_workspace_name = args[:from_workspace]
15
15
  @to_workspace_name = args[:to_workspace]
16
16
  @logger = args[:logger] if args[:logger]
17
+ # @logger.level = Logger::DEBUG
17
18
  end
18
19
 
19
20
  def run
20
- @slm = RallyRestAPI.new(:base_url => @base_url, :username => @username, :password => @password, :logger => @logger, :parse_collections_as_hash => false)
21
+ @slm = RallyRestAPI.new(:base_url => @base_url, :username => @username, :password => @password, :logger => @logger)
22
+ @slm.parse_collections_as_hash = false
21
23
 
22
24
  @from_workspace = @slm.user.subscription.workspaces.find { |w| w.name == @from_workspace_name }
23
25
  @to_workspace = @slm.user.subscription.workspaces.find { |w| w.name == @to_workspace_name }
@@ -29,25 +31,48 @@ class Translate
29
31
 
30
32
  begin
31
33
  # now do the copy
32
- [:iteration, :release, :story, :defect, :test_case].each do |type|
33
- @slm.find_all(type, :workspace => @from_workspace, :order => [:creation_date, :desc]).each do |o|
34
+ allowed_copy_types.each do |type|
35
+ @slm.find_all(type, :workspace => @from_workspace, :order => [:creation_date]).each do |o|
34
36
  o.copy(@to_workspace)
35
37
  end
36
38
  end unless store.copy_finished?
37
39
  store.copy_finished!
40
+ @logger.info "Copy complete. "
38
41
 
39
42
  # Now tangle the objects
40
- [:test_case, :defect].each do |type|
43
+ allowed_tangle_types.each do |type|
41
44
  @slm.find_all(type, :workspace => @from_workspace).each do |o|
42
45
  o.tangle(@to_workspace)
43
46
  end
44
47
  end unless store.tangle_finished?
45
48
  store.tangle_finished!
49
+ @logger.info "Tangle complete!"
50
+ @logger.info "Translation complete. Now run with the -a flag to audit the workspace."
46
51
  rescue Exception => e
47
- @logger.info "Caught an exception #{e}. Remembering where we were."
48
- @logger.info e.backtrace.join("\n")
52
+ @logger.warn "Caught an exception #{e}. Remembering where we were."
53
+ @logger.warn e.backtrace.join("\n")
49
54
  # Remember where we were if there was a failure
50
55
  store.dump
56
+ raise e
51
57
  end
52
58
  end
59
+
60
+ def allowed_copy_types
61
+ types = [:iteration, :release, :feature, :use_case, :supplemental_requirement, :story, :defect, :test_case]
62
+ types.delete :test_case unless allowed? "Test Case"
63
+ types.delete :defect unless allowed? "Defect"
64
+ types
65
+ end
66
+
67
+ def allowed_tangle_types
68
+ types = [:test_case, :defect]
69
+ types.delete :test_case unless allowed? "Test Case"
70
+ types.delete :defect unless allowed? "Defect"
71
+ types
72
+ end
73
+
74
+ def allowed?(type)
75
+ @from_workspace.type_definitions.find { |td| td.name == type }
76
+ end
77
+
53
78
  end
@@ -2,77 +2,137 @@ require 'active_support'
2
2
 
3
3
  module TranslationAudit
4
4
  def audit
5
+ logger.info "Auditing Iterations"
6
+ audit_counts(:iteration, "Iterations")
5
7
  all_copied_fields_should_match_for :iteration
8
+
9
+ logger.info "Auditing Releases"
10
+ audit_counts(:release, "Releases")
6
11
  all_copied_fields_should_match_for :release
7
- all_copied_fields_should_match_for :defect
8
- all_copied_fields_should_match_for :defect, [:last_run]
9
- all_copied_fields_should_match_for :test_case_result, [:test_case]
10
- compare_each :test_case_result do |old, new|
11
- if old.test_case.name != new.test_case.name
12
- self.logger.info diff_string(old, new)
13
- end
14
- end
15
12
 
16
- all_copied_fields_should_match_for :test_case_step, [:test_case]
17
- compare_each :test_case_step do |old, new|
18
- if old.test_case.name != new.test_case.name
19
- self.logger.info diff_string(old, new)
20
- end
21
- end
22
- all_copied_fields_should_match_for :task, [:work_product]
23
- compare_each :task do |old, new|
24
- next if old.work_product.type != "Story"
25
- if old.card.work_product.name != new.work_product.name
26
- self.logger.info diff_string(old, new)
13
+ logger.info "Auditing Test Cases"
14
+ audit_counts(:test_case, "Test Cases")
15
+ all_copied_fields_should_match_for(:test_case, [:last_run]) do |old, new|
16
+ next false unless copy_exists? old, new
17
+ if !old.work_product.nil?
18
+ begin
19
+ if !equal_ignore_whitespace_and_scheduling old.work_product.name, new.work_product.name
20
+ logger.warn "For TestCase '#{old.name}' the TestCase's work product names differ"
21
+ logger.warn " old work product name: #{old.work_product.name}"
22
+ logger.warn " new work product name: #{new.work_product.name}"
23
+ end
24
+ rescue Exception => e
25
+ raise Exception.new("For test_case #{old.name} -- #{old.oid}, the new test_case had no work product")
26
+ end
27
27
  end
28
28
  end
29
29
 
30
- compare_each :defect do |old, new|
31
- if !old.requirement.nil? && old.requirement.type == "Story"
32
- if new.requirement.children
33
- if old.requirement.name != new.requirement.children.first.name
34
- end
35
- else
36
- if old.requirement.name != new.requirement.name
30
+ logger.info "Auditing Defects"
31
+ audit_counts(:defect, "Defects")
32
+ all_copied_fields_should_match_for(:defect, [:closed_date, :submitted_by]) do |old, new|
33
+ next false unless copy_exists? old, new
34
+ if !old.requirement.nil?
35
+ begin
36
+ if !equal_ignore_whitespace_and_scheduling old.requirement.name, new.requirement.name
37
+ logger.warn "For Defect '#{old.name}' the Defect's requirement names differ"
38
+ logger.warn " old requirement name: #{old.requirement.name}"
39
+ logger.warn " new requirement name: #{new.requirement.name}"
37
40
  end
41
+ rescue Exception => e
42
+ raise Exception.new("For defect #{old.name}, the new defect had no requirement")
38
43
  end
39
44
  end
40
45
  end
41
46
 
42
- compare_each :test_case do |old, new|
43
- if !old.work_product.nil? && old.work_product.type == "Story"
44
- if new.work_product.children
45
- if old.work_product.name != new.work_product.children.first.name
46
- end
47
- else
48
- if old.work_product.name != new.work_product.name
49
- end
50
- end
47
+ logger.info "Auditing Test Case Results"
48
+ audit_counts(:test_case_result, "Test Case Results")
49
+ all_copied_fields_should_match_for(:test_case_result, [:test_case, :tester]) do |old, new|
50
+ next false unless copy_exists? old, new
51
+ if !equal_ignore_whitespace_and_scheduling old.test_case.name, new.test_case.name
52
+ logger.warn "For Test Case Result '#{old.name}' the test case names differ"
53
+ logger.warn " old test case name: #{old.test_case.name}"
54
+ logger.warn " new test case name: #{new.test_case.name}"
55
+ end
56
+ end
57
+
58
+ logger.info "Auditing Test Case Steps"
59
+ audit_counts(:test_case_step, "Test Case Steps")
60
+ all_copied_fields_should_match_for(:test_case_step, [:test_case]) do |old, new|
61
+ next false unless copy_exists? old, new
62
+ if !equal_ignore_whitespace_and_scheduling old.test_case.name, new.test_case.name
63
+ logger.warn "For Test Case Step '#{old.name}' the test case names differ"
64
+ logger.warn " old test case name: #{old.test_case.name}"
65
+ logger.warn " new test case name: #{new.test_case.name}"
66
+ end
67
+ end
51
68
 
69
+ logger.info "Auditing Tasks"
70
+ audit_counts(:task, "Tasks")
71
+ all_copied_fields_should_match_for(:task, [:work_product]) do |old, new|
72
+ next false unless copy_exists?(old, new) do |old_object, new_object|
73
+ logger.warn " task belongs to a '#{old_object.card.work_product.type}' with name '#{old_object.card.work_product.name}'"
52
74
  end
75
+ if !equal_ignore_whitespace_and_scheduling old.card.work_product.name, new.work_product.name
76
+ logger.warn "For Task '#{old.name}' the work product names differ"
77
+ logger.warn " old work product name: #{old.card.work_product.name}"
78
+ logger.warn " new work product name: #{new.work_product.name}"
79
+ end
80
+ end
81
+
82
+ logger.info "Auditing Features"
83
+ compare_each :feature, :hierarchical_requirement do |old, new|
84
+ return false unless copy_exists? old, new
53
85
  end
86
+
87
+ logger.info "Audit complete!"
88
+ end
89
+
90
+ def audit_counts(type, type_string)
91
+ source_count = @slm.find_all(type, :workspace => @from_workspace).total_result_count
92
+ dest_count = @slm.find_all(type, :workspace => @to_workspace).total_result_count
93
+ logger.warn("Auditing #{type_string} counts:")
94
+ logger.warn(" #{source_count} #{type_string} found in workspace '#{@from_workspace.name}'")
95
+ logger.warn(" #{dest_count} #{type_string} found in workspace '#{@to_workspace.name}'")
54
96
  end
55
97
 
56
- def compare_each(type)
98
+ def compare_each(type, new_type = type)
57
99
  @slm.find_all(type, :workspace => @from_workspace).each do |old_object|
58
- new_object = @slm.find(type, :workspace => @to_workspace) { equal :object_i_d, TranslationStore[old_object.oid] }.first
59
- if new_object.nil?
60
- self.logger.info "Cound not find a copy for object #{old_object.type} -- #{old_object.name} -- #{old_object.oid}"
61
- next
62
- end
100
+ new_object = @slm.find(new_type, :workspace => @to_workspace) { equal :object_i_d, TranslationStore[old_object.oid] }.first
63
101
  yield old_object, new_object
64
102
  end
65
103
  end
66
104
 
105
+ def copy_exists?(old_object, new_object)
106
+ if new_object.nil?
107
+ self.logger.warn "Cound not find a copy for object #{old_object.type} -- #{old_object.name} -- #{old_object.oid}"
108
+ yield old_object, new_object if block_given?
109
+ return false
110
+ end
111
+ true
112
+ end
113
+
67
114
  def all_copied_fields_should_match_for(type, additional_exceptions = [])
115
+ additional_exceptions << :creation_date
68
116
  compare_each(type) do |old_object, new_object|
117
+ next unless yield old_object, new_object if block_given?
69
118
  diff = diff_objects(old_object, new_object, additional_exceptions)
70
119
  if diff.length > 0
71
- self.logger.info diff_string(old_object, new_object, additional_exceptions)
120
+ self.logger.warn diff_string(old_object, new_object, additional_exceptions)
72
121
  end
73
122
  end
74
123
  end
75
124
 
125
+ def diff_string(old, new, additional_exceptions = [])
126
+ diff = diff_objects(old, new, additional_exceptions)
127
+
128
+ diff_string = "#{old.type} with name '#{old.name}' -- #{old.oid}:#{new.oid} differs\n"
129
+ diff_string << "value\told\t\t\t\tnew\n"
130
+ diff.each do |key, valye|
131
+ diff_string << key.to_s << "\t" << old.elements[key].to_s << "\t\t\t" << new.elements[key].to_s << "\n"
132
+ end
133
+ diff_string
134
+ end
135
+
76
136
  def diff_objects(old_object, new_object, additional_exceptions = [])
77
137
  excepted_attributes = old_object.excepted_attributes + additional_exceptions
78
138
  old_hash = old_object.elements.reject { |k, v| excepted_attributes.include?(k) }
@@ -84,14 +144,13 @@ module TranslationAudit
84
144
  old_hash.diff(new_hash)
85
145
  end
86
146
 
87
- def diff_string(old, new, additional_exceptions = [])
88
- diff = diff_objects(old, new, additional_exceptions)
89
- diff_string = "#{old.type} with name '#{old.name}' differs\n"
90
- diff_string << "\told\t\t\tnew\n"
91
- diff.each do |key, valye|
92
- diff_string << "\t" << old.elements[key] << "\t\t\t" << new.elements[key] << "\n"
93
- end
94
- diff_string << "\n"
95
- diff_string
147
+ def equal_ignore_whitespace_and_scheduling(s1, s2)
148
+ normalized_string(s1) == normalized_string(s2)
96
149
  end
150
+
151
+ def normalized_string(s)
152
+ scheduled_string = "Scheduled child of "
153
+ s.strip.squeeze(" ").gsub(scheduled_string, "")
154
+ end
155
+
97
156
  end
@@ -2,7 +2,7 @@ module Translate #:nodoc:
2
2
  module VERSION #:nodoc:
3
3
  MAJOR = 0
4
4
  MINOR = 0
5
- TINY = 4
5
+ TINY = 6
6
6
 
7
7
  STRING = [MAJOR, MINOR, TINY].join('.')
8
8
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
3
3
  specification_version: 1
4
4
  name: rally_workspace_utils
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.0.4
7
- date: 2007-01-03 00:00:00 -07:00
6
+ version: 0.0.6
7
+ date: 2007-02-06 00:00:00 -07:00
8
8
  summary: A utility to translate a UseCase workspace to a UserStory workspace
9
9
  require_paths:
10
10
  - lib
@@ -43,7 +43,12 @@ files:
43
43
  - lib/translate/copy/story_copy.rb
44
44
  - lib/translate/copy/test_case_result_copy.rb
45
45
  - lib/translate/copy/defect_copy.rb
46
+ - lib/translate/copy/feature_copy.rb
47
+ - lib/translate/copy/use_case_copy.rb
48
+ - lib/translate/copy/supplemental_requirement_copy.rb
49
+ - lib/translate/copy/card_copy.rb
46
50
  - lib/translate/copy/object_copy.rb
51
+ - lib/translate/copy/requirement_copy.rb
47
52
  - lib/translate/copy/test_case_copy.rb
48
53
  - lib/translate/translation_store.rb
49
54
  - test/translation_store_spec.rb
@@ -69,7 +74,7 @@ dependencies:
69
74
  requirements:
70
75
  - - ">="
71
76
  - !ruby/object:Gem::Version
72
- version: 0.6.3
77
+ version: 0.6.4
73
78
  version:
74
79
  - !ruby/object:Gem::Dependency
75
80
  name: activesupport