salesforce_streamer 1.1.1 → 2.0.0.rc1

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: 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