translate 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 +54 -0
- data/bin/translate +2 -0
- data/lib/translate.rb +2 -0
- data/lib/translate/copy.rb +9 -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/translate.rb +60 -0
- data/lib/translate/version.rb +9 -0
- data/setup.rb +1585 -0
- data/test/test_helper.rb +2 -0
- data/test/translate_test.rb +11 -0
- metadata +70 -0
data/Manifest.txt
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
Manifest.txt
|
2
|
+
README.txt
|
3
|
+
Rakefile
|
4
|
+
setup.rb
|
5
|
+
test/test_helper.rb
|
6
|
+
test/translate_test.rb
|
7
|
+
bin/translate
|
8
|
+
lib/translate/copy.rb
|
9
|
+
lib/translate/version.rb
|
10
|
+
lib/translate/translate.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/translate.rb
|
data/README.txt
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
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 = "translate" # 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 = "translate"
|
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.1"]
|
47
|
+
|
48
|
+
|
49
|
+
# == Optional
|
50
|
+
#p.changes - A description of the release's latest changes.
|
51
|
+
#p.extra_deps - An array of rubygem dependencies.
|
52
|
+
#p.spec_extras - A hash of extra values to set in the gemspec.
|
53
|
+
end
|
54
|
+
|
data/bin/translate
ADDED
data/lib/translate.rb
ADDED
@@ -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,75 @@
|
|
1
|
+
class DefectCopy < ObjectCopy
|
2
|
+
|
3
|
+
def shallow_copy(object, new_workspace)
|
4
|
+
|
5
|
+
values = super
|
6
|
+
values.delete(:submitted_by) unless user_exists? @object.submitted_by
|
7
|
+
values
|
8
|
+
end
|
9
|
+
|
10
|
+
def tangle(new_workspace)
|
11
|
+
values = {}
|
12
|
+
new_defect = fetch_object(:defect, @object.oid, new_workspace)
|
13
|
+
|
14
|
+
# If the original defect is in a different project from the stories' card,
|
15
|
+
# then create a new user story, as the parent of this new user story, and make
|
16
|
+
# the requirement on the defect this new story
|
17
|
+
if @object.requirement != nil && @object.requirement.cards != nil && # The defect has a requirement, that is scheduled
|
18
|
+
@object.project != nil && # The defect in not in the parent project
|
19
|
+
@object.requirement.cards.first.project != @object.project # The card in in a different project from the defect
|
20
|
+
|
21
|
+
story_name = @object.requirement.name
|
22
|
+
new_requirement = fetch_object(:artifact, @object.requirement.oid, new_workspace)
|
23
|
+
if new_requirement.parent && new_requirement.parent.project.nil?
|
24
|
+
parent_story = new_requirement.parent
|
25
|
+
else
|
26
|
+
parent_story = new_workspace.rally_rest.create(:hierarchical_requirement,
|
27
|
+
:workspace => new_workspace,
|
28
|
+
:name => "Parent story for #{story_name} to hold defects in project #{@object.project.name}")
|
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,60 @@
|
|
1
|
+
require 'translate/copy'
|
2
|
+
|
3
|
+
class Translate
|
4
|
+
def initialize(args)
|
5
|
+
@base_url = args[:base_url]
|
6
|
+
@username = args[:username]
|
7
|
+
@password = args[:password]
|
8
|
+
@from_workspace_name = args[:from]
|
9
|
+
@to_workspace_name = args[:to]
|
10
|
+
end
|
11
|
+
|
12
|
+
def run
|
13
|
+
@slm = RallyRestAPI.new(:base_url => @base_url, :username => @username, :password => @password)
|
14
|
+
|
15
|
+
@workspace1 = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @from_workspace_name }
|
16
|
+
@workspace2 = @slm.user.subscription.workspaces.values.flatten.find { |w| w.name == @to_workspace_name }
|
17
|
+
|
18
|
+
raise "No such workspace #{@from_workspace_name}" unless @workspace1
|
19
|
+
raise "No such workspace #{@to_workspace_name}" unless @workspace2
|
20
|
+
$TRANSLATED[:from] = @from_workspace_name
|
21
|
+
$TRANSLATED[:to] = @to_workspace_name
|
22
|
+
|
23
|
+
if File::exists? "TRANSLATED"
|
24
|
+
File.open("TRANSLATED", "r") do |f|
|
25
|
+
$TRANSLATED = Marshal.load(f)
|
26
|
+
end
|
27
|
+
puts "Read TRANSLATED"
|
28
|
+
end
|
29
|
+
|
30
|
+
# now do the copy
|
31
|
+
[:iteration, :release, :story, :defect, :test_case].each do |type|
|
32
|
+
@slm.find_all(type, :workspace => @workspace1).each do |o|
|
33
|
+
puts "Copying #{o.type} -- #{o.name}"
|
34
|
+
o.copy(@workspace2)
|
35
|
+
end
|
36
|
+
end unless $TRANSLATED[:copy]
|
37
|
+
# mark these workspaces as copied
|
38
|
+
$TRANSLATED[:copy] = true
|
39
|
+
# dump the oids to disk
|
40
|
+
dump_oids
|
41
|
+
|
42
|
+
# Now tangle the objects
|
43
|
+
[:test_case, :defect].each do |type|
|
44
|
+
@slm.find_all(type, :workspace => @workspace1).each do |o|
|
45
|
+
puts "Tangle #{o.type} -- #{o.name}"
|
46
|
+
o.tangle(@workspace2)
|
47
|
+
end
|
48
|
+
end unless $TRANSLATED[:tangle]
|
49
|
+
|
50
|
+
$TRANSLATED[:tangle] = true
|
51
|
+
dump_oids
|
52
|
+
end
|
53
|
+
|
54
|
+
def dump_oids
|
55
|
+
File.open("TRANSLATED", "w+") do |f|
|
56
|
+
Marshal.dump($TRANSLATED, f)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|