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.
- data/Manifest.txt +5 -0
- data/Rakefile +1 -1
- data/bin/translate_workspace +10 -1
- data/lib/translate/copy.rb +6 -0
- data/lib/translate/copy/card_copy.rb +36 -0
- data/lib/translate/copy/defect_copy.rb +37 -55
- data/lib/translate/copy/feature_copy.rb +2 -0
- data/lib/translate/copy/object_copy.rb +84 -26
- data/lib/translate/copy/requirement_copy.rb +50 -0
- data/lib/translate/copy/rest_object.rb +10 -2
- data/lib/translate/copy/story_copy.rb +65 -28
- data/lib/translate/copy/supplemental_requirement_copy.rb +2 -0
- data/lib/translate/copy/test_case_copy.rb +16 -37
- data/lib/translate/copy/test_case_result_copy.rb +1 -1
- data/lib/translate/copy/use_case_copy.rb +2 -0
- data/lib/translate/translate.rb +31 -6
- data/lib/translate/translation_audit.rb +110 -51
- data/lib/translate/version.rb +1 -1
- metadata +8 -3
data/Manifest.txt
CHANGED
@@ -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.
|
46
|
+
p.extra_deps << ["rally_rest_api", ">= 0.6.4"]
|
47
47
|
p.extra_deps << ["activesupport"]
|
48
48
|
|
49
49
|
|
data/bin/translate_workspace
CHANGED
@@ -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],
|
data/lib/translate/copy.rb
CHANGED
@@ -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?
|
4
|
+
values.delete(:submitted_by) unless user_exists? object.submitted_by
|
6
5
|
values
|
7
6
|
end
|
8
7
|
|
9
|
-
def tangle
|
10
|
-
|
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
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
-
|
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
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
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
|
34
|
+
dups << fetch_object(:defect, dup.oid)
|
70
35
|
end
|
71
36
|
values[:duplicates] = dups
|
72
37
|
end
|
73
|
-
|
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
|
@@ -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(
|
8
|
+
def copy(additional_values = {})
|
7
9
|
unless TranslationStore[@object.oid]
|
8
|
-
|
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
|
13
|
-
|
14
|
-
@
|
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(
|
20
|
+
@new_object = fetch_object(type_as_symbol, @object.oid)
|
19
21
|
end
|
20
|
-
copy_children
|
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(
|
36
|
+
def create_object(values)
|
29
37
|
rally_rest.create(@object.type_as_symbol, values)
|
30
38
|
end
|
31
39
|
|
32
|
-
def
|
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
|
50
|
+
fetch_project @object.project.name
|
39
51
|
end
|
40
52
|
end
|
41
53
|
|
42
|
-
def fetch_project(project_name
|
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 =
|
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
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
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
|
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
|
90
|
-
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
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(
|
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
|
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
|
-
|
2
|
-
|
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
|
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
|
35
|
+
fetch_project @object.cards.first.project.name
|
32
36
|
end
|
33
37
|
end
|
34
38
|
|
35
|
-
|
36
|
-
|
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
|
@@ -1,6 +1,6 @@
|
|
1
|
-
|
1
|
+
require 'active_support/breakpoint'
|
2
2
|
class TestCaseCopy < ObjectCopy
|
3
|
-
def copy_children
|
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
|
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
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
42
|
-
|
43
|
-
#
|
44
|
-
|
45
|
-
|
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
|
-
|
27
|
+
@new_object.update(values) unless values.length == 0
|
51
28
|
end
|
52
29
|
end
|
53
30
|
end
|
31
|
+
|
32
|
+
|
data/lib/translate/translate.rb
CHANGED
@@ -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
|
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
|
-
|
33
|
-
@slm.find_all(type, :workspace => @from_workspace, :order => [:creation_date
|
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
|
-
|
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.
|
48
|
-
@logger.
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
if old.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
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
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(
|
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.
|
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
|
88
|
-
|
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
|
data/lib/translate/version.rb
CHANGED
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.
|
7
|
-
date: 2007-
|
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.
|
77
|
+
version: 0.6.4
|
73
78
|
version:
|
74
79
|
- !ruby/object:Gem::Dependency
|
75
80
|
name: activesupport
|