salesforce_streamer 1.2.0 → 2.0.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: 2174b14554d0de8cd9d40a4ed7d85323316a363915a7030dba34f392bfb7cf66
4
- data.tar.gz: d841e9dca629409c5c0572267da8246d5f21e301c5c7c85d6f0f44cac32ca664
3
+ metadata.gz: 10b71d8f30b4e9eedff2ecabc48ec52d71f276abec73808f80c87d810d3307bb
4
+ data.tar.gz: 377f0fcaf7ccf7c44d1eaf615c12fd9e9e6b5f0fb981cd3e8268db4190c7cb23
5
5
  SHA512:
6
- metadata.gz: 11f4e6a58005ab86abbe38816e1b511bb2201bd4cdb66bfb90d918e9e7e8123158780b32f937106d39f70d987d9527ef25007aecd104063e647f3119bf601ab3
7
- data.tar.gz: 951a5316f531b860f52c0c7198542414209a4384c9361a2e28f10f33ae92406c29def1e79d23f79ea987d04176a896e0186385b4ad1a3db64cf7a330ec9eabf9
6
+ metadata.gz: 59b6418e352e58af09b4e7ba6068bebed370af67feb896d3f0bba73ca1a8741b0bf73d3e67f2ad5dfe0c846694de311e877f0732672d6525cc565b93ffe507c7
7
+ data.tar.gz: 6d4d7dfd2baeb69fb0e7c1487a6ded133a7ab9ec4bcbd6c43a4615ceadeadd2b52df780719bef5a7fa34d96d9fed962a8c2a8d916b1e558c62657c0c4948983e
@@ -9,10 +9,3 @@ update_configs:
9
9
  allowed_updates:
10
10
  - match:
11
11
  update_type: "all"
12
- automerged_updates:
13
- - match:
14
- dependency_type: "development"
15
- update_type: "all"
16
- - match:
17
- dependency_type: "production"
18
- update_type: "all"
@@ -0,0 +1,33 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to track an issue that has been identified
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Mutation/Query**
24
+
25
+ **URL and HTTP method (for non-GQL):**
26
+
27
+ **Sentry or Logs URL:**
28
+
29
+ **User/authentication details**
30
+ Impacted user name or service account
31
+
32
+ **Additional context**
33
+ Add any other context about the problem here.
@@ -0,0 +1 @@
1
+ blank_issues_enabled: false
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: New story
3
+ about: Add a new story for implementation
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the solution**
11
+ A clear and concise description of what you want to happen.
12
+ When will this feature be done?
13
+
14
+ **Describe the users**
15
+ Who are we building this feature for?
16
+
17
+ **Additional context**
18
+ Add any other context or screenshots about the feature request here.
19
+ Link to any applicable documents describing the feature.
20
+
21
+ **Designs**
22
+ Link to any applicable designs on Invision.
@@ -0,0 +1,18 @@
1
+ ## Description
2
+ <!--- Describe your changes in detail -->
3
+
4
+ ## Related issue(s)
5
+ <!--- GH issue number -->
6
+
7
+ ## Motivation and Context
8
+ <!--- Why is this change required? What problem does it solve? -->
9
+ <!--- If it fixes an open issue, please link to the issue here. -->
10
+
11
+ ## How Has This Been Tested?
12
+ <!--- Please describe in detail how you tested your changes. -->
13
+
14
+ ## Screenshots (if appropriate):
15
+ <!--- Please add any screenshots of the feature. -->
16
+
17
+ ## Related PRs
18
+ <!--- Please add links to any related PRs (backend, component packages, etc). -->
@@ -0,0 +1,16 @@
1
+ # automatically approve PRs submitted by Dependabot
2
+ # this will allow Dependabot to automatically merge dependency update PRs where CI passes
3
+ # from: https://github.com/hmarr/auto-approve-action
4
+ name: Auto approve Dependabot PRs
5
+
6
+ on:
7
+ pull_request
8
+
9
+ jobs:
10
+ auto-approve:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: hmarr/auto-approve-action@v2.0.0
14
+ if: github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]'
15
+ with:
16
+ github-token: "${{ secrets.GITHUB_TOKEN }}"
@@ -0,0 +1,67 @@
1
+ # Changelog
2
+
3
+ Sorted so that the most recent change logs are near the top. Only significant
4
+ changes are logged in the change log.
5
+
6
+ ## 2020-08-04 Scott Serok [scott@renofi.com](mailto:scott@renofi.com)
7
+
8
+ v2.0 is released as a major simplification of this library. There are 2
9
+ significant differences from a user's perspective.
10
+
11
+ 1. The YAML configuration requires minor edits to be compatible in v2.0.
12
+ 2. The built-in Redis persistance of the replayId field has been removed. You
13
+ should add a custom middleware and configure the new replay_adapter option.
14
+
15
+ ### PushTopic configuration changes
16
+
17
+ After upgrading to v2, the YAML configuration should be modified. Shift the
18
+ nested "salesforce" block to the left and remove the "salesforce" key.
19
+
20
+ Before v2:
21
+
22
+ name: "TopicName"
23
+ handler: "MyConstant"
24
+ salesforce:
25
+ query: "SELECT Id FROM Lead"
26
+
27
+ As of v2:
28
+
29
+ name: "TopicName"
30
+ handler: "MyConstant"
31
+ query: "SELECT Id FROM Lead"
32
+
33
+ ### Redis Persistance removed
34
+
35
+ The original intention of this library is to manage PushTopic definitions
36
+ and run an event machine that subscribes to the Salesforce Streaming API based
37
+ on those PushTopics.
38
+
39
+ The addition of managing the Replay ID adds unecessary complexity that can be
40
+ incorporated through customization, and so it's been removed. You might use a
41
+ recent commit of v1 of this library for reference how to implement Redis as the
42
+ persistence layer.
43
+
44
+ To record the replayId on every message we can add a piece of middleware
45
+
46
+ class RecordReplayIdMiddleware
47
+ def initialize(handler)
48
+ @handler = handler
49
+ end
50
+
51
+ def call(message)
52
+ @handler.call(message)
53
+ replay_id = message['event']['replayId']
54
+ topic_name = message['topic']
55
+ MyStore.record_replay_id(replay_id, topic_name)
56
+ end
57
+ end
58
+ SalesforceStreamer.config.use_middleware RecordReplayIdMiddleware
59
+
60
+ To retrieve the replayId before subscribing to a PushTopic,
61
+ configure an adapter that returns an integer.
62
+
63
+ SalesforceStreamer.config.replay_adapter = proc { |topic|
64
+ (MyStore.fetch_replay_id(topic.name) || -1).to_i
65
+ }
66
+
67
+ This will be used to set the replayId value when subscribing to the PushTopic.
@@ -1,25 +1,28 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- salesforce_streamer (1.2.0)
5
- faye (= 0.8.9)
6
- restforce (~> 4.2)
4
+ salesforce_streamer (2.0.0)
5
+ dry-initializer (~> 3.0)
6
+ faye (~> 1.4)
7
+ restforce (>= 4.2, < 6.0)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
12
  addressable (2.7.0)
