checkoff 0.92.0 → 0.94.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9b750814d7176f6d66ffefaf0c99995dc1c2d6f44b0dcc99d68f777b84f44941
4
- data.tar.gz: bb0045530c18cf158a8d255818640a6130eb392f2218c49d2da8c2f70e8197de
3
+ metadata.gz: 3728a0da90fb49dedd232b05722fbb87f03789d3bf9bc15513e4fa4b88f9e6fc
4
+ data.tar.gz: 16ce81c8b51905620414ff21f9469ce83c100a61985f355b8a27de20b7234b3c
5
5
  SHA512:
6
- metadata.gz: 10dccc258a5a948f91cee8ff8ee0eef696dfb0a7e5ae92ae07116f26e65364dee2ebf1f89c6fcc0e17fc4ea7c83a89fda89cdab91586c747a54eef1864a7dc74
7
- data.tar.gz: 5aead20f2cf559538115b8f37c394245dd10906603d307a4f4c7879696cd8e03ef40f23b3da7b898f14ae06f48b55136834b6a3bb7420486f7c5915e01ba1a22
6
+ metadata.gz: d73466d69b7b116eb8c58c0053b7cf13cedf288477e7cc8862de091fdd183e5a84355e19841e09df003da9b44d2b87ac90a459845c9186737069002f5312e455
7
+ data.tar.gz: 207d7d34ecdc3435892afe3b96860df2373e36067f8c75866f7b87d5a6f0aac0ebe89b0889b45d6a31ddec1ac6211f592fce4b657aa70ffad5e5c504deeea773
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.92.0)
15
+ checkoff (0.94.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -33,6 +33,10 @@
33
33
  # def projects; end
34
34
  # # @return [Asana::ProxiedResourceClasses::UserTaskList]
35
35
  # def user_task_lists; end
36
+ # # @return [Asana::ProxiedResourceClasses::Portfolio]
37
+ # def portfolios; end
38
+ # # @return [Asana::ProxiedResourceClasses::User]
39
+ # def users; end
36
40
  # end
37
41
  # class Collection < Asana::Resources::Collection; end
38
42
  # module Resources
@@ -160,6 +164,35 @@
160
164
  # def get_user_task_list_for_user(client, user_gid:,
161
165
  # workspace: nil, options: {}); end
162
166
  # end
167
+ # class Portfolio
168
+ # # Returns a list of the portfolios in compact representation that are owned
169
+ # # by the current API user.
170
+ # #
171
+ # # @param workspace [Gid] The workspace or organization to filter portfolios on.
172
+ # # @param owner [String] The user who owns the portfolio. Currently, API users can only get a
173
+ # # list of portfolios that they themselves own.
174
+ # #
175
+ # # @param per_page [Integer] the number of records to fetch per page.
176
+ # # @param options [Hash] the request I/O options.
177
+ # #
178
+ # # @return [Enumerable<Asana::Resources::Portfolio>]
179
+ # def find_all(workspace: required("workspace"), owner: required("owner"), per_page: 20, options: {}); end
180
+ # # Returns the complete record for a single portfolio.
181
+ # #
182
+ # # @param id [Gid] The portfolio to get.
183
+ # # @param options [Hash] the request I/O options.
184
+ #
185
+ # # @return [Asana::Resources::Portfolio,nil]
186
+ # def find_by_id(client, id, options: {}); end
187
+ # end
188
+ # class User
189
+ # # Returns the full user record for the currently authenticated user.
190
+ # #
191
+ # # @param options [Hash] the request I/O options.
192
+ # #
193
+ # # @return [Asana::Resources::User]
194
+ # def me(options: {}); end
195
+ # end
163
196
  # end
164
197
  # end
165
198
  # rubocop:enable Layout/LineLength
@@ -8,6 +8,8 @@ underscored_plural_name="${1:?underscored plural name of entities minus .rb}"
8
8
  underscored_singular_name=$(sed -e 's/s$//g' <<< "${underscored_plural_name}")
9
9
  kabob_case_plural_name=${underscored_plural_name/_/-}
10
10
  class_name="${2:?class name without Checkoff:: prefix}"
11
+ # shellcheck disable=SC2001
12
+ asana_resource_class_name=Asana::Resources::$(sed -e 's/s$//g' <<< "${class_name}")
11
13
 
12
14
  cat > "${underscored_plural_name}.rb" << EOF
13
15
  #!/usr/bin/env ruby
@@ -20,10 +22,13 @@ require_relative 'internal/config_loader'
20
22
  require_relative 'workspaces'
21
23
  require_relative 'clients'
22
24
 
23
- # https://developers.asana.com/docs/${kabob_case_plural_name}
25
+ # https://developers.asana.com/reference/${kabob_case_plural_name}
24
26
 
25
27
  module Checkoff
26
28
  class ${class_name}
29
+ # @!parse
30
+ # extend CacheMethod::ClassMethods
31
+
27
32
  MINUTE = 60
28
33
  HOUR = MINUTE * 60
29
34
  DAY = 24 * HOUR
@@ -31,6 +36,10 @@ module Checkoff
31
36
  LONG_CACHE_TIME = MINUTE * 15
32
37
  SHORT_CACHE_TIME = MINUTE
33
38
 
39
+ # @param config [Hash]
40
+ # @param workspaces [Checkoff::Workspaces]
41
+ # @param clients [Checkoff::Clients]
42
+ # @param client [Asana::Client]
34
43
  def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
35
44
  workspaces: Checkoff::Workspaces.new(config: config),
36
45
  clients: Checkoff::Clients.new(config: config),
@@ -39,31 +48,47 @@ module Checkoff
39
48
  @client = client
40
49
  end
41
50
 
51
+ # @param workspace_name [String]
52
+ # @param ${underscored_singular_name}_name [String]
53
+ #
54
+ # @return [${asana_resource_class_name}]
42
55
  def ${underscored_singular_name}_or_raise(workspace_name, ${underscored_singular_name}_name)
43
- ${underscored_singular_name} = ${underscored_singular_name}(workspace_name, ${underscored_singular_name}_name)
44
- raise "Could not find ${underscored_singular_name} #{${underscored_singular_name}_name} under workspace #{workspace_name}." if ${underscored_singular_name}.nil?
56
+ ${underscored_singular_name}_obj = ${underscored_singular_name}(workspace_name, ${underscored_singular_name}_name)
57
+ raise "Could not find ${underscored_singular_name} #{${underscored_singular_name}_name} under workspace #{workspace_name}." if ${underscored_singular_name}_obj.nil?
45
58
 
46
- ${underscored_singular_name}
59
+ ${underscored_singular_name}_obj
47
60
  end
48
61
  cache_method :${underscored_singular_name}_or_raise, LONG_CACHE_TIME
49
62
 
63
+ # @param workspace_name [String]
64
+ # @param ${underscored_singular_name}_name [String]
65
+ #
66
+ # @return [${asana_resource_class_name},nil]
50
67
  def ${underscored_singular_name}(workspace_name, ${underscored_singular_name}_name)
51
68
  workspace = workspaces.workspace_or_raise(workspace_name)
52
- ${underscored_plural_name} = client.${underscored_plural_name}.get_${underscored_plural_name}_for_workspace(workspace_gid: workspace.gid)
53
- ${underscored_plural_name}.find { |${underscored_singular_name}| ${underscored_singular_name}.name == ${underscored_singular_name}_name }
69
+ ${underscored_singular_name}_objs = client.${underscored_plural_name}.get_${underscored_plural_name}_for_workspace(workspace_gid: workspace.gid)
70
+ ${underscored_singular_name}_objs.find { |${underscored_singular_name}_obj| ${underscored_singular_name}_obj.name == ${underscored_singular_name}_name }
54
71
  end
55
72
  cache_method :${underscored_singular_name}, LONG_CACHE_TIME
56
73
 
57
74
  private
58
75
 
59
- attr_reader :workspaces, :client
76
+ # @return [Checkoff::Workspaces]
77
+ attr_reader :workspaces
78
+
79
+ # @return [Asana::Client]
80
+ attr_reader :client
60
81
 
61
82
  # bundle exec ./${underscored_plural_name}.rb
62
83
  # :nocov:
63
84
  class << self
64
85
  # @return [void]
65
86
  def run
87
+ # @sg-ignore
88
+ # @type [String]
66
89
  workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
90
+ # @sg-ignore
91
+ # @type [String]
67
92
  ${underscored_singular_name}_name = ARGV[1] || raise('Please pass ${underscored_singular_name} name as second argument')
68
93
  ${underscored_plural_name} = Checkoff::${class_name}.new
69
94
  ${underscored_singular_name} = ${underscored_plural_name}.${underscored_singular_name}_or_raise(workspace_name, ${underscored_singular_name}_name)
@@ -72,7 +72,7 @@ module Checkoff
72
72
  end
73
73
 
74
74
  # :ready function
75
- class DuePFunctionEvaluator < FunctionEvaluator
75
+ class ReadyFunctionEvaluator < FunctionEvaluator
76
76
  def matches?
77
77
  fn?(selector, :ready)
78
78
  end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Checkoff
4
+ module Internal
5
+ # Utility methods for working with task dates and times
6
+ class TaskTiming
7
+ # @param time_class [Class<Time>]
8
+ def initialize(time_class: Time)
9
+ @time_class = time_class
10
+ end
11
+
12
+ # @param task [Asana::Resources::Task]
13
+ # @return [Time, nil]
14
+ def start_time(task)
15
+ return @time_class.parse(task.start_at) if task.start_at
16
+ return @time_class.parse(task.start_on) if task.start_on
17
+
18
+ nil
19
+ end
20
+
21
+ # @param task [Asana::Resources::Task]
22
+ # @return [Time, nil]
23
+ def due_time(task)
24
+ return @time_class.parse(task.due_at) if task.due_at
25
+ return @time_class.parse(task.due_on) if task.due_on
26
+
27
+ nil
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,112 @@
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/portfolios
12
+
13
+ module Checkoff
14
+ # Pull portfolios from Asana
15
+ class Portfolios
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 clients [Checkoff::Clients]
29
+ # @param client [Asana::Client]
30
+ def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
31
+ workspaces: Checkoff::Workspaces.new(config: config),
32
+ clients: Checkoff::Clients.new(config: config),
33
+ client: clients.client)
34
+ @workspaces = workspaces
35
+ @client = client
36
+ end
37
+
38
+ # @param workspace_name [String]
39
+ # @param portfolio_name [String]
40
+ #
41
+ # @return [Asana::Resources::Portfolio]
42
+ def portfolio_or_raise(workspace_name, portfolio_name)
43
+ portfolio_obj = portfolio(workspace_name, portfolio_name)
44
+ raise "Could not find portfolio #{portfolio_name} under workspace #{workspace_name}." if portfolio_obj.nil?
45
+
46
+ portfolio_obj
47
+ end
48
+ cache_method :portfolio_or_raise, LONG_CACHE_TIME
49
+
50
+ # @param workspace_name [String]
51
+ # @param portfolio_name [String]
52
+ #
53
+ # @sg-ignore
54
+ # @return [Asana::Resources::Portfolio,nil]
55
+ def portfolio(workspace_name, portfolio_name)
56
+ workspace = workspaces.workspace_or_raise(workspace_name)
57
+ me = client.users.me
58
+ portfolio_objs = client.portfolios.find_all(workspace: workspace.gid,
59
+ owner: me.gid)
60
+ # @type [Asana::Resources::Portfolio, nil]
61
+ portfolio_objs.find { |portfolio_obj| portfolio_obj.name == portfolio_name }
62
+ end
63
+ cache_method :portfolio, LONG_CACHE_TIME
64
+
65
+ # Pull a specific portfolio by gid
66
+ #
67
+ # @param portfolio_gid [String]
68
+ # @param extra_fields [Array<String>]
69
+ #
70
+ # @return [Asana::Resources::Portfolio, nil]
71
+ def portfolio_by_gid(portfolio_gid,
72
+ extra_fields: [])
73
+ options = {
74
+ fields: ['name'],
75
+ }
76
+ options[:fields] += extra_fields
77
+ client.portfolios.find_by_id(portfolio_gid, options: options)
78
+ end
79
+ cache_method :portfolio_by_gid, SHORT_CACHE_TIME
80
+
81
+ private
82
+
83
+ # @return [Checkoff::Workspaces]
84
+ attr_reader :workspaces
85
+
86
+ # @return [Asana::Client]
87
+ attr_reader :client
88
+
89
+ # bundle exec ./portfolios.rb
90
+ # :nocov:
91
+ class << self
92
+ # @return [void]
93
+ def run
94
+ # @sg-ignore
95
+ # @type [String]
96
+ workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
97
+ # @sg-ignore
98
+ # @type [String]
99
+ portfolio_name = ARGV[1] || raise('Please pass portfolio name as second argument')
100
+ portfolios = Checkoff::Portfolios.new
101
+ portfolio = portfolios.portfolio_or_raise(workspace_name, portfolio_name)
102
+ puts "Results: #{portfolio}"
103
+ end
104
+ end
105
+ # :nocov:
106
+ end
107
+ end
108
+
109
+ # :nocov:
110
+ abs_program_name = File.expand_path($PROGRAM_NAME)
111
+ Checkoff::Portfolios.run if abs_program_name == File.expand_path(__FILE__)
112
+ # :nocov:
@@ -5,6 +5,7 @@
5
5
  require_relative 'sections'
