translate 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -0,0 +1,3 @@
1
+ README for translate
2
+ ====================
3
+
@@ -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
+
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env ruby
2
+
@@ -0,0 +1,2 @@
1
+ require 'translate/translate'
2
+
@@ -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,7 @@
1
+ class TestCaseResultCopy < ObjectCopy
2
+ def shallow_copy(object, new_workspace)
3
+ values = super
4
+ values.delete(:tester) unless user_exists? @object.tester
5
+ values
6
+ end
7
+ 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