salesforce_streamer 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b58d79963e641ea50df2ecf51308df7ab452aa508c7e4833535723fac79488c3
4
- data.tar.gz: 4078e279b38b7ace14a5cbdb567d513bfe4d7fc4a55cc84690c2f81ffb8a9e64
3
+ metadata.gz: bb2a264193dc13244ad48222544789f305e483baf9a632ffeb6917f72056b5a7
4
+ data.tar.gz: 0bd3ebadf44a12a7adfa56e07cf1fea47dfe390688df85906a4587e188e3b18c
5
5
  SHA512:
6
- metadata.gz: 5b3bf235265594cad4930d3fbf127cb1416dac8023f293ddb22b75d7f2a98f799e6d8ae60718728e9009b644320eab3cd7c0eea6129b192cc4f7cdd2672d0c4f
7
- data.tar.gz: b9570a7f0d0b6a6cbbaed04a209227b8e7cbd6ed9dbd2daacfbdc2b5227e2b40f46679d479d5025df387c4b861772393499fd9cda168573979ec29a7adad1cca
6
+ metadata.gz: 93986c2fdebc68687bf690a87b3cb1dc542685e5a1db839711d0d925ad78a85d9190a44e818788397bc71d98cb038efa63b4e4dd7138045c832f3ddaff8e3245
7
+ data.tar.gz: c4d9af017d24f23576b03a88fbd6ca637770fda340154cb9d0085f7461994c28a3127a819b26d2b0fa3a2db1fbb5cf97ecfd43d46ba7029d13e81db56220ef8b
data/.gitignore CHANGED
@@ -10,3 +10,4 @@
10
10
 
11
11
  # rspec failure tracking
12
12
  .rspec_status
13
+ .env
data/Gemfile CHANGED
@@ -1,4 +1,6 @@
1
- source "https://rubygems.org"
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
2
4
 
3
5
  # Specify your gem's dependencies in salesforce_streamer.gemspec
4
6
  gemspec
data/Gemfile.lock CHANGED
@@ -1,15 +1,15 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- salesforce_streamer (0.1.0)
4
+ salesforce_streamer (0.2.0)
5
5
  faye (~> 0.8.9)
6
6
  restforce (~> 3.1.0)
7
7
 
8
8
  GEM
9
9
  remote: https://rubygems.org/
10
10
  specs:
11
- addressable (2.6.0)
12
- public_suffix (>= 2.0.2, < 4.0)
11
+ addressable (2.7.0)
12
+ public_suffix (>= 2.0.2, < 5.0)
13
13
  byebug (11.0.1)
14
14
  codecov (0.1.14)
15
15
  json
@@ -45,7 +45,7 @@ GEM
45
45
  http_parser.rb (0.6.0)
46
46
  json (2.2.0)
47
47
  multipart-post (2.1.1)
48
- public_suffix (3.1.1)
48
+ public_suffix (4.0.1)
49
49
  rack (2.0.7)
50
50
  rake (10.5.0)
51
51
  restforce (3.1.0)
@@ -89,4 +89,4 @@ DEPENDENCIES
89
89
  salesforce_streamer!
90
90
 
91
91
  BUNDLED WITH
92
- 2.0.1
92
+ 2.0.2
data/README.md CHANGED
@@ -45,6 +45,12 @@ base: &DEFAULT
45
45
 
46
46
  development:
47
47
  <<: *DEFAULT
48
+
49
+ test:
50
+ <<: *DEFAULT
51
+
52
+ production:
53
+ <<: *DEFAULT
48
54
  ```
49
55
 
50
56
  It's important to note that the way push topics are managed is by the Salesforce
@@ -75,29 +81,31 @@ Set your Restforce ENV variables in order to establish a connection. See the
75
81
  Restforce API documentation for more details. Then start the server using the
76
82
  command line interface.
77
83
 
78
- ### Launch The Streamer
84
+ Configure the `SalesforceStreamer` module.
79
85
 
80
- Launch the `streamer` service, pointing it to your push topic configuration YAML
81
- file and the entry point to your application.
86
+ ```ruby
87
+ # config/initializers/salesforce_streamer.rb
82
88
 
