translate 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 +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
|