confuddle 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (65) hide show
  1. data/.gitignore +18 -0
  2. data/.passwd_to_unfuddle.example.yml +7 -0
  3. data/README.md +40 -0
  4. data/bin/un +833 -0
  5. data/bin/un.cmd +1 -0
  6. data/confuddle.gemspec +22 -0
  7. data/lib/graft/README.rdoc +138 -0
  8. data/lib/graft/Rakefile +43 -0
  9. data/lib/graft/lib/graft/core_ext/hash.rb +9 -0
  10. data/lib/graft/lib/graft/json.rb +14 -0
  11. data/lib/graft/lib/graft/json/attribute.rb +18 -0
  12. data/lib/graft/lib/graft/json/model.rb +28 -0
  13. data/lib/graft/lib/graft/model.rb +43 -0
  14. data/lib/graft/lib/graft/version.rb +13 -0
  15. data/lib/graft/lib/graft/xml.rb +19 -0
  16. data/lib/graft/lib/graft/xml/attribute.rb +55 -0
  17. data/lib/graft/lib/graft/xml/model.rb +49 -0
  18. data/lib/graft/lib/graft/xml/type.rb +91 -0
  19. data/lib/graft/test/test_helper.rb +38 -0
  20. data/lib/graft/test/unit/core_ext/hash_test.rb +29 -0
  21. data/lib/graft/test/unit/json/attribute_test.rb +51 -0
  22. data/lib/graft/test/unit/json/model_test.rb +86 -0
  23. data/lib/graft/test/unit/xml/attribute_test.rb +161 -0
  24. data/lib/graft/test/unit/xml/model_test.rb +173 -0
  25. data/lib/graft/test/unit/xml/type_test.rb +65 -0
  26. data/lib/unfuzzle/.gitignore +4 -0
  27. data/lib/unfuzzle/README.rdoc +129 -0
  28. data/lib/unfuzzle/Rakefile +39 -0
  29. data/lib/unfuzzle/lib/unfuzzle.rb +87 -0
  30. data/lib/unfuzzle/lib/unfuzzle/comment.rb +37 -0
  31. data/lib/unfuzzle/lib/unfuzzle/component.rb +31 -0
  32. data/lib/unfuzzle/lib/unfuzzle/milestone.rb +54 -0
  33. data/lib/unfuzzle/lib/unfuzzle/person.rb +20 -0
  34. data/lib/unfuzzle/lib/unfuzzle/priority.rb +30 -0
  35. data/lib/unfuzzle/lib/unfuzzle/project.rb +62 -0
  36. data/lib/unfuzzle/lib/unfuzzle/request.rb +75 -0
  37. data/lib/unfuzzle/lib/unfuzzle/response.rb +25 -0
  38. data/lib/unfuzzle/lib/unfuzzle/severity.rb +31 -0
  39. data/lib/unfuzzle/lib/unfuzzle/ticket.rb +156 -0
  40. data/lib/unfuzzle/lib/unfuzzle/ticket_report.rb +29 -0
  41. data/lib/unfuzzle/lib/unfuzzle/time_entry.rb +75 -0
  42. data/lib/unfuzzle/lib/unfuzzle/version.rb +13 -0
  43. data/lib/unfuzzle/test/fixtures/component.xml +8 -0
  44. data/lib/unfuzzle/test/fixtures/components.xml +17 -0
  45. data/lib/unfuzzle/test/fixtures/milestone.xml +12 -0
  46. data/lib/unfuzzle/test/fixtures/milestones.xml +25 -0
  47. data/lib/unfuzzle/test/fixtures/project.xml +17 -0
  48. data/lib/unfuzzle/test/fixtures/projects.xml +35 -0
  49. data/lib/unfuzzle/test/fixtures/severities.xml +24 -0
  50. data/lib/unfuzzle/test/fixtures/severity.xml +8 -0
  51. data/lib/unfuzzle/test/fixtures/ticket.xml +25 -0
  52. data/lib/unfuzzle/test/fixtures/tickets.xml +51 -0
  53. data/lib/unfuzzle/test/test_helper.rb +60 -0
  54. data/lib/unfuzzle/test/unit/unfuzzle/component_test.rb +36 -0
  55. data/lib/unfuzzle/test/unit/unfuzzle/milestone_test.rb +100 -0
  56. data/lib/unfuzzle/test/unit/unfuzzle/priority_test.rb +25 -0
  57. data/lib/unfuzzle/test/unit/unfuzzle/project_test.rb +87 -0
  58. data/lib/unfuzzle/test/unit/unfuzzle/request_test.rb +104 -0
  59. data/lib/unfuzzle/test/unit/unfuzzle/response_test.rb +37 -0
  60. data/lib/unfuzzle/test/unit/unfuzzle/severity_test.rb +36 -0
  61. data/lib/unfuzzle/test/unit/unfuzzle/ticket_test.rb +181 -0
  62. data/lib/unfuzzle/test/unit/unfuzzle_test.rb +39 -0
  63. data/lib/unfuzzle/unfuzzle.gemspec +31 -0
  64. data/lib/version.rb +3 -0
  65. metadata +176 -0
