salesforce_streamer 1.2.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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/CHANGELOG.md +67 -0
- data/Gemfile.lock +41 -36
- data/README.md +42 -48
- data/Rakefile +1 -1
- data/lib/core_extensions/cookiejar/cookie_validation.rb +1 -1
- data/lib/salesforce_streamer.rb +4 -7
- data/lib/salesforce_streamer/configuration.rb +19 -9
- data/lib/salesforce_streamer/errors.rb +9 -3
- data/lib/salesforce_streamer/launcher.rb +3 -3
- data/lib/salesforce_streamer/push_topic.rb +31 -35
- data/lib/salesforce_streamer/{topic_manager.rb → salesforce_topic_manager.rb} +7 -6
- data/lib/salesforce_streamer/server.rb +2 -1
- data/lib/salesforce_streamer/version.rb +1 -1
- data/salesforce_streamer.gemspec +4 -2
- metadata +37 -12
- 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: 10b71d8f30b4e9eedff2ecabc48ec52d71f276abec73808f80c87d810d3307bb
|
4
|
+
data.tar.gz: 377f0fcaf7ccf7c44d1eaf615c12fd9e9e6b5f0fb981cd3e8268db4190c7cb23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 59b6418e352e58af09b4e7ba6068bebed370af67feb896d3f0bba73ca1a8741b0bf73d3e67f2ad5dfe0c846694de311e877f0732672d6525cc565b93ffe507c7
|
7
|
+
data.tar.gz: 6d4d7dfd2baeb69fb0e7c1487a6ded133a7ab9ec4bcbd6c43a4615ceadeadd2b52df780719bef5a7fa34d96d9fed962a8c2a8d916b1e558c62657c0c4948983e
|
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/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).to_i
|
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)
|
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
|
+
ast (2.4.1)
|
14
15
|
byebug (11.1.3)
|
15
|
-
codecov (0.
|
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,38 +31,39 @@ GEM
|
|
28
31
|
em-socksify (0.3.2)
|
29
32
|
eventmachine (>= 1.0.0.beta.4)
|
30
33
|
eventmachine (1.2.7)
|
31
|
-
faraday (1.0.
|
34
|
+
faraday (1.0.1)
|
32
35
|
multipart-post (>= 1.2, < 3)
|
33
36
|
faraday_middleware (1.0.0)
|
34
37
|
faraday (~> 1.0)
|
35
|
-
faye (
|
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.1.
|
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.1)
|
64
|
+
faraday (>= 0.9.0, <= 2.0)
|
65
|
+
faraday_middleware (>= 0.8.8, <= 2.0)
|
61
66
|
hashie (>= 1.2.0, < 5.0)
|
62
|
-
json (>= 1.7.5)
|
63
67
|
jwt (>= 1.5.6)
|
64
68
|
rexml (3.2.4)
|
65
69
|
rspec (3.9.0)
|
@@ -75,29 +79,30 @@ GEM
|
|
75
79
|
diff-lcs (>= 1.2.0, < 2.0)
|
76
80
|
rspec-support (~> 3.9.0)
|
77
81
|
rspec-support (3.9.3)
|
78
|
-
rubocop (0.
|
79
|
-
jaro_winkler (~> 1.5.1)
|
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
90
|
unicode-display_width (>= 1.4.0, < 2.0)
|
86
|
-
rubocop-
|
87
|
-
|
88
|
-
rubocop-
|
89
|
-
rubocop (>= 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
102
|
unicode-display_width (1.7.0)
|
96
|
-
|
97
|
-
websocket-driver (0.7.1)
|
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
@@ -20,33 +20,11 @@ And then execute:
|
|
20
20
|
|
21
21
|
## Usage
|
22
22
|
|
23
|
-
### Middleware
|
24
|
-
|
25
|
-
You can initialize the streamer server with any number of middleware classes.
|
26
|
-
When a message is received by a PushTopic subscription, the chain of middleware
|
27
|
-
classes are executed before the message handler is called.
|
28
|
-
|
29
|
-
```ruby
|
30
|
-
# config/initializers/streamer.rb
|
31
|
-
class MySimpleMiddleware
|
32
|
-
def initialize(handler)
|
33
|
-
@handler = handler
|
34
|
-
end
|
35
|
-
def call(message)
|
36
|
-
@handler.call(message)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
SalesforceStreamer.config.use_middleware MySimpleMiddleware
|
41
|
-
```
|
42
|
-
|
43
23
|
### Configure Push Topics
|
44
24
|
|
45
|
-
Create a YAML file to configure your
|
46
|
-
|
47
|
-
|
48
|
-
line. For more information about the `replay:` and `notify_fields_for` options
|
49
|
-
please see the Salesforce Streaming API reference documentation.
|
25
|
+
Create a YAML file to configure your PushTopic subscriptions. When streamer
|
26
|
+
starts up it will check for any differences between Salesforce PushTopics and
|
27
|
+
this yaml and update any differences when `config.manage_topics = true`.
|
50
28
|
|
51
29
|
```yaml
|
52
30
|
# config/streamer.yml
|
@@ -55,12 +33,11 @@ base: &DEFAULT
|
|
55
33
|
accounts:
|
56
34
|
handler: "AccountChangeHandler"
|
57
35
|
replay: -1
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
query: "Select Id, Name From Account"
|
36
|
+
name: "AllAccounts"
|
37
|
+
api_version: "49.0"
|
38
|
+
description: "Sync Accounts"
|
39
|
+
notify_for_fields: "Referenced"
|
40
|
+
query: "Select Id, Name From Account"
|
64
41
|
|
65
42
|
development:
|
66
43
|
<<: *DEFAULT
|
@@ -72,14 +49,6 @@ production:
|
|
72
49
|
<<: *DEFAULT
|
73
50
|
```
|
74
51
|
|
75
|
-
It's important to note that the way push topics are managed is by the Salesforce
|
76
|
-
name attribute. This should uniquely identify each push topic. It is not
|
77
|
-
recommended to change the name of your push topic definitions; otherwise, the
|
78
|
-
push topic manager will not find a push topic in Salesforce resulting in the
|
79
|
-
creation of a brand new push topic. If the push topic manager identifies a
|
80
|
-
difference in any of the other Salesforce attributes, then it will update the
|
81
|
-
push topic in Salesforce before starting the streaming server.
|
82
|
-
|
83
52
|
### Define Message Handlers
|
84
53
|
|
85
54
|
Define your handlers somewhere in your project. They must respond to either
|
@@ -89,14 +58,17 @@ Define your handlers somewhere in your project. They must respond to either
|
|
89
58
|
# lib/account_change_handler.rb
|
90
59
|
# Handle account changes inline
|
91
60
|
class AccountChangeHandler
|
92
|
-
|
93
|
-
|
61
|
+
class << self
|
62
|
+
def call(message)
|
63
|
+
puts message
|
64
|
+
end
|
94
65
|
end
|
95
66
|
end
|
96
67
|
|
97
68
|
# Handle account changes asynchronously
|
98
69
|
class AccountChangeHandler
|
99
70
|
include Sidekiq::Worker
|
71
|
+
|
100
72
|
def perform(message)
|
101
73
|
puts message
|
102
74
|
end
|
@@ -114,13 +86,15 @@ Configure the `SalesforceStreamer` module.
|
|
114
86
|
```ruby
|
115
87
|
# config/initializers/salesforce_streamer.rb
|
116
88
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
89
|
+
SalesforceStreamer.configure do |config|
|
90
|
+
config.logger = Logger.new(STDERR, level: 'INFO')
|
91
|
+
config.exception_adapter = proc { |e| puts e }
|
92
|
+
config.replay_adapter = proc { |topic|
|
93
|
+
(ReplayStore.get(topic.name) || topic.replay).to_i
|
94
|
+
}
|
95
|
+
config.use_middleware AfterMessageReceived
|
96
|
+
config.manage_topics = true
|
97
|
+
end
|
124
98
|
```
|
125
99
|
|
126
100
|
### Launch The Streamer
|
@@ -159,6 +133,26 @@ By default, the executable will load the YAML based on the `RACK_ENV` environmen
|
|
159
133
|
variable, or default to `:development` if not set. You can override this by
|
160
134
|
setting the `config.environment = :integration`
|
161
135
|
|
136
|
+
### Message Handling Middleware
|
137
|
+
|
138
|
+
You can initialize the streamer server with any number of middleware classes.
|
139
|
+
When a message is received by a PushTopic subscription, the chain of middleware
|
140
|
+
classes are executed before the message handler is called.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
# config/initializers/streamer.rb
|
144
|
+
class MySimpleMiddleware
|
145
|
+
def initialize(handler)
|
146
|
+
@handler = handler
|
147
|
+
end
|
148
|
+
|
149
|
+
def call(message)
|
150
|
+
@handler.call(message)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
SalesforceStreamer.config.use_middleware MySimpleMiddleware
|
155
|
+
```
|
162
156
|
## Development
|
163
157
|
|
164
158
|
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake rspec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
data/Rakefile
CHANGED
@@ -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,16 +4,15 @@ require 'optparse'
|
|
4
4
|
require 'restforce'
|
5
5
|
require 'yaml'
|
6
6
|
require 'forwardable'
|
7
|
+
require 'dry-initializer'
|
7
8
|
|
8
9
|
require 'salesforce_streamer/configuration'
|
9
10
|
require 'salesforce_streamer/errors'
|
10
11
|
require 'salesforce_streamer/replay_id_error_extension'
|
11
12
|
require 'salesforce_streamer/log'
|
12
13
|
require 'salesforce_streamer/push_topic'
|
13
|
-
require 'salesforce_streamer/
|
14
|
+
require 'salesforce_streamer/salesforce_topic_manager'
|
14
15
|
require 'salesforce_streamer/salesforce_client'
|
15
|
-
require 'salesforce_streamer/replay_persistence'
|
16
|
-
require 'salesforce_streamer/redis_replay'
|
17
16
|
require 'salesforce_streamer/server'
|
18
17
|
require 'salesforce_streamer/version'
|
19
18
|
require 'salesforce_streamer/launcher'
|
@@ -60,9 +59,7 @@ module SalesforceStreamer
|
|
60
59
|
Configuration.instance
|
61
60
|
end
|
62
61
|
|
63
|
-
|
64
|
-
|
65
|
-
super 'SalesforceStreamer.config.redis_connection not set'
|
66
|
-
end
|
62
|
+
def self.configure
|
63
|
+
yield Configuration.instance
|
67
64
|
end
|
68
65
|
end
|
@@ -1,22 +1,27 @@
|
|
1
1
|
module SalesforceStreamer
|
2
2
|
# Manages server configuration.
|
3
3
|
class Configuration
|
4
|
-
attr_accessor :environment, :logger, :require_path, :config_file,
|
5
|
-
:
|
4
|
+
attr_accessor :environment, :logger, :require_path, :config_file,
|
5
|
+
:manage_topics, :exception_adapter, :replay_adapter
|
6
|
+
attr_reader :middleware
|
6
7
|
|
7
8
|
class << self
|
8
9
|
attr_writer :instance
|
9
|
-
end
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
def configure
|
12
|
+
yield instance
|
13
|
+
end
|
14
|
+
|
15
|
+
def instance
|
16
|
+
@instance ||= new
|
17
|
+
end
|
13
18
|
end
|
14
19
|
|
15
20
|
def initialize
|
16
21
|
@environment = ENV['RACK_ENV'] || :development
|
17
22
|
@logger = Logger.new(IO::NULL)
|
18
23
|
@exception_adapter = proc { |exc| fail(exc) }
|
19
|
-
@
|
24
|
+
@replay_adapter = proc { |topic| topic.id || topic.replay }
|
20
25
|
@manage_topics = false
|
21
26
|
@config_file = './config/streamer.yml'
|
22
27
|
@require_path = './config/environment'
|
@@ -27,12 +32,17 @@ module SalesforceStreamer
|
|
27
32
|
@manage_topics
|
28
33
|
end
|
29
34
|
|
35
|
+
# adds a setup proc to the middleware array
|
30
36
|
def use_middleware(klass, *args, &block)
|
31
|
-
middleware <<
|
37
|
+
@middleware << [klass, args, block]
|
32
38
|
end
|
33
39
|
|
34
|
-
|
35
|
-
|
40
|
+
# returns a ready to use chain of middleware
|
41
|
+
def middleware_runner(last_handler)
|
42
|
+
@middleware.reduce(last_handler) do |next_handler, current_handler|
|
43
|
+
klass, args, block = current_handler
|
44
|
+
klass.new(next_handler, *args, &block)
|
45
|
+
end
|
36
46
|
end
|
37
47
|
|
38
48
|
def push_topic_data
|
@@ -3,13 +3,13 @@ module SalesforceStreamer
|
|
3
3
|
|
4
4
|
class MissingCLIFlagError < StandardError
|
5
5
|
def initialize(flag)
|
6
|
-
super
|
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,37 +1,29 @@
|
|
1
1
|
module SalesforceStreamer
|
2
2
|
# Models the PushTopic object for both Restforce and Streamer
|
3
3
|
class PushTopic
|
4
|
-
|
5
|
-
attr_reader :name, :description, :notify_for_fields, :query,
|
6
|
-
:handler, :handler_constant, :api_version
|
4
|
+
extend Dry::Initializer
|
7
5
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
validate!
|
17
|
-
end
|
6
|
+
option :name
|
7
|
+
option :query, proc { |str| str.gsub(/\s+/, ' ') }
|
8
|
+
option :handler, proc { |str| prepare_handler_proc Object.const_get(str) }
|
9
|
+
option :replay, proc(&:to_i), default: proc { -1 }
|
10
|
+
option :api_version, proc(&:to_s), default: proc { '49.0' }
|
11
|
+
option :notify_for_fields, default: proc { 'Referenced' }
|
12
|
+
option :id, optional: true
|
13
|
+
option :description, optional: true
|
18
14
|
|
19
|
-
|
20
|
-
ReplayPersistence.retrieve(name) || @static_replay
|
21
|
-
end
|
15
|
+
attr_writer :id
|
22
16
|
|
23
17
|
def handle(message)
|
24
|
-
|
25
|
-
|
18
|
+
message['topic'] = @name
|
19
|
+
message_middleware.call(message)
|
26
20
|
rescue StandardError => e
|
27
21
|
Log.error e
|
28
22
|
Configuration.instance.exception_adapter.call e
|
29
23
|
end
|
30
24
|
|
31
|
-
def
|
32
|
-
|
33
|
-
"replay=#{replay} notify_for_fields=#{notify_for_fields} " \
|
34
|
-
"description=#{description} api_version=#{api_version} query=#{query}"
|
25
|
+
def attributes
|
26
|
+
self.class.dry_initializer.public_attributes self
|
35
27
|
end
|
36
28
|
|
37
29
|
private
|
@@ -39,29 +31,33 @@ module SalesforceStreamer
|
|
39
31
|
def validate!
|
40
32
|
fail(PushTopicNameTooLongError, @name) if @name.size > 25
|
41
33
|
|
42
|
-
@
|
34
|
+
@handler = Object.const_get(@handler)
|
43
35
|
true
|
44
36
|
rescue NameError, TypeError => e
|
45
37
|
message = 'handler=' + @handler.to_s + ' exception=' + e.to_s
|
46
38
|
raise(PushTopicHandlerMissingError, message)
|
47
39
|
end
|
48
40
|
|
49
|
-
def
|
50
|
-
Configuration.instance.
|
41
|
+
def message_middleware
|
42
|
+
Configuration.instance.middleware_runner(handler)
|
51
43
|
end
|
52
44
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
else
|
57
|
-
handler_constant
|
58
|
-
end
|
59
|
-
end
|
45
|
+
class << self
|
46
|
+
def strip_spaces(str)
|
47
|
+
fail(NilQueryError, @name) unless str
|
60
48
|
|
61
|
-
|
62
|
-
|
49
|
+
str.gsub(/\s+/, ' ')
|
50
|
+
end
|
63
51
|
|
64
|
-
|
52
|
+
def prepare_handler_proc(constant)
|
53
|
+
if constant.respond_to? :call
|
54
|
+
constant
|
55
|
+
elsif constant.respond_to? :perform_async
|
56
|
+
proc { |message| constant.perform_async message }
|
57
|
+
else
|
58
|
+
fail(UnprocessableHandlerError, constant)
|
59
|
+
end
|
60
|
+
end
|
65
61
|
end
|
66
62
|
end
|
67
63
|
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
module SalesforceStreamer
|
2
|
-
class
|
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,7 +34,8 @@ module SalesforceStreamer
|
|
34
34
|
def start_em
|
35
35
|
EM.run do
|
36
36
|
@push_topics.map do |topic|
|
37
|
-
|
37
|
+
replay_id = Configuration.instance.replay_adapter.call(topic)
|
38
|
+
client.subscribe topic.name, replay: replay_id.to_i do |message|
|
38
39
|
replay_id = message.dig('event', 'replayId')
|
39
40
|
Log.info "Message #{replay_id} received from topic #{topic.name}"
|
40
41
|
topic.handle message
|
data/salesforce_streamer.gemspec
CHANGED
@@ -14,6 +14,7 @@ Gem::Specification.new do |spec|
|
|
14
14
|
|
15
15
|
spec.metadata['homepage_uri'] = spec.homepage
|
16
16
|
spec.metadata['source_code_uri'] = spec.homepage
|
17
|
+
spec.metadata['documentation_uri'] = 'https://www.rubydoc.info/gems/salesforce_streamer'
|
17
18
|
|
18
19
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
19
20
|
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(bin/|spec/|\.rub)}) }
|
@@ -24,8 +25,9 @@ Gem::Specification.new do |spec|
|
|
24
25
|
|
25
26
|
spec.required_ruby_version = '>= 2.6'
|
26
27
|
|
27
|
-
spec.add_dependency '
|
28
|
-
spec.add_dependency '
|
28
|
+
spec.add_dependency 'dry-initializer', '~> 3.0'
|
29
|
+
spec.add_dependency 'faye', '~> 1.4'
|
30
|
+
spec.add_dependency 'restforce', '>= 4.2', '< 6.0'
|
29
31
|
|
30
32
|
spec.add_development_dependency 'byebug'
|
31
33
|
spec.add_development_dependency 'codecov'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: salesforce_streamer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Scott Serok
|
@@ -9,36 +9,56 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: exe
|
11
11
|
cert_chain: []
|
12
|
-
date: 2020-
|
12
|
+
date: 2020-08-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: dry-initializer
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - "~>"
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: '3.0'
|
21
|
+
type: :runtime
|
22
|
+
prerelease: false
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '3.0'
|
14
28
|
- !ruby/object:Gem::Dependency
|
15
29
|
name: faye
|
16
30
|
requirement: !ruby/object:Gem::Requirement
|
17
31
|
requirements:
|
18
|
-
- -
|
32
|
+
- - "~>"
|
19
33
|
- !ruby/object:Gem::Version
|
20
|
-
version:
|
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,9 +167,15 @@ extensions: []
|
|
147
167
|
extra_rdoc_files: []
|
148
168
|
files:
|
149
169
|
- ".dependabot/config.yml"
|
170
|
+
- ".github/ISSUE_TEMPLATE/bug_report.md"
|
171
|
+
- ".github/ISSUE_TEMPLATE/config.yml"
|
172
|
+
- ".github/ISSUE_TEMPLATE/story.md"
|
173
|
+
- ".github/PULL_REQUEST_TEMPLATE.md"
|
174
|
+
- ".github/workflows/auto-approve.yml"
|
150
175
|
- ".gitignore"
|
151
176
|
- ".rspec"
|
152
177
|
- ".travis.yml"
|
178
|
+
- CHANGELOG.md
|
153
179
|
- Gemfile
|
154
180
|
- Gemfile.lock
|
155
181
|
- LICENSE.txt
|
@@ -164,12 +190,10 @@ files:
|
|
164
190
|
- lib/salesforce_streamer/launcher.rb
|
165
191
|
- lib/salesforce_streamer/log.rb
|
166
192
|
- lib/salesforce_streamer/push_topic.rb
|
167
|
-
- lib/salesforce_streamer/redis_replay.rb
|
168
193
|
- lib/salesforce_streamer/replay_id_error_extension.rb
|
169
|
-
- lib/salesforce_streamer/replay_persistence.rb
|
170
194
|
- lib/salesforce_streamer/salesforce_client.rb
|
195
|
+
- lib/salesforce_streamer/salesforce_topic_manager.rb
|
171
196
|
- lib/salesforce_streamer/server.rb
|
172
|
-
- lib/salesforce_streamer/topic_manager.rb
|
173
197
|
- lib/salesforce_streamer/version.rb
|
174
198
|
- salesforce_streamer.gemspec
|
175
199
|
homepage: https://github.com/renofi/salesforce_streamer
|
@@ -178,6 +202,7 @@ licenses:
|
|
178
202
|
metadata:
|
179
203
|
homepage_uri: https://github.com/renofi/salesforce_streamer
|
180
204
|
source_code_uri: https://github.com/renofi/salesforce_streamer
|
205
|
+
documentation_uri: https://www.rubydoc.info/gems/salesforce_streamer
|
181
206
|
post_install_message:
|
182
207
|
rdoc_options: []
|
183
208
|
require_paths:
|
@@ -193,7 +218,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
193
218
|
- !ruby/object:Gem::Version
|
194
219
|
version: '0'
|
195
220
|
requirements: []
|
196
|
-
rubygems_version: 3.
|
221
|
+
rubygems_version: 3.1.2
|
197
222
|
signing_key:
|
198
223
|
specification_version: 4
|
199
224
|
summary: A wrapper around the Restforce Streaming API with a built-in PushTopic manager.
|
@@ -1,55 +0,0 @@
|
|
1
|
-
module SalesforceStreamer
|
2
|
-
class RedisReplay
|
3
|
-
class << self
|
4
|
-
def redis_connection
|
5
|
-
@redis_connection ||= Configuration.instance.redis_connection || fail(RedisConnectionError)
|
6
|
-
end
|
7
|
-
|
8
|
-
attr_writer :redis_connection
|
9
|
-
end
|
10
|
-
|
11
|
-
def connection
|
12
|
-
if RedisReplay.redis_connection.respond_to?(:with)
|
13
|
-
RedisReplay.redis_connection.with do |conn|
|
14
|
-
yield(conn)
|
15
|
-
end
|
16
|
-
else
|
17
|
-
yield RedisReplay.redis_connection
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
# Saves the value to a key with expiration
|
22
|
-
def record(key, value)
|
23
|
-
return unless key && value
|
24
|
-
|
25
|
-
key = namespaced_key(key)
|
26
|
-
value = Integer(value)
|
27
|
-
connection { |c| c.setex key, SECONDS_TO_EXPIRE, value }
|
28
|
-
rescue StandardError => e
|
29
|
-
Configuration.instance.exception_adapter.call e
|
30
|
-
nil
|
31
|
-
end
|
32
|
-
|
33
|
-
def retrieve(key)
|
34
|
-
return unless key
|
35
|
-
|
36
|
-
key = namespaced_key(key)
|
37
|
-
value = connection { |c| c.get key }
|
38
|
-
Integer(value) if value
|
39
|
-
rescue StandardError => e
|
40
|
-
Configuration.instance.exception_adapter.call e
|
41
|
-
nil
|
42
|
-
end
|
43
|
-
|
44
|
-
private
|
45
|
-
|
46
|
-
def namespaced_key(key)
|
47
|
-
NAMESPACE + key.to_s
|
48
|
-
end
|
49
|
-
|
50
|
-
NAMESPACE = 'SalesforceStreamer:'.freeze
|
51
|
-
SECONDS_TO_EXPIRE = 24 * 60 * 60 # 24 hours
|
52
|
-
START = 0
|
53
|
-
STOP = 0
|
54
|
-
end
|
55
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
module SalesforceStreamer
|
2
|
-
# Store values for a given key in a sorted sequence
|
3
|
-
# Retrieves the highest value given a key
|
4
|
-
class ReplayPersistence
|
5
|
-
class << self
|
6
|
-
def record(key, value)
|
7
|
-
Log.debug { "Recording #{key}=#{value}" }
|
8
|
-
Configuration.instance.persistence_adapter&.record key, value
|
9
|
-
end
|
10
|
-
|
11
|
-
def retrieve(key)
|
12
|
-
Configuration.instance.persistence_adapter&.retrieve(key).tap do |v|
|
13
|
-
Log.debug { "Retrieved for #{key} #{v || 'nil'}" }
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|