taskmapper-lighthouse 0.9.0

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