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 +4 -4
- data/.dependabot/config.yml +0 -7
- data/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- data/.github/ISSUE_TEMPLATE/config.yml +1 -0
- data/.github/ISSUE_TEMPLATE/story.md +22 -0
- data/.github/PULL_REQUEST_TEMPLATE.md +18 -0
- data/.github/workflows/auto-approve.yml +16 -0
- data/.travis.yml +3 -2
- data/CHANGELOG.md +67 -0
- data/Gemfile.lock +50 -45
- data/README.md +31 -17
- data/Rakefile +1 -1
- data/lib/core_extensions/cookiejar/cookie_validation.rb +1 -1
- data/lib/salesforce_streamer.rb +2 -10
- data/lib/salesforce_streamer/configuration.rb +18 -2
- data/lib/salesforce_streamer/errors.rb +9 -3
- data/lib/salesforce_streamer/launcher.rb +3 -3
- data/lib/salesforce_streamer/push_topic.rb +41 -25
- data/lib/salesforce_streamer/{topic_manager.rb → salesforce_topic_manager.rb} +7 -6
- data/lib/salesforce_streamer/server.rb +6 -3
- data/lib/salesforce_streamer/version.rb +1 -1
- data/salesforce_streamer.gemspec +3 -2
- metadata +38 -16
- data/CODE_OF_CONDUCT.md +0 -74
- data/lib/salesforce_streamer/message_receiver.rb +0 -19
- data/lib/salesforce_streamer/redis_replay.rb +0 -55
- data/lib/salesforce_streamer/replay_persistence.rb +0 -18
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 980626b26a53a56e8fb6fd2028e538ffd667cbe938a1aa1011c4184813d2936d
|
4
|
+
data.tar.gz: b6ece556e2e34186b9ad77f445c26ebf6532170e4a9f7a8d0662e309775c9427
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1920656978db1b707ba61bd90c7026988b99c9dfbb2c7489f4a8554df97f1ad9000885fa619bff884257fd5794225ba3c3ab23b3172b6753ea57ec8f76e6bb05
|
7
|
+
data.tar.gz: '0099d97a9cb7774c0fe274b0ee46bb68da87dca38f6113310539248f309111341a4a3e917476e4ca686413cf8cd22e5551f6c917a774a60ffab6541f9ad04889'
|
data/.dependabot/config.yml
CHANGED
@@ -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 }}"
|
data/.travis.yml
CHANGED
data/CHANGELOG.md
ADDED
@@ -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.
|
data/Gemfile.lock
CHANGED
@@ -1,25 +1,28 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
salesforce_streamer (
|
5
|
-
|
6
|
-
|
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.
|
14
|
-
byebug (11.1.
|
15
|
-
codecov (0.
|
14
|
+
ast (2.4.1)
|
15
|
+
byebug (11.1.3)
|
16
|
+
codecov (0.2.3)
|
17
|
+
colorize
|
16
18
|
json
|
17
19
|
simplecov
|
18
|
-
|
20
|
+
colorize (0.8.1)
|
19
21
|
cookiejar (0.3.3)
|
20
|
-
diff-lcs (1.
|
22
|
+
diff-lcs (1.4.4)
|
21
23
|
docile (1.3.2)
|
22
|
-
|
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.
|
34
|
+
faraday (1.0.1)
|
32
35
|
multipart-post (>= 1.2, < 3)
|
33
|
-
faraday_middleware (0.
|
34
|
-
faraday (
|
35
|
-
faye (
|
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 (>=
|
40
|
+
em-http-request (>= 1.1.6)
|
38
41
|
eventmachine (>= 0.12.0)
|
39
|
-
faye-websocket (>= 0.
|
42
|
+
faye-websocket (>= 0.11.0)
|
43
|
+
multi_json (>= 1.0.0)
|
40
44
|
rack (>= 1.0.0)
|
41
|
-
|
42
|
-
faye-websocket (0.
|
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
|
-
|
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.
|
52
|
-
parser (2.7.
|
53
|
-
ast (~> 2.4.
|
54
|
-
public_suffix (4.0.
|
55
|
-
rack (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
|
-
|
59
|
-
|
60
|
-
|
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.
|
70
|
-
rspec-support (~> 3.9.
|
71
|
-
rspec-expectations (3.9.
|
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.
|
78
|
-
rubocop (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.
|
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, <
|
86
|
-
rubocop-
|
87
|
-
|
88
|
-
rubocop-
|
89
|
-
rubocop (>= 0.
|
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.
|
96
|
-
|
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.
|
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
|
+
[](https://rubygems.org/gems/salesforce_streamer)
|
1
2
|
[](https://travis-ci.org/RenoFi/salesforce_streamer)
|
2
3
|
|
3
|
-
|
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
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
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
@@ -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
|
12
|
+
return true if tested_domain[-15..].eql?('.salesforce.com')
|
13
13
|
|
14
14
|
# original implementation
|
15
15
|
base = effective_host base_domain
|
data/lib/salesforce_streamer.rb
CHANGED
@@ -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/
|
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,
|
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
|
-
@
|
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
|
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
|
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
|
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 =
|
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.
|
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(
|
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
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
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
|
20
|
-
|
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
|
-
@
|
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
|
42
|
-
|
41
|
+
def message_middleware
|
42
|
+
Configuration.instance.middleware_runner(handler)
|
43
|
+
end
|
43
44
|
|
44
|
-
|
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
|
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
|
11
|
-
Log.info '
|
10
|
+
def upsert_topics!
|
11
|
+
Log.info 'Starting to upsert PushTopic definitions into Salesforce'
|
12
12
|
@push_topics.each do |push_topic|
|
13
|
-
Log.
|
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.
|
33
|
+
Log.info 'No differences detected'
|
33
34
|
false
|
34
35
|
end
|
35
36
|
|
36
37
|
def upsert(push_topic)
|
37
|
-
Log.info "
|
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
|
-
|
38
|
-
|
39
|
-
|
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
|
data/salesforce_streamer.gemspec
CHANGED
@@ -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 '
|
28
|
-
spec.add_dependency '
|
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:
|
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-
|
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:
|
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:
|
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
|
-
-
|
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:
|
218
|
+
version: 1.3.1
|
197
219
|
requirements: []
|
198
|
-
rubygems_version: 3.
|
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.
|
data/CODE_OF_CONDUCT.md
DELETED
@@ -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
|