salesforce_streamer 1.1.1 → 2.0.0.rc1

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: f626431375e34ca6523e657667bcf1121589bc801925608a91df81f5a052b916
4
- data.tar.gz: 9c53ea7736694cb86d992fcbb81f5660321b8917f81deaa723f3b0d921ed3e1c
3
+ metadata.gz: 980626b26a53a56e8fb6fd2028e538ffd667cbe938a1aa1011c4184813d2936d
4
+ data.tar.gz: b6ece556e2e34186b9ad77f445c26ebf6532170e4a9f7a8d0662e309775c9427
5
5
  SHA512:
6
- metadata.gz: f6b94d4f6c2da921d21aed7fa4319c40a2c55b8067e8e940194444b8bdf034faee048222e922a41090e7967461b01c7682c22c8e59df1c6fa6e6f8b5b03aa65a
7
- data.tar.gz: 90d7af5c35d8e2e624051269f07de481f975788a9f6c4448a707f71f65d3b2c6e500bb0caef83c21ea401feb56f804b70a1f48223bfa774a1ddde03bd9e9ac4c
6
+ metadata.gz: 1920656978db1b707ba61bd90c7026988b99c9dfbb2c7489f4a8554df97f1ad9000885fa619bff884257fd5794225ba3c3ab23b3172b6753ea57ec8f76e6bb05
7
+ data.tar.gz: '0099d97a9cb7774c0fe274b0ee46bb68da87dca38f6113310539248f309111341a4a3e917476e4ca686413cf8cd22e5551f6c917a774a60ffab6541f9ad04889'
@@ -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 }}"
@@ -3,8 +3,9 @@ sudo: false
3
3
  language: ruby
4
4
  cache: bundler
5
5
  rvm:
6
- - 2.5
7
6
  - 2.6
7
+ - 2.7
8
8
  before_install: gem install bundler
9
9
  script:
10
- - bundle exec rake
10
+ - bundle exec rake spec
11
+ - bundle exec rake rubocop
@@ -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
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.1.1)
5
- faye (= 0.8.9)
6
- restforce (~> 4.2)
4
+ salesforce_streamer (2.0.0.rc1)
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
- byebug (11.1.1)
15
- codecov (0.1.16)
14
+ ast (2.4.1)
15
+ byebug (11.1.3)
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,76 +31,78 @@ GEM
28
31
  em-socksify (0.3.2)
29
32
  eventmachine (>= 1.0.0.beta.4)
30
33
  eventmachine (1.2.7)
31
- faraday (0.17.3)
34
+ faraday (1.0.1)
32
35
  multipart-post (>= 1.2, < 3)
33
- faraday_middleware (0.14.0)
34
- faraday (>= 0.7.4, < 1.0)
35
- faye (0.8.9)
36
+ faraday_middleware (1.0.0)
37
+ faraday (~> 1.0)
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.0.3)
53
- ast (~> 2.4.0)
54
- public_suffix (4.0.3)
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.0)
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)
66
70
  rspec-core (~> 3.9.0)
67
71
  rspec-expectations (~> 3.9.0)
68
72
  rspec-mocks (~> 3.9.0)
69
- rspec-core (3.9.1)
70
- rspec-support (~> 3.9.1)
71
- rspec-expectations (3.9.0)
73
+ rspec-core (3.9.2)
74
+ rspec-support (~> 3.9.3)
75
+ rspec-expectations (3.9.2)
72
76
  diff-lcs (>= 1.2.0, < 2.0)
73
77
  rspec-support (~> 3.9.0)
74
78
  rspec-mocks (3.9.1)
75
79
  diff-lcs (>= 1.2.0, < 2.0)
76
80
  rspec-support (~> 3.9.0)
