caperoma 0.1.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. data/.ruby-version +1 -0
  3. data/Capefile +48 -0
  4. data/Capefile.template +48 -0
  5. data/Capefile.test +20 -0
  6. data/Gemfile +25 -10
  7. data/Gemfile.lock +196 -77
  8. data/HELP +321 -0
  9. data/README.md +528 -0
  10. data/Rakefile +73 -18
  11. data/VERSION +1 -1
  12. data/bin/caperoma +47 -11
  13. data/caperoma.gemspec +144 -45
  14. data/config/crontab +10 -0
  15. data/config/schedule.rb +21 -0
  16. data/lib/caperoma.rb +409 -9
  17. data/lib/caperoma/models/account.rb +47 -0
  18. data/lib/caperoma/models/application_record.rb +5 -0
  19. data/lib/caperoma/models/branch.rb +6 -0
  20. data/lib/caperoma/models/project.rb +14 -0
  21. data/lib/caperoma/models/property.rb +5 -0
  22. data/lib/caperoma/models/report.rb +177 -0
  23. data/lib/caperoma/models/report_recipient.rb +6 -0
  24. data/lib/caperoma/models/reports/daily_report.rb +23 -0
  25. data/lib/caperoma/models/reports/retrospective_report.rb +19 -0
  26. data/lib/caperoma/models/reports/three_day_report.rb +19 -0
  27. data/lib/caperoma/models/task.rb +368 -0
  28. data/lib/caperoma/models/tasks/bug.rb +36 -0
  29. data/lib/caperoma/models/tasks/chore.rb +40 -0
  30. data/lib/caperoma/models/tasks/feature.rb +27 -0
  31. data/lib/caperoma/models/tasks/fix.rb +56 -0
  32. data/lib/caperoma/models/tasks/meeting.rb +40 -0
  33. data/lib/caperoma/models/tasks/modules/git.rb +65 -0
  34. data/lib/caperoma/models/tasks/task_with_commit.rb +40 -0
  35. data/lib/caperoma/models/tasks/task_with_separate_branch.rb +42 -0
  36. data/lib/caperoma/services/airbrake_email_processor.rb +47 -0
  37. data/lib/caperoma/services/pivotal_fetcher.rb +108 -0
  38. data/lib/caperoma/version.rb +9 -0
  39. data/spec/caperoma_spec.rb +3 -21
  40. data/spec/factories/accounts.rb +10 -0
  41. data/spec/factories/branches.rb +9 -0
  42. data/spec/factories/projects.rb +8 -0
  43. data/spec/factories/report_recipients.rb +7 -0
  44. data/spec/factories/reports.rb +16 -0
  45. data/spec/factories/tasks.rb +37 -0
  46. data/spec/features/bug_spec.rb +60 -0
  47. data/spec/features/chore_spec.rb +60 -0
  48. data/spec/features/command_unknown_spec.rb +14 -0
  49. data/spec/features/config_spec.rb +161 -0
  50. data/spec/features/feature_spec.rb +60 -0
  51. data/spec/features/finish_spec.rb +18 -0
  52. data/spec/features/fix_spec.rb +60 -0
  53. data/spec/features/meeting_spec.rb +22 -0
  54. data/spec/features/projects_spec.rb +17 -0
  55. data/spec/features/report_recipientss_spec.rb +117 -0
  56. data/spec/features/reports_spec.rb +65 -0
  57. data/spec/features/status_spec.rb +33 -0
  58. data/spec/features/version_spec.rb +11 -0
  59. data/spec/models/account_spec.rb +51 -0
  60. data/spec/models/branch_spec.rb +8 -0
  61. data/spec/models/bug_spec.rb +33 -0
  62. data/spec/models/chore_spec.rb +33 -0
  63. data/spec/models/daily_report_spec.rb +38 -0
  64. data/spec/models/feature_spec.rb +33 -0
  65. data/spec/models/fix_spec.rb +55 -0
  66. data/spec/models/meeting_spec.rb +33 -0
  67. data/spec/models/project_spec.rb +11 -0
  68. data/spec/models/report_recipient_spec.rb +22 -0
  69. data/spec/models/report_spec.rb +16 -0
  70. data/spec/models/retrospective_report_spec.rb +38 -0
  71. data/spec/models/task_spec.rb +613 -0
  72. data/spec/models/task_with_commit_spec.rb +105 -0
  73. data/spec/models/task_with_separate_branch_spec.rb +97 -0
  74. data/spec/models/three_day_report_spec.rb +49 -0
  75. data/spec/spec_helper.rb +26 -16
  76. data/spec/support/capefile_generator.rb +36 -0
  77. data/spec/support/database_cleaner.rb +21 -0
  78. data/spec/support/stubs.rb +178 -9
  79. metadata +283 -42
  80. data/.document +0 -5
  81. data/README.rdoc +0 -26
  82. data/lib/caperoma/credentials.rb +0 -13
  83. data/lib/caperoma/jira_client.rb +0 -57
  84. data/spec/caperoma/credentials_spec.rb +0 -25
  85. data/spec/caperoma/jira_spec.rb +0 -35
