checkoff 0.132.0 → 0.134.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a15de58445eeb2a8825341add1da6ab709ddd7f588e6d7dd7677e225143c9be
4
- data.tar.gz: d1a927d6e93ab96e0057be99046ed237858b12b74ce858cdd7c76b6596a0108d
3
+ metadata.gz: 774ff63fdebd9187b4aedf1cad2ee5ba52c4d7f5d582747b0414e698c6c31d9d
4
+ data.tar.gz: de61ee88dc1e575702bbe15df6fcd0e70738a3b0f5ffb10654633260831c9923
5
5
  SHA512:
6
- metadata.gz: 51ca950b44ff215eebf8cd7756b2a527952de5aba760f7e9bec5df6ba5d255cded0b67b00cac2add5b2f08a87955543b7ef9c9b83a0f7718d2e69599d85a9d4b
7
- data.tar.gz: b75ec0c19e1464bff3373f6e87bab3da5f5d867e514db516b461590b1a4aa4f7a7a8b4d786fb140cb4e9fe1c800569070854cdbda75956bb0b212aaae6401f24
6
+ metadata.gz: 767386a4cb59daf9085a545e1e6bb8b76d01ac374785c2c574a285142b0531ebf1c3c7f930b3d104262452bdb1975b8158a2906d157cc88b46bce0852fd357e9
7
+ data.tar.gz: c3f95457b476ff66133dba010920db1feee0f27f98fa35ed92246b677dde95f17fd8347b91de6aa63f2777c2dcebef061a8d5aefaf85638e4c321009b68185d6
data/.circleci/config.yml CHANGED
@@ -119,6 +119,8 @@ jobs:
119
119
  - run_with_languages:
120
120
  label: Make RubyGems release
121
121
  command: |
122
+ set -x
123
+
122
124
  # Coax overcommit into working
123
125
  git config --global user.email "test@test.test"
124
126
  git config --global user.name "Test Test"
@@ -127,6 +129,7 @@ jobs:
127
129
 
128
130
  git status
129
131
  bundle exec bump --commit-message ' [skip ci]' --tag --tag-prefix=v minor
132
+ bundle install
130
133
  # bundle exec needed for overcommit hooks
131
134
  #
132
135
  # if this step fails, check that
data/Gemfile CHANGED
@@ -11,7 +11,7 @@ gem 'fakeweb'
11
11
  gem 'mdl'
12
12
  gem 'minitest-profile'
13
13
  gem 'minitest-reporters'
14
- gem 'mocha', ['~> 2.0.0.alpha.1']
14
+ gem 'mocha', ['>= 2']
15
15
  # 0.58.0 and 0.57.0 don't seem super compatible with signatures, and
16
16
  # magit doesn't seem to want to use the bundled version at the moment,
17
17
  # so let's favor the more recent version...
data/Gemfile.lock CHANGED
@@ -12,7 +12,7 @@ GIT
12
12
  PATH
13
13
  remote: .
14
14
  specs:
15
- checkoff (0.132.0)
15
+ checkoff (0.134.0)
16
16
  activesupport
17
17
  asana (> 0.10.0)
18
18
  cache_method
@@ -22,7 +22,7 @@ PATH
22
22
  GEM
23
23
  remote: https://rubygems.org/
24
24
  specs:
25
- activesupport (7.1.1)
25
+ activesupport (7.1.2)
26
26
  base64
27
27
  bigdecimal
28
28
  concurrent-ruby (~> 1.0, >= 1.0.2)
@@ -37,7 +37,7 @@ GEM
37
37
  ansi (1.5.0)
38
38
  ast (2.4.2)
39
39
  backport (1.2.0)
40
- base64 (0.1.1)
40
+ base64 (0.2.0)
41
41
  benchmark (0.2.1)
42
42
  bigdecimal (3.1.4)
43
43
  builder (3.2.4)
@@ -56,7 +56,7 @@ GEM
56
56
  dalli (3.2.6)
57
57
  diff-lcs (1.5.0)
58
58
  docile (1.4.0)
59
- drb (2.1.1)
59
+ drb (2.2.0)
60
60
  ruby2_keywords
61
61
  e2mmap (0.1.0)