83
- ```
84
- $ bundle exec streamer -C config/streamer.yml -r ./lib/app -x -v INFO
85
- I, [2019-07-09T15:19:55.862296 #78537] INFO -- : Launching Streamer Services
86
- I, [2019-07-09T15:19:55.862351 #78537] INFO -- : Running Topic Manager
87
- I, [2019-07-09T15:19:56.860998 #78537] INFO -- : New PushTopic AllAccounts
88
- I, [2019-07-09T15:19:56.861079 #78537] INFO -- : Upsert PushTopic AllAccounts
89
- I, [2019-07-09T15:19:57.591241 #78537] INFO -- : Starting Server
89
+ require 'redis'
90
+ require 'connection_pool'
91
+
92
+ SalesforceStreamer.config.redis_connection = ConnectionPool.new(size: 5, timeout: 5) { Redis.new }
93
+ SalesforceStreamer.config.logger = Logger.new(STDERR, level: 'INFO')
94
+ SalesforceStreamer.config.exception_adapter = proc { |e| puts e }
95
+ SalesforceStreamer.config.manage_topics = true
90
96
  ```
91
97
 
92
- For very verbose logs, also use the `--verbose-restforce` flag to activate the
93
- Restforce client logger - not recommended for production.
98
+ ### Launch The Streamer
94
99
 
95
- You can start up the server without syncing the push topic configuration if you
96
- know the topics are already configured appropriately. Remove the `-x` flag from
97
- the CLI to skip the topic management component.
100
+ Launch the `streamer` service loads the application code at
101
+ `./config/application` by default if `config.require_path` is unset. It will
102
+ load your push topic configuration from `./config/streamer.yml` if
103
+ `config.config_file` is unset. During the boot sequence it will connect to
104
+ Salesforce using the Restforce client and your configured ENV variables in order
105
+ to upsert push topic definitions.
98
106
 
99
107
  ```
100
- $ bundle exec streamer -C config/streamer.yml -r ./lib/app -v INFO
108
+ $ bundle exec streamer
101
109
  I, [2019-07-08T22:16:34.104271 #26973] INFO -- : Launching Streamer Services
102
110
  I, [2019-07-09T15:19:55.862351 #78537] INFO -- : Running Topic Manager
103
111
  I, [2019-07-09T15:19:56.860998 #78537] INFO -- : New PushTopic AllAccounts
@@ -106,14 +114,23 @@ I, [2019-07-09T15:19:56.861109 #78537] INFO -- : Skipping upsert because manage
106
114
  I, [2019-07-08T22:16:34.794933 #26973] INFO -- : Starting Server
107
115
  ```
108
116
 
109
- By default, the CLI will load the YAML based on the `RACK_ENV` environment
110
- variable, or default to `:development` if not set. You can override this in the
111
- CLI with the `-e ENV` flag.
117
+ By default, the server will start up without syncing the push topic configuration.
118
+ Set the configuration option `config.manage_topics = true` will tell the server
119
+ launcher to update the configuration of the push topic in Salesforce.
112
120
 
113
121
  ```
114
- $ bundle exec streamer -C config/streamer.yml -r ./lib/app -e production
122
+ $ bundle exec streamer
123
+ I, [2019-07-09T15:19:55.862296 #78537] INFO -- : Launching Streamer Services
124
+ I, [2019-07-09T15:19:55.862351 #78537] INFO -- : Running Topic Manager
125
+ I, [2019-07-09T15:19:56.860998 #78537] INFO -- : New PushTopic AllAccounts
126
+ I, [2019-07-09T15:19:56.861079 #78537] INFO -- : Upsert PushTopic AllAccounts
127
+ I, [2019-07-09T15:19:57.591241 #78537] INFO -- : Starting Server
115
128
  ```
116
129
 
130
+ By default, the executable will load the YAML based on the `RACK_ENV` environment
131
+ variable, or default to `:development` if not set. You can override this by
132
+ setting the `config.environment = :integration`
133
+
117
134
  ## Development
118
135
 
119
136
  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
@@ -1,6 +1,8 @@
1
- require "bundler/gem_tasks"
2
- require "rspec/core/rake_task"
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
3
5
 