data/lib/caperoma.rb CHANGED
@@ -1,18 +1,418 @@
1
- require 'sdbm'
1
+ # frozen_string_literal: true
2
+
3
+ $VERBOSE = nil
4
+ require 'active_record'
5
+ require 'sqlite3'
6
+ require 'action_view'
7
+ require 'json'
8
+ require 'jbuilder'
9
+ require 'time_difference'
10
+ require 'pivotal-tracker'
11
+ require 'net/smtp'
12
+ require 'gmail'
13
+ require 'faraday'
14
+ require 'pp'
15
+
16
+ DB_SPEC = {
17
+ adapter: 'sqlite3',
18
+ database: ENV['CAPEROMA_TEST'].present? || ENV['CAPEROMA_INTEGRATION_TEST'].present? ? "#{ENV['HOME']}/.caperoma-test.sqlite3" : "#{ENV['HOME']}/.caperoma.sqlite3"
19
+ }.freeze
20
+
21
+ ActiveRecord::Base.establish_connection(DB_SPEC)
22
+
23
+ require 'caperoma/models/application_record'
24
+
25
+ require 'caperoma/models/account'
26
+ require 'caperoma/models/project'
27
+
28
+ require 'caperoma/models/tasks/modules/git'
29
+ require 'caperoma/models/task'
30
+ require 'caperoma/models/tasks/task_with_commit'
31
+ require 'caperoma/models/tasks/task_with_separate_branch'
32
+ require 'caperoma/models/tasks/chore'
33
+ require 'caperoma/models/tasks/bug'
34
+ require 'caperoma/models/tasks/fix'
35
+ require 'caperoma/models/tasks/feature'
36
+ require 'caperoma/models/tasks/meeting'
37
+
38
+ require 'caperoma/models/report_recipient'
39
+
40
+ require 'caperoma/models/branch'
41
+
42
+ require 'caperoma/models/report'
43
+ require 'caperoma/models/reports/daily_report'
44
+ require 'caperoma/models/reports/three_day_report'
45
+ require 'caperoma/models/reports/retrospective_report'
46
+
47
+ require 'caperoma/version'
48
+
49
+ require 'caperoma/services/pivotal_fetcher'
50
+ require 'caperoma/services/airbrake_email_processor'
2
51
 
3
52
  class Caperoma