12
13
  public_suffix (>= 2.0.2, < 5.0)
13
- ast (2.4.0)
14
+ ast (2.4.1)
14
15
  byebug (11.1.3)
15
- codecov (0.1.16)
16
+ codecov (0.2.3)
17
+ colorize
16
18
  json
17
19
  simplecov
18
- url
20
+ colorize (0.8.1)
19
21
  cookiejar (0.3.3)
20
- diff-lcs (1.3)
22
+ diff-lcs (1.4.4)
21
23
  docile (1.3.2)
22
- em-http-request (1.1.5)
24
+ dry-initializer (3.0.3)
25
+ em-http-request (1.1.6)
23
26
  addressable (>= 2.3.4)
24
27
  cookiejar (!= 0.3.1)
25
28
  em-socksify (>= 0.3)
@@ -28,38 +31,39 @@ GEM
28
31
  em-socksify (0.3.2)
29
32
  eventmachine (>= 1.0.0.beta.4)
30
33
  eventmachine (1.2.7)
31
- faraday (1.0.0)
34
+ faraday (1.0.1)
32
35
  multipart-post (>= 1.2, < 3)
33
36
  faraday_middleware (1.0.0)
34
37
  faraday (~> 1.0)
35
- faye (0.8.9)
38
+ faye (1.4.0)
36
39
  cookiejar (>= 0.3.0)
37
- em-http-request (>= 0.3.0)
40
+ em-http-request (>= 1.1.6)
38
41
  eventmachine (>= 0.12.0)
39
- faye-websocket (>= 0.4.0)
42
+ faye-websocket (>= 0.11.0)
43
+ multi_json (>= 1.0.0)
40
44
  rack (>= 1.0.0)
41
- yajl-ruby (>= 1.0.0)
42
- faye-websocket (0.10.9)
45
+ websocket-driver (>= 0.5.1)
46
+ faye-websocket (0.11.0)
43
47
  eventmachine (>= 0.12.0)
44
48
  websocket-driver (>= 0.5.1)
45
49
  hashie (4.1.0)
46
50
  http_parser.rb (0.6.0)
47
- jaro_winkler (1.5.4)
48
- json (2.3.0)
51
+ json (2.3.1)
49
52
  jwt (2.2.1)
53
+ multi_json (1.15.0)
50
54
  multipart-post (2.1.1)