4
6
  RSpec::Core::RakeTask.new(:spec)
5
7
 
6
- task :default => :spec
8
+ task default: :spec
data/bin/console CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
- require "bundler/setup"
4
- require "salesforce_streamer"
4
+ require 'bundler/setup'
5
+ require 'salesforce_streamer'
5
6
 
6
7
  # You can add fixtures and/or initialization code here to make experimenting
7
8
  # with your gem easier. You can also use a different console, if you like.
@@ -10,5 +11,5 @@ require "salesforce_streamer"
10
11
  # require "pry"
11
12
  # Pry.start
12
13
 
13
- require "irb"
14
+ require 'irb'
14
15
  IRB.start(__FILE__)
data/exe/streamer CHANGED
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'salesforce_streamer'
4
5
  require 'salesforce_streamer/cli'
@@ -10,7 +10,6 @@ module CoreExtensions
10
10
  module CookieValidation
11
11
  def self.extended(base)
12
12
  base.class_eval do
13
-
14
13
  def self.domains_match(tested_domain, base_domain)
15
14
  return true if tested_domain[-15..-1].eql?('.salesforce.com')
16
15
 
@@ -21,7 +20,6 @@ module CoreExtensions
21
20
  domain == tested_domain
22
21
  end
23
22
  end
24
-
25
23
  end
26
24
  end
27
25
  end
@@ -4,66 +4,52 @@ module SalesforceStreamer
4
4
  class CLI
5
5
  def initialize(argv)
6
6
  @argv = argv
7
- @config = Configuration.new
8
- @push_topics_loaded = false
7
+ @config = Configuration.instance
9
8
  setup_options
10
9
  @parser.parse! @argv
11
10
  end
12
11
 
13
12
  def run
14
- validate!
15
- Launcher.new(config: @config).run
13
+ Launcher.new.run
16
14
  end
17
15
 
18
16
  private
19
17
 
20
- def validate!
21
- raise(MissingCLIFlagError, '--config PATH') unless @push_topics_loaded
22
- raise(MissingCLIFlagError, '--require PATH') unless @config.require_path
23
- rescue MissingCLIFlagError => e
24
- puts e
25
- puts @parser
26
- exit 1
27
- end
28
-
29
18
  def setup_options
30
19
  @parser = OptionParser.new do |o|
31
- o.on "-C", "--config PATH", "Load PATH as a config file" do |arg|
32
- @config.load_push_topic_data arg
33
- @push_topics_loaded = true
20
+ o.on '-C', '--config PATH', 'Load PATH as a config file' do |arg|
21
+ @config.config_file = arg
34
22
  end
35
23
 
36
- o.on "-e", "--environment ENVIRONMENT",
37
- "The environment to run the app on (default development)" do |arg|
24
+ o.on '-e', '--environment ENVIRONMENT',
25
+ 'The environment to run the app on (default development)' do |arg|
38
26
  @config.environment = arg
39
27
  end
40
28
 
41
- o.on "-r", "--require PATH", "Load PATH as the entry point to your application" do |arg|
29
+ o.on '-r', '--require PATH', 'Load PATH as the entry point to your application' do |arg|
42
30
  @config.require_path = arg
43
31
  end
44
32
 
45
- o.on "--verbose-restforce", "Activate the Restforce logger" do
33
+ o.on '--verbose-restforce', 'Activate the Restforce logger' do
46
34
  @config.restforce_logger!
47
35
  end
48
36
 
49
- o.on "-v", "--verbose LEVEL", "Set the log level (default no logging)" do |arg|
50
- logger = Logger.new(STDOUT)
51
- logger.level = arg
52
- @config.logger = logger
37
+ o.on '-v', '--verbose LEVEL', 'Set the log level (default no logging)' do |arg|
38
+ @config.logger = Logger.new(STDERR, level: arg)
53
39
  end
54
40
 
55
- o.on "-V", "--version", "Print the version information" do
41
+ o.on '-V', '--version', 'Print the version information' do
56
42
  puts "streamer version #{SalesforceStreamer::VERSION}"
