caperoma 0.1.0 → 4.0.1

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