51
- parallel (1.19.1)
52
- parser (2.7.1.2)
53
- ast (~> 2.4.0)
54
- public_suffix (4.0.4)
55
- rack (2.2.2)
55
+ parallel (1.19.2)
56
+ parser (2.7.1.4)
57
+ ast (~> 2.4.1)
58
+ public_suffix (4.0.5)
59
+ rack (2.2.3)
56
60
  rainbow (3.0.0)
57
61
  rake (13.0.1)
58
- restforce (4.2.2)
59
- faraday (>= 0.9.0, <= 1.0)
60
- faraday_middleware (>= 0.8.8, <= 1.0)
62
+ regexp_parser (1.7.1)
63
+ restforce (5.0.1)
64
+ faraday (>= 0.9.0, <= 2.0)
65
+ faraday_middleware (>= 0.8.8, <= 2.0)
61
66
  hashie (>= 1.2.0, < 5.0)
62
- json (>= 1.7.5)
63
67
  jwt (>= 1.5.6)
64
68
  rexml (3.2.4)
65
69
  rspec (3.9.0)
@@ -75,29 +79,30 @@ GEM
75
79
  diff-lcs (>= 1.2.0, < 2.0)
76
80
  rspec-support (~> 3.9.0)
77
81
  rspec-support (3.9.3)
78
- rubocop (0.82.0)
79
- jaro_winkler (~> 1.5.1)
82
+ rubocop (0.88.0)
80
83
  parallel (~> 1.10)
81
- parser (>= 2.7.0.1)
84
+ parser (>= 2.7.1.1)
82
85
  rainbow (>= 2.2.2, < 4.0)
86
+ regexp_parser (>= 1.7)
83
87
  rexml
88
+ rubocop-ast (>= 0.1.0, < 1.0)
84
89
  ruby-progressbar (~> 1.7)
85
90
  unicode-display_width (>= 1.4.0, < 2.0)
86
- rubocop-performance (1.5.2)
87
- rubocop (>= 0.71.0)
88
- rubocop-rspec (1.39.0)
89
- rubocop (>= 0.68.1)
91
+ rubocop-ast (0.3.0)
92
+ parser (>= 2.7.1.4)
93
+ rubocop-performance (1.7.1)
94
+ rubocop (>= 0.82.0)
95
+ rubocop-rspec (1.42.0)
96
+ rubocop (>= 0.87.0)
90
97
  ruby-progressbar (1.10.1)
91
98
  simplecov (0.18.5)
92
99
  docile (~> 1.1)
93
100
  simplecov-html (~> 0.11)
94
101
  simplecov-html (0.12.2)
95
102
  unicode-display_width (1.7.0)
96
- url (0.3.2)
97
- websocket-driver (0.7.1)
103
+ websocket-driver (0.7.3)
98
104
  websocket-extensions (>= 0.1.0)
99
- websocket-extensions (0.1.4)
100
- yajl-ruby (1.4.1)
105
+ websocket-extensions (0.1.5)
101
106
 
102
107
  PLATFORMS
103
108
  ruby
data/README.md CHANGED
@@ -20,33 +20,11 @@ And then execute:
20
20
 
21
21
  ## Usage
22
22
 
23
- ### Middleware
24
-
25
- You can initialize the streamer server with any number of middleware classes.
26
- When a message is received by a PushTopic subscription, the chain of middleware
27
- classes are executed before the message handler is called.
28
-
29
- ```ruby
30
- # config/initializers/streamer.rb
31
- class MySimpleMiddleware
32
- def initialize(handler)
33
- @handler = handler
34
- end
35
- def call(message)
36
- @handler.call(message)
37
- end
38
- end
39
-
40
- SalesforceStreamer.config.use_middleware MySimpleMiddleware
41
- ```
42
-
43
23
  ### Configure Push Topics
44
24
 
45
- Create a YAML file to configure your server subscriptions. The configuration
46
- for each subscription must have a nested `salesforce:` key. These settings will
47
- be synced to your Salesforce instance when the `-x` flag is set on the command
48
- line. For more information about the `replay:` and `notify_fields_for` options
49
- please see the Salesforce Streaming API reference documentation.
25
+ Create a YAML file to configure your PushTopic subscriptions. When streamer
26
+ starts up it will check for any differences between Salesforce PushTopics and
27
+ this yaml and update any differences when `config.manage_topics = true`.
50
28
 
51
29
  ```yaml
52
30
  # config/streamer.yml
@@ -55,12 +33,11 @@ base: &DEFAULT
55
33
  accounts:
56
34
  handler: "AccountChangeHandler"
57
35
  replay: -1
58
- salesforce:
59
- name: "AllAccounts"
60
- api_version: "41.0"
61
- description: "Sync Accounts"
62
- notify_for_fields: "Referenced"
63
- query: "Select Id, Name From Account"
36
+ name: "AllAccounts"
37
+ api_version: "49.0"
38
+ description: "Sync Accounts"
39
+ notify_for_fields: "Referenced"
40
+ query: "Select Id, Name From Account"
64
41
 
65
42
  development:
66
43
  <<: *DEFAULT
@@ -72,14 +49,6 @@ production:
72
49
  <<: *DEFAULT
73
50
  ```