57
43
  exit 0
58
44
  end
59
45
 
60
- o.on "-x", "--topics", "Activate PushTopic Management (default off)" do
46
+ o.on '-x', '--topics', 'Activate PushTopic Management (default off)' do
61
47
  @config.manage_topics = true
62
48
  end
63
49
 
64
- o.banner = "streamer OPTIONS"
50
+ o.banner = 'bundle exec streamer OPTIONS'
65
51
 
66
- o.on_tail "-h", "--help", "Show help" do
52
+ o.on_tail '-h', '--help', 'Show help' do
67
53
  puts o
68
54
  exit 0
69
55
  end
@@ -3,22 +3,34 @@
3
3
  module SalesforceStreamer
4
4
  # Manages server configuration.
5
5
  class Configuration
6
- attr_accessor :environment, :logger, :require_path
7
- attr_reader :push_topic_data
8
- attr_writer :manage_topics
6
+ attr_accessor :environment, :logger, :require_path, :config_file, :manage_topics, :server, :exception_adapter, :persistence_adapter, :redis_connection
7
+
8
+ class << self
9
+ attr_writer :instance
10
+ end
11
+
12
+ def self.instance
13
+ @instance ||= new
14
+ end
9
15
 
10
16
  def initialize
11
17
  @environment = ENV['RACK_ENV'] || :development
12
18
  @logger = Logger.new(IO::NULL)
19
+ @exception_adapter = proc { |exc| raise exc }
20
+ @persistence_adapter = RedisReplay.new
13
21
  @manage_topics = false
22
+ @config_file = './config/streamer.yml'
23
+ @require_path = './config/application'
14
24
  end
15
25
 
16
26
  def manage_topics?
17
27
  @manage_topics
18
28
  end
19
29
 
20
- def load_push_topic_data(path)
21
- data = YAML.safe_load(File.read(path), [], [], true)
30
+ def push_topic_data
31
+ return @push_topic_data if @push_topic_data
32
+
33
+ data = YAML.safe_load(File.read(config_file), [], [], true)
22
34
  @push_topic_data = data[environment.to_s]
23
35
  end
24
36
 
@@ -15,7 +15,7 @@ module SalesforceStreamer
15
15
 
16
16
  class PushTopicHandlerMissingError < StandardError
17
17
  def initialize(message)
18
- super "Unable to load constant #{message.to_s}."
18
+ super "Unable to load constant #{message}."
19
19
  end
20
20
  end
21
21
 
@@ -5,12 +5,11 @@ module SalesforceStreamer
5
5
  # Streaming API server. It is responsible for upserting each PushTopic and
6
6
  # starting the server.
7
7
  class Launcher
8
- def initialize(config:)
9
- @config = config
10
- @logger = config.logger
8
+ def initialize
9
+ @logger = Configuration.instance.logger
11
10
  load_server_configuration
12
- @manager = TopicManager.new push_topics: @push_topics, config: @config
13
- @server = Server.new push_topics: @push_topics, config: @config
11
+ @manager = TopicManager.new push_topics: @push_topics
12
+ @server = Server.new push_topics: @push_topics
14
13
  end
15
14
 
16
15
  # Manages each PushTopic configured and starts the Streaming API listener.
@@ -29,16 +28,16 @@ module SalesforceStreamer
29
28
  end
30
29
 
31
30
  def require_application
32
- if @config.require_path
31
+ if Configuration.instance.require_path
33
32
  @logger.debug 'Loading the require path'
34
- require @config.require_path
33
+ require Configuration.instance.require_path
35
34
  end
36
35
  end
37
36
 
38
37
  def initialize_push_topics
39
38
  @logger.debug 'Loading and validating PushTopics configuration'
40
39
  @push_topics = []
41
- @config.push_topic_data.values.each do |topic_data|
40
+ Configuration.instance.push_topic_data.values.each do |topic_data|
42
41
  @logger.debug topic_data.to_s
43
42
  @push_topics << PushTopic.new(data: topic_data)
44
43
  end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SalesforceStreamer