4
- def self.chore(args = [])
5
- JiraClient.new.create_chore(args)
53
+ def self.setup
54
+ puts 'Initializing Caperoma'
55
+ ActiveRecord::Base.connection.close
56
+ File.delete(DB_SPEC[:database])
57
+
58
+ puts 'Creating local database for storing work information'
59
+ ActiveRecord::Base.establish_connection(DB_SPEC)
60
+ ActiveRecord::Schema.define do
61
+ create_table :accounts do |t|
62
+ t.column :username, :string
63
+ t.column :email, :string
64
+ t.column :password, :string
65
+ t.column :type, :string
66
+
67
+ t.timestamps
68
+ end
69
+
70
+ create_table :projects do |t|
71
+ t.column :name, :string
72
+ t.column :jira_project_id, :string
73
+ t.column :folder_path, :string
74
+ t.column :jira_url, :string
75
+ t.column :jira_project_id, :string
76
+ t.column :github_repo, :string
77
+ t.column :feature_jira_task_id, :string
78
+ t.column :bug_jira_task_id, :string
79
+ t.column :chore_jira_task_id, :string
80
+ t.column :fix_jira_task_id, :string
81
+ t.column :meeting_jira_task_id, :string
82
+ t.column :jira_transition_id_todo, :string
83
+ t.column :jira_transition_id_in_progress, :string
84
+ t.column :jira_transition_id_done, :string
85
+ t.column :pivotal_tracker_project_id, :string
86
+ t.column :create_features_in_pivotal, :boolean
87
+ t.column :create_bugs_in_pivotal, :boolean
88
+ t.column :create_chores_in_pivotal, :boolean
89
+ t.column :create_fixes_in_pivotal_as_chores, :boolean
90
+ t.column :create_meetings_in_pivotal_as_chores, :boolean
91
+
92
+ t.timestamps
93
+ end
94
+ add_index :projects, :jira_project_id
95
+
96
+ create_table :branches do |t|
97
+ t.column :project_id, :integer
98
+ t.column :name, :string
99
+
100
+ t.timestamps
101
+ end
102
+ add_index :branches, :project_id
103
+
104
+ create_table :tasks do |t|
105
+ t.column :project_id, :integer
106
+ t.column :branch_id, :integer
107
+ t.column :title, :string
108
+ t.column :description, :text
109
+ t.column :url, :text
110
+ t.column :uuid, :string
111
+ t.column :type, :string
112
+ t.column :jira_id, :string
113
+ t.column :jira_key, :string
114
+ t.column :jira_url, :string
115
+ t.column :parent_branch, :string
116
+ t.column :pivotal_id, :string
117
+ t.column :started_at, :datetime
118
+ t.column :finished_at, :datetime
119
+ t.column :daily_report_id, :integer
120
+ t.column :three_day_report_id, :integer
121
+ t.column :retrospective_report_id, :integer
122
+
123
+ t.timestamps
124
+ end
125
+ add_index :tasks, :project_id
126
+ add_index :tasks, :branch_id
127
+ add_index :tasks, :daily_report_id
128
+ add_index :tasks, :three_day_report_id
129
+ add_index :tasks, :retrospective_report_id
130
+
131
+ create_table :reports do |t|
132
+ t.column :content, :text
133
+ t.column :tasks_for_tomorrow, :text
134
+ t.column :type, :string
135
+
136
+ t.timestamps
137
+ end
138
+
139
+ create_table :report_recipients do |t|
140
+ t.column :email, :string
141
+
142
+ t.timestamps
143
+ end
144
+ end
145
+
146
+ puts 'Done'
147
+
148
+ puts "Database with all your data is located at: #{DB_SPEC[:database]}"
6
149
  end
7
150
 
8
- def self.config(args = [])
9
- Credentials.write(args)
151
+ def self.init
152
+ if `git -C "#{project.folder_path}" rev-parse --is-inside-work-tree`.strip == 'true'
153
+ template_path = File.join(File.dirname(__FILE__), '..', 'Capefile.template')
154
+ new_path = `git rev-parse --show-toplevel`.strip + '/Capefile'
155
+
156
+ FileUtils.cp template_path, new_path
157
+
158
+ puts 'Capefile successfully created.'
159
+ puts 'Please open Capefile and add your Jira/Git/Pivotal settings.'
160
+ else
161
+ puts "You don't seem to be inside a git project. Please go into a folder that has a git repository initiated."
162
+ end
10
163
  end
11
164
 