74
51
 
75
- It's important to note that the way push topics are managed is by the Salesforce
76
- name attribute. This should uniquely identify each push topic. It is not
77
- recommended to change the name of your push topic definitions; otherwise, the
78
- push topic manager will not find a push topic in Salesforce resulting in the
79
- creation of a brand new push topic. If the push topic manager identifies a
80
- difference in any of the other Salesforce attributes, then it will update the
81
- push topic in Salesforce before starting the streaming server.
82
-
83
52
  ### Define Message Handlers
84
53
 
85
54
  Define your handlers somewhere in your project. They must respond to either
@@ -89,14 +58,17 @@ Define your handlers somewhere in your project. They must respond to either
89
58
  # lib/account_change_handler.rb
90
59
  # Handle account changes inline
91
60
  class AccountChangeHandler
92
- def self.call(message)
93
- puts message
61
+ class << self
62
+ def call(message)
63
+ puts message
64
+ end
94
65
  end
95
66
  end
96
67
 
97
68
  # Handle account changes asynchronously
98
69
  class AccountChangeHandler
99
70
  include Sidekiq::Worker
71
+
100
72
  def perform(message)
101
73
  puts message
102
74
  end
@@ -114,13 +86,15 @@ Configure the `SalesforceStreamer` module.
114
86
  ```ruby
115
87
  # config/initializers/salesforce_streamer.rb
116
88
 
117
- require 'redis'
118
- require 'connection_pool'
119
-
120
- SalesforceStreamer.config.redis_connection = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
121
- SalesforceStreamer.config.logger = Logger.new(STDERR, level: 'INFO')
122
- SalesforceStreamer.config.exception_adapter = proc { |e| puts e }
123
- SalesforceStreamer.config.manage_topics = true
89
+ SalesforceStreamer.configure do |config|
90
+ config.logger = Logger.new(STDERR, level: 'INFO')
91
+ config.exception_adapter = proc { |e| puts e }
92
+ config.replay_adapter = proc { |topic|
93
+ (ReplayStore.get(topic.name) || topic.replay).to_i
94
+ }
95
+ config.use_middleware AfterMessageReceived
96
+ config.manage_topics = true
97
+ end
124
98
  ```
125
99
 
126
100
  ### Launch The Streamer
@@ -159,6 +133,26 @@ By default, the executable will load the YAML based on the `RACK_ENV` environmen
159
133
  variable, or default to `:development` if not set. You can override this by
160
134
  setting the `config.environment = :integration`
161
135
 
136
+ ### Message Handling Middleware
137
+
138
+ You can initialize the streamer server with any number of middleware classes.
139
+ When a message is received by a PushTopic subscription, the chain of middleware
140
+ classes are executed before the message handler is called.
141
+
142
+ ```ruby
143
+ # config/initializers/streamer.rb
144
+ class MySimpleMiddleware
145
+ def initialize(handler)
146
+ @handler = handler
147
+ end
148
+
149
+ def call(message)
150
+ @handler.call(message)
151
+ end
152
+ end
153
+
154
+ SalesforceStreamer.config.use_middleware MySimpleMiddleware
155
+ ```
162
156
  ## Development
163
157
 
164
158
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
data/Rakefile CHANGED
@@ -5,4 +5,4 @@ require 'rubocop/rake_task'
5
5
  RSpec::Core::RakeTask.new(:spec)
6
6
  RuboCop::RakeTask.new
7
7
 
8
- task default: %i[rubocop:auto_correct spec]
8
+ task default: %i[spec rubocop:auto_correct]
@@ -9,7 +9,7 @@ module CoreExtensions
9
9
  def self.extended(base)
10
10
  base.class_eval do
11
11
  def self.domains_match(tested_domain, base_domain)
12
- return true if tested_domain[-15..-1].eql?('.salesforce.com')
12
+ return true if tested_domain[-15..].eql?('.salesforce.com')
13
13
 
14
14
  # original implementation
15
15
  base = effective_host base_domain
@@ -4,16 +4,15 @@ require 'optparse'
4
4
  require 'restforce'
5
5
  require 'yaml'
6
6
  require 'forwardable'
7
+ require 'dry-initializer'
7
8
 
8
9
  require 'salesforce_streamer/configuration'
9
10
  require 'salesforce_streamer/errors'
10
11
  require 'salesforce_streamer/replay_id_error_extension'