4
+ class MessageReceiver
5
+ class << self
6
+ # @param topic [String] The unique Salesforce Topic name
7
+ # @param handler [Object] An object that responds to .call(message)
8
+ # @param message [Hash] The event payload
9
+ def call(topic, handler, message)
10
+ if handler.respond_to? :perform_async
11
+ handler.perform_async message
12
+ else
13
+ handler.call message
14
+ end
15
+ ReplayPersistence.record topic, message.dig('event', 'replayId')
16
+ rescue StandardError => e
17
+ Configuration.instance.exception_adapter.call e
18
+ end
19
+ end
20
+ end
21
+ end
@@ -4,12 +4,12 @@ module SalesforceStreamer
4
4
  # Models the PushTopic object for both Restforce and Streamer
5
5
  class PushTopic
6
6
  attr_accessor :id
7
- attr_reader :name, :replay, :description, :notify_for_fields, :query,
8
- :handler, :handler_constant, :api_version
7
+ attr_reader :name, :description, :notify_for_fields, :query,
8
+ :handler, :handler_constant, :api_version
9
9
 
10
10
  def initialize(data:)
11
11
  @handler = data['handler']
12
- @replay = data.dig('replay')&.to_i || -1
12
+ @static_replay = data.dig('replay')&.to_i || -1
13
13
  @name = data.dig('salesforce', 'name')
14
14
  @api_version = data.dig('salesforce', 'api_version') || '41.0'
15
15
  @description = data.dig('salesforce', 'description') || @name
@@ -18,6 +18,12 @@ module SalesforceStreamer
18
18
  validate!
19
19
  end
20
20
 
21
+ def replay
22
+ @replay ||= (ReplayPersistence.retrieve(name) || @static_replay).tap do |replayId|
23
+ Configuration.instance.logger.info ['PushTopic name=', name, ' starting at replayId=', replayId].join
24
+ end
25
+ end
26
+
21
27
  def to_s
22
28
  "PushTopic id=#{id} name=#{name} handler=#{handler} " \
23
29
  "replay=#{replay} notify_for_fields=#{notify_for_fields} " \
@@ -28,6 +34,7 @@ module SalesforceStreamer
28
34
 
29
35
  def validate!
30
36
  raise(PushTopicNameTooLongError, @name) if @name.size > 25
37
+
31
38
  @handler_constant = Object.const_get(@handler)
32
39
  true
33
40
  rescue NameError, TypeError => e
@@ -37,6 +44,7 @@ module SalesforceStreamer
37
44
 
38
45
  def strip_spaces(str)
39
46
  raise(NilQueryError, @name) unless str
47
+
40
48
  str.gsub(/\s+/, ' ')
41
49
  end
42
50
  end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SalesforceStreamer
4
+ class RedisReplay
5
+ class << self
6
+ def redis_connection
7
+ @redis_connection ||= Configuration.instance.redis_connection || raise(RedisConnectionError)
8
+ end
9
+
10
+ attr_writer :redis_connection
11
+ end
12
+
13
+ def connection
14
+ if RedisReplay.redis_connection.respond_to?(:with)
15
+ RedisReplay.redis_connection.with do |conn|
16
+ yield(conn)
17
+ end
18
+ else
19
+ yield RedisReplay.redis_connection
20
+ end
21
+ end
22
+
23
+ # Saves the value in a sorted set named by the key
24
+ # The score is the ReplayId integer value
25
+ def record(key, value)
26
+ return unless key && value
27
+
28
+ key = namespaced_key(key)
29
+ value = Integer(value)
30
+ # The score is the value
31
+ connection { |c| c.zadd key, value, value }
32
+ rescue StandardError, TypeError => e
33
+ Configuration.instance.exception_adapter.call e
34
+ nil
35
+ end
36
+
37
+ # Retrives the highest value in the sorted set
38
+ def retrieve(key)
39
+ return unless key
40
+
41
+ key = namespaced_key(key)
42
+ value = connection { |c| c.zrevrange(key, START, STOP)&.first }
43
+ Integer(value) if value
44
+ rescue StandardError => e
45
+ Configuration.instance.exception_adapter.call e
46
+ nil
47
+ end
48
+
49
+ private
50
+
51
+ def namespaced_key(key)
52
+ NAMESPACE + key.to_s
53
+ end
54
+
55
+ NAMESPACE = 'SalesforceStreamer:'
56
+ START = 0
57
+ STOP = 0
58
+ end
59
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SalesforceStreamer
4
+ # Store values for a given key in a sorted sequence
5
+ # Retrieves the highest value given a key
6
+ class ReplayPersistence
7
+ class << self
8
+ def record(key, value)
9
+ Configuration.instance.logger.debug ['Recording ', key, '=', value].join
10
+ Configuration.instance.persistence_adapter&.record key, value
11
+ end
12
+
13
+ def retrieve(key)
14
+ Configuration.instance.persistence_adapter&.retrieve(key).tap do |v|
15
+ Configuration.instance.logger.debug ['Retrieved for ', key, ' ', v || 'nil'].join
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -25,21 +25,20 @@ module SalesforceStreamer
25
25
  # Returns true or raises an exception if the upsert fails
