rally_workspace_utils 0.0.1
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 +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
|