11
12
  require 'salesforce_streamer/log'
12
13
  require 'salesforce_streamer/push_topic'
13
- require 'salesforce_streamer/topic_manager'
14
+ require 'salesforce_streamer/salesforce_topic_manager'
14
15
  require 'salesforce_streamer/salesforce_client'
15
- require 'salesforce_streamer/replay_persistence'
16
- require 'salesforce_streamer/redis_replay'
17
16
  require 'salesforce_streamer/server'
18
17
  require 'salesforce_streamer/version'
19
18
  require 'salesforce_streamer/launcher'
@@ -60,9 +59,7 @@ module SalesforceStreamer
60
59
  Configuration.instance
61
60
  end
62
61
 
63
- class RedisConnectionError < StandardError
64
- def initialize
65
- super 'SalesforceStreamer.config.redis_connection not set'
66
- end
62
+ def self.configure
63
+ yield Configuration.instance
67
64
  end
68
65
  end
@@ -1,22 +1,27 @@
1
1
  module SalesforceStreamer
2
2
  # Manages server configuration.
3
3
  class Configuration
4
- attr_accessor :environment, :logger, :require_path, :config_file, :manage_topics,
5
- :server, :exception_adapter, :persistence_adapter, :redis_connection, :middleware
4
+ attr_accessor :environment, :logger, :require_path, :config_file,
5
+ :manage_topics, :exception_adapter, :replay_adapter
6
+ attr_reader :middleware
6
7
 
7
8
  class << self
8
9
  attr_writer :instance
9
- end
10
10
 
11
- def self.instance
12
- @instance ||= new
11
+ def configure
12
+ yield instance
13
+ end
14
+
15
+ def instance
16
+ @instance ||= new
17
+ end
13
18
  end
14
19
 
15
20
  def initialize
16
21
  @environment = ENV['RACK_ENV'] || :development
17
22
  @logger = Logger.new(IO::NULL)
18
23
  @exception_adapter = proc { |exc| fail(exc) }
19
- @persistence_adapter = RedisReplay.new
24
+ @replay_adapter = proc { |topic| topic.id || topic.replay }
20
25
  @manage_topics = false
21
26
  @config_file = './config/streamer.yml'
22
27
  @require_path = './config/environment'
@@ -27,12 +32,17 @@ module SalesforceStreamer
27
32
  @manage_topics
28
33
  end
29
34
 
35
+ # adds a setup proc to the middleware array
30
36
  def use_middleware(klass, *args, &block)
31
- middleware << proc { |app| klass.new(app, *args, &block) }
37
+ @middleware << [klass, args, block]
32
38
  end
33
39
 
34
- def middleware_chain_for(app)
35
- middleware.reduce(app) { |memo, middleware| middleware.call(memo) }
40
+ # returns a ready to use chain of middleware
41
+ def middleware_runner(last_handler)
42
+ @middleware.reduce(last_handler) do |next_handler, current_handler|
43
+ klass, args, block = current_handler
44
+ klass.new(next_handler, *args, &block)
45
+ end
36
46
  end
37
47
 
38
48
  def push_topic_data
@@ -3,13 +3,13 @@ module SalesforceStreamer
3
3
 
4
4
  class MissingCLIFlagError < StandardError
5
5
  def initialize(flag)
6
- super 'Missing required command line flag: ' + flag.to_s
6
+ super "Missing required command line flag: #{flag}"
7
7
  end
8
8
  end
9
9
 
10
10
  class NilQueryError < StandardError
11
11
  def initialize(name)
12
- super 'Query not defined for ' + name.to_s
12
+ super "Query not defined for #{name}"
13
13
  end
14
14
  end
15
15
 
@@ -21,7 +21,13 @@ module SalesforceStreamer
21
21
 
22
22
  class PushTopicNameTooLongError < StandardError
23
23
  def initialize(name)
24
- super 'PushTopic name: ' + name.to_s + ' (' + name.size.to_s + '/25)'
24
+ super "PushTopic name: #{name} (#{name.size}/25)"
25
+ end
26
+ end
27
+
28
+ class UnprocessableHandlerError < StandardError
29
+ def initialize(constant)
30
+ super "#{constant} does not repond to .call or .perform_async"
25
31
  end
26
32
  end
27
33
  end
@@ -5,14 +5,14 @@ module SalesforceStreamer
5
5
  class Launcher
6
6
  def initialize
7
7
  load_server_configuration
8
- @manager = TopicManager.new push_topics: @push_topics
8
+ @manager = SalesforceTopicManager.new push_topics: @push_topics
9
9
  @server = Server.new push_topics: @push_topics
10
10
  end
11
11
 
12
12
  # Manages each PushTopic configured and starts the Streaming API listener.
13
13
  def run
14
14
  Log.info 'Launching Streamer Services'