26
26
  def upsert_push_topic(push_topic)
27
27
  @client.upsert!('PushTopic', :Id,
28
- 'Id' => push_topic.id,
29
- 'Name' => push_topic.name,
30
- 'ApiVersion' => push_topic.api_version,
31
- 'Description' => push_topic.description,
32
- 'NotifyForFields' => push_topic.notify_for_fields,
33
- 'Query' => push_topic.query
34
- )
28
+ 'Id' => push_topic.id,
29
+ 'Name' => push_topic.name,
30
+ 'ApiVersion' => push_topic.api_version,
31
+ 'Description' => push_topic.description,
32
+ 'NotifyForFields' => push_topic.notify_for_fields,
33
+ 'Query' => push_topic.query)
35
34
  end
36
35
 
37
36
  private
38
37
 
39
38
  QUERY = <<~SOQL.chomp.freeze
40
- SELECT Id, Name, ApiVersion, Description, NotifyForFields, Query, isActive
41
- FROM PushTopic
42
- WHERE Name = '{{NAME}}'
39
+ SELECT Id, Name, ApiVersion, Description, NotifyForFields, Query, isActive
40
+ FROM PushTopic
41
+ WHERE Name = '{{NAME}}'
43
42
  SOQL
44
43
  end
45
44
  end
@@ -4,8 +4,8 @@ module SalesforceStreamer
4
4
  class Server
5
5
  attr_writer :push_topics
6
6
 
7
- def initialize(config:, push_topics: [])
8
- @logger = config.logger
7
+ def initialize(push_topics: [])
8
+ @logger = Configuration.instance.logger
9
9
  @push_topics = push_topics
10
10
  @client = Restforce.new
11
11
  end
@@ -32,9 +32,7 @@ module SalesforceStreamer
32
32
  EM.run do
33
33
  @push_topics.map do |topic|
34
34
  @client.subscribe topic.name, replay: topic.replay.to_i do |msg|
35
- @logger.debug(msg)
36
- topic.handler_constant.call(msg)
37
- @logger.info("Message processed: channel=#{topic.name} replayId=#{msg.dig('event', 'replayId')}")
35
+ MessageReceiver.call topic.name, topic.handler_constant, msg
38
36
  end
39
37
  end
40
38
  end
@@ -4,10 +4,9 @@ module SalesforceStreamer
4
4
  class TopicManager
5
5
  attr_reader :push_topics
6
6
 
7
- def initialize(push_topics:, config:)
7
+ def initialize(push_topics:)
8
8
  @push_topics = push_topics
9
- @config = config
10
- @logger = config.logger
9
+ @logger = Configuration.instance.logger
11
10
  @client = SalesforceClient.new
12
11
  end
13
12
 
@@ -15,9 +14,7 @@ module SalesforceStreamer
15
14
  @logger.info 'Running Topic Manager'
16
15
  @push_topics.each do |push_topic|
17
16
  @logger.debug push_topic.to_s
18
- if diff?(push_topic)
19
- upsert(push_topic)
20
- end
17
+ upsert(push_topic) if diff?(push_topic)
21
18
  end
22
19
  end
23
20
 
