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.
@@ -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