15
- @manager.run
15
+ @manager.upsert_topics!
16
16
  @server.push_topics = @manager.push_topics
17
17
  @server.run
18
18
  end
@@ -36,7 +36,7 @@ module SalesforceStreamer
36
36
  @push_topics = []
37
37
  Configuration.instance.push_topic_data.each_value do |topic_data|
38
38
  Log.debug topic_data.to_s
39
- @push_topics << PushTopic.new(data: topic_data)
39
+ @push_topics << PushTopic.new(topic_data.transform_keys(&:to_sym))
40
40
  end
41
41
  end
42
42
  end
@@ -1,37 +1,29 @@
1
1
  module SalesforceStreamer
2
2
  # Models the PushTopic object for both Restforce and Streamer
3
3
  class PushTopic
4
- attr_accessor :id
5
- attr_reader :name, :description, :notify_for_fields, :query,
6
- :handler, :handler_constant, :api_version
4
+ extend Dry::Initializer
7
5
 
8
- def initialize(data:)
9
- @handler = data['handler']
10
- @static_replay = data.dig('replay')&.to_i || -1
11
- @name = data.dig('salesforce', 'name')
12
- @api_version = data.dig('salesforce', 'api_version') || '41.0'
13
- @description = data.dig('salesforce', 'description') || @name
14
- @notify_for_fields = data.dig('salesforce', 'notify_for_fields') || 'Referenced'
15
- @query = strip_spaces(data.dig('salesforce', 'query'))
16
- validate!
17
- end
6
+ option :name
7
+ option :query, proc { |str| str.gsub(/\s+/, ' ') }
8
+ option :handler, proc { |str| prepare_handler_proc Object.const_get(str) }
9
+ option :replay, proc(&:to_i), default: proc { -1 }
10
+ option :api_version, proc(&:to_s), default: proc { '49.0' }
11
+ option :notify_for_fields, default: proc { 'Referenced' }
12
+ option :id, optional: true
13
+ option :description, optional: true
18
14
 
19
- def replay
20
- ReplayPersistence.retrieve(name) || @static_replay
21
- end
15
+ attr_writer :id
22
16
 
23
17
  def handle(message)
24
- handle_chain.call(message)
25
- ReplayPersistence.record @name, message.dig('event', 'replayId')
18
+ message['topic'] = @name
19
+ message_middleware.call(message)
26
20
  rescue StandardError => e
27
21
  Log.error e
28
22
  Configuration.instance.exception_adapter.call e
29
23
  end
30
24
 
31
- def to_s
32
- "PushTopic id=#{id} name=#{name} handler=#{handler} " \
33
- "replay=#{replay} notify_for_fields=#{notify_for_fields} " \
34
- "description=#{description} api_version=#{api_version} query=#{query}"
25
+ def attributes
26
+ self.class.dry_initializer.public_attributes self
35
27
  end
36
28
 
37
29
  private
@@ -39,29 +31,33 @@ module SalesforceStreamer
39
31
  def validate!
40
32
  fail(PushTopicNameTooLongError, @name) if @name.size > 25
41
33
 
42
- @handler_constant = Object.const_get(@handler)
34
+ @handler = Object.const_get(@handler)
43
35
  true
44
36
  rescue NameError, TypeError => e
45
37
  message = 'handler=' + @handler.to_s + ' exception=' + e.to_s
46
38
  raise(PushTopicHandlerMissingError, message)
47
39
  end
48
40
 
49
- def handle_chain
50
- Configuration.instance.middleware_chain_for(handler_proc)
41
+ def message_middleware
42
+ Configuration.instance.middleware_runner(handler)
51
43
  end
52
44
 
53
- def handler_proc
54
- if handler_constant.respond_to? :perform_async
55
- proc { |message| handler_constant.perform_async message }
56
- else
57
- handler_constant
58
- end
59
- end
45
+ class << self
46
+ def strip_spaces(str)
47
+ fail(NilQueryError, @name) unless str
60
48
 
61
- def strip_spaces(str)
62
- fail(NilQueryError, @name) unless str
49
+ str.gsub(/\s+/, ' ')
50
+ end
63
51
 
64
- str.gsub(/\s+/, ' ')
52
+ def prepare_handler_proc(constant)
53
+ if constant.respond_to? :call
54
+ constant
55
+ elsif constant.respond_to? :perform_async
56
+ proc { |message| constant.perform_async message }
57
+ else
58
+ fail(UnprocessableHandlerError, constant)
59
+ end
60
+ end
65
61
  end
66
62
  end
67
63
  end
@@ -1,5 +1,5 @@
1
1
  module SalesforceStreamer
2
- class TopicManager
2
+ class SalesforceTopicManager
3
3
  attr_reader :push_topics
4
4
 
5
5
  def initialize(push_topics:)
