harvest-reaper 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 51daa09f27f419593b19c0fa38f1904fc44763184a00c85046806555257ad014
4
- data.tar.gz: b96ef2902f4aaef8b3f1e5475448341ea66dc0a7c9817a8087be34e3cfff585e
3
+ metadata.gz: 18efa0f778351655322cf90cbc38299bbeb9f2bde6f28ff1c92dcaa4aa1224f8
4
+ data.tar.gz: 1c744e6ca524d2d010669c3fe4037efeea21b9c3f3ba042364f532b7a054b444
5
5
  SHA512:
6
- metadata.gz: bd5c0797f6bace2d63b2e5b17c924d8e611dc58a2e9b03d8e6ea475623cfdaeba118df23db4eaa7917361698ee2ece44c7cfe236d771ce62648de2b2d7775776
7
- data.tar.gz: e70c34577415a4f6ac3f6b5d69af78ade0f8c8e1487ea4346402626aad66e8c40418fac2c53cc15f94a267d02bef7c86a6581c5f5c260c1d8aab05784ff2164b
6
+ metadata.gz: ff9d304da8e77067d1cfe89d78320e2711ce9100299c3353651bce26df85795b900e651e1a92e749925864655b338da7cc5adcc759df6f57a61ca12cd5a27781
7
+ data.tar.gz: a61ece54635d3d7ed46ceb7aa169e91cfa7ec99d54ad84ab6847b27dc3788ec0811c7b1d9d14f816607a86fb7abb2a1fa975030d1ab4c3543e3544481fad17fa
data/.gitignore CHANGED
@@ -9,3 +9,4 @@
9
9
 
10
10
  # rspec failure tracking
11
11
  .rspec_status
12
+ .DS_Store
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- harvest-reaper (0.1.3)
4
+ harvest-reaper (0.1.4)
5
5
  ruby-progressbar (~> 1.10.1)
6
6
  terminal-table (~> 1.8.0)
7
7
  thor (~> 0.20.3)
data/assets/alipay.jpg ADDED
Binary file
@@ -1,3 +1,3 @@
1
1
  module Reaper
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
data/lib/reaper.rb CHANGED
@@ -13,7 +13,6 @@ require 'ruby-progressbar'
13
13
 
14
14
  module Reaper
15
15
  class Error < StandardError; end
16
- # Your code goes here...
17
16
 
18
17
  HARVEST_CLIENT_ID = 'c4CbEqRlWx1ziSITWP03BwjN'
19
18
 
@@ -413,7 +412,7 @@ module Reaper
413
412
  to = fri.to_s
414
413
 
415
414
  puts "Fetching your time entries from #{from} to #{to}"
416
- raw_entries = Reaper.request("time_entries?from=#{from}&to=#{to}")['time_entries']
415
+ raw_entries = Reaper.request("time_entries?user_id=#{$user_id}&from=#{from}&to=#{to}")['time_entries']
417
416
 
418
417
  if raw_entries.empty?
419
418
  puts "Cannot find any time entries in the week of #{from}"
@@ -448,14 +447,14 @@ module Reaper
448
447
 
449
448
  print_time_entries mon, fri, entries
450
449
 
451
- raw_entries
450
+ return raw_entries, entries
452
451
  end
453
452
 
454
453
  desc "delete DATE/WEEK-ALIAS", "Delete all Harvest time entries in the given week"
455
454
  def delete(date_str)
456
455
  mon, fri = get_week_range_from_date_str date_str
457
456
 
458
- entries = show date_str
457
+ entries, _ = show date_str
459
458
 
460
459
  if !entries.empty?
461
460
  puts "Delete #{entries.size} entrires from #{mon} to #{fri}? (Y/n)"
@@ -482,141 +481,9 @@ module Reaper
482
481
  end
483
482
 
484
483
  desc "submit DATE/WEEK-ALIAS", "Submit Harvest time entries for you based on the configuration"
484
+ option :excluded, :type => :string
485
485
  def submit(date_str)
