checkoff 0.97.0 → 0.99.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 +6 -26
- data/lib/checkoff/internal/selector_classes/task.rb +22 -2
- data/lib/checkoff/internal/task_selector_evaluator.rb +7 -2
- data/lib/checkoff/internal/task_timing.rb +42 -7
- data/lib/checkoff/sections.rb +65 -3
- data/lib/checkoff/task_selectors.rb +9 -2
- data/lib/checkoff/tasks.rb +4 -1
- 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: f0aa9c88e96e1094d284c59a9c7146ff72f393f2e16442c8e320b3253ab0221d
|
|
4
|
+
data.tar.gz: 7f6021289d7fb0e331cb6b993b10307287c365d9a00d5fbe789e5b6c13972dad
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ab4476c149bf918e5d138f71a5d1c5df78192f3fa3fa13e8ef0e8a2d7c7198b34268a78ab14e6009237d3cf0113316f3ff91222d22893606c5ecc1ab31830d55
|
|
7
|
+
data.tar.gz: 4f804bcfc9548b7106fcd80c5764da4250bf024e9dc7f1c2b95460078b89de3a238ba2b28f6d44188cd3fee074a0d348925ef780b751534a0f801f08b74fab24
|
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.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../function_evaluator'
|
|
4
|
+
require 'checkoff/internal/task_timing'
|
|
4
5
|
|
|
5
6
|
module Checkoff
|
|
6
7
|
module SelectorClasses
|
|
@@ -9,10 +10,14 @@ module Checkoff
|
|
|
9
10
|
class FunctionEvaluator < ::Checkoff::SelectorClasses::FunctionEvaluator
|
|
10
11
|
# @param selector [Array<(Symbol, Array)>,String]
|
|
11
12
|
# @param tasks [Checkoff::Tasks]
|
|
13
|
+
# @param timelines [Checkoff::Timelines]
|
|
12
14
|
def initialize(selector:,
|
|
13
|
-
tasks
|
|
15
|
+
tasks:,
|
|
16
|
+
timelines:)
|
|
14
17
|
@selector = selector
|
|
15
18
|
@tasks = tasks
|
|
19
|
+
@timelines = timelines
|
|
20
|
+
@task_timing = ::Checkoff::Internal::TaskTiming.new
|
|
16
21
|
super()
|
|
17
22
|
end
|
|
18
23
|
|
|
@@ -41,31 +46,6 @@ module Checkoff
|
|
|
41
46
|
raise "Teach me how to handle field #{field_name}"
|
|
42
47
|
end
|
|
43
48
|
|
|
44
|
-
# @param task [Asana::Resources::Task]
|
|
45
|
-
# @param field_name [Symbol]
|
|
46
|
-
#
|
|
47
|
-
# @sg-ignore
|
|
48
|
-
# @return [Date, Time, nil]
|
|
49
|
-
def pull_date_or_time_field_by_name(task, field_name)
|
|
50
|
-
if field_name == :due
|
|
51
|
-
return Time.parse(task.due_at) unless task.due_at.nil?
|
|
52
|
-
|
|
53
|
-
return Date.parse(task.due_on) unless task.due_on.nil?
|
|
54
|
-
|
|
55
|
-
return nil
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
if field_name == :start
|
|
59
|
-
return Time.parse(task.start_at) unless task.start_at.nil?
|
|
60
|
-
|
|
61
|
-
return Date.parse(task.start_on) unless task.start_on.nil?
|
|
62
|
-
|
|
63
|
-
return nil
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
raise "Teach me how to handle field #{field_name}"
|
|
67
|
-
end
|
|
68
|
-
|
|
69
49
|
# @sg-ignore
|
|
70
50
|
# @param task [Asana::Resources::Task]
|
|
71
51
|
# @param custom_field_gid [String]
|
|
@@ -138,8 +138,8 @@ module Checkoff
|
|
|
138
138
|
end_n_days_from_now_time = (Time.now + (end_num_days_from_now * 24 * 60 * 60))
|
|
139
139
|
|
|
140
140
|
# @type [Date, Time, nil]
|
|
141
|
-
task_date_or_time =
|
|
142
|
-
|
|
141
|
+
task_date_or_time = @task_timing.date_or_time_field_by_name(task, :start) ||
|
|
142
|
+
@task_timing.date_or_time_field_by_name(task, :due)
|
|
143
143
|
|
|
144
144
|
return false if task_date_or_time.nil?
|
|
145
145
|
|
|
@@ -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
|
|
@@ -5,27 +5,62 @@ module Checkoff
|
|
|
5
5
|
# Utility methods for working with task dates and times
|
|
6
6
|
class TaskTiming
|
|
7
7
|
# @param time_class [Class<Time>]
|
|
8
|
-
|
|
8
|
+
# @param date_class [Class<Date>]
|
|
9
|
+
def initialize(time_class: Time, date_class: Date)
|
|
9
10
|
@time_class = time_class
|
|
11
|
+
@date_class = date_class
|
|
10
12
|
end
|
|
11
13
|
|
|
12
14
|
# @param task [Asana::Resources::Task]
|
|
13
15
|
# @return [Time, nil]
|
|
14
16
|
def start_time(task)
|
|
15
|
-
|
|
16
|
-
return @time_class.parse(task.start_on) if task.start_on
|
|
17
|
-
|
|
18
|
-
nil
|
|
17
|
+
date_or_time_field_by_name(task, :start)&.to_time
|
|
19
18
|
end
|
|
20
19
|
|
|
21
20
|
# @param task [Asana::Resources::Task]
|
|
22
21
|
# @return [Time, nil]
|
|
23
22
|
def due_time(task)
|
|
24
|
-
|
|
25
|
-
|
|
23
|
+
date_or_time_field_by_name(task, :due)&.to_time
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @param task [Asana::Resources::Task]
|
|
27
|
+
# @param field_name [Symbol]
|
|
28
|
+
#
|
|
29
|
+
# @sg-ignore
|
|
30
|
+
# @return [Date, Time, nil]
|
|
31
|
+
def start_date_or_time(task)
|
|
32
|
+
return @time_class.parse(task.start_at) unless task.start_at.nil?
|
|
33
|
+
|
|
34
|
+
return @date_class.parse(task.start_on) unless task.start_on.nil?
|
|
26
35
|
|
|
27
36
|
nil
|
|
28
37
|
end
|
|
38
|
+
|
|
39
|
+
# @param task [Asana::Resources::Task]
|
|
40
|
+
# @param field_name [Symbol]
|
|
41
|
+
#
|
|
42
|
+
# @sg-ignore
|
|
43
|
+
# @return [Date, Time, nil]
|
|
44
|
+
def due_date_or_time(task)
|
|
45
|
+
return @time_class.parse(task.due_at) unless task.due_at.nil?
|
|
46
|
+
|
|
47
|
+
return @date_class.parse(task.due_on) unless task.due_on.nil?
|
|
48
|
+
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# @param task [Asana::Resources::Task]
|
|
53
|
+
# @param field_name [Symbol]
|
|
54
|
+
#
|
|
55
|
+
# @sg-ignore
|
|
56
|
+
# @return [Date, Time, nil]
|
|
57
|
+
def date_or_time_field_by_name(task, field_name)
|
|
58
|
+
return due_date_or_time(task) if field_name == :due
|
|
59
|
+
|
|
60
|
+
return start_date_or_time(task) if field_name == :start
|
|
61
|
+
|
|
62
|
+
raise "Teach me how to handle field #{field_name}"
|
|
63
|
+
end
|
|
29
64
|
end
|
|
30
65
|
end
|
|
31
66
|
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
|
data/lib/checkoff/tasks.rb
CHANGED
|
@@ -27,6 +27,7 @@ module Checkoff
|
|
|
27
27
|
# @param workspaces [Checkoff::Workspaces]
|
|
28
28
|
# @param sections [Checkoff::Sections]
|
|
29
29
|
# @param time_class [Class<Time>]
|
|
30
|
+
# @param date_class [Class<Date>]
|
|
30
31
|
# @param asana_task [Class<Asana::Resources::Task>]
|
|
31
32
|
def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
|
|
32
33
|
client: Checkoff::Clients.new(config: config).client,
|
|
@@ -35,10 +36,12 @@ module Checkoff
|
|
|
35
36
|
sections: Checkoff::Sections.new(config: config,
|
|
36
37
|
client: client),
|
|
37
38
|
time_class: Time,
|
|
39
|
+
date_class: Date,
|
|
38
40
|
asana_task: Asana::Resources::Task)
|
|
39
41
|
@config = config
|
|
40
42
|
@sections = sections
|
|
41
43
|
@time_class = time_class
|
|
44
|
+
@date_class = date_class
|
|
42
45
|
@asana_task = asana_task
|
|
43
46
|
@client = client
|
|
44
47
|
@workspaces = workspaces
|
|
@@ -167,7 +170,7 @@ module Checkoff
|
|
|
167
170
|
|
|
168
171
|
# @return [Checkoff::Internal::TaskTiming]
|
|
169
172
|
def task_timing
|
|
170
|
-
@task_timing ||= Checkoff::Internal::TaskTiming.new(time_class: @time_class)
|
|
173
|
+
@task_timing ||= Checkoff::Internal::TaskTiming.new(time_class: @time_class, date_class: @date_class)
|
|
171
174
|
end
|
|
172
175
|
|
|
173
176
|
# @return [Checkoff::Internal::TaskHashes]
|
|
@@ -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.99.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
|