@@ -34,13 +31,14 @@ module SalesforceStreamer
34
31
  return true unless push_topic.query.eql?(hashie.Query)
35
32
  return true unless push_topic.notify_for_fields.eql?(hashie.NotifyForFields)
36
33
  return true unless push_topic.api_version.to_s.eql?(hashie.ApiVersion.to_s)
34
+
37
35
  @logger.debug 'No differences detected'
38
36
  false
39
37
  end
40
38
 
41
39
  def upsert(push_topic)
42
40
  @logger.info "Upsert PushTopic #{push_topic.name}"
43
- if @config.manage_topics?
41
+ if Configuration.instance.manage_topics?
44
42
  @client.upsert_push_topic(push_topic)
45
43
  else
46
44
  @logger.info 'Skipping upsert because manage topics is off'
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SalesforceStreamer
4
- VERSION = '0.1.1'
4
+ VERSION = '0.2.0'
5
5
  end
@@ -11,6 +11,9 @@ require 'salesforce_streamer/errors'
11
11
  require 'salesforce_streamer/push_topic'
12
12
  require 'salesforce_streamer/topic_manager'
13
13
  require 'salesforce_streamer/salesforce_client'
14
+ require 'salesforce_streamer/replay_persistence'
15
+ require 'salesforce_streamer/redis_replay'
16
+ require 'salesforce_streamer/message_receiver'
14
17
  require 'salesforce_streamer/server'
15
18
  require 'salesforce_streamer/version'
16
19
  require 'salesforce_streamer/launcher'
@@ -43,14 +46,23 @@ require 'core_extensions/cookiejar/cookie_validation'
43
46
  #
44
47
  # Turn on verbose logging for troubleshooting. The -r flag will activate the
45
48
  # Restforce logger, so this is not recommended for production. The -v flag
46
- # activates a Logger to STDOUT with DEBUG level by default. Set the log level
49
+ # activates a Logger to STDERR with DEBUG level by default. Set the log level
47
50
  # with the -v flag.
48
51
  #
49
- # bundle exec streamer -C config/streamer.yml -r
52
+ # bundle exec streamer
50
53
  # bundle exec streamer -C config/streamer.yml -v
51
54
  # bundle exec streamer -C config/streamer.yml -v INFO
52
55
  # bundle exec streamer -C config/streamer.yml -r -v
53
56
  # bundle exec streamer -C config/streamer.yml -r -v INFO
54
57
  #
55
58
  module SalesforceStreamer
59
+ def self.config
60
+ Configuration.instance
61
+ end
62
+
63
+ class RedisConnectionError < StandardError
64
+ def initialize
65
+ super 'SalesforceStreamer.config.redis_connection not set'
66
+ end
67
+ end
56
68
  end
@@ -1,44 +1,45 @@
1
+ # frozen_string_literal: true
1
2
 
2
- lib = File.expand_path("../lib", __FILE__)
3
+ lib = File.expand_path('lib', __dir__)
3
4
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
- require "salesforce_streamer/version"
5
+ require 'salesforce_streamer/version'
5
6
 
6
7
  Gem::Specification.new do |spec|
7
- spec.name = "salesforce_streamer"
8
+ spec.name = 'salesforce_streamer'
8
9
  spec.version = SalesforceStreamer::VERSION
9
- spec.authors = ["Scott Serok"]
10
- spec.email = ["scott@renofi.com"]
10
+ spec.authors = ['Scott Serok']
11
+ spec.email = ['scott@renofi.com']
11
12
 
12
13
  spec.summary = 'A wrapper around the Restforce Streaming API.'
13
14
  spec.description = 'A wrapper around the Restforce Streaming API.'
14
15
  spec.homepage = 'https://github.com/renofi/salesforce_streamer'
15
- spec.license = "MIT"
16
+ spec.license = 'MIT'
16
17
 
17
18
  # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
19
  # to allow pushing to a single host or delete this section to allow pushing to any host.
19
20
  if spec.respond_to?(:metadata)
