rally_workspace_utils 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Manifest.txt +17 -0
- data/README.txt +3 -0
- data/Rakefile +55 -0
- data/bin/copy_workspace +2 -0
- data/bin/translate_workspace +90 -0
- data/lib/rally_workspace_utils.rb +2 -0
- data/lib/translate/copy/defect_copy.rb +75 -0
- data/lib/translate/copy/object_copy.rb +97 -0
- data/lib/translate/copy/rest_object.rb +29 -0
- data/lib/translate/copy/story_copy.rb +44 -0
- data/lib/translate/copy/test_case_copy.rb +53 -0
- data/lib/translate/copy/test_case_result_copy.rb +7 -0
- data/lib/translate/copy.rb +9 -0
- data/lib/translate/translate.rb +69 -0
- data/lib/translate/translation_audit.rb +76 -0
- data/lib/translate/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/rally_workspace_utils_test.rb +11 -0
- metadata +80 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Manifest.txt
|
2
|
+
README.txt
|
3
|
+
Rakefile
|
4
|
+
setup.rb
|
5
|
+
bin/translate_workspace
|
6
|
+
bin/copy_workspace
|
7
|
+
lib/translate/copy.rb
|
8
|
+
lib/translate/version.rb
|
9
|
+
lib/translate/translate.rb
|
10
|
+
lib/translate/translation_audit.rb
|
11
|
+
lib/translate/copy/rest_object.rb
|
12
|
+
lib/translate/copy/story_copy.rb
|
13
|
+
lib/translate/copy/test_case_result_copy.rb
|
14
|
+
lib/translate/copy/defect_copy.rb
|
15
|
+
lib/translate/copy/object_copy.rb
|
16
|
+
lib/translate/copy/test_case_copy.rb
|
17
|
+
lib/rally_workspace_utils.rb
|
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'rake/clean'
|
4
|
+
require 'rake/testtask'
|
5
|
+
require 'rake/packagetask'
|
6
|
+
require 'rake/gempackagetask'
|
7
|
+
require 'rake/rdoctask'
|
8
|
+
require 'rake/contrib/rubyforgepublisher'
|
9
|
+
require 'fileutils'
|
10
|
+
require 'hoe'
|
11
|
+
include FileUtils
|
12
|
+
require File.join(File.dirname(__FILE__), 'lib', 'translate', 'version')
|
13
|
+
|
14
|
+
AUTHOR = "bcotton" # can also be an array of Authors
|
15
|
+
EMAIL = "bcotton@rallydev.com"
|
16
|
+
DESCRIPTION = "A utility to translate a UseCase workspace to a UserStory workspace"
|
17
|
+
GEM_NAME = "rally_workspace_utils" # what ppl will type to install your gem
|
18
|
+
RUBYFORGE_PROJECT = "rally-rest-api" # The unix name for your project
|
19
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
20
|
+
RELEASE_TYPES = %w( gem ) # can use: gem, tar, zip
|
21
|
+
|
22
|
+
|
23
|
+
NAME = "rally_workspace_utils"
|
24
|
+
REV = nil # UNCOMMENT IF REQUIRED: File.read(".svn/entries")[/committed-rev="(d+)"/, 1] rescue nil
|
25
|
+
VERS = ENV['VERSION'] || (Translate::VERSION::STRING + (REV ? ".#{REV}" : ""))
|
26
|
+
CLEAN.include ['**/.*.sw?', '*.gem', '.config']
|
27
|
+
RDOC_OPTS = ['--quiet', '--title', "translate documentation",
|
28
|
+
"--opname", "index.html",
|
29
|
+
"--line-numbers",
|
30
|
+
"--main", "README",
|
31
|
+
"--inline-source"]
|
32
|
+
|
33
|
+
# Generate all the Rake tasks
|
34
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
35
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
36
|
+
p.author = AUTHOR
|
37
|
+
p.description = DESCRIPTION
|
38
|
+
p.email = EMAIL
|
39
|
+
p.summary = DESCRIPTION
|
40
|
+
p.url = HOMEPATH
|
41
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
42
|
+
p.test_globs = ["test/**/*_test.rb"]
|
43
|
+
p.clean_globs = CLEAN #An array of file patterns to delete on clean.
|
44
|
+
|
45
|
+
p.extra_deps = []
|
46
|
+
p.extra_deps << ["rally_rest_api", ">= 0.6.2"]
|
47
|
+
p.extra_deps << ["activesupport"]
|
48
|
+
|
49
|
+
|
50
|
+
# == Optional
|
51
|
+
#p.changes - A description of the release's latest changes.
|
52
|
+
#p.extra_deps - An array of rubygem dependencies.
|
53
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
54
|
+
end
|
55
|
+
|
data/bin/copy_workspace
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
$:.unshift File.dirname(__FILE__) + '/../lib'
|
4
|
+
|
5
|
+
require 'getoptlong'
|
6
|
+
require 'logger'
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rally_rest_api'
|
9
|
+
|
10
|
+
require 'rally_workspace_utils'
|
11
|
+
|
12
|
+
opts = GetoptLong.new(
|
13
|
+
["--base_url", "-b", GetoptLong::REQUIRED_ARGUMENT],
|
14
|
+
["--debug", "-d", GetoptLong::NO_ARGUMENT],
|
15
|
+
["--from_workspace", "-f", GetoptLong::REQUIRED_ARGUMENT],
|
16
|
+
["--to_workspace", "-t", GetoptLong::REQUIRED_ARGUMENT],
|
17
|
+
["--username", "-u", GetoptLong::REQUIRED_ARGUMENT],
|
18
|
+
["--password", "-p", GetoptLong::REQUIRED_ARGUMENT],
|
19
|
+
["--audit", "-a", GetoptLong::NO_ARGUMENT],
|
20
|
+
["--logfile", "-l", GetoptLong::REQUIRED_ARGUMENT],
|
21
|
+
["--help", "-h", GetoptLong::NO_ARGUMENT]
|
22
|
+
)
|
23
|
+
|
24
|
+
def usage
|
25
|
+
puts "usage: #{$0}"
|
26
|
+
puts " -b|--base_url <url> The base URL of the system to translate. Defaults to 'https://rally1.rallydev.com/slm'"
|
27
|
+
puts " -d|--debug Turn debugging on"
|
28
|
+
puts " -f|--from_workspace <workspace name> The name of the workspace we are translating. Must be a UseCase style workspace"
|
29
|
+
puts " -t|--to_workspace <workspace name> The name of the workspace we are tranlating to. Must be a UserStory style workspace"
|
30
|
+
puts " -a|--audit Compare the old workspace against the new one"
|
31
|
+
puts " -l|--logfile <filename> A file the logger should log to. Defaults to STDOUT"
|
32
|
+
puts " -u|--username <username> The rally login of a subscription administrator."
|
33
|
+
puts " -p|--password <password> The password of the above user."
|
34
|
+
puts " -h|--help This help"
|
35
|
+
exit
|
36
|
+
end
|
37
|
+
|
38
|
+
args = {:base_url => "https://rally1.rallydev.com/slm"}
|
39
|
+
|
40
|
+
debug = false
|
41
|
+
logfile = STDOUT
|
42
|
+
|
43
|
+
opts.each do |opt, arg|
|
44
|
+
case opt
|
45
|
+
when "--base_url"
|
46
|
+
args[:base_url] = arg
|
47
|
+
when "--from_workspace"
|
48
|
+
puts "arg = #{arg}"
|
49
|
+
args[:from_workspace] = arg
|
50
|
+
when "--to_workspace"
|
51
|
+
args[:to_workspace] = arg
|
52
|
+
when "--username"
|
53
|
+
args[:username] = arg
|
54
|
+
when "--password"
|
55
|
+
args[:password] = arg
|
56
|
+
when "--debug"
|
57
|
+
debug = true
|
58
|
+
RestBuilder::DEBUG = true
|
59
|
+
when "--logfile"
|
60
|
+
logfile = arg
|
61
|
+
when "--help"
|
62
|
+
usage
|
63
|
+
else
|
64
|
+
puts "Unknown argument #{opt}"
|
65
|
+
usage
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
[:base_url, :to_workspace, :from_workspace, :username, :password].each do |arg|
|
72
|
+
unless args[arg]
|
73
|
+
puts "#{arg} is a required argument"
|
74
|
+
usage
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
logger = Logger.new(logfile)
|
79
|
+
if debug
|
80
|
+
logger.level = Logger::DEBUG
|
81
|
+
else
|
82
|
+
logger.level = Logger::INFO
|
83
|
+
end
|
84
|
+
args[:logger] = logger
|
85
|
+
|
86
|
+
translate = Translate.new(args)
|
87
|
+
puts translate.inspect
|
88
|
+
translate.run
|
89
|
+
|
90
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
class DefectCopy < ObjectCopy
|
2
|
+
|
3
|
+
def shallow_copy(object, new_workspace)
|
4
|
+
values = super
|
5
|
+
values.delete(:submitted_by) unless user_exists? @object.submitted_by
|
6
|
+
values
|
7
|
+
end
|
8
|
+
|
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
|
19
|
+
|
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
|
40
|
+
|
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
|
48
|
+
|
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
|
55
|
+
|
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
|
63
|
+
end
|
64
|
+
|
65
|
+
# tangle the duplicate defects
|
66
|
+
unless @object.duplicates.nil?
|
67
|
+
dups = []
|
68
|
+
@object.duplicates.values.each do |dup|
|
69
|
+
dups << fetch_object(:defect, dup.oid, new_workspace)
|
70
|
+
end
|
71
|
+
values[:duplicates] = dups
|
72
|
+
end
|
73
|
+
new_defect.update(values) unless values.length == 0
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
require 'pp'
|
2
|
+
|
3
|
+
class ObjectCopy
|
4
|
+
def initialize(object)
|
5
|
+
@object = object
|
6
|
+
end
|
7
|
+
|
8
|
+
def copy(new_workspace, additional_values = {})
|
9
|
+
values = shallow_copy(@object, new_workspace)
|
10
|
+
values.merge! additional_values
|
11
|
+
|
12
|
+
values[:workspace] = new_workspace
|
13
|
+
values[:project] = find_project_in_new_workspace(new_workspace)
|
14
|
+
|
15
|
+
@new_object = create_object(new_workspace.rally_rest, values)
|
16
|
+
remember @object, @new_object
|
17
|
+
copy_children(new_workspace)
|
18
|
+
|
19
|
+
@new_object
|
20
|
+
end
|
21
|
+
|
22
|
+
def remember old, new
|
23
|
+
$TRANSLATED ||= {}
|
24
|
+
$TRANSLATED[old.oid] = new.oid
|
25
|
+
end
|
26
|
+
|
27
|
+
def create_object(rally_rest, values)
|
28
|
+
rally_rest.create(@object.type_as_symbol, values)
|
29
|
+
end
|
30
|
+
|
31
|
+
def find_project_in_new_workspace(new_workspace)
|
32
|
+
# I'm assuming (after talking to nate) that if the project is nil, there there is more then one project in the
|
33
|
+
# workspace. At least for single project workspaces that were migrated for project scoping, this is true.
|
34
|
+
if @object.project.nil?
|
35
|
+
return nil
|
36
|
+
else
|
37
|
+
fetch_project(@object.project.name, new_workspace)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def fetch_project(project_name, workspace)
|
42
|
+
@@cached_projects ||= {}
|
43
|
+
return @@cached_projects[project_name] if @@cached_projects.key? project_name
|
44
|
+
project = workspace.rally_rest.find(:project, :workspace => workspace) { equal :name, project_name }.first
|
45
|
+
@@cached_projects[project_name] = project
|
46
|
+
end
|
47
|
+
|
48
|
+
def shallow_copy(object, new_workspace)
|
49
|
+
keys_to_exclude = excluded_attributes(object)
|
50
|
+
values = object.elements.reject { |key, value| keys_to_exclude.include? key }
|
51
|
+
values.delete(:owner) unless user_exists? @object.owner
|
52
|
+
|
53
|
+
# Here we need to fix any deleted custom dropdown values that have been deleted
|
54
|
+
values.each do |attribute, value|
|
55
|
+
if @object.typedef.custom_dropdown_attributes.key? attribute
|
56
|
+
attrdef = @object.typedef.custom_dropdown_attributes[attribute]
|
57
|
+
unless attrdef.allowed_values.include? value
|
58
|
+
values[attribute] = nil
|
59
|
+
puts "Deleteing #{attribute} = #{value} from @{object.name} because it no longer exists"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
values
|
65
|
+
end
|
66
|
+
|
67
|
+
def excluded_attributes(object)
|
68
|
+
object.typedef.reference_attributes(true).keys
|
69
|
+
end
|
70
|
+
|
71
|
+
def copy_children(new_workspace)
|
72
|
+
end
|
73
|
+
|
74
|
+
def user_exists?(username)
|
75
|
+
@@cached_users ||= {}
|
76
|
+
|
77
|
+
return true if username.nil?
|
78
|
+
return @@cached_users[username] if @@cached_users.key? username
|
79
|
+
|
80
|
+
result = @object.rally_rest.find(:user) { equal :login_name, username }
|
81
|
+
if result.total_result_count > 0
|
82
|
+
@@cached_users[username] = true
|
83
|
+
else
|
84
|
+
@@cached_users[username] = false
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def fetch_object(type, oid, workspace)
|
89
|
+
@@cached_objects ||= {}
|
90
|
+
new_oid = $TRANSLATED[oid]
|
91
|
+
return @@cached_objects[new_oid] if @@cached_objects.key? oid
|
92
|
+
|
93
|
+
object = @object.rally_rest.find(type, :workspace => workspace) { equal :object_i_d, new_oid }.first
|
94
|
+
@@cached_objects[new_oid] = object
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rally_rest_api'
|
3
|
+
|
4
|
+
class RestObject
|
5
|
+
def copy(new_workspace, additional_values = {})
|
6
|
+
copy_class.new(self).copy(new_workspace, additional_values)
|
7
|
+
end
|
8
|
+
|
9
|
+
def tangle(new_workspace)
|
10
|
+
copy_class.new(self).tangle(new_workspace)
|
11
|
+
end
|
12
|
+
|
13
|
+
def excepted_attributes
|
14
|
+
[:object_id, :creation_date, :formatted_id, :owner] + self.typedef.reference_attributes(true).keys + self.typedef.custom_dropdown_attributes.keys
|
15
|
+
end
|
16
|
+
|
17
|
+
def copy_class
|
18
|
+
case self.type_as_symbol
|
19
|
+
when :card : CardCopy
|
20
|
+
when :story : StoryCopy
|
21
|
+
when :test_case : TestCaseCopy
|
22
|
+
when :defect : DefectCopy
|
23
|
+
when :test_case_result : TestCaseResultCopy
|
24
|
+
else
|
25
|
+
ObjectCopy
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
@@ -0,0 +1,44 @@
|
|
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
|
20
|
+
|
21
|
+
end
|
22
|
+
values
|
23
|
+
end
|
24
|
+
|
25
|
+
def find_project_in_new_workspace(new_workspace)
|
26
|
+
if @object.cards.nil?
|
27
|
+
return super
|
28
|
+
elsif @object.cards.first.project.nil?
|
29
|
+
return super
|
30
|
+
else
|
31
|
+
fetch_project(@object.cards.first.project.name, new_workspace)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_object(rally_rest, values)
|
36
|
+
rally_rest.create(:hierarchical_requirement, values)
|
37
|
+
end
|
38
|
+
|
39
|
+
def copy_children(new_workspace)
|
40
|
+
@object.cards.first.tasks.values.flatten.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
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
class TestCaseCopy < ObjectCopy
|
3
|
+
def copy_children(new_workspace)
|
4
|
+
@object.results.each do |result|
|
5
|
+
result.copy(new_workspace, :test_case => @new_object)
|
6
|
+
end unless @object.results.nil?
|
7
|
+
|
8
|
+
@object.steps.each do |step|
|
9
|
+
step.copy(new_workspace, :test_case => @new_object)
|
10
|
+
end unless @object.steps.nil?
|
11
|
+
end
|
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
|
32
|
+
|
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
|
40
|
+
|
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
|
+
|
49
|
+
# update the new test case
|
50
|
+
new_test_case.update(values) unless values.length == 0
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rally_rest_api'
|
3
|
+
|
4
|
+
require 'translate/copy/rest_object'
|
5
|
+
require 'translate/copy/object_copy'
|
6
|
+
require 'translate/copy/defect_copy'
|
7
|
+
require 'translate/copy/story_copy'
|
8
|
+
require 'translate/copy/test_case_copy'
|
9
|
+
require 'translate/copy/test_case_result_copy'
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/copy'
|
2
|
+
require File.dirname(__FILE__) + '/translation_audit'
|
3
|
+
|
4
|
+
class Translate
|
5
|
+
include TranslationAudit
|
6
|
+
|
7
|
+
def initialize(args)
|
8
|
+
@base_url = args[:base_url]
|
9
|
+
@username = args[:username]
|
10
|
+
@password = args[:password]
|
11
|
+
@from_workspace_name = args[:from_workspace]
|
12
|
+
@to_workspace_name = args[:to_workspace]
|
13
|
+
@logger = args[:logger] if args[:logger]
|
14
|
+
$TRANSLATED ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def run
|
18
|
+
@slm = RallyRestAPI.new(:base_url => @base_url, :username => @username, :password => @password, :logger => @logger)
|
19
|
+
|
20
|
+
@from_workspace = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @from_workspace_name }
|
21
|
+
@to_workspace = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @to_workspace_name }
|
22
|
+
|
23
|
+
raise "No such workspace #{@from_workspace_name}" unless @from_workspace
|
24
|
+
raise "No such workspace #{@to_workspace_name}" unless @to_workspace
|
25
|
+
|
26
|
+
|
27
|
+
if File::exists? "TRANSLATED"
|
28
|
+
File.open("TRANSLATED", "r") do |f|
|
29
|
+
$TRANSLATED = Marshal.load(f)
|
30
|
+
end
|
31
|
+
|
32
|
+
if $TRANSLATED[:from] != @from_workspace.oid && $TRANSLATED[:to] != @to_workspace.oid
|
33
|
+
$TRANSLATED = {}
|
34
|
+
$TRANSLATED[:from] = @from_workspace.oid
|
35
|
+
$TRANSLATED[:to] = @to_workspace.oid
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# now do the copy
|
40
|
+
[:iteration, :release, :story, :defect, :test_case].each do |type|
|
41
|
+
@slm.find_all(type, :workspace => @from_workspace).each do |o|
|
42
|
+
@logger.info "Copying #{o.type} -- #{o.name}"
|
43
|
+
o.copy(@to_workspace)
|
44
|
+
end
|
45
|
+
end unless $TRANSLATED[:copy]
|
46
|
+
# mark these workspaces as copied
|
47
|
+
$TRANSLATED[:copy] = true
|
48
|
+
# dump the oids to disk
|
49
|
+
dump_oids
|
50
|
+
|
51
|
+
# Now tangle the objects
|
52
|
+
[:test_case, :defect].each do |type|
|
53
|
+
@slm.find_all(type, :workspace => @from_workspace).each do |o|
|
54
|
+
@logger.info "Tangle #{o.type} -- #{o.name}"
|
55
|
+
o.tangle(@to_workspace)
|
56
|
+
end
|
57
|
+
end unless $TRANSLATED[:tangle]
|
58
|
+
|
59
|
+
$TRANSLATED[:tangle] = true
|
60
|
+
dump_oids
|
61
|
+
end
|
62
|
+
|
63
|
+
def dump_oids
|
64
|
+
File.open("TRANSLATED", "w+") do |f|
|
65
|
+
Marshal.dump($TRANSLATED, f)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
|
2
|
+
module TranslationAudit
|
3
|
+
def audit
|
4
|
+
all_copied_fields_should_match_for :iteration
|
5
|
+
all_copied_fields_should_match_for :release
|
6
|
+
all_copied_fields_should_match_for :defect
|
7
|
+
all_copied_fields_should_match_for :defect, [:last_run]
|
8
|
+
all_copied_fields_should_match_for :test_case_result, [:test_case]
|
9
|
+
compare_each :test_case_result do |old, new|
|
10
|
+
if old.test_case.name != new.test_case.name
|
11
|
+
puts "#{old.type} -- #{old.name} is different"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
all_copied_fields_should_match_for :test_case_step, [:test_case]
|
16
|
+
compare_each :test_case_step do |old, new|
|
17
|
+
if old.test_case.name != new.test_case.name
|
18
|
+
puts "#{old.type} -- #{old.name} is different"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
all_copied_fields_should_match_for :task, [:work_product]
|
22
|
+
compare_each :task do |old, new|
|
23
|
+
if old.card.work_product.name != new.work_product.name
|
24
|
+
puts "#{old.type} -- #{old.name} is different"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
compare_each :defect do |old, new|
|
29
|
+
if !old.requirement.nil? && old.requirement.type == "Story"
|
30
|
+
if new.requirement.children
|
31
|
+
old.requirement.name.should == new.requirement.children.values.first.name
|
32
|
+
else
|
33
|
+
old.requirement.name.should == new.requirement.name
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
compare_each :test_case do |old, new|
|
39
|
+
if !old.work_product.nil? && old.work_product.type == "Story"
|
40
|
+
if new.work_product.children
|
41
|
+
old.work_product.name.should == new.work_product.children.values.first.name
|
42
|
+
else
|
43
|
+
old.work_product.name.should == new.work_product.name
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def compare_each(type)
|
51
|
+
@slm.find_all(type, :workspace => @from_workspace).each do |old_object|
|
52
|
+
new_object = @slm.find(type, :workspace => @to_workspace) { equal :object_i_d, $TRANSLATED[old_object.oid] }.first
|
53
|
+
if new_object.nil?
|
54
|
+
puts "Cound not find an copy for object #{old_object.type} -- #{old_object.name} -- #{old_object.oid}"
|
55
|
+
next
|
56
|
+
end
|
57
|
+
yield old_object, new_object
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def all_copied_fields_should_match_for(type, additional_exceptions = [])
|
62
|
+
compare_each(type) do |old_object, new_object|
|
63
|
+
excepted_attributes = old_object.excepted_attributes + additional_exceptions
|
64
|
+
old_hash = old_object.elements.reject { |k, v| excepted_attributes.include?(k) }
|
65
|
+
new_hash = {}
|
66
|
+
old_hash.each_key do |k|
|
67
|
+
new_hash[k] = new_object.elements[k]
|
68
|
+
end
|
69
|
+
diff = old_hash.diff(new_hash)
|
70
|
+
if diff.length > 0
|
71
|
+
puts "#{old_hash.inspect} shoud == #{new_hash.inspect}"
|
72
|
+
puts "all_copied_fields_should_match_for #{type} diff = #{diff.inspect}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|