rallytastic 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gem 'rake'
4
+ gem 'jeweler'
5
+ gem 'mongoid', '2.0.0.beta.15'
6
+ gem 'bson_ext'
7
+ gem 'rest-client'
8
+ gem 'rspec'
9
+ gem 'autotest'
10
+ gem 'thor'
data/Gemfile.lock ADDED
@@ -0,0 +1,51 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.0.0.rc)
5
+ activesupport (= 3.0.0.rc)
6
+ builder (~> 2.1.2)
7
+ i18n (~> 0.4.1)
8
+ activesupport (3.0.0.rc)
9
+ autotest (4.3.2)
10
+ bson (1.0.4)
11
+ bson_ext (1.0.4)
12
+ builder (2.1.2)
13
+ gemcutter (0.6.1)
14
+ git (1.2.5)
15
+ i18n (0.4.1)
16
+ jeweler (1.4.0)
17
+ gemcutter (>= 0.1.0)
18
+ git (>= 1.2.5)
19
+ rubyforge (>= 2.0.0)
20
+ json_pure (1.4.3)
21
+ mime-types (1.16)
22
+ mongo (1.0.6)
23
+ bson (>= 1.0.4)
24
+ mongoid (2.0.0.beta.15)
25
+ activemodel (= 3.0.0.rc)
26
+ bson (= 1.0.4)
27
+ mongo (= 1.0.6)
28
+ tzinfo (= 0.3.22)
29
+ will_paginate (~> 3.0.pre)
30
+ rake (0.8.7)
31
+ rest-client (1.6.0)
32
+ mime-types (>= 1.16)
33
+ rspec (1.3.0)
34
+ rubyforge (2.0.4)
35
+ json_pure (>= 1.1.7)
36
+ thor (0.14.0)
37
+ tzinfo (0.3.22)
38
+ will_paginate (3.0.pre2)
39
+
40
+ PLATFORMS
41
+ ruby
42
+
43
+ DEPENDENCIES
44
+ autotest
45
+ bson_ext
46
+ jeweler
47
+ mongoid (= 2.0.0.beta.15)
48
+ rake
49
+ rest-client
50
+ rspec
51
+ thor
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Matt Clark
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,85 @@
1
+ = rallytastic
2
+ == Description
3
+
4
+ Do you use Rally? Every day? Do you use it enough that you've run out of ways to hack the site to get that last ounce of customization from it? The reports, for example, while very good, aren't quite what you need to run your dev shop, are they? If you've taken a look at the RallyAPI longingly in the past, but written it off as too much work to do anything meaningful with, then Rallytastic is offering you a helping hand.
5
+
6
+ Rallytastic is a set of objects built on MongoDB and Mongoid, along with Thor tasks that pull data out of Rally and stuff it into those objects. Once there, Rallytastic tries to get out of your way and lets the data be your playground. Have at it with a platic shovel and Tonka Bulldozer if that'll get you up in the morning. You were born to get down with your bad Rally self. Be Awesome.
7
+
8
+ Rallytastic is intended to be the foundation of a web app that reports User Story data back to Rally users. It augments Rally proper primarily as an extention to the reporting capabilities. For example, if you are more concerned with the throughput of your dev teams than their velocity, Rallytastic is going to help you build custom fit measuring tools for that. It has built in helpers to walk through the revision logs of your stories and identify key events in their lifecycle. You can track work in progress(WIP) levels over time, measure how long stories sit prioritized on your backlog before dev starts, identify queues and bottlenecks anywhere between story definition time and release, and much more.
9
+
10
+ == Congiguration
11
+
12
+ In a file that is required from lib/rallytastic.rb, you can configure Rally authentication.
13
+ RallyAPI::configure do |config|
14
+ config.user = "username@AwesomeCompany.com"
15
+ config.password = "password"
16
+ end
17
+
18
+ You also need to have Mongo running, and let your app know what database to use. Either do it the Rails .yml way, or put something like this in a config file and include it:
19
+ Mongoid.configure do |config|
20
+ name = "rallytastic_dev"
21
+ host = "localhost"
22
+ config.master = Mongo::Connection.new.db(name)
23
+ config.persist_in_safe_mode = false
24
+ end
25
+
26
+
27
+ You should also set mongo to use a testing database in spec/spec_helper.rb
28
+ Mongoid.configure do |config|
29
+ name = "rallytastic_test"
30
+ host = "localhost"
31
+ config.master = Mongo::Connection.new.db(name)
32
+ config.persist_in_safe_mode = false
33
+ end
34
+
35
+
36
+ ==Usage
37
+
38
+ === Populating your MongoDB for the first time
39
+ > bundle exec thor scraper:projects
40
+ > bundle exec thor scraper:iterations
41
+ > bundle exec thor scraper:refresh
42
+
43
+ === Revisions are pulled on request per iteration
44
+ > bundle exec thor scraper:revisions "Iteration Name"
45
+
46
+ === On an ongoing basis, updating stories is quicker
47
+ > bundle exec thor scraper:refresh
48
+ # update the story revisions for the iteration you are interested in
49
+ > bundle exec thor scraper:revisions "Iteration Name"
50
+
51
+ === Thor Tasks
52
+ ==== scraper:help [TASK]
53
+ # Describe available tasks or one specific task
54
+ ==== scraper:refresh
55
+ # Pulls down Rally stories since the last run, and creates Iterations and Projects as needed. This is the task you want to put in your job scheduler.
56
+ ==== scraper:stories
57
+ # Walk Rally stories updated since last run
58
+ ==== scraper:update_non_stories
59
+ # The scrape:stroies task creates shell iterations and projects for any that it doesn't know about yet. This task looks for those, and pull their information down from Rally
60
+ ==== scraper:revisions iteration_name
61
+ # Get the revisions for stories hanging off an iteration
62
+ ==== scraper:projects
63
+ # Bootstrapping method that is primarily useful when you are setting up Rallytastic for the first time. It will walk Rally and pull down all projects. Technically, you don't need to run this because using the scrape:stories; scrape:update_non_stories; will fill this information in for you. It may be a bit quicker though to run this first if you have a lot of projects because it does batch queries
64
+ ==== scraper:iterations
65
+ # Bootstrapping method that is primarily useful when you are setting up Rallytastic for the first time. It will walk Rally and pull down all Iterations. Technically, you don't need to run this because using the scrape:stories; scrape:update_non_stories; will fill this information in for you. It may be a bit quicker though to run this first if you have a lot of iterations because it does batch queries
66
+ <End of Thor Tasks>
67
+
68
+ == TODO
69
+ * Add Releases
70
+ * Taging stories
71
+ * Reporting engine
72
+
73
+ == Note on Patches/Pull Requests
74
+
75
+ * Fork the project.
76
+ * Make your feature addition or bug fix.
77
+ * Add tests for it. This is important so I don't break it in a
78
+ future version unintentionally.
79
+ * Commit, do not mess with rakefile, version, or history.
80
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
81
+ * Send me a pull request. Bonus points for topic branches.
82
+
83
+ == Copyright
84
+
85
+ Copyright (c) 2010 Matt Clark. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "rallytastic"
8
+ gem.summary = %Q{Its a Rally story teleporter}
9
+ gem.description = %Q{longer description of your gem}
10
+ gem.email = "winescout@gmail.com"
11
+ gem.homepage = "http://github.com/winescout/rallytastic"
12
+ gem.authors = ["Matt Clark"]
13
+ gem.add_development_dependency "rspec", ">= 1.2.9"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+ Spec::Rake::SpecTask.new(:spec) do |spec|
23
+ spec.libs << 'lib' << 'spec'
24
+ spec.spec_files = FileList['spec/**/*_spec.rb']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
28
+ spec.libs << 'lib' << 'spec'
29
+ spec.pattern = 'spec/**/*_spec.rb'
30
+ spec.rcov = true
31
+ end
32
+
33
+ task :spec => :check_dependencies
34
+
35
+ task :default => :spec
36
+
37
+ require 'rake/rdoctask'
38
+ Rake::RDocTask.new do |rdoc|
39
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
40
+
41
+ rdoc.rdoc_dir = 'rdoc'
42
+ rdoc.title = "rallytastic #{version}"
43
+ rdoc.rdoc_files.include('README*')
44
+ rdoc.rdoc_files.include('lib/**/*.rb')
45
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/lib/iteration.rb ADDED
@@ -0,0 +1,52 @@
1
+ class Iteration
2
+ include Mongoid::Document
3
+ include Rally::ParsingHelpers
4
+ extend Rally::ParsingHelperClassMethods
5
+
6
+ class << self
7
+ def rally_uri
8
+ "/iteration.js"
9
+ end
10
+ end
11
+
12
+ field :name
13
+ field :rally_uri
14
+ field :created_on, :type => Date
15
+ field :description
16
+ field :notes
17
+ field :state
18
+ field :start_date, :type => Date
19
+ field :end_date, :type => Date
20
+ field :resources
21
+ field :theme
22
+
23
+ referenced_in :project
24
+ references_many :stories
25
+
26
+ def refresh hash_values=nil
27
+ @rally_hash = hash_values
28
+ from_rally :rally_uri, :_ref
29
+ from_rally :name
30
+ from_rally :theme
31
+ from_rally :state
32
+ from_rally :created_on, :_CreatedAt
33
+ from_rally :start_date, :StartDate
34
+ from_rally :end_date, :EndDate
35
+ from_rally :resources
36
+
37
+ self.save
38
+ rescue ArgumentError #getting some bad created_on dates
39
+ puts "Errored on #{self.name}"
40
+ self.save # save what you can
41
+ end
42
+
43
+ #has to be called after refresh, or with hash_values passed in
44
+ def associate hash_values=nil
45
+ @rally_hash = hash_values if hash_values
46
+ if @rally_hash.has_key?("Project")
47
+ project = Project.find_or_create_by(:rally_uri => @rally_hash["Project"]["_ref"])
48
+ project.iterations << self
49
+ end
50
+ self.save
51
+ end
52
+ end
@@ -0,0 +1,23 @@
1
+ module Parser
2
+ class Base
3
+ attr_accessor :story
4
+ def initialize story
5
+ @story = story
6
+ end
7
+
8
+ def revisions
9
+ @story.revisions
10
+ end
11
+
12
+ protected
13
+ def latest_revision_date_matching regexp
14
+ revision = revisions.desc(:created_on).select{|r| r.description =~ regexp}.first
15
+ revision.created_on if revision
16
+ end
17
+
18
+ def first_revision_date_matching regexp
19
+ revision = revisions.asc(:created_on).select{|r| r.description =~ regexp}.first
20
+ revision.created_on if revision
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Parser
2
+ class Madison < Base
3
+ def sized_on
4
+ first_revision_date_matching /TASK ESTIMATE TOTAL changed from \[0.0\]/
5
+ end
6
+
7
+ def prioritized_on
8
+ last_revision_date_matching /PROJECT changed from \[[^\]]*\] to \[Madison\]/
9
+ end
10
+
11
+ def started_on
12
+ last_revision_date_matching /ITERATION added/
13
+ end
14
+
15
+ def completed_on
16
+ last_revision_date_matching /SCHEDULE STATE changed from \[[^\]]*\] to [Complete]/
17
+ end
18
+
19
+ def accepted_on
20
+ last_revision_date_matching /SCHEDULE STATE changed from \[[^\]]*\] to [Accepted]/
21
+ end
22
+ end
23
+ end
data/lib/project.rb ADDED
@@ -0,0 +1,46 @@
1
+ class Project
2
+ include Mongoid::Document
3
+ include Rally::ParsingHelpers
4
+ extend Rally::ParsingHelperClassMethods
5
+
6
+ class << self
7
+ def rally_uri
8
+ "/project.js"
9
+ end
10
+ end
11
+
12
+ field :name
13
+ field :rally_uri
14
+ field :created_on
15
+ field :description
16
+ field :notes
17
+ field :state
18
+
19
+ field :revision_parser
20
+
21
+ referenced_in :parent, :class_name => "Project"
22
+ references_many :children, :class_name => "Project"
23
+ references_many :iterations
24
+
25
+ def refresh hash_values=nil
26
+ @rally_hash = hash_values if hash_values
27
+ from_rally :name
28
+ from_rally :description
29
+ from_rally :state
30
+ from_rally :notes
31
+
32
+ self.save
33
+
34
+ end
35
+
36
+ #must be called after refresh, or with has_values passed in
37
+ def associate hash_values=nil
38
+ @rally_hash = hash_values if hash_values
39
+ #TODO: associate with user when users are supported
40
+ if @rally_hash["Parent"]
41
+ parent = Project.find_or_create_by(:rally_uri => @rally_hash["Parent"]["_ref"])
42
+ self.parent = parent
43
+ end
44
+ self.save
45
+ end
46
+ end
@@ -0,0 +1,70 @@
1
+ module Rally
2
+ module ParsingHelperClassMethods
3
+ def node_name
4
+ if self.to_s == "Story"
5
+ "HierarchicalRequirement"
6
+ else
7
+ self.to_s
8
+ end
9
+ end
10
+
11
+ def from_uri(uri)
12
+ obj = first(:conditions => {:rally_uri => uri}) || new(:rally_uri => uri)
13
+ obj.refresh
14
+ end
15
+
16
+ def rally_query options={}
17
+ RallyAPI.all(self, options)
18
+ end
19
+ end
20
+
21
+ module ParsingHelpers
22
+ def raw_json options = {}
23
+ options[:refresh] = options[:refresh] || false
24
+
25
+ unless self.rally_uri
26
+ raise "Could not fetch resource"
27
+ end
28
+
29
+ if @rally_hash
30
+ return @rally_hash
31
+ else
32
+ @rally_hash = get_fields_from_rally
33
+ end
34
+
35
+ @rally_hash
36
+
37
+ end
38
+
39
+ private
40
+
41
+ def get_fields_from_rally
42
+ rally_hash = RallyAPI.get(self)
43
+
44
+ #special case for heirarchical_requirements
45
+ if rally_hash.has_key? "HierarchicalRequirement"
46
+ rally_hash = rally_hash["HierarchicalRequirement"]
47
+ end
48
+
49
+ rally_hash
50
+ end
51
+
52
+ #next 3 are for mapping from rally to mongo
53
+ def from_rally attr, key=nil
54
+ key = key || attr.capitalize
55
+ self.send("#{attr.to_s}=", raw_json[key.to_s])
56
+ end
57
+
58
+ def parse_refs key, rally_objects
59
+ if self.respond_to?(key) && !rally_objects.nil?
60
+ self.send("#{key.to_s}=", rally_objects.collect{|i| i["_ref"]})
61
+ end
62
+ end
63
+
64
+ def parse_ref key, rally_object
65
+ if self.respond_to?(key) && !rally_object.nil?
66
+ self.send("#{key.to_s}=", rally_object["_ref"])
67
+ end
68
+ end
69
+ end
70
+ end