taskmapper-lighthouse 0.9.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/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.2
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ gem "taskmapper", "~> 0.8"
7
+ gem "xml-simple", "~> 1.1"
8
+ gem "addressable", "~> 2.2"
9
+ gem "lighthouse-api", "~> 2.0"
10
+ # Add dependencies to develop your gem here.
11
+ # Include everything needed to run rake, tests, features, etc.
12
+ group :development do
13
+ gem "rspec", "~> 2.8"
14
+ gem "jeweler", "~> 1.6"
15
+ gem "simplecov", "~> 0.5", :platforms => :ruby_19
16
+ gem "rcov", "~> 1.0", :platforms => :ruby_18
17
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,58 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ activemodel (3.2.1)
5
+ activesupport (= 3.2.1)
6
+ builder (~> 3.0.0)
7
+ activeresource (3.2.1)
8
+ activemodel (= 3.2.1)
9
+ activesupport (= 3.2.1)
10
+ activesupport (3.2.1)
11
+ i18n (~> 0.6)
12
+ multi_json (~> 1.0)
13
+ addressable (2.2.7)
14
+ builder (3.0.0)
15
+ diff-lcs (1.1.3)
16
+ git (1.2.5)
17
+ hashie (1.2.0)
18
+ i18n (0.6.0)
19
+ jeweler (1.6.4)
20
+ bundler (~> 1.0)
21
+ git (>= 1.2.5)
22
+ rake
23
+ lighthouse-api (2.0)
24
+ activeresource (>= 3.0.0)
25
+ activesupport (>= 3.0.0)
26
+ multi_json (1.0.4)
27
+ rake (0.9.2.2)
28
+ rcov (1.0.0)
29
+ rspec (2.8.0)
30
+ rspec-core (~> 2.8.0)
31
+ rspec-expectations (~> 2.8.0)
32
+ rspec-mocks (~> 2.8.0)
33
+ rspec-core (2.8.0)
34
+ rspec-expectations (2.8.0)
35
+ diff-lcs (~> 1.1.2)
36
+ rspec-mocks (2.8.0)
37
+ simplecov (0.5.4)
38
+ multi_json (~> 1.0.3)
39
+ simplecov-html (~> 0.5.3)
40
+ simplecov-html (0.5.3)
41
+ taskmapper (0.8.0)
42
+ activeresource (~> 3.0)
43
+ activesupport (~> 3.0)
44
+ hashie (~> 1.2)
45
+ xml-simple (1.1.1)
46
+
47
+ PLATFORMS
48
+ ruby
49
+
50
+ DEPENDENCIES
51
+ addressable (~> 2.2)
52
+ jeweler (~> 1.6)
53
+ lighthouse-api (~> 2.0)
54
+ rcov (~> 1.0)
55
+ rspec (~> 2.8)
56
+ simplecov (~> 0.5)
57
+ taskmapper (~> 0.8)
58
+ xml-simple (~> 1.1)
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009-2010 The Hybrid Group
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
+ NONE
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ # taskmapper-lighthouse
2
+
3
+ This is a provider for [taskmapper](http://ticketrb.com). It provides interoperability with [Lighthouse](http://www.lighthouseapp.com/) through the taskmapper gem.
4
+
5
+ # Usage and Examples
6
+
7
+ First we have to instantiate a new taskmapper instance:
8
+
9
+ lighthouse = TaskMapper.new(:lighthouse, {:subdomain => "rails"})
10
+ lighthouse = TaskMapper.new(:lighthouse, {:username => "code", :password => "m4st3r!", :account => "taskmapper"})
11
+ lighthouse = TaskMapper.new(:lighthouse, {:token => "5ff546af3df4a3c02d3a719b0ff1c3fcc5351c97", :account => "lhdemo"})
12
+
13
+ The :account is the name of the account which should be the same as the subdomain used to access the account's projects. For you convenience, you can also pass in :subdomain in place of :account. If you pass in both, it'll use the :account value. If you do not pass in the token or both the username and password, it will only access public information for the account.
14
+
15
+ Tokens allow access to a specific project or account without having to give out your login credentials. It can be nullified if necessary. I highly suggest you use tokens. For more information, please see Lighthouse's FAQ [How do I get an API token?](http://help.lighthouseapp.com/faqs/api/how-do-i-get-an-api-token)
16
+
17
+ == Finding Projects
18
+
19
+ project = lighthouse.project['project_name']
20
+ project = lighthouse.project.find(:id => 505)
21
+
22
+ == Finding Tickets
23
+
24
+ tickets = project.tickets
25
+
26
+
27
+ ## Requirements
28
+
29
+ * rubygems (obviously)
30
+ * taskmapper gem (latest version preferred)
31
+ * jeweler gem (only if you want to repackage and develop)
32
+
33
+ The taskmapper gem should automatically be installed during the installation of this gem if it is not already installed.
34
+
35
+ ## Other Notes
36
+
37
+ Since this and the taskmapper gem is still primarily a work-in-progress, minor changes may be incompatible with previous versions. Please be careful about using and updating this gem in production.
38
+
39
+ If you see or find any issues, feel free to open up an issue report.
40
+
41
+
42
+ ## Note on Patches/Pull Requests
43
+
44
+ * Fork the project.
45
+ * Make your feature addition or bug fix.
46
+ * Add tests for it. This is important so I don't break it in a
47
+ future version unintentionally.
48
+ * Commit, do not mess with rakefile, version, or history.
49
+ (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)
50
+ * Send me a pull request. Bonus points for topic branches.
51
+
52
+ ## Copyright
53
+
54
+ Copyright (c) 2010 The Hybrid Group. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,41 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "taskmapper-lighthouse"
8
+ gem.summary = %Q{TaskMapper Provider for Lighthouse}
9
+ gem.description = %Q{Allows taskmapper to interact with Lighthouse's issue tracking system.}
10
+ gem.email = "hong.quach@abigfisch.com"
11
+ gem.homepage = "http://github.com/kiafaldorius/taskmapper-lighthouse"
12
+ gem.authors = ["Hong"]
13
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
14
+ end
15
+ Jeweler::GemcutterTasks.new
16
+ rescue LoadError
17
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
18
+ end
19
+
20
+ require 'rspec/core'
21
+ require 'rspec/core/rake_task'
22
+ RSpec::Core::RakeTask.new(:spec) do |spec|
23
+ spec.pattern = FileList['spec/**/*_spec.rb']
24
+ end
25
+
26
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
27
+ spec.pattern = 'spec/**/*_spec.rb'
28
+ spec.rcov = true
29
+ end
30
+
31
+ task :default => :spec
32
+
33
+ require 'rake/rdoctask'
34
+ Rake::RDocTask.new do |rdoc|
35
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
36
+
37
+ rdoc.rdoc_dir = 'rdoc'
38
+ rdoc.title = "taskmapper-kanbanpad#{version}"
39
+ rdoc.rdoc_files.include('README*')
40
+ rdoc.rdoc_files.include('lib/**/*.rb')
41
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.9.0
@@ -0,0 +1,85 @@
1
+ module TaskMapper::Provider
2
+ module Lighthouse
3
+ # The comment class for taskmapper-lighthouse
4
+ #
5
+ # Due to the way lighthouse handles tickets, comments aren't really comments, but
6
+ # versions of the ticket.
7
+ #
8
+ # * author => user_name (read-only)
9
+ # * body => description
10
+ # * id => position in the versions array (set by the initializer)
11
+ # * created_at
12
+ # * updated_at
13
+ # * ticket_id => number (read-only)
14
+ # * project_id => (set by the initializer)
15
+ class Comment < TaskMapper::Provider::Base::Comment
16
+ API = ::Lighthouse::Ticket
17
+
18
+ # A custom find_by_id
19
+ # The "comment" id is it's index in the versions array. An id of 0 therefore exists and
20
+ # should be the first ticket (original)
21
+ def self.find_by_id(project_id, ticket_id, id)
22
+ self.new API.find(ticket_id, :params => {:project_id => project_id}), id
23
+ end
24
+
25
+ # A custom find_by_attributes
26
+ #
27
+ def self.find_by_attributes(project_id, ticket_id, attributes = {})
28
+ result = self.search(project_id, ticket_id, attributes)
29
+ result[0].shift
30
+ result[0].collect do |comment|
31
+ self.new(result[1], index_of(result[1].versions, comment)) if !comment.body.blank?
32
+ end.compact
33
+ end
34
+
35
+ # The Array#index method doesn't work for the versions...
36
+ # because it seems they're all equal.
37
+ def self.index_of(versions, needle)
38
+ result = nil
39
+ versions.each_with_index do |version, index|
40
+ result = index if version.attributes == needle.attributes
41
+ end
42
+ result
43
+ end
44
+
45
+ def self.create(*options)
46
+ attributes = options.first
47
+ ticket_id = attributes.delete(:ticket_id) || attributes.delete('ticket_id')
48
+ project_id = attributes.delete(:project_id) || attributes.delete('project_id')
49
+ ticket = self::API.find(ticket_id, :params => {:project_id => project_id})
50
+ attributes.each do |k, v|
51
+ ticket.send("#{k}=", v)
52
+ end
53
+ versions = ticket.attributes.delete('versions')
54
+ ticket.save
55
+ ticket.attributes['versions'] = versions
56
+ self.find_by_id project_id, ticket_id, ticket.versions.length
57
+ end
58
+
59
+ def initialize(ticket, id)
60
+ @system_data ||= {}
61
+ return super(ticket) unless ticket.respond_to?('versions') and ticket.versions.respond_to?('[]')
62
+ @system_data[:ticket] = @system_data[:client] = ticket
63
+ @system_data[:version] = ticket.versions[id]
64
+ self.project_id = ticket.prefix_options[:project_id]
65
+ self.id = id
66
+ super(@system_data[:version].attributes)
67
+ end
68
+
69
+ # A custom searcher
70
+ #
71
+ # It returns a custom result because we need the original ticket to make a comment.
72
+ def self.search(project_id, ticket_id, options = {}, limit = 1000)
73
+ ticket = API.find(ticket_id, :params => {:project_id => project_id})
74
+ comments = ticket.versions
75
+ [search_by_attribute(comments, options, limit), ticket]
76
+ end
77
+
78
+ # The author's name
79
+ def author
80
+ user_name
81
+ end
82
+
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,77 @@
1
+ module TaskMapper::Provider
2
+ # This is the Lighthouse Provider for taskmapper
3
+ module Lighthouse
4
+ include TaskMapper::Provider::Base
5
+ PROJECT_API = ::Lighthouse::Project
6
+ TICKET_API = ::Lighthouse::Ticket
7
+
8
+ # This is for cases when you want to instantiate using TaskMapper::Provider::Lighthouse.new(auth)
9
+ def self.new(auth = {})
10
+ TaskMapper.new(:lighthouse, auth)
11
+ end
12
+
13
+ # The authorize and initializer for this provider
14
+ def authorize(auth = {})
15
+ @authentication ||= TaskMapper::Authenticator.new(auth)
16
+ auth = @authentication
17
+ if auth.account.nil? or (auth.token.nil? and (auth.username.nil? and auth.password.nil?))
18
+ raise "Please provide at least an account (subdomain) and token or username and password)"
19
+ end
20
+ ::Lighthouse::Base.format = :json
21
+ ::Lighthouse.account = auth.account || auth.subdomain
22
+ if auth.token
23
+ ::Lighthouse.token = auth.token
24
+ elsif auth.username && auth.password
25
+ ::Lighthouse.authenticate(auth.username, auth.password)
26
+ end
27
+ end
28
+
29
+ def valid?
30
+ begin
31
+ PROJECT_API.find(:first)
32
+ true
33
+ rescue
34
+ false
35
+ end
36
+ end
37
+
38
+ # The projects
39
+ #
40
+ # We have to merge in the auth information because, due to the class-based authentication
41
+ # mechanism, if we don't reset the authorize information for every request, it would
42
+ # end up using whatever the previous instantiated object's account info is.
43
+ def projects(*options)
44
+ authorize
45
+ super(*options)
46
+ end
47
+
48
+ # The project
49
+ def project(*options)
50
+ authorize
51
+ super(*options)
52
+ end
53
+
54
+ # The tickets
55
+ #
56
+ # Due to the nature of lighthouse, we must have the project_id to pull tickets. You can
57
+ # pass in the id through this format:
58
+ #
59
+ # .tickets(22)
60
+ # .tickets(:project_id => 22)
61
+ #
62
+ # To conform to taskmapper's standard of returning all tickets on a call to this method
63
+ # without any parameters, if no parameters are passed, it will return all tickets for whatever
64
+ # the first project is.
65
+ def tickets(*options)
66
+ authorize
67
+ super(*options)
68
+ end
69
+
70
+ # the ticket
71
+ def ticket(*options)
72
+ authorize
73
+ super(*options)
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,28 @@
1
+ module TaskMapper::Provider
2
+ module Lighthouse
3
+ # Project class for taskmapper-lighthouse
4
+ #
5
+ #
6
+ class Project < TaskMapper::Provider::Base::Project
7
+ attr_accessor :prefix_options
8
+ API = ::Lighthouse::Project
9
+ # Delete this project
10
+ def destroy
11
+ result = super
12
+ result.is_a?(Net::HTTPOK)
13
+ end
14
+
15
+ # copy from this.copy(that) copies that into this
16
+ def copy(project)
17
+ project.tickets.each do |ticket|
18
+ copy_ticket = self.ticket!(:title => ticket.title, :description => ticket.description)
19
+ ticket.comments.each do |comment|
20
+ copy_ticket.comment!(:body => comment.body)
21
+ sleep 1
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,153 @@
1
+ module TaskMapper::Provider
2
+ module Lighthouse
3
+ # Ticket class for taskmapper-lighthouse
4
+ #
5
+ # Due to the way Lighthouse stores tickets, we actually end up creating a new ticket version
6
+ # every time we edit tickets. Their api FAQ states these attributes as the only editable ones(?):
7
+ #
8
+ # * title
9
+ # * body - follows the same formatting rules.
10
+ # * state - Can be any one of these: new, open, resolved, hold, invalid. Optional, set to open by default for new tickets.
11
+ # * assigned-user-id - optional
12
+ # * milestone-id - optional
13
+ # * tag - space or comma delimited list of tags
14
+ #
15
+ # We had to remap things a bit since lighthouse doesn't name things as taskmapper specifies.
16
+ #
17
+ # * id => number (read-only)
18
+ # * status => state
19
+ # * resolution => ticket.latest_body
20
+ # * description => ticket.original_body (setting a new description creates a new body)
21
+ # * assignee => assigned_user_name (read-only)
22
+ # * requestor => creator_name (read-only)
23
+ # * project_id => prefix_options[:project_id]
24
+ # * priority
25
+ # * title
26
+ # * created_at
27
+ # * updated_at
28
+
29
+ class Ticket < TaskMapper::Provider::Base::Ticket
30
+ @@allowed_states = ['new', 'open', 'resolved', 'hold', 'invalid']
31
+ attr_accessor :prefix_options
32
+ API = ::Lighthouse::Ticket
33
+
34
+ # This is to get the ticket id
35
+ # We can't set ids, so there's no 'id=' method.
36
+ def id
37
+ @system_data[:client].number
38
+ end
39
+
40
+ # This is to get the status, mapped to state
41
+ def status
42
+ state
43
+ end
44
+
45
+ # This is to set the status, mapped to state
46
+ def status=(stat)
47
+ stat = state unless @@allowed_states.include?(stat)
48
+ self.state = stat
49
+ end
50
+
51
+ # Get the resolution, mapped to latest_body
52
+ def resolution
53
+ self.latest_body
54
+ end
55
+
56
+ # Set the resolution...also sets state to resolved
57
+ def resolution=(res)
58
+ state = 'resolved'
59
+ self.body = res
60
+ end
61
+
62
+ # Get the description, mapped to original_body
63
+ def description
64
+ self.original_body
65
+ end
66
+
67
+ # Set the description, mapped to body, which actually does a comment
68
+ def description=(desc)
69
+ self.body = desc
70
+ end
71
+
72
+ # Get the assigned person's name
73
+ def assignee
74
+ self.assigned_user_name
75
+ end
76
+
77
+ # Get the requestor's name
78
+ def requestor
79
+ self.creator_name
80
+ end
81
+
82
+ # Get the project id
83
+ def project_id
84
+ prefix_options[:project_id]
85
+ end
86
+
87
+ # Set the body
88
+ def body=(bod)
89
+ @system_data[:client].body = nil
90
+ super(bod)
91
+ end
92
+
93
+ # Tags, a little helper for the ticket tagging
94
+ def tags
95
+ return @tags if @tags
96
+ tagz = self.tag.split(/([\w\d]+)|"([\w \d]+)"/)
97
+ tagz.delete(' ')
98
+ tagz.delete('')
99
+ @tags = tagz
100
+ end
101
+
102
+ # Gotta unset the body attribute...otherwise every save ends up using that body
103
+ def save
104
+ # self.tag = @tags.reduce([]) do |mem, t|
105
+ # t = "\"#{t}\"" if t.include?(' ')
106
+ # mem << t
107
+ # end.join(' ') if @tags
108
+ @system_data[:client].attributes.delete('versions')
109
+ result = super
110
+ body = nil
111
+ @system_data[:client].body = nil
112
+ result
113
+ end
114
+
115
+ # The closer
116
+ def close(resolution = 'resolved')
117
+ resolution = 'resolved' unless @@allowed_states.include?(resolution)
118
+ ticket = ::Lighthouse::Ticket.find(self.id, :params => {:project_id => self.prefix_options[:project_id]})
119
+ ticket.state = resolution
120
+ ticket.save
121
+ end
122
+
123
+ class << self
124
+
125
+ def create(options)
126
+ super translate options,
127
+ :description => :body,
128
+ :status => :state,
129
+ :assignee => :assigned_user_id
130
+ end
131
+
132
+ # Lighthouse limits us to a 100 ticket limit per page...
133
+ # Since the paging sucks...we probably want to store this rather than do a call each time
134
+ def search(project_id, options = {}, limit = 1000)
135
+ page = 1
136
+ tickets = ::Lighthouse::Ticket.find(:all, :params => {:project_id => project_id, :limit => 100, :page => page})
137
+ result = []
138
+ while tickets.length > 0 and page <= 3 #limit to page 3 since lighthouse is so slow...
139
+ result += tickets.collect { |ticket| self.new ticket }
140
+ page += 1
141
+ tickets = ::Lighthouse::Ticket.find(:all, :params => {:project_id => project_id, :limit => 100, :page => page})
142
+ end
143
+ search_by_attribute(result, options, limit)
144
+ end
145
+
146
+ private
147
+ def translate(hash, mapping)
148
+ Hash[hash.map { |k, v| [mapping[k] ||= k, v]}]
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end