Class: HarvestWorklog::AggregateCLI

Inherits:
Object
  • Object
show all
Defined in:
lib/harvest_worklog/aggregate_cli.rb

Constant Summary collapse

PER_PAGE =
100

Class Method Summary collapse

Class Method Details

.entry_label(count) ⇒ Object



93
94
95
# File 'lib/harvest_worklog/aggregate_cli.rb', line 93

def self.(count)
  count == 1 ? "entry" : "entries"
end

.fetch_entries(client, from:, to:) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/harvest_worklog/aggregate_cli.rb', line 43

def self.fetch_entries(client, from:, to:)
  entries = []
  page = 1
  loop do
    response = client.request(
      :get,
      "/v2/time_entries",
      params: { from: from.iso8601, to: to.iso8601, page:, per_page: PER_PAGE }
    )
    entries.concat(response.fetch("time_entries"))
    page = response["next_page"]
    break if page.nil?
  end
  entries
end

.matches?(entry, options) ⇒ Boolean

Returns:

  • (Boolean)


59
60
61
62
# File 'lib/harvest_worklog/aggregate_cli.rb', line 59

def self.matches?(entry, options)
  name_matches?(entry.dig("project", "name"), options[:project]) &&
    name_matches?(entry.dig("task", "name"), options[:task])
end

.name_matches?(actual, expected) ⇒ Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/harvest_worklog/aggregate_cli.rb', line 64

def self.name_matches?(actual, expected)
  expected.nil? || actual&.casecmp?(expected)
end

.option_parser(options) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/harvest_worklog/aggregate_cli.rb', line 27

def self.option_parser(options)
  OptionParser.new do |opts|
    opts.banner = "Usage: harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]"
    opts.on("--project NAME", "Filter by Harvest project name") { |value| options[:project] = value }
    opts.on("--task NAME", "Filter by Harvest task name") { |value| options[:task] = value }
    opts.on("-h", "--help", "Show this help") do
      puts opts
      exit 0
    end
  end
end


68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/harvest_worklog/aggregate_cli.rb', line 68

def self.print_summary(output, entries, from:, to:)
  output.puts "#{entries.length} #{(entries.length)}, #{HarvestWorklog.display_hours(total_hours(entries))}h from #{from.iso8601} through #{to.iso8601}"
  output.puts "By date:"
  (from..to).each do |date|
    date_entries = entries.select { |entry| entry.fetch("spent_date") == date.iso8601 }
    output.puts "  #{date.iso8601}: #{date_entries.length} #{(date_entries.length)}, #{HarvestWorklog.display_hours(total_hours(date_entries))}h"
  end
  output.puts "By project/task:"

  groups = entries.group_by { |entry| [entry.dig("project", "name"), entry.dig("task", "name")] }
  if groups.empty?
    output.puts "  none"
    return
  end

  groups.keys.sort_by { |project, task| [project.downcase, task.downcase] }.each do |project, task|
    group = groups.fetch([project, task])
    output.puts "  #{project} / #{task}: #{group.length} #{(group.length)}, #{HarvestWorklog.display_hours(total_hours(group))}h"
  end
end

.run(arguments, output: $stdout, error: $stderr, client: nil) ⇒ Object



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# File 'lib/harvest_worklog/aggregate_cli.rb', line 7

def self.run(arguments, output: $stdout, error: $stderr, client: nil)
  options = {}
  parser = option_parser(options)
  dates = parser.parse(arguments)
  validate!(dates)

  from = Date.iso8601(dates[0])
  to = Date.iso8601(dates[1])
  raise Error, "end date must not be before start date" if to < from

  client ||= Marlens::HarvestApiV2::Client.from_environment
  entries = fetch_entries(client, from:, to:).select { |entry| matches?(entry, options) }
  print_summary(output, entries, from:, to:)
  0
rescue Error, Marlens::HarvestApiV2::Error, OptionParser::ParseError, Date::Error => e
  error.puts "Error: #{e.message}"
  error.puts parser if parser
  1
end

.total_hours(entries) ⇒ Object



89
90
91
# File 'lib/harvest_worklog/aggregate_cli.rb', line 89

def self.total_hours(entries)
  entries.sum { |entry| Float(entry.fetch("hours")) }
end

.validate!(dates) ⇒ Object

Raises:



39
40
41
# File 'lib/harvest_worklog/aggregate_cli.rb', line 39

def self.validate!(dates)
  raise Error, "FROM and TO are required" unless dates.length == 2
end