62
62
  fakeweb (1.3.0)
@@ -104,7 +104,7 @@ GEM
104
104
  mixlib-shellout
105
105
  method_source (1.0.0)
106
106
  mini_portile2 (2.8.2)
107
- minitest (5.18.0)
107
+ minitest (5.20.0)
108
108
  minitest-profile (0.0.2)
109
109
  minitest-reporters (1.5.0)
110
110
  ansi
@@ -116,11 +116,12 @@ GEM
116
116
  tomlrb
117
117
  mixlib-shellout (3.2.7)
118
118
  chef-utils
119
- mocha (2.0.0.alpha.1)
119
+ mocha (2.1.0)
120
+ ruby2_keywords (>= 0.0.5)
120
121
  multi_json (1.15.0)
121
122
  multi_xml (0.6.0)
122
123
  multipart-post (2.1.1)
123
- mutex_m (0.1.2)
124
+ mutex_m (0.2.0)
124
125
  nokogiri (1.15.2)
125
126
  mini_portile2 (~> 2.8.2)
126
127
  racc (~> 1.4)
@@ -232,7 +233,7 @@ DEPENDENCIES
232
233
  mdl
233
234
  minitest-profile
234
235
  minitest-reporters
235
- mocha (~> 2.0.0.alpha.1)
236
+ mocha (>= 2)
236
237
  overcommit (>= 0.60.0, < 0.61.0)
237
238
  pry
238
239
  punchlist