12
- def self.test
13
- puts 'works'
165
+ # todo: here is project.jira_url. but it has an undefined method project.
166
+ # should move this method to be inside a project (get ids for this project or something).
167
+ def self.get_jira_issue_type_ids
168
+ conn = Faraday.new(url: project.jira_url) do |c|
169
+ c.basic_auth(Account.jira.email, Account.jira.password)
170
+ c.adapter Faraday.default_adapter
171
+ end
172
+
173
+ response = conn.get do |request|
174
+ request.url 'rest/api/3/issuetype.json'
175
+ #request.body = data
176
+ request.headers['User-Agent'] = 'Caperoma'
177
+ request.headers['Content-Type'] = 'application/json'
178
+ end
179
+
180
+ puts 'Received these issue types:'
181
+
182
+ result = JSON.parse(response.body)
183
+
184
+ result.each do |item|
185
+ puts "ID: #{item['id']}, Name: #{item['name']}"
186
+ end
187
+ end
188
+
189
+ def self.get_jira_transition_ids
190
+ conn = Faraday.new(url: project.jira_url) do |c|
191
+ c.basic_auth(Account.jira.email, Account.jira.password)
192
+ c.adapter Faraday.default_adapter
193
+ end
194
+
195
+ response = conn.post do |request|
196
+ request.url 'rest/api/3/issue/{some_issue_key_or_id}/transitions.json'
197
+ request.body = data
198
+ request.headers['User-Agent'] = 'Caperoma'
199
+ request.headers['Content-Type'] = 'application/json'
200
+ end
201
+
202
+ puts 'Received these transition types:'
203
+ JSON.parse(response).each do |item|
204
+ puts "ID: #{item.id}, Name: #{item.name}"
205
+ end
14
206
  end
15
207
 