77
- rspec-support (3.9.2)
78
- rubocop (0.80.0)
79
- jaro_winkler (~> 1.5.1)
81
+ rspec-support (3.9.3)
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
- unicode-display_width (>= 1.4.0, < 1.7)
86
- rubocop-performance (1.5.2)
87
- rubocop (>= 0.71.0)
88
- rubocop-rspec (1.38.1)
89
- rubocop (>= 0.68.1)
90
+ unicode-display_width (>= 1.4.0, < 2.0)
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
- unicode-display_width (1.6.1)
96
- url (0.3.2)
97
- websocket-driver (0.7.1)
102
+ unicode-display_width (1.7.0)
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
@@ -1,8 +1,7 @@
1
+ [![Gem Version](https://badge.fury.io/rb/salesforce_streamer.svg)](https://rubygems.org/gems/salesforce_streamer)
1
2
  [![Build Status](https://travis-ci.org/RenoFi/salesforce_streamer.svg?branch=master)](https://travis-ci.org/RenoFi/salesforce_streamer)
2
3
 
3
- [![codecov](https://codecov.io/gh/RenoFi/salesforce_streamer/branch/master/graph/badge.svg)](https://codecov.io/gh/RenoFi/salesforce_streamer)
4
-
5
- # SalesforceStreamer
4
+ # salesforce_streamer
6
5
 
7
6
  A wrapper around the Restforce Streaming API to receive real time updates from
8
7
  your Salesforce instance with a built-in PushTopic manager.
@@ -21,6 +20,26 @@ And then execute:
21
20
 
22
21
  ## Usage
23
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
+
24
43
  ### Configure Push Topics
25
44
 
26
45
  Create a YAML file to configure your server subscriptions. The configuration
@@ -36,12 +55,11 @@ base: &DEFAULT
36
55
  accounts:
37
56
  handler: "AccountChangeHandler"
38
57
  replay: -1
39
- salesforce:
40
- name: "AllAccounts"
41
- api_version: "41.0"
42
- description: "Sync Accounts"
43
- notify_for_fields: "Referenced"
44
- query: "Select Id, Name From Account"
58
+ name: "AllAccounts"
59
+ api_version: "49.0"
60
+ description: "Sync Accounts"
61
+ notify_for_fields: "Referenced"
62
+ query: "Select Id, Name From Account"
45
63
 
46
64
  development:
47
65
  <<: *DEFAULT
@@ -95,12 +113,12 @@ Configure the `SalesforceStreamer` module.
95
113
  ```ruby
96
114
  # config/initializers/salesforce_streamer.rb
97
115
 
98
- require 'redis'
99
- require 'connection_pool'
100
-
101
- SalesforceStreamer.config.redis_connection = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
102
116
  SalesforceStreamer.config.logger = Logger.new(STDERR, level: 'INFO')
103
117
  SalesforceStreamer.config.exception_adapter = proc { |e| puts e }
118
+ SalesforceStreamer.config.replay_adapter = proc { |topic|
119
+ topic.id || Store.get(topic.name) || topic.replay
120
+ }
121
+ SalesforceStreamer.config.use_middleware AfterMessageReceived
104
122
  SalesforceStreamer.config.manage_topics = true
105
123
  ```
106
124
 
@@ -153,7 +171,3 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/renofi
153
171
  ## License
154
172
 
155
173
  The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
156
-
157
- ## Code of Conduct
158
-
159
- Everyone interacting in the SalesforceStreamer project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/renofi/salesforce_streamer/blob/master/CODE_OF_CONDUCT.md).
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,17 +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
- require 'salesforce_streamer/message_receiver'
18
16
  require 'salesforce_streamer/server'
19
17
  require 'salesforce_streamer/version'
20
18
  require 'salesforce_streamer/launcher'
@@ -60,10 +58,4 @@ module SalesforceStreamer
60
58
  def self.config
61
59
  Configuration.instance
62
60
  end
63
-
64
- class RedisConnectionError < StandardError
65
- def initialize
66
- super 'SalesforceStreamer.config.redis_connection not set'
67
- end
68
- end
69
61
  end
@@ -1,7 +1,9 @@
1
1
  module SalesforceStreamer
2
2
  # Manages server configuration.
3
3
  class Configuration
4
- attr_accessor :environment, :logger, :require_path, :config_file, :manage_topics, :server, :exception_adapter, :persistence_adapter, :redis_connection
4
+ attr_accessor :environment, :logger, :require_path, :config_file,
5
+ :manage_topics, :exception_adapter, :replay_adapter
6
+ attr_reader :middleware
5
7
 
6
8
  class << self
7
9
  attr_writer :instance
@@ -15,16 +17,30 @@ module SalesforceStreamer
15
17
  @environment = ENV['RACK_ENV'] || :development
16
18
  @logger = Logger.new(IO::NULL)
17
19
  @exception_adapter = proc { |exc| fail(exc) }
18
- @persistence_adapter = RedisReplay.new
20
+ @replay_adapter = proc { |topic| topic.id || topic.replay }
19
21
  @manage_topics = false
20
22
  @config_file = './config/streamer.yml'
21
23
  @require_path = './config/environment'
24
+ @middleware = []
22
25
  end
23
26
 
24
27
  def manage_topics?
25
28
  @manage_topics
26
29
  end
27
30
 
31
+ # adds a setup proc to the middleware array
32
+ def use_middleware(klass, *args, &block)
33
+ @middleware << [klass, args, block]
34
+ end
35
+
36
+ # returns a ready to use chain of middleware
37
+ def middleware_runner(last_handler)
38
+ @middleware.reduce(last_handler) do |next_handler, current_handler|
39
+ klass, args, block = current_handler
40
+ klass.new(next_handler, *args, &block)
41
+ end
42
+ end
43
+
28
44
  def push_topic_data
29
45
  return @push_topic_data if @push_topic_data
30
46
 
@@ -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,29 +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
7
-
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!
4
+ extend Dry::Initializer
5
+
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
14
+
15
+ attr_writer :id
16
+
17
+ def handle(message)
18
+ message['topic'] = @name
19
+ message_middleware.call(message)
20
+ rescue StandardError => e
21
+ Log.error e
22
+ Configuration.instance.exception_adapter.call e
17
23
  end
18
24
 
19
- def replay
20
- ReplayPersistence.retrieve(name) || @static_replay
21
- end
22
-
23
- def to_s
24
- "PushTopic id=#{id} name=#{name} handler=#{handler} " \
25
- "replay=#{replay} notify_for_fields=#{notify_for_fields} " \
26
- "description=#{description} api_version=#{api_version} query=#{query}"
25
+ def attributes
26
+ self.class.dry_initializer.public_attributes self
27
27
  end
28
28
 
29
29
  private
@@ -31,17 +31,33 @@ module SalesforceStreamer
31
31
  def validate!
32
32
  fail(PushTopicNameTooLongError, @name) if @name.size > 25
33
33
 
34
- @handler_constant = Object.const_get(@handler)
34
+ @handler = Object.const_get(@handler)
35
35
  true
36
36
  rescue NameError, TypeError => e
37
37
  message = 'handler=' + @handler.to_s + ' exception=' + e.to_s
38
38
  raise(PushTopicHandlerMissingError, message)
39
39
  end
40
40
 
41
- def strip_spaces(str)
42
- fail(NilQueryError, @name) unless str
41
+ def message_middleware
42
+ Configuration.instance.middleware_runner(handler)
43
+ end
43
44
 
44
- str.gsub(/\s+/, ' ')
45
+ class << self
46
+ def strip_spaces(str)
47
+ fail(NilQueryError, @name) unless str
48
+
49
+ str.gsub(/\s+/, ' ')
50
+ end
51
+
52
+ def prepare_handler_proc(constant)
53
+ if constant.respond_to? :call
54
+ constant
55
+ elsif constant.respond_to? :perform_async
56
+ proc { |message| handler_constant.perform_async message }
57
+ else
58
+ fail(UnprocessableHandlerError, constant)
59
+ end
60
+ end
45
61
  end
46
62
  end
47
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,9 +34,12 @@ 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 |msg|
38
- Log.info "Message received from topic #{topic.name}"
39
- MessageReceiver.call topic.name, topic.handler_constant, msg
37
+ replay_id = Configuration.instance.replay_adapter.call(topic)
38
+ client.subscribe topic.name, replay: replay_id.to_i do |message|
39
+ replay_id = message.dig('event', 'replayId')
40
+ Log.info "Message #{replay_id} received from topic #{topic.name}"
41
+ topic.handle message
42
+ topic.id = replay_id
40
43
  end
41
44
  end
42
45
  end
@@ -1,3 +1,3 @@
1
1
  module SalesforceStreamer
2
- VERSION = '1.1.1'.freeze
2
+ VERSION = '2.0.0.rc1'.freeze
3
3
  end
@@ -24,8 +24,9 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = '>= 2.6'
26
26
 
27
- spec.add_dependency 'faye', '0.8.9'
28
- spec.add_dependency 'restforce', '~> 4.2'
27
+ spec.add_dependency 'dry-initializer', '~> 3.0'
28
+ spec.add_dependency 'faye', '~> 1.4'
29
+ spec.add_dependency 'restforce', '>= 4.2', '< 6.0'
29
30
 
30
31
  spec.add_development_dependency 'byebug'
31
32
  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.1.1
4
+ version: 2.0.0.rc1
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-02-28 00:00:00.000000000 Z
12
+ date: 2020-08-04 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,10 +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"
153
- - CODE_OF_CONDUCT.md
178
+ - CHANGELOG.md
154
179
  - Gemfile
155
180
  - Gemfile.lock
156
181
  - LICENSE.txt
@@ -164,14 +189,11 @@ files:
164
189
  - lib/salesforce_streamer/errors.rb
165
190
  - lib/salesforce_streamer/launcher.rb
166
191
  - lib/salesforce_streamer/log.rb
167
- - lib/salesforce_streamer/message_receiver.rb
168
192
  - lib/salesforce_streamer/push_topic.rb
169
- - lib/salesforce_streamer/redis_replay.rb
170
193
  - lib/salesforce_streamer/replay_id_error_extension.rb
171
- - lib/salesforce_streamer/replay_persistence.rb
172
194
  - lib/salesforce_streamer/salesforce_client.rb
195
+ - lib/salesforce_streamer/salesforce_topic_manager.rb
173
196
  - lib/salesforce_streamer/server.rb
174
- - lib/salesforce_streamer/topic_manager.rb
175
197
  - lib/salesforce_streamer/version.rb
176
198
  - salesforce_streamer.gemspec
177
199
  homepage: https://github.com/renofi/salesforce_streamer
@@ -191,11 +213,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
213
  version: '2.6'
192
214
  required_rubygems_version: !ruby/object:Gem::Requirement
193
215
  requirements:
194
- - - ">="
216
+ - - ">"
195
217
  - !ruby/object:Gem::Version
196
- version: '0'
218
+ version: 1.3.1
197
219
  requirements: []
198
- rubygems_version: 3.0.6
220
+ rubygems_version: 3.1.2
199
221
  signing_key:
200
222
  specification_version: 4
201
223
  summary: A wrapper around the Restforce Streaming API with a built-in PushTopic manager.
@@ -1,74 +0,0 @@
1
- # Contributor Covenant Code of Conduct
2
-
3
- ## Our Pledge
4
-
5
- In the interest of fostering an open and welcoming environment, we as
6
- contributors and maintainers pledge to making participation in our project and
7
- our community a harassment-free experience for everyone, regardless of age, body
8
- size, disability, ethnicity, gender identity and expression, level of experience,
9
- nationality, personal appearance, race, religion, or sexual identity and
10
- orientation.
11
-
12
- ## Our Standards
13
-
14
- Examples of behavior that contributes to creating a positive environment
15
- include:
16
-
17
- * Using welcoming and inclusive language
18
- * Being respectful of differing viewpoints and experiences
19
- * Gracefully accepting constructive criticism
20
- * Focusing on what is best for the community
21
- * Showing empathy towards other community members
22
-
23
- Examples of unacceptable behavior by participants include:
24
-
25
- * The use of sexualized language or imagery and unwelcome sexual attention or
26
- advances
27
- * Trolling, insulting/derogatory comments, and personal or political attacks
28
- * Public or private harassment
29
- * Publishing others' private information, such as a physical or electronic
30
- address, without explicit permission
31
- * Other conduct which could reasonably be considered inappropriate in a
32
- professional setting
33
-
34
- ## Our Responsibilities
35
-
36
- Project maintainers are responsible for clarifying the standards of acceptable
37
- behavior and are expected to take appropriate and fair corrective action in
38
- response to any instances of unacceptable behavior.
39
-
40
- Project maintainers have the right and responsibility to remove, edit, or
41
- reject comments, commits, code, wiki edits, issues, and other contributions
42
- that are not aligned to this Code of Conduct, or to ban temporarily or
43
- permanently any contributor for other behaviors that they deem inappropriate,
44
- threatening, offensive, or harmful.
45
-
46
- ## Scope
47
-
48
- This Code of Conduct applies both within project spaces and in public spaces
49
- when an individual is representing the project or its community. Examples of
50
- representing a project or community include using an official project e-mail
51
- address, posting via an official social media account, or acting as an appointed
52
- representative at an online or offline event. Representation of a project may be
53
- further defined and clarified by project maintainers.
54
-
55
- ## Enforcement
56
-
57
- Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
- reported by contacting the project team at scott@renofi.com. All
59
- complaints will be reviewed and investigated and will result in a response that
60
- is deemed necessary and appropriate to the circumstances. The project team is
61
- obligated to maintain confidentiality with regard to the reporter of an incident.
62
- Further details of specific enforcement policies may be posted separately.
63
-
64
- Project maintainers who do not follow or enforce the Code of Conduct in good
65
- faith may face temporary or permanent repercussions as determined by other
66
- members of the project's leadership.
67
-
68
- ## Attribution
69
-
70
- This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
- available at [http://contributor-covenant.org/version/1/4][version]
72
-
73
- [homepage]: http://contributor-covenant.org
74
- [version]: http://contributor-covenant.org/version/1/4/
@@ -1,19 +0,0 @@
1
- module SalesforceStreamer
2
- class MessageReceiver
3
- class << self
4
- # @param topic [String] The unique Salesforce Topic name
5
- # @param handler [Object] An object that responds to .call(message)
6
- # @param message [Hash] The event payload
7
- def call(topic, handler, message)
8
- if handler.respond_to? :perform_async
9
- handler.perform_async message
10
- else
11
- handler.call message
12
- end
13
- ReplayPersistence.record topic, message.dig('event', 'replayId')
14
- rescue StandardError => e
15
- Configuration.instance.exception_adapter.call e
16
- end
17
- end
18
- end
19
- end
@@ -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