@@ -0,0 +1,88 @@
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 'internal/asana_event_filter'
9
+ require_relative 'workspaces'
10
+ require_relative 'clients'
11
+
12
+ # https://developers.asana.com/reference/events
13
+
14
+ module Checkoff
15
+ # Methods related to the Asana events / webhooks APIs
16
+ class Events
17
+ # @!parse
18
+ # extend CacheMethod::ClassMethods
19
+
20
+ MINUTE = 60
21
+ private_constant :MINUTE
22
+ HOUR = MINUTE * 60
23
+ private_constant :HOUR
24
+ DAY = 24 * HOUR
25
+ private_constant :DAY
26
+ REALLY_LONG_CACHE_TIME = HOUR * 1
27
+ private_constant :REALLY_LONG_CACHE_TIME
28
+ LONG_CACHE_TIME = MINUTE * 15
29
+ private_constant :LONG_CACHE_TIME
30
+ SHORT_CACHE_TIME = MINUTE
31
+ private_constant :SHORT_CACHE_TIME
32
+
33
+ # @param config [Hash]
34
+ # @param workspaces [Checkoff::Workspaces]
35
+ # @param clients [Checkoff::Clients]
36
+ # @param client [Asana::Client]
37
+ # @param asana_event_filter_class [Class<Checkoff::Internal::AsanaEventFilter>]
38
+ def initialize(config: Checkoff::Internal::ConfigLoader.load(:asana),
39
+ workspaces: Checkoff::Workspaces.new(config: config),
40
+ clients: Checkoff::Clients.new(config: config),
41
+ client: clients.client,
42
+ asana_event_filter_class: Checkoff::Internal::AsanaEventFilter)
43
+ @workspaces = workspaces
44
+ @client = client
45
+ @asana_event_filter_class = asana_event_filter_class
46
+ end
47
+
48
+ # @param filters [Array<Hash>, nil] The filters to match against
49
+ # @param asana_events [Array<Hash>] The events that Asana sent
50
+ #
51
+ # @return [Array<Hash>] The events that should be acted on
52
+ def filter_asana_events(filters, asana_events)
53
+ asana_event_filter = @asana_event_filter_class.new(filters: filters)
54
+ asana_events.select { |event| asana_event_filter.matches?(event) }
55
+ end
56
+
57
+ private
58
+
59
+ # @return [Checkoff::Workspaces]
60
+ attr_reader :workspaces
61
+
62
+ # @return [Asana::Client]
63
+ attr_reader :client
64
+
65
+ # bundle exec ./events.rb
66
+ # :nocov:
67
+ class << self
68
+ # @return [void]
69
+ def run
70
+ # @sg-ignore
71
+ # @type [String]
72
+ # workspace_name = ARGV[0] || raise('Please pass workspace name as first argument')
73
+ # @sg-ignore
74
+ # @type [String]
75
+ # event_name = ARGV[1] || raise('Please pass event name as second argument')
76
+ # events = Checkoff::Events.new
77
+ # event = events.event_or_raise(workspace_name, event_name)
78
+ # puts "Results: #{event}"
79
+ end
80
+ end
81
+ # :nocov:
82
+ end
83
+ end
84
+
85
+ # :nocov:
86
+ abs_program_name = File.expand_path($PROGRAM_NAME)
87
+ Checkoff::Events.run if abs_program_name == File.expand_path(__FILE__)
88
+ # :nocov:
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'logging'
4
+
5
+ module Checkoff
6
+ module Internal
7
+ # Uses an enhanced version of Asana event filter configuration
8
+ #
9
+ # See https://developers.asana.com/reference/createwebhook | body
10
+ # params | data | filters for a general description of the scheme.
11
+ #
12
+ # Additional supported filter keys:
13
+ #
14
+ # * 'checkoff:parent.gid' - requires that the 'gid' key in the 'parent' object
15
+ # match the given value
16
+ class AsanaEventFilter
17
+ include Logging
18
+
19
+ # @param filters [Array<Hash>, nil] The filters to match against
20
+ def initialize(filters:)
21
+ @filters = filters
22
+ end
23
+
24
+ # @param asana_event [Hash] The event that Asana sent
25
+ def matches?(asana_event)
26
+ logger.debug { "Filtering using #{@filters.inspect}" }
27
+ return true if @filters.nil?
28
+
29
+ @filters.any? do |filter|
30
+ out = filter_matches_asana_event?(filter, asana_event)
31
+ logger.debug { "Filter #{filter.inspect} matched? #{out} against event #{asana_event.inspect}" }
32
+ out
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ # @param filter [Hash]
39
+ # @param asana_event [Hash]
40
+ #
41
+ # @sg-ignore
42
+ # @return [Boolean]
43
+ def filter_matches_asana_event?(filter, asana_event)
44
+ # @param key [String]
45
+ # @param value [String, Array<String>]
46
+ filter.all? do |key, value|
47
+ asana_event_matches_filter_item?(key, value, asana_event)
48
+ end
49
+ end
50
+
51
+ # @param key [String]
52
+ # @param value [String, Array<String>]
53
+ # @param asana_event [Hash]
54
+ #
55
+ # @sg-ignore
56
+ # @return [Boolean]
57
+ def asana_event_matches_filter_item?(key, value, asana_event)
58
+ case key
59
+ when 'resource_type'
60
+ asana_event.fetch('resource', {})['resource_type'] == value
61
+ when 'resource_subtype'
62
+ asana_event.fetch('resource', {})['resource_subtype'] == value
63
+ when 'action'
64
+ asana_event['action'] == value
65
+ when 'fields'
66
+ value.include? asana_event.fetch('change', {})['field']
67
+ when 'checkoff:parent.gid'
68
+ asana_event.fetch('parent', {})['gid'] == value
69
+ else
70
+ raise "Unknown filter key #{key}"
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'logger'
4
+
5
+ # include this to add ability to log at different levels
6
+ module Logging
7
+ # @return [::Logger]
8
+ def logger
9
+ # @sg-ignore
10
+ @logger ||= if defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
11
+ # @sg-ignore
12
+ Rails.logger
13
+ else
14
+ ::Logger.new($stdout, level: log_level)
15
+ end
16
+ end
17
+
18
+ # @param message [String,nil]
19
+ #
20
+ # @return [void]
21
+ def error(message = nil, &block)
22
+ logger.error(message, &block)
23
+ end
24
+
25
+ # @param message [String,nil]
26
+ #
27
+ # @return [void]
28
+ def warn(message = nil, &block)
29
+ logger.warn(message, &block)
30
+ end
31
+
32
+ # @param message [String,nil]
33
+ #
34
+ # @return [void]
35
+ def info(message = nil, &block)
36
+ logger.info(message, &block)
37
+ end
38
+
39
+ # @param message [String,nil]
40
+ #
41
+ # @return [void]
42
+ def debug(message = nil, &block)
43
+ logger.debug(message, &block)
44
+ end
45
+
46
+ # @param message [String,nil]
47
+ #
48
+ # @return [void]
49
+ def finer(message = nil, &block)
50
+ # No such level by default
51
+ #
52
+ # logger.finer(message, &block)
53
+ end
54
+
55
+ private
56
+
57
+ # @sg-ignore
58
+ # @return [Symbol]
59
+ def log_level
60
+ # @sg-ignore
61
+ ENV.fetch('LOG_LEVEL', 'INFO').downcase.to_sym
62
+ end
63
+ end
@@ -54,15 +54,41 @@ module Checkoff
54
54
  @asana_resources_collection_class = asana_resources_collection_class