@@ -0,0 +1,18 @@
1
+ .DS_Store
2
+ log
3
+ log/*.log
4
+ tmp/*
5
+ nbproject
6
+ .tmp_*
7
+ .project
8
+ .idea/**/*
9
+ .idea/*
10
+ .idea
11
+ .rakeTasks
12
+ *.iml
13
+ *.pid
14
+ database.yml
15
+ *.auth
16
+ .generators
17
+ .rakeTasks
18
+ *.gem
@@ -0,0 +1,7 @@
1
+ subdomain: kripton
2
+ username: superman
3
+ password: ihateluther
4
+ default_project_id: 54321 # ticket number 1234 or #1234
5
+ projects_aliases:
6
+ 54321: a # number like a#1234
7
+ 12345: b # number like b#1234
@@ -0,0 +1,40 @@
1
+ Console Unfuddle
2
+ ================
3
+
4
+ Utility for work with unfuddle.com account from console
5
+
6
+ 1. gem install confuddle
7
+ 2. un install
8
+ 3. Edit HOME_DIR/.passwd_to_unfuddle.yml and fill with your own values
9
+ 4. $ un projects
10
+ and set for each project aliases "un alias"
11
+ and set current project "un curr"
12
+
13
+ Tasks:
14
+
15
+ un addcm NUMBER # add ticket comment
16
+ un addt NUMBER HOURS COMMENT [DATE] # add time
17
+ un alias PROJECT_ID ALIAS # set project alias
18
+ un all [REGEXP] # show all tickets for current project
19
+ un alla [REGEXP] # show all (in all projects) tickets
20
+ un assi TICKETS [NEW_ASSIGNEE] # update tickets assingee
21
+ un at PERIOD # show all times report for account (PERIOD = [tm lm tw lw y [0-9]+])
22
+ un atm PERIOD # show my times report for account (PERIOD = [tm lm tw lw y [0-9]+])
23
+ un clear # clear caches
24
+ un curr ID_OR_ALIAS # set current project id
25
+ un help [TASK] # Describe available tasks or one specific task
26
+ un my # show tickets assignee to me for current project
27
+ un mya # show tickets (in all projects) assignee to me
28
+ un new ALIAS TITLE [ASSIGNEE] [PRIO] # create ticket
29
+ un notify PARAMS # Notify about changes with tickets by Gnome-Notify, PARAMS='status,comments'
30
+ un op TICKET_ID # Open selected ticked from current project in browser
31
+ un projects # show all projects for your account
32
+ un show NUMBER [t] # show ticket by number, [t] - show TimeEntries
33
+ un t PERIOD # show all times report (PERIOD = [tm lm tw lw y [0-9]+])
34
+ un tm PERIOD # show my times report (PERIOD = [tm lm tw lw y [0-9]+])
35
+ un upd TICKETS NEW_STATUS # update tickets statuses
36
+
37
+ Used gems:
38
+
39
+ unfuzzle by Patrick Reagan of Viget Labs
40
+ grapt by Patrick Reagan
data/bin/un ADDED
@@ -0,0 +1,833 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Console Unfuddle
4
+ # by Makarchev K. 2011
5
+ # by Varamashvili M. 2011
6
+
7
+ WIN = RUBY_PLATFORM['mingw'] || RUBY_PLATFORM['mswin']
8
+ MAC = RUBY_PLATFORM['darwin']
9
+
10
+ $KCODE='u' if RUBY_VERSION <= "1.8.7"
11
+
12
+ require 'rubygems'
13
+ require 'yaml'
14
+ require 'active_support'
15
+ require 'active_support/time'
16
+
17
+ if(WIN)
18
+ require 'iconv'
19
+ require 'win32console'
20
+ require File.expand_path(File.join(File.dirname(__FILE__), %w(.. lib unfuzzle lib unfuzzle) ))
21
+ else
22
+ dirname = File.symlink?(__FILE__) ? File.dirname(File.readlink(__FILE__)) : File.dirname(__FILE__)
23
+ require File.expand_path(File.join(dirname, %w(.. lib unfuzzle lib unfuzzle) ))
24
+ end
25
+
26
+ require 'thor'
27
+ require 'readline'
28
+
29
+ class Unfuddle < Thor
30
+
31
+ UNFUDDLE_ENC = "utf-8"
32
+ WIN_ENC = "cp866"
33
+ CMD_WIN_ENC = "windows-1251"
34
+ HOME_DIR = ENV['HOME']
35
+ PASS_FILE = File.expand_path("#{HOME_DIR}/.passwd_to_unfuddle.yml")
36
+ CACHED_PEOPLE = "#{HOME_DIR}/.cached_people.yml"
37
+ CACHED_PROJECTS = "#{HOME_DIR}/.cached_projects.yml"
38
+ CACHED_TICKETS = "#{HOME_DIR}/.cached_tickets.yml"
39
+ CACHED_PERIOD = 10.days
40
+ CACHED_PROJECTS_PERIOD = 10.days
41
+ CACHED_TICKETS_PERIOD = 60 * 60
42
+
43
+ attr_accessor :default_project_id, :projects_aliases
44
+
45
+ def initialize(*args)
46
+ task_name = args[2][:current_task].name rescue 'unknown'
47
+
48
+ unless task_name == 'install'
49
+
50
+ get_config
51
+
52
+ unless me_id
53
+ say "Error: no matches for me", :red, true
54
+ exit 1
55
+ end
56
+
57
+ @started_at = Time.now
58
+ end
59
+
60
+ super
61
+ end
62
+
63
+
64
+ private
65
+ def get_config
66
+ unless File.exists?(PASS_FILE)
67
+ say "Error: not found #{PASS_FILE}, call `un install`", :red, true
68
+ exit 1
69
+ end
70
+
71
+ cfg = YAML.load_file(PASS_FILE)
72
+ Unfuzzle.subdomain = cfg['subdomain']
73
+ Unfuzzle.username = cfg['username']
74
+ Unfuzzle.password = cfg['password']
75
+ Unfuzzle.use_ssl = true
76
+
77
+ self.default_project_id = cfg['default_project_id']
78
+ self.default_project_id = cached_projects.keys.first if default_project_id.blank? || ([self.default_project_id] & cached_projects.keys).empty?
79
+
80
+ self.projects_aliases = cfg['projects_aliases'] || {}
81
+ end
82
+
83
+ def set_config
84
+ h = {}
85
+ h['subdomain'] = Unfuzzle.subdomain
86
+ h['username'] = Unfuzzle.username
87
+ h['password'] = Unfuzzle.password
88
+
89
+ h['default_project_id'] = self.default_project_id
90
+ h['projects_aliases'] = self.projects_aliases
91
+
92
+ File.open(PASS_FILE, "w+"){ |file| file.puts(h.to_yaml) }
93
+ end
94
+
95
+ if WIN
96
+ def say(*args)
97
+ args[0] = Iconv.new(WIN_ENC, UNFUDDLE_ENC).iconv(args.at(0).to_s) rescue args.at(0).to_s
98
+ super
99
+ end
100
+ end
101
+
102
+ def enc_input(text)
103
+ if WIN
104
+ Iconv.new(UNFUDDLE_ENC, WIN_ENC).iconv(text.to_s)
105
+ else
106
+ text
107
+ end
108
+ end
109
+
110
+ def enc_cmd(text)
111
+ if WIN
112
+ Iconv.new(UNFUDDLE_ENC, CMD_WIN_ENC).iconv(text.to_s)
113
+ else
114
+ text
115
+ end
116
+ end
117
+
118
+ def color(prio)
119
+ case prio
120
+ when 1; :blue
121
+ when 2; :cyan
122
+ when 3; nil
123
+ when 4; :red
124
+ when 5; :on_red
125
+ end
126
+ end
127
+
128
+ def read_text
129
+ lines = []
130
+ while line = Readline::readline("> ")
131
+ lines << line
132
+ end
133
+
134
+ rescue Interrupt => e
135
+ ensure
136
+ return enc_input(lines * "\n")
137
+ end
138
+
139
+ # people {id => name}
140
+ def people
141
+ @people ||= {}.tap do |h|
142
+ cached_people.each{|id, data| h[id] = data[:name] }
143
+ end
144
+ end
145
+
146
+ # cached people
147
+ def cached_people
148
+ return @cached_people if @cached_people
149
+
150
+ # cached peoples
151
+ if File.exists?(CACHED_PEOPLE) && File.ctime(CACHED_PEOPLE) > (Time.now - CACHED_PERIOD)
152
+ @cached_people = YAML.load_file(CACHED_PEOPLE)
153
+ else
154
+ people = Unfuzzle::Person.all
155
+ @cached_people = {}
156
+ people.each do |person|
157
+ name = if person.last_name.to_s.blank?
158
+ person.first_name.to_s.split(" ").reverse.join(" ")
159
+ else
160
+ person.last_name.to_s + " " + person.first_name.to_s
161
+ end
162
+ name = name.split(" ")
163
+ if name.size > 0
164
+ name[1] = name[1].to_s.mb_chars[0].to_s + "."
165
+ end
166
+ name = name.join(" ")
167
+ @cached_people[person.id] = {:name => name, :login => person.username}
168
+ end
169
+ File.open(CACHED_PEOPLE, 'w'){|f| f.write(YAML.dump(@cached_people))}
170
+ end
171
+
172
+ @cached_people
173
+ end
174
+
175
+ # my id
176
+ def me_id
177
+ @me_id ||= cached_people.detect{|id, data| data[:login] == Unfuzzle.username}.at(0)
178
+ end
179
+
180
+ def me
181
+ @me ||= people[me_id]
182
+ end
183
+
184
+
185
+ def cached_projects
186
+ return @cached_projects if @cached_projects
187
+
188
+ # cached peoples
189
+ if File.exists?(CACHED_PROJECTS) && File.ctime(CACHED_PROJECTS) > (Time.now - CACHED_PROJECTS_PERIOD)
190
+ @cached_projects = YAML.load_file(CACHED_PROJECTS)
191
+ else
192
+ projects = Unfuzzle.projects
193
+ @cached_projects = {}
194
+ projects.each do |project|
195
+ @cached_projects[project.id] = project.name
196
+ end
197
+ File.open(CACHED_PROJECTS, 'w'){|f| f.write(YAML.dump(@cached_projects))}
198
+ end
199
+
200
+ @cached_projects
201
+ end
202
+
203
+ def cached_tickets(params)
204
+ return @cached_tickets if @cached_tickets
205
+ if File.exists?(CACHED_TICKETS)
206
+ yaml = YAML.load_file(CACHED_TICKETS)
207
+ @cached_tickets = yaml[params.to_s] || []
208
+ else
209
+ @cached_tickets = []
210
+ end
211
+ @cached_tickets
212
+ end
213
+
214
+ def set_cached_tickets(t, params)
215
+ yaml = if File.exists?(CACHED_TICKETS)
216
+ YAML.load_file(CACHED_TICKETS)
217
+ else
218
+ {}
219
+ end
220
+
221
+ yaml[params.to_s] = t.map(&:to_hash)
222
+
223
+ File.open(CACHED_TICKETS, 'w'){|f| f.write(YAML.dump(yaml))}
224
+ end
225
+
226
+ # number with alias
227
+ def number_with_a(ticket)
228
+ number_with_a_by_params(ticket.project_id, ticket.number)
229
+ end
230
+
231
+ def number_with_a_by_params(project_id, number)
232
+ return number.to_s if project_id == self.default_project_id
233
+ self.projects_aliases[ project_id ].to_s + number.to_s
234
+ end
235
+
236
+ def find_project_id_by_alias(al)
237
+ return default_project_id if al.blank?
238
+
239
+ res = self.projects_aliases.detect{|k,v| v == al}
240
+ if res.present?
241
+ res.first.to_i
242
+ else
243
+ default_project_id
244
+ end
245
+ end
246
+
247
+ # parse number
248
+ # f1234 -> [project_id, 1234]
249
+ # p1235 -> [project_id, 1235]
250
+ # 1235 -> [default_project_id, 1235]
251
+ def parse_number(entered_number)
252
+ entered_number.to_s =~ /(.*?)([0-9]+)/i
253
+ prj = $1.to_s
254
+ num = $2.to_s
255
+
256
+ if prj.blank?
257
+ [default_project_id, num.to_i]
258
+ else
259
+ [find_project_id_by_alias(prj), num.to_i]
260
+ end
261
+ end
262
+
263
+ # === Methods ===
264
+
265
+ # find persons by regexp, if blank than find me
266
+ def find_persons(regx = "")
267
+ return nil if regx.nil?
268
+
269
+ res = []
270
+ if regx == ""
271
+ return [me_id]
272
+ else
273
+ regx = enc_cmd(regx)
274
+ res = people.select{|id, login| login.mb_chars.downcase =~ /#{regx.mb_chars.strip.downcase}/i}
275
+ end
276
+
277
+ if !res.empty?
278
+ res.map &:first
279
+ else
280
+ say "no peoples matched #{regx}"
281
+ []
282
+ end
283
+ end
284
+
285
+ def show_projects #!
286
+ projects = Unfuzzle.projects
287
+
288
+ say " ", nil, false
289
+ say "id".ljust(10), nil, false
290
+ say "name".ljust(20), nil, false
291
+ say 'title'.ljust(20), nil, false
292
+ say "disk-usage".ljust(20), nil, false
293
+ say "alias"
294
+
295
+ projects.each do |project|
296
+ say project.id.to_i == self.default_project_id ? " * " : " ", nil, false
297
+ say project.id.to_s.ljust(10), :yellow, false
298
+ say project.slug.ljust(20), :green, false
299
+ say project.name.ljust(20), nil, false
300
+ say project.disk_usage.to_s.ljust(20), nil, false
301
+ say projects_aliases[project.id].to_s
302
+ end
303
+ end
304
+
305
+ # Ticket Heads
306
+
307
+ def render_ticket_head(ticket)
308
+ color = color(ticket.priority)
309
+ say number_with_a(ticket).to_s.ljust(9), :yellow, false
310
+ say ticket.title.mb_chars[0..60].ljust(65), color, false
311
+ say ticket.status.ljust(14), color, false
312
+ say people[ticket.reporter_id].to_s.mb_chars.ljust(15), color, false
313
+ say people[ticket.assignee_id].to_s.mb_chars.ljust(15), color, false
314
+ say ticket.hours.to_s, color, true
315
+ end
316
+
317
+ def render_ticket_heads(tickets)
318
+ groups = {}
319
+ tickets.each do |ticket|
320
+ groups[ticket.status] ||= []
321
+ groups[ticket.status] << ticket
322
+ end
323
+
324
+ groups.each do |group, tickets|
325
+ tickets = tickets.sort_by{|t| t.number.to_i}
326
+ tickets.each{|ticket| render_ticket_head(ticket) }
327
+ say ''
328
+ end
329
+ end
330
+
331
+ def render_ticket_heads_by_people(t)
332
+ groups = {}
333
+ t.each do |ticket|
334
+ groups[ticket.assignee_id] ||= []
335
+ groups[ticket.assignee_id] << ticket
336
+ end
337
+
338
+ groups.each do |ass_id, tickets|
339
+ say "============== #{people[ass_id].to_s.mb_chars} ================ (#{tickets.size}) tickets", :on_red, true
340
+ render_ticket_heads tickets
341
+ say ''
342
+ end
343
+ end
344
+
345
+ def render_ticket_heads_by_project(t)
346
+ groups = {}
347
+ t.each do |ticket|
348
+ groups[ticket.project_id] ||= []
349
+ groups[ticket.project_id] << ticket
350
+ end
351
+
352
+ groups.each do |project_id, tickets|
353
+ say "Project => #{cached_projects[project_id]}: ", :yellow, true
354
+ render_ticket_heads_by_people tickets
355
+ say ''
356
+ end
357
+ end
358
+
359
+ def show_all_active_tickets(name = nil, all_projects = false) #!
360
+ ass_ids = find_persons(name == "" ? nil : name) || []
361
+ t = Unfuzzle::Ticket.all_by_dinamic_report(all_projects ? nil : [default_project_id], false)
362
+ t.reject!{|ticket| !ass_ids.blank? && !ass_ids.include?(ticket.assignee_id) }
363
+ render_ticket_heads_by_project t
364
+ end
365
+
366
+ def show_my_tickets(all_projects = false) #!
367
+ t = Unfuzzle::Ticket.all_by_dinamic_report(all_projects ? nil : [default_project_id], true)
368
+ render_ticket_heads_by_project t
369
+ end
370
+
371
+ # TimeEntry
372
+
373
+ def render_times(times, sum_only = false)
374
+ times = times.sort_by{|t| t.date.to_s }
375
+
376
+ sum = 0
377
+ title_present = false
378
+
379
+ times.each do |time|
380
+ unless sum_only
381
+ #say time.ticket_id.to_s.ljust(8), :yellow, false
382
+ title_present = true if time.title.present?
383
+ say time.title.to_s.mb_chars[0..30].ljust(34), :yellow, false if title_present
384
+ say time.description.mb_chars[0..50].ljust(54), nil, false
385
+ say (time.hours.to_s + " h.").ljust(9), nil, false
386
+ say people[time.person_id].to_s.mb_chars.ljust(13), nil, false
387
+ say time.date, nil
388
+ end
389
+
390
+ sum += time.hours.to_f
391
+ end
392
+
393
+ say ''.ljust(34), nil, false if title_present
394
+ say ''.ljust(54), nil, false
395
+ say (sum.to_s + " h.").ljust(10), :red, true
396
+
397
+ sum
398
+ end
399
+
400
+ def render_people_times(times, sum_only = false)
401
+ groups = {}
402
+ times.each do |time|
403
+ groups[time.person_id] ||= []
404
+ groups[time.person_id] << time
405
+ end
406
+
407
+ groups.each do |group, times|
408
+ say "============== #{people[group].to_s.mb_chars} ================", :on_red, true
409
+ render_times(times, sum_only)
410
+ say ''
411
+ end
412
+ end
413
+
414
+ def filter_times(times, for_id = nil)
415
+ times.select do |time|
416
+ time.person_id == for_id
417
+ end
418
+ end
419
+
420
+ def times_report(me = false, period = 0, sum_only=false)
421
+ p = periods(period)
422
+ t = Unfuzzle::TimeEntry.time_invested(default_project_id, p.at(0), p.at(1))
423
+ t = filter_times(t, me_id) if me
424
+ render_people_times(t, sum_only)
425
+ end
426
+
427
+ def all_times_report(me = false, period = 0, sum_only=false)
428
+ p = periods(period)
429
+ t = Unfuzzle::TimeEntry.all_time_invested(p.at(0), p.at(1))
430
+ t = filter_times(t, me_id) if me
431
+ render_people_times(t, sum_only)
432
+ end
433
+
434
+ def periods(period)
435
+ case period
436
+ when 'ty' then [Time.now.beginning_of_year, Time.now]
437
+ when 'ly' then [(Time.now.beginning_of_year-1.seconds).beginning_of_year, (Time.now.beginning_of_year-1.seconds)]
438
+ when 'tm' then [Time.now.beginning_of_month, Time.now]
439
+ when 'lm' then [(Time.now.beginning_of_month-1.seconds).beginning_of_month, (Time.now.beginning_of_month-1.seconds)]
440
+ when 'tw' then [Time.now.beginning_of_week, Time.now]
441
+ when 'lw' then [(Time.now.beginning_of_week-1.seconds).beginning_of_week, (Time.now.beginning_of_week-1.seconds)]
442
+ when 'y' then [(Time.new - 1.day).beginning_of_day, ((Time.new - 1.day).end_of_day)]
443
+ else [Time.now - period.to_i.days, Time.now]
444
+ end
445
+ end
446
+
447
+ # Ticket
448
+
449
+ def render_comment(cm)
450
+ say people[cm.author_id].to_s.mb_chars, :on_blue, false
451
+ say " " + cm.created_at.to_s, nil, true
452
+ say cm.body.mb_chars, nil, true
453
+ end
454
+
455
+ def render_ticket(t, comments = [], time_entries = [])
456
+ color = color(t.priority)
457
+
458
+ say number_with_a(t).to_s, :on_red
459
+ say t.title.to_s.mb_chars, :on_blue
460
+ say cached_projects[t.project_id].to_s, color, true
461
+ say t.status, color
462
+ say t.priority_name, color
463
+ say people[t.reporter_id].to_s + " => " + people[t.assignee_id].to_s, color, true
464
+ say t.description.to_s.mb_chars, color, true
465
+ say t.hours.to_s + " h.", color
466
+
467
+ if !time_entries.blank?
468
+ say ''
469
+ say "Time Entries: ", :blue, true
470
+ render_times(time_entries)
471
+ end
472
+
473
+ if !comments.blank?
474
+ say ''
475
+ say 'Comments: ', :on_red, true
476
+ comments.each{|cm| render_comment(cm) }
477
+ end
478
+ end
479
+
480
+ def show_ticket(num, opt)
481
+ prj, number = parse_number(num)
482
+ t, cm = Unfuzzle::Ticket.find_first_by_project_id_and_number_with_comments(prj, number)
483
+ times = (opt == "t") ? Unfuzzle::TimeEntry.all_for_ticket(t) : []
484
+ render_ticket(t, cm, times)
485
+ end
486
+
487
+ def open_ticket_in_browser(num)
488
+ prj, number = parse_number(num)
489
+ url = "https://#{Unfuzzle.subdomain}.unfuddle.com/a#/projects/#{prj}/tickets/by_number/#{number}"
490
+ if(WIN)
491
+ system("start #{url}")
492
+ elsif(MAC)
493
+ system("open #{url}")
494
+ else
495
+ system("xdg-open #{url}")
496
+ end
497
+ end
498
+
499
+ #
500
+ def update_tickets(tickets, new_status)
501
+ tickets = tickets.split(",").map &:strip
502
+ if !tickets.blank? && !new_status.blank?
503
+ ts = tickets.map{|num|
504
+ prj, number = parse_number(num)
505
+ Unfuzzle::Ticket.find_first_by_project_id_and_number(prj, number) rescue nil
506
+ }.compact
507
+ say "Update tickets #{ts.map(&:number) * ','} to status #{new_status}"
508
+
509
+ ts.each do |t|
510
+ t.status = new_status
511
+ t.update
512
+ render_ticket(t)
513
+ end
514
+ else
515
+ say "no one tickets"
516
+ end
517
+ end
518
+
519
+ def update_tickets_assignee(tickets, new_assignee)
520
+ tickets = tickets.split(",").map &:strip
521
+ if !tickets.blank?
522
+ ts = tickets.map{|num|
523
+ prj, number = parse_number(num)
524
+ Unfuzzle::Ticket.find_first_by_project_id_and_number(prj, number) rescue nil
525
+ }.compact
526
+
527
+ # find person
528
+ ass_ids = find_persons(new_assignee)
529
+ exit 1 if ass_ids.size < 1
530
+
531
+ person = people[ass_ids.first]
532
+
533
+ say "Update tickets #{ts.map(&:number) * ','} to assignee #{person}"
534
+
535
+ ts.each do |t|
536
+ t.assignee_id = ass_ids.first
537
+ t.update
538
+ render_ticket(t)
539
+ end
540
+ else
541
+ say "no one tickets"
542
+ end
543
+ end
544
+
545
+ def new_ticket(title, assignee = "", priority = 3)
546
+ # find user
547
+ ass_id = find_persons(assignee).first
548
+
549
+ prio = priority.blank? ? 3 : priority.to_i
550
+
551
+ t = Unfuzzle::Ticket.new
552
+ t.title = enc_cmd(title)
553
+ t.assignee_id = ass_id
554
+ t.reporter_id = find_persons.first # me
555
+ t.priority = prio
556
+ t.project_id = default_project_id
557
+
558
+ say "Now enter description"
559
+ t.description = read_text
560
+
561
+ say "Create ticket: ", nil, true
562
+ say t.title, nil, true
563
+ say t.description, nil, true
564
+ say people[t.assignee_id]
565
+ say ''
566
+
567
+ t.create
568
+ render_ticket(t)
569
+ end
570
+
571
+ def add_comment(number)
572
+ prj, number = parse_number(number)
573
+ t, cms = Unfuzzle::Ticket.find_first_by_project_id_and_number_with_comments(prj, number)
574
+
575
+ say "Enter body of comment: ", :red, true
576
+ text = read_text
577
+
578
+ cm = Unfuzzle::Comment.new
579
+ cm.body = text
580
+ cm.author_id = me_id
581
+
582
+ cm.create(t.project_id, t.id)
583
+
584
+ cms += [cm]
585
+
586
+ render_ticket(t, cms)
587
+ end
588
+
589
+ def add_time(number, hours, comment = "", date = "")
590
+ prj, number = parse_number(number)
591
+ t, cms = Unfuzzle::Ticket.find_first_by_project_id_and_number_with_comments(prj, number)
592
+
593
+ time = Unfuzzle::TimeEntry.new
594
+ time.description = enc_cmd(comment)
595
+ time.hours = hours
596
+ time.ticket_id = t.id
597
+ time.date = date.blank? ? Time.now.strftime("%Y-%m-%d") : date
598
+ time.person_id = me_id
599
+
600
+ time.create(t.project_id, t.id)
601
+
602
+ times = Unfuzzle::TimeEntry.all_for_ticket(t)
603
+
604
+ t.hours = (t.hours.to_f + time.hours.to_f).to_s
605
+ render_ticket(t, cms, times)
606
+ end
607
+
608
+ def set_current(number)
609
+ if cached_projects.keys.include?(number.to_i)
610
+ self.default_project_id = number.to_i
611
+ set_config
612
+ say "set current project to #{number}"
613
+ say ''
614
+ show_projects
615
+ elsif projects_aliases.values.include?(number.to_s) # alias?
616
+ self.default_project_id = projects_aliases.detect{|k,v| v == number.to_s}.first rescue self.default_project_id
617
+ set_config
618
+ say "set current project to #{number}"
619
+ say ''
620
+ show_projects
621
+ else
622
+ say "bad number #{number}, should be in #{cached_projects.keys.inspect}"
623
+ end
624
+ end
625
+
626
+ def set_alias(number, _alias)
627
+ if cached_projects.keys.include?(number.to_i)
628
+ self.projects_aliases[number.to_i] = _alias
629
+ set_config
630
+ say "set project #{number} alias to #{_alias}"
631
+ say
632
+ show_projects
633
+ else
634
+ say "bad number #{number}, should be in #{cached_projects.keys.inspect}"
635
+ end
636
+ end
637
+
638
+ def notify_periodic(params = '')
639
+ t = Unfuzzle::Ticket.all_by_dinamic_report(nil, true).map(&:to_hash)
640
+
641
+ # options
642
+ show_new = true # показывать новые созданные тикеты
643
+ show_statuses = true # показывать изменения статусов тикетов
644
+ show_closed = true # показывать какие были закрыты
645
+ all_tickets = true # учитывать ли все тикеты (не только привязанные ко мне)
646
+ my_tickets = true # делать нотификацию только тикетов которые созданы мной или на меня
647
+ all_projects = true # тикеты брать со всех проектов или с текущего
648
+
649
+ params = [show_new,show_statuses,show_closed,all_tickets,my_tickets,all_projects]
650
+
651
+ t = Unfuzzle::Ticket.all_by_dinamic_report(all_projects ? nil : [default_project_id], !all_tickets).map(&:to_hash)
652
+
653
+ cached = cached_tickets(params)
654
+
655
+ if cached.size == 0
656
+ say "first time: caching ..."
657
+ set_cached_tickets(t, params)
658
+ return
659
+ end
660
+
661
+ if my_tickets
662
+ cached = cached.select{|c| c['assignee-id'] == me_id || c['reporter-id'] == me_id }
663
+ t = t.select{|c| c['assignee-id'] == me_id || c['reporter-id'] == me_id }
664
+ end
665
+
666
+ cached_h = {}
667
+ t_h = {}
668
+
669
+ # into hash
670
+ cached.each{|c| k = [c['project-id'], c['number']]; cached_h[k] = c }
671
+ t.each{|c| k = [c['project-id'], c['number']]; t_h[k] = c }
672
+
673
+
674
+ message = []
675
+
676
+ # new tickets
677
+ if show_new && (t_h.keys - cached_h.keys).present?
678
+ (t_h.keys - cached_h.keys).each{|key| message << "New ticket #{number_with_a_by_params(key[0], key[1])}: '#{t_h[key]['summary']}'"}
679
+ end
680
+
681
+ # change status tickets
682
+ chanched = []
683
+ t_h.each{|key, value| chanched << key if cached_h[key] && value['status'] != cached_h[key]['status']}
684
+
685
+ if show_statuses && chanched.present?
686
+ chanched.each{|key| message << "#{number_with_a_by_params(key[0], key[1])}: '#{t_h[key]['summary']}' => #{t_h[key]['status']}"}
687
+ end
688
+
689
+ # closed tickets
690
+ if show_closed && (cached_h.keys - t_h.keys).present?
691
+ (cached_h.keys - t_h.keys).each{|key| message << "Closed ticket #{number_with_a_by_params(key[0], key[1])}: '#{cached_h[key]['summary']}'"}
692
+ end
693
+
694
+ message = message * "\n"
695
+ notify_message(message)
696
+
697
+ set_cached_tickets(t, params)
698
+ end
699
+
700
+ def notify_message(message)
701
+ return if message.empty?
702
+ if WIN
703
+ say message
704
+
705
+ elsif MAC
706
+ system("growlnotify --name 'confuddle' -s -m '#{message}'")
707
+
708
+ else
709
+ system("dbus-launch notify-send -t 5000 \"Unfuddle notification\" \"#{message}\"")
710
+ end
711
+ end
712
+
713
+ public
714
+
715
+ desc "projects", "show all projects for your account"
716
+ def projects
717
+ show_projects
718
+ end
719
+
720
+ desc "all [REGEXP]", "show all tickets for current project"
721
+ def all(regexp = "")
722
+ show_all_active_tickets(regexp)
723
+ end
724
+
725
+ desc "my", "show tickets assignee to me for current project"
726
+ def my
727
+ show_my_tickets
728
+ end
729
+
730
+ desc "alla [REGEXP]", "show all (in all projects) tickets"
731
+ def alla(regexp = "")
732
+ show_all_active_tickets(regexp, true)
733
+ end
734
+
735
+ desc "mya", "show tickets (in all projects) assignee to me"
736
+ def mya
737
+ show_my_tickets true
738
+ end
739
+
740
+ desc "show NUMBER [t]", "show ticket by number, [t] - show TimeEntries"
741
+ def show(number, opt = "")
742
+ show_ticket(number, opt)
743
+ end
744
+
745
+ # Upd
746
+ desc "upd TICKETS NEW_STATUS", "update tickets statuses"
747
+ def upd(tickets, new_status)
748
+ update_tickets(tickets, new_status)
749
+ end
750
+
751
+ desc "assi TICKETS [NEW_ASSIGNEE]", "update tickets assingee"
752
+ def assi(tickets, new_assignee = "")
753
+ update_tickets_assignee(tickets, new_assignee)
754
+ end
755
+
756
+ desc "clear", "clear caches"
757
+ def clear
758
+ require 'fileutils'
759
+ FileUtils.rm(CACHED_PEOPLE) rescue nil
760
+ FileUtils.rm(CACHED_PROJECTS) rescue nil
761
+ say "cached cleared"
762
+ end
763
+
764
+ # Times
765
+ desc "tm PERIOD", "show my times report (PERIOD = [tm lm tw lw y [0-9]+])"
766
+ def tm(period = 0, sum_only='')
767
+ times_report(true, period, sum_only == 's')
768
+ end
769
+
770
+ desc "t PERIOD", "show all times report (PERIOD = [tm lm tw lw y [0-9]+])"
771
+ def t(period = 0, sum_only='')
772
+ times_report(false, period, sum_only == 's')
773
+ end
774
+
775
+ desc "atm PERIOD", "show my times report for account (PERIOD = [tm lm tw lw y [0-9]+])"
776
+ def atm(period = 0, sum_only='')
777
+ all_times_report(true, period, sum_only == 's')
778
+ end
779
+
780
+ desc "at PERIOD", "show all times report for account (PERIOD = [tm lm tw lw y [0-9]+])"
781
+ def at(period = 0, sum_only='')
782
+ all_times_report(false, period, sum_only == 's')
783
+ end
784
+
785
+ # Add
786
+ desc "addt NUMBER HOURS COMMENT [DATE]", "add time"
787
+ def addt(ticket_number, hours, comment, date = nil)
788
+ add_time(ticket_number, hours, comment, date)
789
+ end
790
+
791
+ desc "addcm NUMBER", "add ticket comment"
792
+ def addcm(ticket_number)
793
+ add_comment(ticket_number)
794
+ end
795
+
796
+ desc "new TITLE [ASSIGNEE] [PRIO]", "create ticket"
797
+ def new(title, assignee = "", priority = 3)
798
+ new_ticket(title, assignee, priority)
799
+ end
800
+
801
+ desc "curr ID_OR_ALIAS", "set current project id"
802
+ def curr(current)
803
+ set_current(current)
804
+ end
805
+
806
+ desc "alias PROJECT_ID ALIAS", "set project alias"
807
+ def alias(project_id, _alias)
808
+ set_alias(project_id, _alias)
809
+ end
810
+
811
+ desc "op TICKET_ID", "Open selected ticked from current project in browser"
812
+ def op(ticket_id)
813
+ open_ticket_in_browser(ticket_id)
814
+ end
815
+
816
+ desc "notify PARAMS", "Notify about changes with tickets by Gnome-Notify, PARAMS='status,comments'"
817
+ def notify(params = '')
818
+ notify_periodic params
819
+ end
820
+
821
+ desc "install", "copy sample config into home"
822
+ def install
823
+ require 'fileutils'
824
+ file = File.expand_path(File.join(File.dirname(__FILE__), %w(.. .passwd_to_unfuddle.example.yml) ))
825
+ FileUtils.cp(file, PASS_FILE)
826
+ say("Config copyed!", :green)
827
+ say("Edit file #{PASS_FILE}!", :red)
828
+ end
829
+
830
+ default_task(:my) # by default
831
+ end
832
+
833
+ Unfuddle.start