fresnel 0.5.4
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/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
|