@@ -7,10 +7,11 @@ module SalesforceStreamer
7
7
  @client = SalesforceClient.new
8
8
  end
9
9
 
10
- def run
11
- Log.info 'Running Topic Manager'
10
+ def upsert_topics!
11
+ Log.info 'Starting to upsert PushTopic definitions into Salesforce'
12
12
  @push_topics.each do |push_topic|
13
- Log.debug push_topic.to_s
13
+ Log.info push_topic.name
14
+ Log.debug push_topic.attributes.to_json
14
15
  upsert(push_topic) if diff?(push_topic)
15
16
  end
16
17
  end
@@ -29,12 +30,12 @@ module SalesforceStreamer
29
30
  return true unless push_topic.notify_for_fields.eql?(hashie.NotifyForFields)
30
31
  return true unless push_topic.api_version.to_s.eql?(hashie.ApiVersion.to_s)
31
32
 
32
- Log.debug 'No differences detected'
33
+ Log.info 'No differences detected'
33
34
  false
34
35
  end
35
36
 
36
37
  def upsert(push_topic)
37
- Log.info "Upsert PushTopic #{push_topic.name}"
38
+ Log.info "Upserting PushTopic"
38
39
  if Configuration.instance.manage_topics?
39
40
  @client.upsert_push_topic(push_topic)
40
41
  else
@@ -34,7 +34,8 @@ module SalesforceStreamer
34
34
  def start_em
35
35
  EM.run do
36
36
  @push_topics.map do |topic|
37
- client.subscribe topic.name, replay: topic.replay.to_i do |message|
37
+ replay_id = Configuration.instance.replay_adapter.call(topic)
38
+ client.subscribe topic.name, replay: replay_id.to_i do |message|
38
39
  replay_id = message.dig('event', 'replayId')
39
40
  Log.info "Message #{replay_id} received from topic #{topic.name}"
40
41
  topic.handle message
@@ -1,3 +1,3 @@
1
1
  module SalesforceStreamer
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '2.0.0'.freeze
3
3
  end
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = spec.homepage
17
+ spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/salesforce_streamer'
17
18
 
18
19
  spec.files = Dir.chdir(File.expand_path(__dir__)) do
19
20
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin/|spec/|\.rub)}) }
@@ -24,8 +25,9 @@ Gem::Specification.new do |spec|
24
25
 
25
26
  spec.required_ruby_version = '>= 2.6'
26
27
 
27
- spec.add_dependency 'faye', '0.8.9'
28
- spec.add_dependency 'restforce', '~> 4.2'
28
+ spec.add_dependency 'dry-initializer', '~> 3.0'
29
+ spec.add_dependency 'faye', '~> 1.4'
30
+ spec.add_dependency 'restforce', '>= 4.2', '< 6.0'
29
31
 
30
32
  spec.add_development_dependency 'byebug'
31
33
  spec.add_development_dependency 'codecov'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_streamer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Serok
@@ -9,36 +9,56 @@ authors:
9
9
  autorequire:
10
10
  bindir: exe
11
11
  cert_chain: []
12
- date: 2020-05-11 00:00:00.000000000 Z
12
+ date: 2020-08-14 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: dry-initializer
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: '3.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: '3.0'
14
28
  - !ruby/object:Gem::Dependency
15
29
  name: faye
16
30
  requirement: !ruby/object:Gem::Requirement
17
31
  requirements:
18
- - - '='
32
+ - - "~>"
19
33
  - !ruby/object:Gem::Version
20
- version: 0.8.9
34
+ version: '1.4'
21
35
  type: :runtime
22
36
  prerelease: false
23
37
  version_requirements: !ruby/object:Gem::Requirement
24
38
  requirements:
25
- - - '='
39
+ - - "~>"
26
40
  - !ruby/object:Gem::Version
27
- version: 0.8.9
41
+ version: '1.4'
28
42
  - !ruby/object:Gem::Dependency
29
43
  name: restforce
30
44
  requirement: !ruby/object:Gem::Requirement
31
45
  requirements:
32
- - - "~>"
46
+ - - ">="
33
47
  - !ruby/object:Gem::Version
34
48
  version: '4.2'
49
+ - - "<"
50
+ - !ruby/object:Gem::Version
51
+ version: '6.0'
35
52
  type: :runtime
36
53
  prerelease: false
37
54
  version_requirements: !ruby/object:Gem::Requirement
38
55
  requirements:
39
- - - "~>"
56
+ - - ">="
40
57
  - !ruby/object:Gem::Version
41
58
  version: '4.2'
59
+ - - "<"
60
+ - !ruby/object:Gem::Version
61
+ version: '6.0'
42
62
  - !ruby/object:Gem::Dependency
43
63
  name: byebug