486
- mon, fri = get_week_range_from_date_str date_str
487
-
488
- Reaper.check_auth
489
-
490
- abort 'Cannot find Reaper configuration. Please run `reaper config update` first.' unless Reaper.load_config
491
-
492
- existing_entries = show date_str
493
-
494
- if existing_entries && !existing_entries.empty?
495
- puts ''
496
- abort "You have existing time entries within the specified date range. Reaper submit won't work in this case.\nIf you are sure they can be removed, please run `reaper delete #{date_str}` first."
497
- end
498
-
499
- hours_per_day = 8
500
- days = 5
501
-
502
- dates = []
503
- days.times { |n| dates << mon + n }
504
-
505
- hours = []
506
-
507
- negative_offset_days = 0
508
- days.times do |n|
509
- # we don't want you to always work less than 8 hours,
510
- # 2 is the max number of your less working days
511
- is_positive_offset = negative_offset_days <= 4 ? [true, false].sample : true
512
- negative_offset_days += 1 unless is_positive_offset
513
-
514
- offset = is_positive_offset ? $config[:daily_positive_offset] : $config[:daily_negative_offset]
515
- offset = round_hours(rand() * offset) * (is_positive_offset ? 1 : -1)
516
- hours << offset + hours_per_day
517
- end
518
-
519
- total_hours = hours.inject(:+)
520
-
521
- tasks = $config[:tasks]
522
-
523
- tasks_hours = 0
524
- tasks.each_with_index do |t, i|
525
- if i < tasks.size - 1
526
- t[:hours] = round_hours(total_hours * t[:percentage])
527
- tasks_hours += t[:hours]
528
- else
529
- t[:hours] = total_hours - tasks_hours
530
- end
531
- end
532
-
533
- tasks_cpy = tasks.dup
534
-
535
- entries = {}
536
-
537
- hours.each_with_index do |h, i|
538
- date = dates[i]
539
-
540
- slots = []
541
- slots_num = (h / 0.5).to_i
542
- slots_num.times do |n|
543
- t = tasks_cpy.sample
544
- t[:hours] -= 0.5
545
-
546
- slots << {
547
- 'project_id' => t[:pid],
548
- 'task_id' => t[:tid],
549
- 'spent_date' => date.to_s,
550
- 'hours' => 0.5
551
- }
552
-
553
- if t[:hours] <= 0
554
- tasks_cpy.delete t
555
- end
556
- end
557
-
558
- tasks.each do |t|
559
- slots_per_task = slots.select do |s|
560
- t[:pid] == s['project_id'] && t[:tid] == s['task_id']
561
- end
562
-
563
- if !slots_per_task.empty?
564
- entry = slots_per_task.first
565
- entry['hours'] = slots_per_task.inject(0) { |sum, s| sum + s['hours'] }
566
- # puts entry
567
-
568
- daily_tasks = entries[date] || []
569
- entries[date] = daily_tasks if daily_tasks.empty?
570
-
571
- daily_tasks << {
572
- :project => t[:pname],
573
- :project_code => t[:pcode],
574
- :task => t[:tname],
575
- :client => t[:client],
576
- :hours => entry['hours'],
577
- :project_id => t[:pid],
578
- :task_id => t[:tid],
579
- :spent_date => entry['spent_date'],
580
- }
581
-
582
- daily_tasks.shuffle!
583
- end
584
- end
585
- end
586
-
587
- print_time_entries mon, fri, entries
588
-
589
- puts ''
590
- puts "A random set of time entries (from #{mon} to #{fri}) generated, submit now? (Y/n)"
591
- confirm = $stdin.gets.chomp
592
- abort 'Submit cancelled' unless confirm == 'Y'
593
-
594
- post_data = entries.values.flatten.map do |e|
595
- {
596
- 'user_id' => $user_id,
597
- 'project_id' => e[:project_id],
598
- 'task_id' => e[:task_id],
599
- 'spent_date' => e[:spent_date],
600
- 'hours' => e[:hours]
601
- }
602
- end
603
-
604
- progressbar = ProgressBar.create(
605
- :title => 'Submitting',
606
- :total => post_data.size,
607
- :format => '%t %c/%C %B'
608
- )
609
-
610
- post_data.each do |e|
611
- rsp = post 'time_entries', e
612
- if rsp
613
- progressbar.increment
614
- else
615
- abort "Submit request failed. Your submit actions may not be completed. Please run `reaper show #{date_str}` to check."
616
- end
617
- end
618
-
619
- puts "#{post_data.size} time entries submitted successfully. You can run `reaper show #{date_str}` to check."
486
+ submit_time_entries date_str, options[:excluded]
620
487
  end
621
488
 
622
489
  no_commands do
@@ -728,9 +595,213 @@ module Reaper
728
595
  table.style = { :all_separators => true }
729
596
  puts table
730
597
  end
