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