44
64
  requirement: !ruby/object:Gem::Requirement
@@ -147,9 +167,15 @@ extensions: []
147
167
  extra_rdoc_files: []
148
168
  files:
149
169
  - ".dependabot/config.yml"
170
+ - ".github/ISSUE_TEMPLATE/bug_report.md"
171
+ - ".github/ISSUE_TEMPLATE/config.yml"
172
+ - ".github/ISSUE_TEMPLATE/story.md"
173
+ - ".github/PULL_REQUEST_TEMPLATE.md"
174
+ - ".github/workflows/auto-approve.yml"
150
175
  - ".gitignore"
151
176
  - ".rspec"
152
177
  - ".travis.yml"
178
+ - CHANGELOG.md
153
179
  - Gemfile
154
180
  - Gemfile.lock
155
181
  - LICENSE.txt
@@ -164,12 +190,10 @@ files:
164
190
  - lib/salesforce_streamer/launcher.rb
165
191
  - lib/salesforce_streamer/log.rb
166
192
  - lib/salesforce_streamer/push_topic.rb
167
- - lib/salesforce_streamer/redis_replay.rb
168
193
  - lib/salesforce_streamer/replay_id_error_extension.rb
169
- - lib/salesforce_streamer/replay_persistence.rb
170
194
  - lib/salesforce_streamer/salesforce_client.rb
195
+ - lib/salesforce_streamer/salesforce_topic_manager.rb
171
196
  - lib/salesforce_streamer/server.rb
172
- - lib/salesforce_streamer/topic_manager.rb
173
197
  - lib/salesforce_streamer/version.rb
174
198
  - salesforce_streamer.gemspec
175
199
  homepage: https://github.com/renofi/salesforce_streamer
@@ -178,6 +202,7 @@ licenses:
178
202
  metadata:
179
203
  homepage_uri: https://github.com/renofi/salesforce_streamer
180
204
  source_code_uri: https://github.com/renofi/salesforce_streamer
205
+ documentation_uri: https://www.rubydoc.info/gems/salesforce_streamer
181
206
  post_install_message:
182
207
  rdoc_options: []
183
208
  require_paths:
@@ -193,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
193
218
  - !ruby/object:Gem::Version
194
219
  version: '0'
195
220
  requirements: []
196
- rubygems_version: 3.0.6
221
+ rubygems_version: 3.1.2
197
222
  signing_key:
198
223
  specification_version: 4
199
224
  summary: A wrapper around the Restforce Streaming API with a built-in PushTopic manager.
@@ -1,55 +0,0 @@
1
- module SalesforceStreamer
2
- class RedisReplay
3
- class << self
4
- def redis_connection
5
- @redis_connection ||= Configuration.instance.redis_connection || fail(RedisConnectionError)
6
- end
7
-
8
- attr_writer :redis_connection
9
- end
10
-
11
- def connection
12
- if RedisReplay.redis_connection.respond_to?(:with)
13
- RedisReplay.redis_connection.with do |conn|
14
- yield(conn)
15
- end
16
- else
17
- yield RedisReplay.redis_connection
18
- end
19
- end
20
-
21
- # Saves the value to a key with expiration
22
- def record(key, value)
23
- return unless key && value
24
-
25
- key = namespaced_key(key)
26
- value = Integer(value)
27
- connection { |c| c.setex key, SECONDS_TO_EXPIRE, value }
28
- rescue StandardError => e
29
- Configuration.instance.exception_adapter.call e
30
- nil
31
- end
32
-
33
- def retrieve(key)
34
- return unless key
35
-
36
- key = namespaced_key(key)
37
- value = connection { |c| c.get key }
38
- Integer(value) if value
39
- rescue StandardError => e
40
- Configuration.instance.exception_adapter.call e
41
- nil
42
- end
43
-
44
- private
45
-
46
- def namespaced_key(key)
47
- NAMESPACE + key.to_s
48
- end
49
-
50
- NAMESPACE = 'SalesforceStreamer:'.freeze
51
- SECONDS_TO_EXPIRE = 24 * 60 * 60 # 24 hours
52
- START = 0
53
- STOP = 0
54
- end
55
- end
@@ -1,18 +0,0 @@
1
- module SalesforceStreamer
2
- # Store values for a given key in a sorted sequence
3
- # Retrieves the highest value given a key
4
- class ReplayPersistence
5
- class << self
6
- def record(key, value)
7
- Log.debug { "Recording #{key}=#{value}" }
8
- Configuration.instance.persistence_adapter&.record key, value
9
- end
10
-
11
- def retrieve(key)
12
- Configuration.instance.persistence_adapter&.retrieve(key).tap do |v|
13
- Log.debug { "Retrieved for #{key} #{v || 'nil'}" }
14
- end
15
- end
16
- end
17
- end
18
- end