598
+
599
+ def submit_time_entries(date_str, excluded_weekdays_str)
600
+ mon, fri = get_week_range_from_date_str date_str
601
+
602
+ if excluded_weekdays_str && !excluded_weekdays_str.empty?
603
+ excluded_weekdays = excluded_weekdays_str
604
+ .split(',')
605
+ .map { |s| s.strip.downcase }
606
+ .uniq
607
+
608
+ valid_weekdays = %w(mon tue wed thu fri)
609
+ unless (excluded_weekdays - valid_weekdays).empty? && excluded_weekdays.size < valid_weekdays.size
610
+ abort "Argument 'excluded' contains invalid options. Only single or multiple values (comma separated) in #{valid_weekdays} is allowed."
611
+ end
612
+
613
+ excluded_weekdays_offset = excluded_weekdays.map { |d| valid_weekdays.index d }.sort
614
+ end
615
+
616
+ has_excluded = excluded_weekdays_offset != nil && !excluded_weekdays_offset.empty?
617
+
618
+ Reaper.check_auth
619
+
620
+ abort 'Cannot find Reaper configuration. Please run `reaper config update` first.' unless Reaper.load_config
621
+
622
+ _, existing_entries = show date_str
623
+
624
+ if existing_entries
625
+ num = existing_entries.values.map { |v| v.size }.inject(:+)
626
+
627
+ if num > 0
628
+ puts ''
629
+
630
+ is_clean_after_excluded = false
631
+
632
+ if has_excluded
633
+ entries = existing_entries.select { |k, v| !(excluded_weekdays_offset.include? (k - mon)) }
634
+ if entries.values.map { |v| v.size }.inject(:+) > 0
635
+ abort "You have existing time entries within the specified date range. Reaper submit won't work in this case.\nIf you are sure they can be removed, please run `reaper delete #{date_str}` first."
636
+ else
637
+ is_clean_after_excluded = true
638
+ end
639
+ end
640
+
641
+ if !is_clean_after_excluded
642
+ non_vocation_entries = existing_entries.select do |k, v|
643
+ case v.size
644
+ when 0
645
+ false
646
+ when 1
647
+ entry = v.first
648
+ entry[:client] != 'Time Off' || entry[:hours] != 8
649
+ else
650
+ true
651
+ end
652
+ end
653
+
654
+ # all the entries are 8 hours vacation/public holiday/sick leave
655
+ if non_vocation_entries.empty?
656
+ case num
657
+ when 5
658
+ abort "All 5 days within the specified week have been marked as 'Time Off'. Reaper submit won't work in this case.\nIf you are sure they can be removed, please run `reaper delete #{date_str}` first."
659
+ else
660
+ puts "#{num} days within the specified week have been marked as 'Time Off'. Do you want to exclude them and submit time entries for the rest of the days? (Y/n)"
661
+ excluded_dates = existing_entries.select { |k, v| !v.empty? }.keys
662
+ excluded_arg = excluded_dates.map { |d| d.strftime('%a') }.join(',')
663
+
664
+ confirm = $stdin.gets.chomp
665
+ abort unless confirm == 'Y'
666
+ submit_time_entries date_str, excluded_arg
667
+ return
668
+ end
669
+ end
670
+
671
+ abort "You have existing time entries within the specified date range. Reaper submit won't work in this case.\nIf you are sure they can be removed, please run `reaper delete #{date_str}` first."
672
+ end
673
+ end
674
+ end
675
+
676
+ hours_per_day = 8
677
+ days = has_excluded ? 5 - excluded_weekdays_offset.size : 5
678
+
679
+ dates = []
680
+ 5.times { |n| dates << mon + n if !has_excluded || !(excluded_weekdays_offset.include? n) }
681
+
682
+ hours = []
683
+
684
+ negative_offset_days = 0
685
+ days.times do |_|
686
+ # we don't want you to always work less than 8 hours,
687
+ # 2 is the max number of your less working days
688
+ is_positive_offset = negative_offset_days <= 2 ? [true, false].sample : true
689
+ negative_offset_days += 1 unless is_positive_offset
690
+
691
+ offset = is_positive_offset ? $config[:daily_positive_offset] : $config[:daily_negative_offset]
692
+ offset = round_hours(rand() * offset) * (is_positive_offset ? 1 : -1)
693
+ hours << offset + hours_per_day
694
+ end
695
+
696
+ total_hours = hours.inject(:+)
697
+
698
+ tasks = $config[:tasks]
699
+
700
+ tasks_hours = 0
701
+ tasks.each_with_index do |t, i|
702
+ if i < tasks.size - 1
703
+ t[:hours] = round_hours(total_hours * t[:percentage])
704
+ tasks_hours += t[:hours]
705
+ else
706
+ t[:hours] = total_hours - tasks_hours
707
+ end
708
+ end
709
+
710
+ tasks_cpy = tasks.dup
731
711
 
