fresnel 0.5.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +151 -0
- data/Rakefile +54 -0
- data/bin/fresnel +17 -0
- data/fresnel.gemspec +36 -0
- data/lib/fresnel.rb +479 -0
- data/lib/fresnel/cache.rb +72 -0
- data/lib/fresnel/cli.rb +87 -0
- data/lib/fresnel/date_parser.rb +5 -0
- data/lib/fresnel/frame.rb +40 -0
- data/lib/fresnel/lighthouse.rb +386 -0
- data/lib/fresnel/setup_wizard.rb +54 -0
- metadata +98 -0
data/README.markdown
ADDED
@@ -0,0 +1,151 @@
|
|
1
|
+
Fresnel
|
2
|
+
--------------
|
3
|
+
|
4
|
+
A console manager to LighthouseApp.com using the official lighthouse api
|
5
|
+
|
6
|
+
With Fresnel you can browse you complete lighthouse account.
|
7
|
+
|
8
|
+
Current features :
|
9
|
+
==================
|
10
|
+
|
11
|
+
- list projects
|
12
|
+
- list ticket bins
|
13
|
+
- list tickets (also in bins)
|
14
|
+
- create tickets
|
15
|
+
- assign tickets
|
16
|
+
- comment on tickets
|
17
|
+
- state changes
|
18
|
+
|
19
|
+
|
20
|
+
How to install
|
21
|
+
==============
|
22
|
+
|
23
|
+
Use 'rake gem' to build the gem or 'rake install' to build and install the gem.
|
24
|
+
|
25
|
+
Cache
|
26
|
+
=====
|
27
|
+
|
28
|
+
You can control the cache time in your global config file : ~/.fresnel
|
29
|
+
|
30
|
+
add a key like :
|
31
|
+
cache_timeout: 60
|
32
|
+
|
33
|
+
This will set the timeout of the cache to 1 minute.
|
34
|
+
When fresnel makes changes, the cache will be invalidated automagically.
|
35
|
+
We would recommend to use a minimum of 10 seconds.
|
36
|
+
|
37
|
+
Terminal size
|
38
|
+
=============
|
39
|
+
|
40
|
+
By default we detect your terminal size,
|
41
|
+
if that fails it will be set to 80.
|
42
|
+
Though if you like to override it you can !
|
43
|
+
|
44
|
+
in your global config file : ~/.fresnel
|
45
|
+
add a key like :
|
46
|
+
term_size: 60
|
47
|
+
|
48
|
+
The ticket overview table will add columns if it fits, in the following order :
|
49
|
+
|
50
|
+
* ticket number *
|
51
|
+
* state *
|
52
|
+
* title *
|
53
|
+
- assigned to
|
54
|
+
- by
|
55
|
+
- tags
|
56
|
+
- created at
|
57
|
+
- updated at
|
58
|
+
|
59
|
+
items with a * are always shown ;)
|
60
|
+
|
61
|
+
Getting started
|
62
|
+
===============
|
63
|
+
|
64
|
+
Once you have Fresnel installed, run 'fresnel help' to see the syntax.
|
65
|
+
|
66
|
+
Problems, Comments and Suggestions
|
67
|
+
==================================
|
68
|
+
|
69
|
+
Please post them on https://govannon.lighthouseapp.com/projects/42260-fresnel/
|
70
|
+
or mail it to : ticket+govannon.42260-7vwej7yr@lighthouseapp.com
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
Examples
|
75
|
+
========
|
76
|
+
|
77
|
+
fresnel help
|
78
|
+
|
79
|
+
+-----------------------------------------------------------------------------+
|
80
|
+
| Fresnel - A lighthouseapp console manager - help |
|
81
|
+
+-----------------------------------------------------------------------------+
|
82
|
+
| |
|
83
|
+
| Fresnel is a Console App that helps manage Lighthouse (LH). |
|
84
|
+
| You can find LH at http://lighthouseapp.com |
|
85
|
+
| |
|
86
|
+
| fresnel help This screen |
|
87
|
+
| fresnel bins Show all ticket bins |
|
88
|
+
| fresnel bin <id> Show ticket in bin <id> |
|
89
|
+
| fresnel projects Show all projects |
|
90
|
+
| fresnel <id> comment Show comments for ticket |
|
91
|
+
| fresnel <id> Show ticket details |
|
92
|
+
| fresnel <id> assign Assign ticket to user |
|
93
|
+
| fresnel <id> claim Assign ticket to self |
|
94
|
+
| fresnel <id> online Open browser for ticket |
|
95
|
+
| fresnel <id> [open|closed|hold|resolved|invalid] Change ticket state |
|
96
|
+
| fresnel tickets Show all tickets |
|
97
|
+
| fresnel create Create a ticket |
|
98
|
+
| |
|
99
|
+
+-----------------------------------------------------------------------------+
|
100
|
+
| Created by Narnach & Smeevil - licence : mit |
|
101
|
+
+-----------------------------------------------------------------------------+
|
102
|
+
|
103
|
+
fresnel projects
|
104
|
+
|
105
|
+
fetching projects...
|
106
|
+
+---+--------------+--------+--------------+
|
107
|
+
| # | project name | public | open tickets |
|
108
|
+
+---+--------------+--------+--------------+
|
109
|
+
| 0 | Fresnel | true | 3 |
|
110
|
+
| 1 | Rails | true | 123 |
|
111
|
+
+---+--------------+--------+--------------+
|
112
|
+
|
113
|
+
|
114
|
+
fresnel tickets
|
115
|
+
|
116
|
+
Fetching unresolved tickets...
|
117
|
+
+----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
|
118
|
+
| # | state | title | tags | by | assigned to | created at | updated at |
|
119
|
+
+----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
|
120
|
+
| 10 | new | Add option to open Hoptoad link in browser | medium | Wes Oldenbeuving | nobody | Today 11:22 | Today 11:23 |
|
121
|
+
| 9 | new | cache invalidation | medium | Smeevil | nobody | Today 08:17 | Today 08:18 |
|
122
|
+
| 6 | open | would be nice if we could create a new lighthouse p... | low | Smeevil | Smeevil | 02-12-09 22:10 | 02-12-09 22:16 |
|
123
|
+
+----+-------+--------------------------------------------------------+--------+------------------+-------------+----------------+----------------+
|
124
|
+
|
125
|
+
fresnel 6
|
126
|
+
|
127
|
+
+-----------------------------------------------------------------------------+
|
128
|
+
| Ticket #6 : would be nice if we could create a new lighthouse projec... |
|
129
|
+
| Date : 02-12-09 22:10 by Smeevil |
|
130
|
+
| Tags : low |
|
131
|
+
+-----------------------------------------------------------------------------+
|
132
|
+
| |
|
133
|
+
| |
|
134
|
+
+-----------------------------------------------------------------------------+
|
135
|
+
|
136
|
+
Assignment changed 02-12-09 21:14 => Smeevil by Smeevil
|
137
|
+
|
138
|
+
+-----------------------------------------------------------------------------+
|
139
|
+
| Smeevil 02-12-09 21:24 |
|
140
|
+
+-----------------------------------------------------------------------------+
|
141
|
+
| |
|
142
|
+
| seems its changing state ! |
|
143
|
+
| now lets see if we can do it again ! |
|
144
|
+
| |
|
145
|
+
+-----------------------------------------------------------------------------+
|
146
|
+
| Assignment changed => Wes Oldenbeuving |
|
147
|
+
+-----------------------------------------------------------------------------+
|
148
|
+
|
149
|
+
Assignment changed 02-12-09 22:10 => Smeevil by Smeevil
|
150
|
+
|
151
|
+
Current state : open
|
data/Rakefile
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require "rake"
|
2
|
+
require "rake/clean"
|
3
|
+
require "rake/gempackagetask"
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
################################################################################
|
7
|
+
### Gem
|
8
|
+
################################################################################
|
9
|
+
|
10
|
+
begin
|
11
|
+
# Parse gemspec using the github safety level.
|
12
|
+
file = Dir['*.gemspec'].first
|
13
|
+
data = File.read(file)
|
14
|
+
spec = nil
|
15
|
+
# FIXME: Lowered SAFE from 3 to 2 to work with Ruby 1.9 due to rubygems
|
16
|
+
# performing a require internally
|
17
|
+
Thread.new { spec = eval("$SAFE = 2\n%s" % data)}.join
|
18
|
+
|
19
|
+
# Create the gem tasks
|
20
|
+
Rake::GemPackageTask.new(spec) do |package|
|
21
|
+
package.gem_spec = spec
|
22
|
+
end
|
23
|
+
rescue Exception => e
|
24
|
+
printf "WARNING: Error caught (%s): %s\n%s", e.class.name, e.message, e.backtrace[0...5].map {|l| ' %s' % l}.join("\n")
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Package and install the gem for the current version'
|
28
|
+
task :install => :gem do
|
29
|
+
system "sudo gem install -l pkg/%s-%s.gem" % [spec.name, spec.version]
|
30
|
+
end
|
31
|
+
|
32
|
+
desc 'Show files missing from gemspec'
|
33
|
+
task :diff do
|
34
|
+
files = %w[
|
35
|
+
Rakefile
|
36
|
+
*README* *readme*
|
37
|
+
*LICENSE*
|
38
|
+
*.gemspec deps.rip
|
39
|
+
bin/*
|
40
|
+
lib/**/*
|
41
|
+
spec/**/*
|
42
|
+
].map {|pattern| Dir.glob(pattern)}.flatten.select{|f| File.file?(f)}
|
43
|
+
missing_files = files - spec.files
|
44
|
+
extra_files = spec.files - files
|
45
|
+
puts "Missing files:"
|
46
|
+
puts missing_files.join(" ")
|
47
|
+
puts "Extra files:"
|
48
|
+
puts extra_files.join(" ")
|
49
|
+
end
|
50
|
+
|
51
|
+
desc 'Local install the latest gem version'
|
52
|
+
task :reinstall do
|
53
|
+
system("rm -f pkg/*.gem && rake gem && gem install pkg/*.gem")
|
54
|
+
end
|
data/bin/fresnel
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'fresnel'
|
3
|
+
require 'fresnel/cli'
|
4
|
+
|
5
|
+
COLOR=false
|
6
|
+
@@term_size=`tput cols`.to_i
|
7
|
+
@@term_size=80 if @@term_size==0 #if tputs does not exist
|
8
|
+
@@debug=false
|
9
|
+
@@cache_timeout=1.minute
|
10
|
+
|
11
|
+
Signal.trap("SIGINT") {
|
12
|
+
puts "Stopping..."
|
13
|
+
exit(0)
|
14
|
+
}
|
15
|
+
|
16
|
+
system("clear")
|
17
|
+
Cli.new(ARGV).run!
|
data/fresnel.gemspec
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
# Project
|
3
|
+
s.name = 'fresnel'
|
4
|
+
s.summary = "Fresnel is a console manager to LighthouseApp.com using the official lighthouse api."
|
5
|
+
s.description = s.summary
|
6
|
+
s.version = '0.5.4'
|
7
|
+
s.date = '2009-12-04'
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Gerard de Brieder", "Wes Oldenbeuving"]
|
10
|
+
s.email = "smeevil@gmail.com"
|
11
|
+
s.homepage = "http://www.github.com/smeevil/fresnel"
|
12
|
+
|
13
|
+
# Files
|
14
|
+
root_files = %w[README.markdown Rakefile fresnel.gemspec]
|
15
|
+
bin_files = %w[fresnel]
|
16
|
+
fresnel_files = %w[cache cli date_parser frame lighthouse setup_wizard]
|
17
|
+
lib_files = %w[fresnel] + fresnel_files.map {|f| "fresnel/#{f}"}
|
18
|
+
s.bindir = "bin"
|
19
|
+
s.require_path = "lib"
|
20
|
+
s.executables = bin_files
|
21
|
+
s.test_files = []
|
22
|
+
s.files = root_files + s.test_files + bin_files.map {|f| 'bin/%s' % f} + lib_files.map {|f| 'lib/%s.rb' % f}
|
23
|
+
|
24
|
+
# rdoc
|
25
|
+
s.has_rdoc = true
|
26
|
+
s.extra_rdoc_files = %w[ README.markdown]
|
27
|
+
s.rdoc_options << '--inline-source' << '--line-numbers' << '--main' << 'README.rdoc'
|
28
|
+
|
29
|
+
# Dependencies
|
30
|
+
s.add_dependency 'activesupport', ">= 2.3.0"
|
31
|
+
s.add_dependency 'terminal-table', ">= 1.3.0"
|
32
|
+
s.add_dependency 'highline', ">= 1.5.1"
|
33
|
+
|
34
|
+
# Requirements
|
35
|
+
s.required_ruby_version = ">= 1.8.0"
|
36
|
+
end
|
data/lib/fresnel.rb
ADDED
@@ -0,0 +1,479 @@
|
|
1
|
+
require 'active_support'
|
2
|
+
require 'terminal-table/import'
|
3
|
+
require 'highline/import'
|
4
|
+
|
5
|
+
require "fresnel/lighthouse"
|
6
|
+
require "fresnel/date_parser"
|
7
|
+
require "fresnel/cache"
|
8
|
+
require "fresnel/setup_wizard"
|
9
|
+
require "fresnel/frame"
|
10
|
+
|
11
|
+
HighLine.track_eof = false
|
12
|
+
|
13
|
+
class String
|
14
|
+
def wrap(col = 80)
|
15
|
+
self.gsub(/(.{1,#{col}})( +|$\n?)|(.{1,#{col}})/,
|
16
|
+
"\\1\\3\n")
|
17
|
+
end
|
18
|
+
|
19
|
+
def truncate(size)
|
20
|
+
"#{self.strip[0..size]}#{"..." if self.size>size}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class Fresnel
|
25
|
+
attr_reader :global_config_file, :project_config_file, :app_description
|
26
|
+
attr_accessor :lighthouse, :current_project_id, :cache, :cache_timeout, :current_user_id
|
27
|
+
|
28
|
+
def initialize(options=Hash.new)
|
29
|
+
@global_config_file="#{ENV['HOME']}/.fresnel"
|
30
|
+
@project_config_file=File.expand_path('.fresnel')
|
31
|
+
@app_description="A lighthouseapp console manager"
|
32
|
+
@lighthouse=Lighthouse
|
33
|
+
@cache=Cache.new
|
34
|
+
Lighthouse.account, Lighthouse.token, @current_user_id = load_global_config
|
35
|
+
@current_project_id=load_project_config
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
def load_global_config
|
40
|
+
if File.exists? self.global_config_file
|
41
|
+
config = YAML.load_file(self.global_config_file)
|
42
|
+
|
43
|
+
@@cache_timeout=config['cache_timeout'] if config.has_key?('cache_timeout')
|
44
|
+
@@debug=config['debug'] if config.has_key?('debug')
|
45
|
+
@@term_size=config['term_size'] if config.has_key?('term_size')
|
46
|
+
|
47
|
+
if config && config.class==Hash && config.has_key?('account') && config.has_key?('token') && config.has_key?('user_id')
|
48
|
+
return [config['account'], config['token'], config['user_id']]
|
49
|
+
else
|
50
|
+
puts Frame.new(:header=>"Warning !",:body=>"global config did not validate , recreating")
|
51
|
+
SetupWizard.global(self)
|
52
|
+
load_global_config
|
53
|
+
end
|
54
|
+
else
|
55
|
+
puts Frame.new(:header=>"Notice",:body=>"global config not found at #{self.global_config_file}, starting wizard")
|
56
|
+
SetupWizard.global(self)
|
57
|
+
load_global_config
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def load_project_config
|
62
|
+
if File.exists? self.project_config_file
|
63
|
+
config = YAML.load_file(self.project_config_file)
|
64
|
+
if config && config.class==Hash && config.has_key?('project_id')
|
65
|
+
return config['project_id']
|
66
|
+
else
|
67
|
+
puts Frame.new(:header=>"Warning !",:body=>"project config found but project_id was not declared")
|
68
|
+
load_project_config
|
69
|
+
end
|
70
|
+
else
|
71
|
+
puts Frame.new(:header=>"Notice",:body=>"project config not found at #{self.project_config_file}, starting wizard")
|
72
|
+
SetupWizard.project(self)
|
73
|
+
load_project_config
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def account
|
78
|
+
lighthouse.account
|
79
|
+
end
|
80
|
+
|
81
|
+
def token
|
82
|
+
lighthouse.token
|
83
|
+
end
|
84
|
+
|
85
|
+
def ask_for_action(actions_available="")
|
86
|
+
if actions_available.present?
|
87
|
+
puts actions_available.wrap
|
88
|
+
regexp="^(#{actions_available.scan(/\[(.*?)\]/).flatten.join("|")}|[0-9]+)$"
|
89
|
+
else
|
90
|
+
regexp="^(q|[0-9]+)$"
|
91
|
+
end
|
92
|
+
ask("Action : ") do |q|
|
93
|
+
q.default="q"
|
94
|
+
q.validate=/#{regexp}/
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_project
|
99
|
+
puts "create project is not implemented yet"
|
100
|
+
end
|
101
|
+
|
102
|
+
def projects(options=Hash.new)
|
103
|
+
options[:object]||=false
|
104
|
+
system("clear") unless options[:clear]==false || options[:object]
|
105
|
+
options[:selectable]||false
|
106
|
+
print "Fetching projects..." unless options[:object]
|
107
|
+
projects_data=cache.load(:name=>"fresnel_projects",:action=>"Lighthouse::Project.find(:all)")
|
108
|
+
puts " [done] - data is #{projects_data.age}s old , max is #{@@cache_timeout}s"
|
109
|
+
project_table = table do |t|
|
110
|
+
t.headings=[]
|
111
|
+
t.headings << '#' if options[:selectable]
|
112
|
+
t.headings += ['project name', 'public', 'open tickets']
|
113
|
+
|
114
|
+
projects_data.each_with_index do |project,i|
|
115
|
+
row=Array.new
|
116
|
+
row << i if options[:selectable]
|
117
|
+
row+=[project.name, project.public, {:value=>project.open_tickets_count, :alignment=>:right}]
|
118
|
+
t << row
|
119
|
+
end
|
120
|
+
end
|
121
|
+
if options[:object]
|
122
|
+
return projects_data
|
123
|
+
else
|
124
|
+
puts(project_table)
|
125
|
+
unless options[:setup]
|
126
|
+
action=ask_for_action("[q]uit, [c]reate or project #")
|
127
|
+
case action
|
128
|
+
when "c" then create_project
|
129
|
+
when /\d+/ then tickets(:project_id=>projects_data[action.to_i].id)
|
130
|
+
else
|
131
|
+
exit(0)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def tickets(options=Hash.new)
|
138
|
+
system("clear")
|
139
|
+
options[:all] ? print("Fetching all tickets#{" in bin #{options[:bin_name]}" if options[:bin_name].present?}...") : print("Fetching unresolved tickets#{" in bin #{options[:bin_name]}" if options[:bin_name].present?}...")
|
140
|
+
STDOUT.flush
|
141
|
+
project_id=options[:project_id]||self.current_project_id
|
142
|
+
tickets=options[:tickets]||cache.load(:name=>"fresnel_project_#{project_id}_tickets#{"_all" if options[:all]}", :action=>"Lighthouse::Ticket.find(:all, :params=>{:project_id=>#{project_id} #{",:q=>'not-state:closed'" unless options[:all]}})")
|
143
|
+
puts " [done] - data is #{tickets.age}s old , max is #{@@cache_timeout}s"
|
144
|
+
if tickets.any?
|
145
|
+
tickets_table = table do |t|
|
146
|
+
prepped_headings=[
|
147
|
+
{:value=>'#',:alignment=>:center},
|
148
|
+
{:value=>'state',:alignment=>:center},
|
149
|
+
{:value=>'title',:alignment=>:center},
|
150
|
+
]
|
151
|
+
prepped_headings << {:value=>'assigned to',:alignment=>:center} if @@term_size>=90
|
152
|
+
prepped_headings << {:value=>'by',:alignment=>:center} if @@term_size>=105
|
153
|
+
prepped_headings << {:value=>'tags',:alignment=>:center} if @@term_size>=120
|
154
|
+
prepped_headings << 'created at' if @@term_size>=140
|
155
|
+
prepped_headings << 'updated at' if @@term_size>=160
|
156
|
+
|
157
|
+
t.headings = prepped_headings #must assign the heading at once, else it will b0rk the output
|
158
|
+
|
159
|
+
tickets.sort_by(&:number).reverse.each do |ticket|
|
160
|
+
cols=[
|
161
|
+
{:value=>ticket.number, :alignment=>:right},
|
162
|
+
{:value=>ticket.state,:alignment=>:center},
|
163
|
+
"#{ticket.title.truncate(50)}"
|
164
|
+
]
|
165
|
+
cols << (ticket.assigned_user_name.split(" ").first.truncate(10) rescue "nobody") if @@term_size>=90
|
166
|
+
cols << ticket.creator_name.split(" ").first.truncate(10) if @@term_size>=105
|
167
|
+
cols << (ticket.tag.truncate(9) rescue "") if @@term_size>=120
|
168
|
+
cols << {:value=>DateParser.string(ticket.created_at.to_s), :alignment=>:right} if @@term_size>=140
|
169
|
+
cols << {:value=>DateParser.string(ticket.updated_at.to_s), :alignment=>:right} if @@term_size>=160
|
170
|
+
|
171
|
+
t << cols #must assign the cols at once, else it will b0rk the output
|
172
|
+
end
|
173
|
+
end
|
174
|
+
puts tickets_table
|
175
|
+
action=ask_for_action("[q]uit, [b]ins, [p]rojects, #{options[:all] ? "[u]nresolved" : "[a]ll"}, [c]reate or ticket #")
|
176
|
+
case action
|
177
|
+
when "b" then get_bins
|
178
|
+
when "c" then create
|
179
|
+
when "p" then projects(:selectable=>true)
|
180
|
+
when "a" then tickets(:all=>true)
|
181
|
+
when "u" then self.tickets
|
182
|
+
when /\d+/ then show_ticket(action)
|
183
|
+
else
|
184
|
+
exit(0)
|
185
|
+
end
|
186
|
+
else
|
187
|
+
puts Frame.new(:header=>"Notice",:body=>"no #{"unresolved " unless options[:all]}tickets #{"in bin #{options[:bin_name]}"}...")
|
188
|
+
action=ask_for_action("[q]uit, [b]ins, [p]rojects, [u]nresolved, [a]ll, [c]reate")
|
189
|
+
case action
|
190
|
+
when "b" then get_bins
|
191
|
+
when "c" then create
|
192
|
+
when "p" then projects(:selectable=>true)
|
193
|
+
when "a" then tickets(:all=>true)
|
194
|
+
when "u" then self.tickets
|
195
|
+
else
|
196
|
+
exit(0)
|
197
|
+
end
|
198
|
+
|
199
|
+
end
|
200
|
+
|
201
|
+
end
|
202
|
+
|
203
|
+
def get_bins(project_id=self.current_project_id)
|
204
|
+
system("clear")
|
205
|
+
print "Fetching ticket bins..."
|
206
|
+
STDOUT.flush
|
207
|
+
bins=cache.load(:name=>"fresnel_project_#{project_id}_bins",:action=>"Lighthouse::Project.find(#{project_id}).bins")
|
208
|
+
puts " [done] - data is #{bins.age}s old , max is #{@@cache_timeout}s"
|
209
|
+
bins.reject!{|b|true unless b.user_id==self.current_user_id || b.shared}
|
210
|
+
bins_table = table do |t|
|
211
|
+
t.headings = ['#', 'bin', 'tickets', 'query']
|
212
|
+
bins.each_with_index do |bin,i|
|
213
|
+
t << [i, bin.name,{:value=>bin.tickets_count, :alignment=>:right},bin.query]
|
214
|
+
end
|
215
|
+
end
|
216
|
+
puts bins_table
|
217
|
+
bin_id=ask_for_action
|
218
|
+
if bin_id=="q"
|
219
|
+
exit(0)
|
220
|
+
else
|
221
|
+
puts "Fetching tickets in bin : #{bins[bin_id.to_i].name}"
|
222
|
+
tickets(:tickets=>bins[bin_id.to_i].tickets)
|
223
|
+
def tickets.age=(seconds)
|
224
|
+
@age_in_seconds=seconds
|
225
|
+
end
|
226
|
+
def tickets.age
|
227
|
+
@age_in_seconds
|
228
|
+
end
|
229
|
+
tickets.age=0
|
230
|
+
return tickets
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
def get_tickets_in_bin(bin)
|
235
|
+
bins=cache.load(:name=>"fresnel_project_#{self.current_project_id}_bins",:action=>"Lighthouse::Project.find(#{self.current_project_id}).bins")
|
236
|
+
bins.reject!{|b|true unless b.user_id==self.current_user_id || b.shared}
|
237
|
+
data=bins[bin.to_i].tickets
|
238
|
+
|
239
|
+
def data.age=(seconds)
|
240
|
+
@age_in_seconds=seconds
|
241
|
+
end
|
242
|
+
def data.age
|
243
|
+
@age_in_seconds
|
244
|
+
end
|
245
|
+
data.age=0
|
246
|
+
|
247
|
+
tickets(:tickets=>data, :bin_name=>bins[bin.to_i].name)
|
248
|
+
end
|
249
|
+
|
250
|
+
def get_ticket(number)
|
251
|
+
cache.load(:name=>"fresnel_ticket_#{number}",:action=>"Lighthouse::Ticket.find(#{number}, :params => { :project_id => #{self.current_project_id} })")
|
252
|
+
end
|
253
|
+
|
254
|
+
def get_project(project_id=self.current_project_id)
|
255
|
+
cache.load(:name=>"fresnel_project_#{project_id}",:action=>"Lighthouse::Project.find(#{project_id})")
|
256
|
+
end
|
257
|
+
|
258
|
+
def get_project_members(project_id=self.current_project_id)
|
259
|
+
cache.load(:name=>"fresnel_project_#{project_id}_members", :action=>"Lighthouse::Project.find(#{project_id}).memberships", :timeout=>1.day)
|
260
|
+
end
|
261
|
+
|
262
|
+
def show_ticket(number)
|
263
|
+
system("clear")
|
264
|
+
ticket = get_ticket(number)
|
265
|
+
puts Frame.new(
|
266
|
+
:header=>[
|
267
|
+
"Ticket ##{number} : #{ticket.title.chomp.truncate(55)}",
|
268
|
+
"Date : #{DateParser.string(ticket.created_at.to_s)} by #{ticket.creator_name}",
|
269
|
+
"Tags : #{ticket.tag}"
|
270
|
+
],
|
271
|
+
:body=>ticket.versions.first.body
|
272
|
+
)
|
273
|
+
ticket.versions.each_with_index do |v,i|
|
274
|
+
next if i==0
|
275
|
+
if v.respond_to?(:diffable_attributes) && v.body.nil?
|
276
|
+
puts "\nState changed #{DateParser.string(v.created_at.to_s)} from #{v.diffable_attributes.state} => #{v.state} by #{v.user_name}\n\n" if v.diffable_attributes.respond_to?(:state)
|
277
|
+
puts "\nAssignment changed #{DateParser.string(v.created_at.to_s)} => #{v.assigned_user_name rescue "Nobody"} by #{v.user_name}\n\n" if v.diffable_attributes.respond_to?(:assigned_user)
|
278
|
+
else
|
279
|
+
user_date=v.user_name.capitalize
|
280
|
+
date=DateParser.string(v.created_at.to_s)
|
281
|
+
user_date=user_date.ljust((@@term_size-5)-date.size)
|
282
|
+
user_date+=date
|
283
|
+
|
284
|
+
footer=Array.new
|
285
|
+
footer<<"State changed from #{v.diffable_attributes.state} => #{v.state}" if v.diffable_attributes.respond_to?(:state)
|
286
|
+
footer<<"Assignment changed => #{v.assigned_user_name}" if v.diffable_attributes.respond_to?(:assigned_user)
|
287
|
+
|
288
|
+
puts Frame.new(:header=>user_date,:body=>v.body,:footer=>footer)
|
289
|
+
end
|
290
|
+
end
|
291
|
+
puts "Current state : #{ticket.versions.last.state}"
|
292
|
+
action=ask_for_action("[q]uit, [t]ickets, [b]ins, [c]omment, [a]ssign, [r]esolve, [s]elf, [o]pen, [h]old, [w]eb, [l]inks")
|
293
|
+
case action
|
294
|
+
when "t" then tickets
|
295
|
+
when "b" then get_bins
|
296
|
+
when "c" then comment(number)
|
297
|
+
when "a" then assign(:ticket=>number)
|
298
|
+
when "r" then change_state(:ticket=>number,:state=>"resolved")
|
299
|
+
when "s" then claim(:ticket=>number)
|
300
|
+
when "o" then change_state(:ticket=>number,:state=>"open")
|
301
|
+
when "h" then change_state(:ticket=>number,:state=>"hold")
|
302
|
+
when "w" then open_browser_for_ticket(number)
|
303
|
+
when "l" then links(number)
|
304
|
+
else
|
305
|
+
exit(0)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
def links(number)
|
310
|
+
ticket = get_ticket(number)
|
311
|
+
links = ticket.versions.map{ |version|
|
312
|
+
version.body.to_s.scan(/(http|https)(:\/\/)([a-zA-Z0-9.\/_-]+)| (www\.[a-zA-Z0-9.\/_-]+)/).map{ |url|
|
313
|
+
url.join
|
314
|
+
}
|
315
|
+
}.flatten.uniq
|
316
|
+
if links.size == 0
|
317
|
+
puts "No links found"
|
318
|
+
sleep 1
|
319
|
+
elsif links.size == 1
|
320
|
+
url = links.first
|
321
|
+
url="http://#{url}" unless url=~/^http/
|
322
|
+
`open '#{url}'`
|
323
|
+
else
|
324
|
+
link_table=table do |t|
|
325
|
+
t.headings=['#','link']
|
326
|
+
links.each_with_index{|link,i|t << [i,link]}
|
327
|
+
end
|
328
|
+
puts link_table
|
329
|
+
pick=ask("open link # : ", Integer) do |q|
|
330
|
+
q.below=links.size
|
331
|
+
q.above=-1
|
332
|
+
end
|
333
|
+
url=links[pick]
|
334
|
+
url="http://#{url}" unless url=~/^http/
|
335
|
+
`open '#{url}'`
|
336
|
+
end
|
337
|
+
show_ticket(number)
|
338
|
+
end
|
339
|
+
|
340
|
+
def comment(number,state=nil)
|
341
|
+
puts "create comment for #{number}"
|
342
|
+
ticket=get_ticket(number)
|
343
|
+
File.open("/tmp/fresnel_ticket_#{number}_comment", "w+") do |f|
|
344
|
+
f.puts "# Please enter the comment for this ticket. Lines starting"
|
345
|
+
f.puts "# with '#' will be ignored, and an empty message aborts the commit."
|
346
|
+
`echo "q" | fresnel #{number}`.each{ |l| f.write "# #{l}" }
|
347
|
+
end
|
348
|
+
system("mate -w /tmp/fresnel_ticket_#{number}_comment")
|
349
|
+
body=Array.new
|
350
|
+
File.read("/tmp/fresnel_ticket_#{number}_comment").each do |l|
|
351
|
+
body << l unless l=~/^#/
|
352
|
+
end
|
353
|
+
|
354
|
+
body=body.to_s
|
355
|
+
if body.blank?
|
356
|
+
puts Frame.new(:header=>"Warning !", :body=>"Aborting comment because it was blank !")
|
357
|
+
else
|
358
|
+
ticket.body=body
|
359
|
+
ticket.state=state unless state.nil?
|
360
|
+
if ticket.save
|
361
|
+
cache.clear(:name=>"fresnel_ticket_#{number}")
|
362
|
+
show_ticket(number)
|
363
|
+
else
|
364
|
+
puts "something went wrong"
|
365
|
+
puts $!
|
366
|
+
end
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
def create
|
371
|
+
system("mate -w /tmp/fresnel_new_ticket")
|
372
|
+
if File.exists?("/tmp/fresnel_new_ticket")
|
373
|
+
data=File.read("/tmp/fresnel_new_ticket")
|
374
|
+
body=Array.new
|
375
|
+
title=""
|
376
|
+
if data.blank?
|
377
|
+
puts Frame.new(:header=>"Warning !", :body=>"Aborting creation because the ticket was blank !")
|
378
|
+
else
|
379
|
+
data.each do |l|
|
380
|
+
if title.blank?
|
381
|
+
title=l
|
382
|
+
next
|
383
|
+
end
|
384
|
+
body << l
|
385
|
+
end
|
386
|
+
body=body.to_s
|
387
|
+
tags=ask("Tags : ")
|
388
|
+
tags=tags.split(" ")
|
389
|
+
end
|
390
|
+
puts "creating ticket..."
|
391
|
+
ticket = Lighthouse::Ticket.new(
|
392
|
+
:project_id=>self.current_project_id,
|
393
|
+
:title=>title,
|
394
|
+
:body=>body
|
395
|
+
)
|
396
|
+
ticket.tags=tags
|
397
|
+
if ticket.save
|
398
|
+
File.delete("/tmp/fresnel_new_ticket")
|
399
|
+
show_ticket(ticket.number)
|
400
|
+
else
|
401
|
+
puts "something went wrong !"
|
402
|
+
puts $!
|
403
|
+
end
|
404
|
+
else
|
405
|
+
puts Frame.new(:header=>"Warning !", :body=>"Aborting creation because the ticket was blank !")
|
406
|
+
end
|
407
|
+
end
|
408
|
+
|
409
|
+
def change_state(options)
|
410
|
+
puts "should change state to #{options.inspect}"
|
411
|
+
ticket=get_ticket(options[:ticket])
|
412
|
+
old_state=ticket.state
|
413
|
+
options[:state]="resolved" if options[:state]=~/closed?/
|
414
|
+
if options[:state]=~/resolved|invalid/
|
415
|
+
comment(options[:ticket],options[:state])
|
416
|
+
else
|
417
|
+
ticket.state=options[:state]
|
418
|
+
ticket.assigned_user_id=options[:user_id] if options[:user_id].present?
|
419
|
+
if ticket.save
|
420
|
+
cache.clear(:name=>"fresnel_ticket_#{options[:ticket]}")
|
421
|
+
if options[:user_id].present?
|
422
|
+
user_name = Lighthouse::User.find(options[:user_id]).name
|
423
|
+
puts Frame.new(:header=>"Success",:body=>"State has changed from #{old_state} to #{options[:state]} #{"and is reassigned to #{user_name}"}")
|
424
|
+
end
|
425
|
+
show_ticket(options[:ticket])
|
426
|
+
else
|
427
|
+
puts Frame.new(:header=>"Error !",:body=>"Something went wrong ! #{$!}")
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
def open_browser_for_ticket(number)
|
433
|
+
#fast
|
434
|
+
#`open "https://#{self.account}.lighthouseapp.com/projects/#{self.current_project_id}/tickets/#{number}"`
|
435
|
+
#or save
|
436
|
+
puts "opening ticket #{number}in browser"
|
437
|
+
`open "#{get_ticket(number).url}"`
|
438
|
+
show_ticket(number)
|
439
|
+
end
|
440
|
+
|
441
|
+
def assign(options)
|
442
|
+
unless options[:user_id]
|
443
|
+
puts "should assign ticket #{options[:ticket]} to someone :"
|
444
|
+
members=get_project_members
|
445
|
+
members_table = table do |t|
|
446
|
+
t.headings = ['#', 'user_id', 'username']
|
447
|
+
members.each_with_index do |member,i|
|
448
|
+
t << [i, member.user.id, member.user.name]
|
449
|
+
end
|
450
|
+
end
|
451
|
+
puts members_table
|
452
|
+
pick=ask("Assign to # : ",Integer) do |q|
|
453
|
+
q.above=-1
|
454
|
+
q.below=members.count
|
455
|
+
end
|
456
|
+
options[:user_id]=members[pick].user.id
|
457
|
+
end
|
458
|
+
ticket=get_ticket(options[:ticket])
|
459
|
+
ticket.assigned_user_id=options[:user_id]
|
460
|
+
if ticket.save
|
461
|
+
cache.clear(:name=>"fresnel_ticket_#{options[:ticket]}")
|
462
|
+
user_name = Lighthouse::User.find(options[:user_id]).name
|
463
|
+
puts Frame.new(:header=>"Success",:body=>"Reassigned ticket ##{options[:ticket]} to #{user_name} !")
|
464
|
+
else
|
465
|
+
puts Frame.new(:header=>"Error",:body=>"assigning failed !")
|
466
|
+
end
|
467
|
+
end
|
468
|
+
|
469
|
+
def claim(options)
|
470
|
+
puts "current user is : #{Lighthouse::User.find(self.current_user_id).name}"
|
471
|
+
ticket=get_ticket(options[:ticket])
|
472
|
+
if ticket.state=="new"
|
473
|
+
change_state(:ticket=>options[:ticket], :state=>"open", :user_id=>self.current_user_id) #get around the cache ...
|
474
|
+
else
|
475
|
+
assign(:ticket=>options[:ticket],:user_id=>self.current_user_id)
|
476
|
+
end
|
477
|
+
|
478
|
+
end
|
479
|
+
end
|