harvest-worklog 0.9.0 → 0.10.0
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.
- checksums.yaml +4 -4
- data/README.md +6 -2
- data/lib/harvest_worklog/aggregate_cli.rb +4 -2
- data/lib/harvest_worklog/timesheet_cli.rb +2 -1
- data/lib/harvest_worklog/version.rb +1 -1
- data/lib/harvest_worklog/work_entry_cli.rb +10 -5
- data/lib/harvest_worklog.rb +13 -2
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b50e3af9480bb1ed9d5a98d4e746b3476cf14b10b04ce90dfacaacafa872bd52
|
|
4
|
+
data.tar.gz: cf030243de7c163c4fb1287af66bf60f50669b55fed524c1e304ff00d1b0ab21
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a70c1b74ccc40f2641a5668830783e7b5f3f6773d0bec2fe825dd1591ac0d4ca79f5e057a5b75d6f36dd98a20d106210b153af4f8364929533aca6f420c5a3e8
|
|
7
|
+
data.tar.gz: 52e27558d3af81deb788fce38892e7cb7410907f4974ef553f6e2c3998385054ebb6415a88531ec3465e39f4d8fda9bf63a72bc93b5e63ed72ca277ca0d24e6a
|
data/README.md
CHANGED
|
@@ -21,7 +21,9 @@ Personal Access Tokens are the intended Harvest authentication method for person
|
|
|
21
21
|
|
|
22
22
|
```text
|
|
23
23
|
harvest-worklog time-off FROM TO --project NAME --task NAME [options]
|
|
24
|
+
harvest-worklog time-off FROM TO --project-id ID --task-id ID [options]
|
|
24
25
|
harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
|
|
26
|
+
harvest-worklog work-entry DATE --project-id ID --task-id ID --hours HOURS --notes NOTES [options]
|
|
25
27
|
harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]
|
|
26
28
|
harvest-worklog timesheet DATE --project NAME [--task NAME]
|
|
27
29
|
```
|
|
@@ -42,6 +44,8 @@ The default holiday region is `ca_yt`. Add `--holiday-region REGION` or set `HAR
|
|
|
42
44
|
|
|
43
45
|
A time-off block never creates weekend entries. When names are supplied, it resolves them from your active personal Harvest assignments, so a Project Manager role is not required.
|
|
44
46
|
|
|
47
|
+
The `harvest_record_time_off` OMP tool accepts the same name pair or ID pair. Its optional `holidayRegions` array adds per-call regions to the configured defaults.
|
|
48
|
+
|
|
45
49
|
### Reviewed ordinary work
|
|
46
50
|
|
|
47
51
|
The `work-entry` command checks for existing or locked entries for its project, task, and date before creating a new duration entry. Use `--dry-run` to complete that preflight without a write. OMP Project Time uses the same path.
|
|
@@ -66,9 +70,9 @@ The `timesheet` command reads the authenticated user's compact daily Harvest vie
|
|
|
66
70
|
harvest-worklog timesheet today --project WRAP
|
|
67
71
|
```
|
|
68
72
|
|
|
69
|
-
OMP exposes the same CLI-only path through `/harvest-worklog today WRAP` and the read-only `harvest_time_sheet` tool. The wrapper never calls Harvest directly. A later, separate feature may upload reviewed content through the Harvest API; keep that write path separate from this read-only formatter.
|
|
73
|
+
OMP exposes the same CLI-only path through `/harvest-worklog timesheet today --project WRAP` and the read-only `harvest_time_sheet` tool. The wrapper never calls Harvest directly. A later, separate feature may upload reviewed content through the Harvest API; keep that write path separate from this read-only formatter.
|
|
70
74
|
|
|
71
|
-
Type `/harvest-worklog ` in OMP to discover
|
|
75
|
+
Type `/harvest-worklog ` in OMP to discover `timesheet`, then select `today`, `yesterday`, or an ISO date before the contextual `--project` and `--task` options. Completion keeps filter values as editable text, and the slash command delegates the selected timesheet argument vector to the CLI.
|
|
72
76
|
|
|
73
77
|
## OMP settings
|
|
74
78
|
|
|
@@ -8,7 +8,7 @@ module HarvestWorklog
|
|
|
8
8
|
options = {}
|
|
9
9
|
parser = option_parser(options)
|
|
10
10
|
dates = parser.parse(arguments)
|
|
11
|
-
validate!(dates)
|
|
11
|
+
validate!(dates, options)
|
|
12
12
|
|
|
13
13
|
from = Date.iso8601(dates[0])
|
|
14
14
|
to = Date.iso8601(dates[1])
|
|
@@ -36,8 +36,10 @@ module HarvestWorklog
|
|
|
36
36
|
end
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def self.validate!(dates)
|
|
39
|
+
def self.validate!(dates, options = {})
|
|
40
40
|
raise Error, "FROM and TO are required" unless dates.length == 2
|
|
41
|
+
raise Error, "--project must not be blank" if options[:project]&.strip&.empty?
|
|
42
|
+
raise Error, "--task must not be blank" if options[:task]&.strip&.empty?
|
|
41
43
|
end
|
|
42
44
|
|
|
43
45
|
def self.fetch_entries(client, from:, to:, user_id: nil)
|
|
@@ -46,7 +46,8 @@ module HarvestWorklog
|
|
|
46
46
|
|
|
47
47
|
def self.validate!(dates, options)
|
|
48
48
|
raise Error, "DATE is required" unless dates.length == 1
|
|
49
|
-
raise Error, "--project is required" unless options[:project] && !options[:project].empty?
|
|
49
|
+
raise Error, "--project is required" unless options[:project] && !options[:project].strip.empty?
|
|
50
|
+
raise Error, "--task must not be blank" if options[:task]&.strip&.empty?
|
|
50
51
|
end
|
|
51
52
|
|
|
52
53
|
def self.print_timesheet(output, entries, spent_date:, requested_project:)
|
|
@@ -42,7 +42,9 @@ module HarvestWorklog
|
|
|
42
42
|
end
|
|
43
43
|
|
|
44
44
|
if options[:dry_run]
|
|
45
|
-
|
|
45
|
+
project = options[:project] || "project ##{options[:project_id]}"
|
|
46
|
+
task = options[:task] || "task ##{options[:task_id]}"
|
|
47
|
+
output.puts "Would create #{spent_date.iso8601}: #{HarvestWorklog.display_hours(options[:hours])}h on #{project} / #{task}; #{options[:notes]}"
|
|
46
48
|
return 0
|
|
47
49
|
end
|
|
48
50
|
|
|
@@ -63,7 +65,10 @@ module HarvestWorklog
|
|
|
63
65
|
|
|
64
66
|
def self.option_parser(options)
|
|
65
67
|
OptionParser.new do |opts|
|
|
66
|
-
opts.banner =
|
|
68
|
+
opts.banner = <<~USAGE
|
|
69
|
+
Usage: harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
|
|
70
|
+
harvest-worklog work-entry DATE --project-id ID --task-id ID --hours HOURS --notes NOTES [options]
|
|
71
|
+
USAGE
|
|
67
72
|
opts.on("--project NAME", "Harvest project name") { |value| options[:project] = value }
|
|
68
73
|
opts.on("--task NAME", "Harvest task name") { |value| options[:task] = value }
|
|
69
74
|
opts.on("--project-id ID", Integer, "Harvest project ID") { |value| options[:project_id] = value }
|
|
@@ -82,12 +87,12 @@ module HarvestWorklog
|
|
|
82
87
|
def self.validate!(dates, options)
|
|
83
88
|
raise Error, "DATE is required" unless dates.length == 1
|
|
84
89
|
raise Error, "--hours must be a positive finite number" unless options[:hours]&.positive? && options[:hours].finite?
|
|
85
|
-
raise Error, "--notes is required" unless options[:notes] && !options[:notes].empty?
|
|
90
|
+
raise Error, "--notes is required" unless options[:notes] && !options[:notes].strip.empty?
|
|
86
91
|
|
|
87
92
|
names_provided = options[:project] || options[:task]
|
|
88
93
|
ids_provided = options[:project_id] || options[:task_id]
|
|
89
|
-
valid_names = options[:project] && options[:task]
|
|
90
|
-
valid_ids = options[:project_id] && options[:task_id]
|
|
94
|
+
valid_names = options[:project] && !options[:project].strip.empty? && options[:task] && !options[:task].strip.empty?
|
|
95
|
+
valid_ids = options[:project_id]&.positive? && options[:task_id]&.positive?
|
|
91
96
|
raise Error, "supply --project and --task, or --project-id and --task-id" unless valid_names || valid_ids
|
|
92
97
|
raise Error, "do not combine project/task names with IDs" if names_provided && ids_provided
|
|
93
98
|
end
|
data/lib/harvest_worklog.rb
CHANGED
|
@@ -52,7 +52,9 @@ module HarvestWorklog
|
|
|
52
52
|
<<~USAGE
|
|
53
53
|
Usage:
|
|
54
54
|
harvest-worklog time-off FROM TO --project NAME --task NAME [options]
|
|
55
|
+
harvest-worklog time-off FROM TO --project-id ID --task-id ID [options]
|
|
55
56
|
harvest-worklog work-entry DATE --project NAME --task NAME --hours HOURS --notes NOTES [options]
|
|
57
|
+
harvest-worklog work-entry DATE --project-id ID --task-id ID --hours HOURS --notes NOTES [options]
|
|
56
58
|
harvest-worklog aggregate FROM TO [--project NAME] [--task NAME]
|
|
57
59
|
harvest-worklog timesheet DATE --project NAME [--task NAME]
|
|
58
60
|
|
|
@@ -70,6 +72,7 @@ module HarvestWorklog
|
|
|
70
72
|
options = { hours: 7.0, dry_run: false, holiday_regions: holiday_regions_from_environment }
|
|
71
73
|
parser = option_parser(options)
|
|
72
74
|
dates = parser.parse(arguments)
|
|
75
|
+
normalize_holiday_regions!(options[:holiday_regions])
|
|
73
76
|
validate!(dates, options)
|
|
74
77
|
|
|
75
78
|
from = Date.iso8601(dates[0])
|
|
@@ -133,11 +136,12 @@ module HarvestWorklog
|
|
|
133
136
|
raise Error, "FROM and TO are required" unless dates.length == 2
|
|
134
137
|
raise Error, "--hours must be a positive finite number" unless options[:hours].positive? && options[:hours].finite?
|
|
135
138
|
raise Error, "--holiday-region or HARVEST_HOLIDAY_REGIONS is required" if options[:holiday_regions].empty?
|
|
139
|
+
raise Error, "--notes must not be blank" if options[:notes]&.strip&.empty?
|
|
136
140
|
|
|
137
141
|
names_provided = options[:project] || options[:task]
|
|
138
142
|
ids_provided = options[:project_id] || options[:task_id]
|
|
139
|
-
valid_names = options[:project] && options[:task]
|
|
140
|
-
valid_ids = options[:project_id] && options[:task_id]
|
|
143
|
+
valid_names = options[:project] && !options[:project].strip.empty? && options[:task] && !options[:task].strip.empty?
|
|
144
|
+
valid_ids = options[:project_id]&.positive? && options[:task_id]&.positive?
|
|
141
145
|
raise Error, "supply --project and --task, or --project-id and --task-id" unless valid_names || valid_ids
|
|
142
146
|
raise Error, "do not combine project/task names with IDs" if names_provided && ids_provided
|
|
143
147
|
end
|
|
@@ -146,6 +150,13 @@ module HarvestWorklog
|
|
|
146
150
|
ENV.fetch("HARVEST_HOLIDAY_REGIONS", "ca_yt").split(",").map { |region| region.strip.downcase }.reject(&:empty?)
|
|
147
151
|
end
|
|
148
152
|
|
|
153
|
+
def self.normalize_holiday_regions!(regions)
|
|
154
|
+
regions.map! { |region| region.strip.downcase }
|
|
155
|
+
regions.reject!(&:empty?)
|
|
156
|
+
regions.uniq!
|
|
157
|
+
regions
|
|
158
|
+
end
|
|
159
|
+
|
|
149
160
|
def self.resolve_assignment(client, project_name, task_name)
|
|
150
161
|
# ponytail: one 2,000-item page covers personal assignments; follow cursor pagination if that ceiling is exceeded.
|
|
151
162
|
matches = client.active_personal_task_assignments.select do |assignment|
|