16
- require 'caperoma/jira_client'
17
- require 'caperoma/credentials'
208
+ def self.drop_db
209
+ puts 'Deleting work history and settings'
210
+ ActiveRecord::Base.connection.close
211
+ File.delete(DB_SPEC[:database])
212
+ puts 'Work history and settings deleted'
213
+ end
214
+
215
+ def self.create_task(argv)
216
+ # test if Capefile exists
217
+ capefile_filename = (ENV['CAPEROMA_TEST'].blank? && ENV['CAPEROMA_INTEGRATION_TEST'].blank?) ? 'Capefile' : 'Capefile.test'
218
+
219
+ if File.exist?(capefile_filename)
220
+ capedata = nil
221
+ jira_url = nil
222
+ jira_project_id = nil
223
+ github_repo = nil
224
+ feature_jira_task_id = nil
225
+ bug_jira_task_id = nil
226
+ chore_jira_task_id = nil
227
+ fix_jira_task_id = nil
228
+ meeting_jira_task_id = nil
229
+ jira_transition_id_todo = nil
230
+ jira_transition_id_in_progress = nil
231
+ jira_transition_id_done = nil
232
+ pivotal_tracker_project_id = nil
233
+
234
+ create_features_in_pivotal = nil
235
+ create_bugs_in_pivotal = nil
236
+ create_chores_in_pivotal = nil
237
+ create_fixes_in_pivotal_as_chores = nil
238
+ create_meetings_in_pivotal_as_chores = nil
239
+
240
+ capedata = YAML.load_file(capefile_filename)
241
+ if capedata
242
+ jira_url = capedata['jira_url']
243
+ jira_project_id = capedata['jira_project_id']
244
+ github_repo = capedata['github_repo']
245
+
246
+ jira_issue_type_ids = capedata['jira_issue_type_ids']
247
+
248
+ if jira_issue_type_ids
249
+ feature_jira_task_id = jira_issue_type_ids['feature']
250
+ bug_jira_task_id = jira_issue_type_ids['bug']
251
+ chore_jira_task_id = jira_issue_type_ids['chore']
252
+ fix_jira_task_id = jira_issue_type_ids['fix']
253
+ meeting_jira_task_id = jira_issue_type_ids['meeting']
254
+ end
255
+
256
+ jira_transition_ids = capedata['jira_transition_ids']
257
+
258
+ if jira_transition_ids
259
+ jira_transition_id_todo = jira_transition_ids['todo']
260
+ jira_transition_id_in_progress = jira_transition_ids['in_progress']
261
+ jira_transition_id_done = jira_transition_ids['done']
262
+ end
263
+
264
+ pivotal_tracker_project_id = capedata['pivotal_tracker_project_id']
265
+
266
+ create_features_in_pivotal = capedata['create_features_in_pivotal']
267
+ create_bugs_in_pivotal = capedata['create_bugs_in_pivotal']
268
+ create_chores_in_pivotal = capedata['create_chores_in_pivotal']
269
+ create_fixes_in_pivotal_as_chores = capedata['create_fixes_in_pivotal_as_chores']
270
+ create_meetings_in_pivotal_as_chores = capedata['create_meetings_in_pivotal_as_chores']
271
+
272
+ folder_path = `git rev-parse --show-toplevel`.strip
273
+
274
+ # find a project
275
+ title_flag_position = nil
276
+ title = nil
277
+ description_flag_position = nil
278
+ description = nil
279
+ project_id_flag_position = nil
280
+ project_id = nil
281
+ pivotal_id_flag_position = nil
282
+ pivotal_id = nil
283
+ additional_time_flag_position = nil
284
+ additional_time = nil
285
+
286
+ title_flag_position = argv.index('-t') || argv.index('--title')
287
+ title = argv[title_flag_position + 1] if title_flag_position
288
+ description_flag_position = argv.index('-d') || argv.index('--description')
289
+ description = argv[description_flag_position + 1] if description_flag_position
290
+ pivotal_id_flag_position = argv.index('-p') || argv.index('-ptid') || argv.index('--pivotal_task_id')
291
+ pivotal_id = argv[pivotal_id_flag_position + 1] if pivotal_id_flag_position
292
+ additional_time_flag_position = argv.index('-a') || argv.index('--additional_time')
293
+ additional_time = argv[additional_time_flag_position + 1] if additional_time_flag_position
294
+
295
+ if title
296
+ project = Project.all.select { |project| project.jira_project_id == jira_project_id || project.pivotal_tracker_project_id == pivotal_tracker_project_id || project.folder_path == folder_path || project.github_repo == github_repo }.first
297
+
298
+ project ||= Project.new
299
+
300
+ project.folder_path = folder_path
301
+ project.jira_url = jira_url
302
+ project.jira_project_id = jira_project_id
303
+ project.github_repo = github_repo
304
+ project.feature_jira_task_id = feature_jira_task_id
305
+ project.bug_jira_task_id = bug_jira_task_id
306
+ project.chore_jira_task_id = chore_jira_task_id
307
+ project.fix_jira_task_id = fix_jira_task_id
308
+ project.meeting_jira_task_id = meeting_jira_task_id
309
+ project.jira_transition_id_todo = jira_transition_id_todo
310
+ project.jira_transition_id_in_progress = jira_transition_id_in_progress
311
+ project.jira_transition_id_done = jira_transition_id_done
312
+ project.pivotal_tracker_project_id = pivotal_tracker_project_id
313
+ project.create_features_in_pivotal = create_features_in_pivotal
314
+ project.create_bugs_in_pivotal = create_bugs_in_pivotal
315
+ project.create_chores_in_pivotal = create_chores_in_pivotal
316
+ project.create_fixes_in_pivotal_as_chores = create_fixes_in_pivotal_as_chores
317
+ project.create_meetings_in_pivotal_as_chores = create_meetings_in_pivotal_as_chores
318
+ project.save
319
+
320
+
321
+ case argv[0]
322
+ when 'chore'
323
+ project.chores.create(title: title, description: description, project_id: project_id, pivotal_id: pivotal_id, additional_time: additional_time)
324
+ when 'bug'
325
+ project.bugs.create(title: title, description: description, project_id: project_id, pivotal_id: pivotal_id, additional_time: additional_time)
326
+ when 'feature'
327
+ project.features.create(title: title, description: description, project_id: project_id, pivotal_id: pivotal_id, additional_time: additional_time)
328
+ when 'fix'
329
+ project.fixes.create(title: title, description: description, project_id: project_id, pivotal_id: pivotal_id, additional_time: additional_time)
330
+ when 'meeting'
331
+ project.meetings.create(title: title, description: description, project_id: project_id, pivotal_id: pivotal_id, additional_time: additional_time)
332
+ end
333
+ else
334
+ puts "Title is required. Add -t \"my #{argv[0]} title\" flag."
335
+ end
336
+ else
337
+ puts 'Can not parse Capfile. Is it formatted properly?'
338
+ end
339
+ else
340
+ puts 'Capefile not found. Are you in the project folder? If yes, run "caperoma init" to create Capefile.'
341
+ end
342
+
343
+ end
344
+
345
+ def self.get_jira_project_ids
346
+ puts 'Getting projects from Jira'
347
+
348
+ conn = Faraday.new(url: Caperoma::Capefile::JIRA_URL) do |c|
349
+ c.basic_auth(Account.jira.email, Account.jira.password)
350
+ c.adapter Faraday.default_adapter
351
+ end
352
+
353
+ response = conn.get do |request|
354
+ request.url 'rest/api/3/project.json'
355
+ request.headers['User-Agent'] = 'Caperoma'
356
+ request.headers['Content-Type'] = 'application/json'
357
+ end
358
+
359
+ JSON.parse(response.body).each_with_index do |project|
360
+ pp "Name: #{project['name']}, jira_project_id: #{project['id']}"
361
+ end
362
+ end
363
+
364
+ def self.manage_recipients(argv)
365
+ case argv[1] # flag
366
+ when /^(create|add|--add|--create|-a|-c)$/
367
+ ReportRecipient.create email: argv[2]
368
+ when /^(remove|delete|--remove|--delete|-d|-r)$/
369
+ ReportRecipient.where(email: argv[2]).destroy_all
370
+ when nil
371
+ ReportRecipient.all.each { |x| puts x.email }
372
+ else
373
+ help
374
+ end
375
+ end
376
+
377
+ def self.manage_reports(argv)
378
+ case argv[1] # flag
379
+ when /^(daily|-d)$/
380
+ DailyReport.create
381
+ when /^(three_day|-t)$/
382
+ ThreeDayReport.create
383
+ when /^(weekly|-w)$/
384
+ RetrospectiveReport.create
385
+ when 'auto'
386
+ case argv[2] # subflag
387
+ when 'on'
388
+ Report.schedule
389
+ when 'off'
390
+ Report.unschedule
391
+ else
392
+ help
393
+ end
394
+ else
395
+ help
396
+ end
397
+ end
398
+
399
+ def self.manage_accounts(argv)
400
+ case argv[1] # flag
401
+ when /^(create|add|--add|--create|-a|-c)$/
402
+ Account.create(type: argv[2], email: argv[3], password: argv[4], username: argv[5])
403
+ when /^(remove|delete|--remove|--delete|-d|-r)$/
404
+ Account.where(type: argv[2]).destroy_all
405
+ when nil
406
+ Account.all.each { |x| puts "#{x.type[2..-1].capitalize}: #{x.email}" }
407
+ puts ''
408
+ puts 'to delete run "caperoma accounts remove "--<type>"'
409
+ puts 'to update run "... accounts --add "--<type>" again'
410
+ else
411
+ help
412
+ end
413
+ end
414
+
415
+ def self.help
416
+ puts File.read(File.join(File.dirname(__FILE__), '..', 'HELP'))
417
+ end
18
418
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Account < ApplicationRecord
4
+ self.inheritance_column = nil
5
+
6
+ validates :email, presence: true
7
+ validates :password, presence: true
8
+ validates :type, presence: true, inclusion: { in: %w[--jira --pivotal --gmail --git --caperoma] }
9
+
10
+ before_create :destroy_others
11
+ before_create :inform_creation_started
12
+ after_create :inform_creation_finished
13
+
14
+ def self.caperoma
15
+ where(type: '--caperoma').first
16
+ end
17
+
18
+ def self.jira
19
+ where(type: '--jira').first
20
+ end
21
+
22
+ def self.pivotal
23
+ where(type: '--pivotal').first
24
+ end
25
+
26
+ def self.gmail
27
+ where(type: '--gmail').first
28
+ end
29
+
30
+ def self.git
31
+ where(type: '--git').first
32
+ end
33
+
34
+ private
35
+
36
+ def destroy_others
37
+ Account.where(type: type).destroy_all
38
+ end
39
+
40
+ def inform_creation_started
41
+ puts 'Saving credentials'
42
+ end
43
+
44
+ def inform_creation_finished
45
+ puts 'Credentials saved'
46
+ end
47
+ end