rally_workspace_utils 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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