checkoff 0.97.0 → 0.98.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/Gemfile.lock +1 -1
- data/config/definitions.rb +8 -0
- data/lib/checkoff/internal/selector_classes/task/function_evaluator.rb +4 -1
- data/lib/checkoff/internal/selector_classes/task.rb +20 -0
- data/lib/checkoff/internal/task_selector_evaluator.rb +7 -2
- data/lib/checkoff/sections.rb +65 -3
- data/lib/checkoff/task_selectors.rb +9 -2
- data/lib/checkoff/timelines.rb +121 -0
- data/lib/checkoff/version.rb +1 -1
- data/lib/checkoff.rb +1 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2725cd346d11c05b4742b3dd59d1202064a3a9036ec37c2037332a02f93165eb
|
|
4
|
+
data.tar.gz: b02da55d5036d05eee1c3fb58237cf42e68c28b788be66cd0f41a07647b9e6eb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a8a4286f693c686211aa41db250b7d3825b9a846a923e53f7327ca0d94ba714f7b94ebfa8ec55964506721d4912d419318e8c2e94ef147b0ed586151a5b5d252
|
|
7
|
+
data.tar.gz: 1c1d1f4a88a447e27f3bc9beaf10ff6fec97f0b55ced249eb7cdfbcd2e36955bb5230fb3991339eecd19f44f6b5d9c2ef646746a6ee175b5d4db2d1964305f00
|
data/Gemfile.lock
CHANGED
data/config/definitions.rb
CHANGED
|
@@ -42,6 +42,8 @@
|
|
|
42
42
|
# module Resources
|
|
43
43
|
# # https://developers.asana.com/reference/gettask
|
|
44
44
|
# class Task
|
|
45
|
+
# # @return [String]
|
|
46
|
+
# def resource_subtype; end
|
|
45
47
|
# # @return [String,nil]
|
|
46
48
|
# def due_at; end
|
|
47
49
|
# # @return [String,nil]
|
|
@@ -136,6 +138,12 @@
|
|
|
136
138
|
# # @param project_gid [String]
|
|
137
139
|
# # @return [Enumerable<Asana::Resources::Section>]
|
|
138
140
|
# def get_sections_for_project(project_gid:, options: {}); end
|
|
141
|
+
# # Returns the complete record for a single section.
|
|
142
|
+
# #
|
|
143
|
+
# # @param [String] id - The section to get.
|
|
144
|
+
# # @param options [Hash] - the request I/O options.
|
|
145
|
+
# # @return [Asana::Resources::Section]
|
|
146
|
+
# def find_by_id(id, options: {}); end
|
|
139
147
|
# end
|
|
140
148
|
# class Project
|
|
141
149
|
# # Returns the compact project records for all projects in the workspace.
|
|
@@ -9,10 +9,13 @@ module Checkoff
|
|
|
9
9
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
10
10
|
# @param selector [Array<(Symbol, Array)>,String]
|
|
11
11
|
# @param tasks [Checkoff::Tasks]
|
|
12
|
+
# @param timelines [Checkoff::Timelines]
|
|
12
13
|
def initialize(selector:,
|
|
13
|
-
tasks
|
|
14
|
+
tasks:,
|
|
15
|
+
timelines:)
|
|
14
16
|
@selector = selector
|
|
15
17
|
@tasks = tasks
|
|
18
|
+
@timelines = timelines
|
|
16
19
|
super()
|
|
17
20
|
end
|
|
18
21
|
|
|
@@ -353,6 +353,26 @@ module Checkoff
|
|
|
353
353
|
estimate_hours > allocated_hours
|
|
354
354
|
end
|
|
355
355
|
end
|
|
356
|
+
|
|
357
|
+
# :dependent_on_previous_section_last_milestone
|
|
358
|
+
class DependentOnPreviousSectionLastMilestoneFunctionEvaluator < FunctionEvaluator
|
|
359
|
+
FUNCTION_NAME = :dependent_on_previous_section_last_milestone
|
|
360
|
+
|
|
361
|
+
def matches?
|
|
362
|
+
fn?(selector, FUNCTION_NAME)
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# @param task [Asana::Resources::Task]
|
|
366
|
+
# @param project_name [String]
|
|
367
|
+
# @param limit_to_portfolio_gid [String, nil] If specified,
|
|
368
|
+
# only projects in this portfolio will be evaluated.
|
|
369
|
+
#
|
|
370
|
+
# @return [Boolean]
|
|
371
|
+
def evaluate(task, limit_to_portfolio_gid: nil)
|
|
372
|
+
@timelines.task_dependent_on_previous_section_last_milestone?(task,
|
|
373
|
+
limit_to_portfolio_gid: limit_to_portfolio_gid)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
356
376
|
end
|
|
357
377
|
end
|
|
358
378
|
end
|
|
@@ -9,10 +9,13 @@ module Checkoff
|
|
|
9
9
|
class TaskSelectorEvaluator < SelectorEvaluator
|
|
10
10
|
# @param task [Asana::Resources::Task]
|
|
11
11
|
# @param tasks [Checkoff::Tasks]
|
|
12
|
+
# @param timelines [Checkoff::Timelines]
|
|
12
13
|
def initialize(task:,
|
|
13
|
-
tasks: Checkoff::Tasks.new
|
|
14
|
+
tasks: Checkoff::Tasks.new,
|
|
15
|
+
timelines: Checkoff::Timelines.new)
|
|
14
16
|
@item = task
|
|
15
17
|
@tasks = tasks
|
|
18
|
+
@timelines = timelines
|
|
16
19
|
super()
|
|
17
20
|
end
|
|
18
21
|
|
|
@@ -35,12 +38,14 @@ module Checkoff
|
|
|
35
38
|
|
|
36
39
|
# @return [Hash]
|
|
37
40
|
def initializer_kwargs
|
|
38
|
-
{ tasks: tasks }
|
|
41
|
+
{ tasks: tasks, timelines: timelines }
|
|
39
42
|
end
|
|
40
43
|
|
|
41
44
|
# @return [Asana::Resources::Task]
|
|
42
45
|
attr_reader :item
|
|
43
46
|
# @return [Checkoff::Tasks]
|
|
44
47
|
attr_reader :tasks
|
|
48
|
+
# @return [Checkoff::Timelines]
|
|
49
|
+
attr_reader :timelines
|
|
45
50
|
end
|
|
46
51
|
end
|
data/lib/checkoff/sections.rb
CHANGED
|
@@ -56,13 +56,23 @@ module Checkoff
|
|
|
56
56
|
#
|
|
57
57
|
# @return [Enumerable<Asana::Resources::Section>]
|
|
58
58
|
def sections_or_raise(workspace_name, project_name, extra_fields: [])
|
|
59
|
-
fields = %w[name] + extra_fields
|
|
60
59
|
project = project_or_raise(workspace_name, project_name)
|
|
61
|
-
|
|
62
|
-
options: { fields: fields })
|
|
60
|
+
sections_by_project_gid(project.gid, extra_fields: extra_fields)
|
|
63
61
|
end
|
|
64
62
|
cache_method :sections_or_raise, SHORT_CACHE_TIME
|
|
65
63
|
|
|
64
|
+
# Returns a list of Asana API section objects for a given project GID
|
|
65
|
+
# @param project_gid [String]
|
|
66
|
+
# @param extra_fields [Array<String>]
|
|
67
|
+
#
|
|
68
|
+
# @return [Enumerable<Asana::Resources::Section>]
|
|
69
|
+
def sections_by_project_gid(project_gid, extra_fields: [])
|
|
70
|
+
fields = %w[name] + extra_fields
|
|
71
|
+
client.sections.get_sections_for_project(project_gid: project_gid,
|
|
72
|
+
options: { fields: fields })
|
|
73
|
+
end
|
|
74
|
+
cache_method :sections_by_project_gid, SHORT_CACHE_TIME
|
|
75
|
+
|
|
66
76
|
# Given a workspace name and project name, then provide a Hash of
|
|
67
77
|
# tasks with section name -> task list of the uncompleted tasks
|
|
68
78
|
# @param workspace_name [String]
|
|
@@ -85,6 +95,20 @@ module Checkoff
|
|
|
85
95
|
end
|
|
86
96
|
end
|
|
87
97
|
|
|
98
|
+
# @param section_gid [String]
|
|
99
|
+
# @param only_uncompleted [Boolean]
|
|
100
|
+
# @param extra_fields [Array<String>]
|
|
101
|
+
#
|
|
102
|
+
# @return [Enumerable<Asana::Resources::Task>]
|
|
103
|
+
def tasks_by_section_gid(section_gid,
|
|
104
|
+
only_uncompleted: true,
|
|
105
|
+
extra_fields: [])
|
|
106
|
+
options = projects.task_options
|
|
107
|
+
options[:options][:fields] += extra_fields
|
|
108
|
+
options[:completed_since] = '9999-12-01' if only_uncompleted
|
|
109
|
+
client.tasks.get_tasks(section: section_gid, **options)
|
|
110
|
+
end
|
|
111
|
+
|
|
88
112
|
# XXX: Rename to section_tasks
|
|
89
113
|
#
|
|
90
114
|
# Pulls task objects from a specified section
|
|
@@ -147,8 +171,45 @@ module Checkoff
|
|
|
147
171
|
name
|
|
148
172
|
end
|
|
149
173
|
|
|
174
|
+
# @param section [Asana::Resources::Section]
|
|
175
|
+
#
|
|
176
|
+
# @return [Asana::Resources::Section, nil]
|
|
177
|
+
def previous_section(section)
|
|
178
|
+
sections = sections_by_project_gid(section.project.fetch('gid'))
|
|
179
|
+
|
|
180
|
+
# @type [Array<Asana::Resources::Section>]
|
|
181
|
+
sections = sections.to_a
|
|
182
|
+
|
|
183
|
+
index = sections.find_index { |s| s.gid == section.gid }
|
|
184
|
+
return nil if index.nil? || index.zero?
|
|
185
|
+
|
|
186
|
+
sections[index - 1]
|
|
187
|
+
end
|
|
188
|
+
cache_method :previous_section, SHORT_CACHE_TIME
|
|
189
|
+
|
|
190
|
+
# @param gid [String]
|
|
191
|
+
#
|
|
192
|
+
# @return [Asana::Resources::Section]
|
|
193
|
+
def section_by_gid(gid)
|
|
194
|
+
options = {}
|
|
195
|
+
Asana::Resources::Section.new(parse(client.get("/sections/#{gid}", options: options)).first,
|
|
196
|
+
client: client)
|
|
197
|
+
end
|
|
198
|
+
|
|
150
199
|
private
|
|
151
200
|
|
|
201
|
+
# https://github.com/Asana/ruby-asana/blob/master/lib/asana/resource_includes/response_helper.rb#L7
|
|
202
|
+
# @param response [Faraday::Response]
|
|
203
|
+
#
|
|
204
|
+
# @return [Array<Hash, Hash>]
|
|
205
|
+
def parse(response)
|
|
206
|
+
data = response.body.fetch('data') do
|
|
207
|
+
raise("Unexpected response body: #{response.body}")
|
|
208
|
+
end
|
|
209
|
+
extra = response.body.except('data')
|
|
210
|
+
[data, extra]
|
|
211
|
+
end
|
|
212
|
+
|
|
152
213
|
# @return [Asana::Client]
|
|
153
214
|
attr_reader :client
|
|
154
215
|
|
|
@@ -188,6 +249,7 @@ module Checkoff
|
|
|
188
249
|
# @param project_gid [String]
|
|
189
250
|
# @return [void]
|
|
190
251
|
def file_task_by_section(by_section, task, project_gid)
|
|
252
|
+
# @sg-ignore
|
|
191
253
|
# @type [Array<Hash>]
|
|
192
254
|
membership = task.memberships.find { |m| m['project']['gid'] == project_gid }
|
|
193
255
|
raise "Could not find task in project_gid #{project_gid}: #{task}" if membership.nil?
|
|
@@ -23,12 +23,16 @@ module Checkoff
|
|
|
23
23
|
# @param [Hash] config
|
|
24
24
|
# @param [Asana::Client] client
|
|
25
25
|
# @param [Checkoff::Tasks] tasks
|
|
26
|
+
# @param [Checkoff::Timelines] timelines
|
|
26
27
|
def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
|
|
27
28
|
client: Checkoff::Clients.new(config: config).client,
|
|
28
29
|
tasks: Checkoff::Tasks.new(config: config,
|
|
29
|
-
client: client)
|
|
30
|
+
client: client),
|
|
31
|
+
timelines: Checkoff::Timelines.new(config: config,
|
|
32
|
+
client: client))
|
|
30
33
|
@config = config
|
|
31
34
|
@tasks = tasks
|
|
35
|
+
@timelines = timelines
|
|
32
36
|
end
|
|
33
37
|
|
|
34
38
|
# @param [Asana::Resources::Task] task
|
|
@@ -36,7 +40,7 @@ module Checkoff
|
|
|
36
40
|
# task details. Examples: [:tag, 'foo'] [:not, [:tag, 'foo']] [:tag, 'foo']
|
|
37
41
|
# @return [Boolean]
|
|
38
42
|
def filter_via_task_selector(task, task_selector)
|
|
39
|
-
evaluator = TaskSelectorEvaluator.new(task: task, tasks: tasks)
|
|
43
|
+
evaluator = TaskSelectorEvaluator.new(task: task, tasks: tasks, timelines: timelines)
|
|
40
44
|
evaluator.evaluate(task_selector)
|
|
41
45
|
end
|
|
42
46
|
|
|
@@ -45,6 +49,9 @@ module Checkoff
|
|
|
45
49
|
# @return [Checkoff::Tasks]
|
|
46
50
|
attr_reader :tasks
|
|
47
51
|
|
|
52
|
+
# @return [Checkoff::Timelines]
|
|
53
|
+
attr_reader :timelines
|
|
54
|
+
|
|
48
55
|
# bundle exec ./task_selectors.rb
|
|
49
56
|
# :nocov:
|
|
50
57
|
class << self
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
# frozen_string_literal: true
|
|
4
|
+
|
|
5
|
+
require 'forwardable'
|
|
6
|
+
require 'cache_method'
|
|
7
|
+
require_relative 'internal/config_loader'
|
|
8
|
+
require_relative 'workspaces'
|
|
9
|
+
require_relative 'clients'
|
|
10
|
+
|
|
11
|
+
# https://developers.asana.com/reference/timelines
|
|
12
|
+
|
|
13
|
+
module Checkoff
|
|
14
|
+
# Manages timelines of dependent tasks with dates and milestones
|
|
15
|
+
class Timelines
|
|
16
|
+
# @!parse
|
|
17
|
+
# extend CacheMethod::ClassMethods
|
|
18
|
+
|
|
19
|
+
MINUTE = 60
|
|
20
|
+
HOUR = MINUTE * 60
|
|
21
|
+
DAY = 24 * HOUR
|
|
22
|
+
REALLY_LONG_CACHE_TIME = HOUR * 1
|
|
23
|
+
LONG_CACHE_TIME = MINUTE * 15
|
|
24
|
+
SHORT_CACHE_TIME = MINUTE
|
|
25
|
+
|
|
26
|
+
# @param config [Hash]
|
|
27
|
+
# @param workspaces [Checkoff::Workspaces]
|
|
28
|
+
# @param sections [Checkoff::Sections]
|
|
29
|
+
# @param tasks [Checkoff::Tasks]
|
|
30
|
+
# @param clients [Checkoff::Clients]
|
|
31
|
+
# @param client [Asana::Client]
|
|
32
|
+
def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
|
|
33
|
+
workspaces: Checkoff::Workspaces.new(config: config),
|
|
34
|
+
sections: Checkoff::Sections.new(config: config),
|
|
35
|
+
tasks: Checkoff::Tasks.new(config: config),
|
|
36
|
+
clients: Checkoff::Clients.new(config: config),
|
|
37
|
+
client: clients.client)
|
|
38
|
+
@workspaces = workspaces
|
|
39
|
+
@sections = sections
|
|
40
|
+
@tasks = tasks
|
|
41
|
+
@client = client
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param task [Asana::Resources::Task]
|
|
45
|
+
# @param limit_to_portfolio_gid [String, nil]
|
|
46
|
+
# @param project_name [String]
|
|
47
|
+
def task_dependent_on_previous_section_last_milestone?(task, limit_to_portfolio_gid: nil)
|
|
48
|
+
task_data = @tasks.task_to_h(task)
|
|
49
|
+
# @sg-ignore
|
|
50
|
+
# @type [Array<Hash{String => Hash{String => String}}>]
|
|
51
|
+
memberships_data = task_data.fetch('memberships')
|
|
52
|
+
memberships_data.all? do |membership_data|
|
|
53
|
+
# @type [Hash{String => String}]
|
|
54
|
+
section_data = membership_data.fetch('section')
|
|
55
|
+
section_gid = section_data.fetch('gid')
|
|
56
|
+
section = @sections.section_by_gid(section_gid)
|
|
57
|
+
task_data_dependent_on_previous_section_last_milestone?(task_data, section)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# @param section_gid [String]
|
|
62
|
+
#
|
|
63
|
+
# @return [Asana::Resources::Task,nil]
|
|
64
|
+
def last_milestone_in_section(section_gid)
|
|
65
|
+
# @type [Array<Asana::Resources::Task>]
|
|
66
|
+
task_list = @sections.tasks_by_section_gid(section_gid).to_a
|
|
67
|
+
last_task = task_list.last
|
|
68
|
+
last_task&.resource_subtype == 'milestone' ? last_task : nil
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
private
|
|
72
|
+
|
|
73
|
+
# @param task_data [Hash]
|
|
74
|
+
# @param section [Asana::Resources::Section]
|
|
75
|
+
#
|
|
76
|
+
# @return [Boolean]
|
|
77
|
+
def task_data_dependent_on_previous_section_last_milestone?(task_data, section)
|
|
78
|
+
# @sg-ignore
|
|
79
|
+
# @type [Array<Hash{String => String}>]
|
|
80
|
+
dependencies = task_data.fetch('dependencies')
|
|
81
|
+
return false if dependencies.empty?
|
|
82
|
+
|
|
83
|
+
previous_section = @sections.previous_section(section)
|
|
84
|
+
return false if previous_section.nil?
|
|
85
|
+
|
|
86
|
+
previous_section_last_milestone = last_milestone_in_section(previous_section.gid)
|
|
87
|
+
return false if previous_section_last_milestone.nil?
|
|
88
|
+
|
|
89
|
+
dependencies.any? { |dependency| dependency.fetch('gid') == previous_section_last_milestone.gid }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Checkoff::Workspaces]
|
|
93
|
+
attr_reader :workspaces
|
|
94
|
+
|
|
95
|
+
# @return [Asana::Client]
|
|
96
|
+
attr_reader :client
|
|
97
|
+
|
|
98
|
+
# bundle exec ./timelines.rb
|
|
99
|
+
# :nocov:
|
|
100
|
+
class << self
|
|
101
|
+
# @return [void]
|
|
102
|
+
def run
|
|
103
|
+
# @sg-ignore
|
|
104
|
+
# @type [String]
|
|
105
|
+
# workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
|
|
106
|
+
# @sg-ignore
|
|
107
|
+
# @type [String]
|
|
108
|
+
# timeline_name = ARGV[1] || raise('Please pass timeline name as second argument')
|
|
109
|
+
# timelines = Checkoff::Timelines.new
|
|
110
|
+
# timeline = timelines.timeline_or_raise(workspace_name, timeline_name)
|
|
111
|
+
# puts "Results: #{timeline}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
# :nocov:
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# :nocov:
|
|
119
|
+
abs_program_name = File.expand_path($PROGRAM_NAME)
|
|
120
|
+
Checkoff::Timelines.run if abs_program_name == File.expand_path(__FILE__)
|
|
121
|
+
# :nocov:
|
data/lib/checkoff/version.rb
CHANGED
data/lib/checkoff.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: checkoff
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.98.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Vince Broz
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2023-10-
|
|
11
|
+
date: 2023-10-27 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -166,6 +166,7 @@ files:
|
|
|
166
166
|
- lib/checkoff/task_searches.rb
|
|
167
167
|
- lib/checkoff/task_selectors.rb
|
|
168
168
|
- lib/checkoff/tasks.rb
|
|
169
|
+
- lib/checkoff/timelines.rb
|
|
169
170
|
- lib/checkoff/timing.rb
|
|
170
171
|
- lib/checkoff/version.rb
|
|
171
172
|
- lib/checkoff/workspaces.rb
|