55
55
  end
56
56
 
57
+ # Perform an equivalent search API to an Asana search URL in the
58
+ # web UI. Not all URL parameters are supported; each one must be
59
+ # added here manually. In addition, not all are supported in the
60
+ # Asana API in a compatible way, so they may result in more tasks
61
+ # being fetched than actually returned as filtering is done
62
+ # manually.
63
+ #
57
64
  # @param [String] workspace_name
58
65
  # @param [String] url
59
66
  # @param [Array<String>] extra_fields
67
+ #
60
68
  # @return [Enumerable<Asana::Resources::Task>]
61
69
  def task_search(workspace_name, url, extra_fields: [])
62
70
  workspace = workspaces.workspace_or_raise(workspace_name)
63
71
  # @sg-ignore
64
72
  api_params, task_selector = @search_url_parser.convert_params(url)
65
- path = "/workspaces/#{workspace.gid}/tasks/search"
73
+ raw_task_search(api_params, workspace_gid: workspace.gid, task_selector: task_selector,
74
+ extra_fields: extra_fields)
75
+ end
76
+ cache_method :task_search, LONG_CACHE_TIME
77
+
78
+ # Perform a search using the Asana Task Search API:
79
+ #
80
+ # https://developers.asana.com/reference/searchtasksforworkspace
81
+ #
82
+ # @param [Hash<Symbol, Object>] api_params
83
+ # @param [String] workspace_gid
84
+ # @param [String] url
85
+ # @param [Array<String>] extra_fields
86
+ # @param [Array] task_selector
87
+ #
88
+ # @return [Enumerable<Asana::Resources::Task>]
89
+ def raw_task_search(api_params, workspace_gid:, extra_fields: [], task_selector: [])
90
+ # @sg-ignore
91
+ path = "/workspaces/#{workspace_gid}/tasks/search"
66
92
  options = calculate_api_options(extra_fields)
67
93
  tasks = @asana_resources_collection_class.new(parse(client.get(path,
68
94
  params: api_params,
@@ -71,7 +97,6 @@ module Checkoff
71
97
  client: client)
72
98
  tasks.select { |task| task_selectors.filter_via_task_selector(task, task_selector) }
73
99
  end
74
- cache_method :task_search, LONG_CACHE_TIME
75
100
 
76
101
  private
77
102
 
@@ -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.132.0'
6
+ VERSION = '0.134.0'
7
7
  end
data/lib/checkoff.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require 'checkoff/version'
4
4
  require 'checkoff/clients'
5
+ require 'checkoff/events'
5
6
  require 'checkoff/workspaces'
6
7
  require 'checkoff/portfolios'
7
8
  require 'checkoff/projects'
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.132.0
4
+ version: 0.134.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-11-07 00:00:00.000000000 Z
11
+ date: 2023-11-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -131,8 +131,11 @@ files:
131
131
  - lib/checkoff/clients.rb
132
132
  - lib/checkoff/create-entity.sh
133
133
  - lib/checkoff/custom_fields.rb
134
+ - lib/checkoff/events.rb
135
+ - lib/checkoff/internal/asana_event_filter.rb
134
136
  - lib/checkoff/internal/config_loader.rb
135
137
  - lib/checkoff/internal/create-class.sh
138
+ - lib/checkoff/internal/logging.rb
136
139
  - lib/checkoff/internal/project_hashes.rb
137
140
  - lib/checkoff/internal/project_selector_evaluator.rb
138
141
  - lib/checkoff/internal/project_timing.rb