6
6
  require_relative 'workspaces'
7
7
  require_relative 'internal/config_loader'
8
+ require_relative 'internal/task_timing'
8
9
  require 'asana'
9
10
 
10
11
  module Checkoff
@@ -57,8 +58,8 @@ module Checkoff
57
58
  def task_ready?(task, ignore_dependencies: false)
58
59
  return false if !ignore_dependencies && incomplete_dependencies?(task)
59
60
 
60
- start = start_time(task)
61
- due = due_time(task)
61
+ start = task_timing.start_time(task)
62
+ due = task_timing.due_time(task)
62
63
 
63
64
  return true if start.nil? && due.nil?
64
65
 
@@ -79,12 +80,12 @@ module Checkoff
79
80
  only_uncompleted: true,
80
81
  extra_fields: [])
81
82
  # @sg-ignore
82
- tasks = tasks_from_section(workspace_name,
83
- project_name,
84
- section_name: section_name,
85
- only_uncompleted: only_uncompleted,
86
- extra_fields: extra_fields)
87
- tasks.find { |task| task.name == task_name }
83
+ t = tasks(workspace_name,
84
+ project_name,
85
+ section_name: section_name,
86
+ only_uncompleted: only_uncompleted,
87
+ extra_fields: extra_fields)
88
+ t.find { |task| task.name == task_name }
88
89
  end