20
- spec.metadata["homepage_uri"] = spec.homepage
21
- spec.metadata["source_code_uri"] = spec.homepage
22
- spec.metadata["changelog_uri"] = spec.homepage
21
+ spec.metadata['homepage_uri'] = spec.homepage
22
+ spec.metadata['source_code_uri'] = spec.homepage
23
+ spec.metadata['changelog_uri'] = spec.homepage
23
24
  else
24
- raise "RubyGems 2.0 or newer is required to protect against " \
25
- "public gem pushes."
25
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
26
+ 'public gem pushes.'
26
27
  end
27
28
 
28
29
  # Specify which files should be added to the gem when it is released.
29
30
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
30
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
31
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
31
32
  `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
32
33
  end
33
- spec.bindir = "exe"
34
+ spec.bindir = 'exe'
34
35
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
35
- spec.require_paths = ["lib"]
36
+ spec.require_paths = ['lib']
36
37
 
37
- spec.add_development_dependency "bundler", "~> 2.0"
38
- spec.add_development_dependency "rake", "~> 10.0"
39
- spec.add_development_dependency "rspec", "~> 3.0"
40
- spec.add_development_dependency "byebug", "~> 11.0.1"
41
- spec.add_development_dependency "codecov", "~> 0.1.14"
42
- spec.add_dependency "restforce", "~> 3.1.0"
43
- spec.add_dependency "faye", "~> 0.8.9"
38
+ spec.add_development_dependency 'bundler', '~> 2.0'
39
+ spec.add_development_dependency 'byebug', '~> 11.0.1'
40
+ spec.add_development_dependency 'codecov', '~> 0.1.14'
41
+ spec.add_development_dependency 'rake', '~> 10.0'
42
+ spec.add_development_dependency 'rspec', '~> 3.0'
43
+ spec.add_dependency 'faye', '~> 0.8.9'
44
+ spec.add_dependency 'restforce', '~> 3.1.0'
44
45
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: salesforce_streamer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Scott Serok
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2019-07-17 00:00:00.000000000 Z
11
+ date: 2019-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -25,89 +25,89 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '2.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake
28
+ name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '10.0'
33
+ version: 11.0.1
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '10.0'
40
+ version: 11.0.1
41
41
  - !ruby/object:Gem::Dependency
42
- name: rspec
42
+ name: codecov
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: '3.0'
47
+ version: 0.1.14
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: '3.0'
54
+ version: 0.1.14
55
55
  - !ruby/object:Gem::Dependency
56
- name: byebug
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - "~>"
60
60
  - !ruby/object:Gem::Version
61
- version: 11.0.1
61
+ version: '10.0'
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - "~>"
67
67
  - !ruby/object:Gem::Version
68
- version: 11.0.1
68
+ version: '10.0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: codecov
70
+ name: rspec
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - "~>"
74
74
  - !ruby/object:Gem::Version
75
- version: 0.1.14
75
+ version: '3.0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - "~>"
81
81
  - !ruby/object:Gem::Version
82
- version: 0.1.14
82
+ version: '3.0'
83
83
  - !ruby/object:Gem::Dependency
84
- name: restforce
84
+ name: faye
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: 3.1.0
89
+ version: 0.8.9
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: 3.1.0
96
+ version: 0.8.9
97
97
  - !ruby/object:Gem::Dependency
98
- name: faye
98
+ name: restforce
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 0.8.9
103
+ version: 3.1.0
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 0.8.9
110
+ version: 3.1.0
111
111
  description: A wrapper around the Restforce Streaming API.
112
112
  email:
113
113
  - scott@renofi.com
@@ -134,7 +134,10 @@ files:
134
134
  - lib/salesforce_streamer/configuration.rb
135
135
  - lib/salesforce_streamer/errors.rb
136
136
  - lib/salesforce_streamer/launcher.rb
137
+ - lib/salesforce_streamer/message_receiver.rb
137
138
  - lib/salesforce_streamer/push_topic.rb
139
+ - lib/salesforce_streamer/redis_replay.rb
140
+ - lib/salesforce_streamer/replay_persistence.rb
138
141
  - lib/salesforce_streamer/salesforce_client.rb
139
142
  - lib/salesforce_streamer/server.rb
140
143
  - lib/salesforce_streamer/topic_manager.rb