712
+ entries = {}
713
+
714
+ hours.each_with_index do |h, i|
715
+ date = dates[i]
716
+
717
+ slots = []
718
+ slots_num = (h / 0.5).to_i
719
+ slots_num.times do |n|
720
+ t = tasks_cpy.sample
721
+ t[:hours] -= 0.5
722
+
723
+ slots << {
724
+ 'project_id' => t[:pid],
725
+ 'task_id' => t[:tid],
726
+ 'spent_date' => date.to_s,
727
+ 'hours' => 0.5
728
+ }
729
+
730
+ if t[:hours] <= 0
731
+ tasks_cpy.delete t
732
+ end
733
+ end
734
+
735
+ tasks.each do |t|
736
+ slots_per_task = slots.select do |s|
737
+ t[:pid] == s['project_id'] && t[:tid] == s['task_id']
738
+ end
739
+
740
+ if !slots_per_task.empty?
741
+ entry = slots_per_task.first
742
+ entry['hours'] = slots_per_task.inject(0) { |sum, s| sum + s['hours'] }
743
+ # puts entry
744
+
745
+ daily_tasks = entries[date] || []
746
+ entries[date] = daily_tasks if daily_tasks.empty?
747
+
748
+ daily_tasks << {
749
+ :project => t[:pname],
750
+ :project_code => t[:pcode],
751
+ :task => t[:tname],
752
+ :client => t[:client],
753
+ :hours => entry['hours'],
754
+ :project_id => t[:pid],
755
+ :task_id => t[:tid],
756
+ :spent_date => entry['spent_date'],
757
+ }
758
+
759
+ daily_tasks.shuffle!
760
+ end
761
+ end
762
+ end
763
+
764
+ print_time_entries mon, fri, entries
765
+
766
+ puts ''
767
+
768
+ range = "from #{mon} to #{fri}"
769
+ range << ", #{excluded_weekdays.join(', ')} excluded" if has_excluded
770
+
771
+ puts "A random set of time entries (#{range}) generated, submit now? (Y/n)"
772
+ confirm = $stdin.gets.chomp
773
+ abort 'Submit cancelled' unless confirm == 'Y'
774
+
775
+ post_data = entries.values.flatten.map do |e|
776
+ {
777
+ 'user_id' => $user_id,
778
+ 'project_id' => e[:project_id],
779
+ 'task_id' => e[:task_id],
780
+ 'spent_date' => e[:spent_date],
781
+ 'hours' => e[:hours]
782
+ }
783
+ end
784
+
785
+ progressbar = ProgressBar.create(
786
+ :title => 'Submitting',
787
+ :total => post_data.size,
788
+ :format => '%t %c/%C %B'
789
+ )
790
+
791
+ post_data.each do |e|
792
+ rsp = post 'time_entries', e
793
+ if rsp
794
+ progressbar.increment
795
+ else
796
+ abort "Submit request failed. Your submit actions may not be completed. Please run `reaper show #{date_str}` to check."
797
+ end
798
+ end
799
+
800
+ puts "#{post_data.size} time entries submitted successfully. You can run `reaper show #{date_str}` to check."
801
+ end
802
+
732
803
  # helpers
733
-
804
+
734
805
  def num_to_hours(num)
735
806
  num
736
807
  end
@@ -781,6 +852,13 @@ module Reaper
781
852
  return mon, (mon + 4)
782
853
  end
783
854
  end
855
+
856
+ desc "donate", "Buy me a milktea ❤️"
857
+ def donate
858
+ puts 'If you think Reaper saves your time, please consider buying the author a milktea ❤️'
859
+ pay_img = File.join Reaper.root, 'assets/alipay.jpg'
860
+ `qlmanage -p #{pay_img} 2>/dev/null`
861
+ end
784
862
 
785
863
  desc "version", "Show current Reaper version"
786
864
  def version
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: harvest-reaper
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.1.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - heartace
@@ -102,7 +102,6 @@ executables:
102
102
  extensions: []
103
103
  extra_rdoc_files: []
104
104
  files:
105
- - ".DS_Store"
106
105
  - ".gitignore"
107
106
  - ".rspec"
108
107
  - ".travis.yml"
@@ -111,6 +110,7 @@ files:
111
110
  - LICENSE.txt
112
111
  - README.md
113
112
  - Rakefile
113
+ - assets/alipay.jpg
114
114
  - assets/reaper_config_template.html
115
115
  - bin/.DS_Store
116
116
  - bin/console
data/.DS_Store DELETED
Binary file