89
90
  cache_method :task, SHORT_CACHE_TIME
90
91
 
@@ -146,16 +147,20 @@ module Checkoff
146
147
 
147
148
  private
148
149
 
150
+ # @return [Checkoff::Internal::TaskTiming]
151
+ def task_timing
152
+ @task_timing ||= Checkoff::Internal::TaskTiming.new(time_class: @time_class)
153
+ end
154
+
149
155
  # @param workspace_name [String]
150
156
  # @param project_name [String, Symbol]
151
- # @param section_name [String, nil, :unspecified]
157
+ # @param section_name [String, nil, Symbol<:unspecified>]
152
158
  # @param only_uncompleted [Boolean]
153
159
  # @param extra_fields [Array<String>]
160
+ #
154
161
  # @return [Enumerable<Asana::Resources::Task>]
155
- def tasks_from_section(workspace_name, project_name,
156
- section_name:,
157
- only_uncompleted:,
158
- extra_fields:)
162
+ def tasks(workspace_name, project_name,
163
+ only_uncompleted:, extra_fields:, section_name: :unspecified)
159
164
  if section_name == :unspecified
160
165
  project = projects.project_or_raise(workspace_name, project_name)
161
166
  projects.tasks_from_project(project,
@@ -182,23 +187,5 @@ module Checkoff
182
187
  def default_assignee_gid
183
188
  @config.fetch(:default_assignee_gid)
184
189
  end
185
-
186
- # @param task [Asana::Resources::Task]
187
- # @return [Time, nil]
188
- def start_time(task)
189
- return @time_class.parse(task.start_at) if task.start_at
190
- return @time_class.parse(task.start_on) if task.start_on
191
-
192
- nil
193
- end
194
-
195
- # @param task [Asana::Resources::Task]
196
- # @return [Time, nil]
197
- def due_time(task)
198
- return @time_class.parse(task.due_at) if task.due_at
199
- return @time_class.parse(task.due_on) if task.due_on
200
-
201
- nil
202
- end
203
190
  end
204
191
  end
@@ -3,5 +3,5 @@
3
3
  # Command-line and gem client for Asana (unofficial)
4
4
  module Checkoff
5
5
  # Version of library
6
- VERSION = '0.92.0'
6
+ VERSION = '0.94.0'
7
7
  end
data/lib/checkoff.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'checkoff/version'
4
4
  require 'checkoff/clients'
5
5
  require 'checkoff/workspaces'
6
+ require 'checkoff/portfolios'
6
7
  require 'checkoff/projects'
7
8
  require 'checkoff/sections'
8
9
  require 'checkoff/subtasks'
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.92.0
4
+ version: 0.94.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-22 00:00:00.000000000 Z
11
+ date: 2023-10-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -153,7 +153,9 @@ files:
153
153
  - lib/checkoff/internal/selector_classes/task/function_evaluator.rb
154
154
  - lib/checkoff/internal/selector_evaluator.rb
155
155
  - lib/checkoff/internal/task_selector_evaluator.rb
156
+ - lib/checkoff/internal/task_timing.rb
156
157
  - lib/checkoff/my_tasks.rb
158
+ - lib/checkoff/portfolios.rb
157
159
  - lib/checkoff/project_selectors.rb
158
160
  - lib/checkoff/projects.rb
159
161
  - lib/checkoff/section_selectors.rb