mixboard 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0d0a1f66401e36fe1c9dfcac7514a27d9808a92b2416b8ca37a10bb738ab408f
4
+ data.tar.gz: 9b404a6b5b57186e759aa27314ed81a45d054f5cb3bb96fbfbd08ffa5118ed3a
5
+ SHA512:
6
+ metadata.gz: 92128dccbba4f852a99662525589a38ac169e9ef14e3086570737f9e8e55bc109dba73c7200dab090b2fbfcb5809fb47835dff60c42a9a60b90629f646fee85f
7
+ data.tar.gz: a1c9d465c7259ca915c1d680ca8caa6f55fdf56fc3f1d0d2f4f0da82d60ec96ca020d6a40a7b2df13b912859d955e6847c64ca96ab8cbb1996c296603ae751ee
@@ -0,0 +1,25 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright © `2020` `Braze, Inc.`
5
+
6
+ Permission is hereby granted, free of charge, to any person
7
+ obtaining a copy of this software and associated documentation
8
+ files (the “Software”), to deal in the Software without
9
+ restriction, including without limitation the rights to use,
10
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
11
+ copies of the Software, and to permit persons to whom the
12
+ Software is furnished to do so, subject to the following
13
+ conditions:
14
+
15
+ The above copyright notice and this permission notice shall be
16
+ included in all copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
19
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
20
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
21
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
22
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
23
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
24
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25
+ OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,180 @@
1
+ # Mixboard
2
+
3
+ Mixboard is a framework for routing signals from sources to sinks. Signals can be metrics,
4
+ log messages, errors, or any other similar piece of information one might want to route
5
+ from a source to a sink. Conditional routing is available via filters, and transformation
6
+ of signals is possible via signal processors. A specific end-to-end route is called a channel.
7
+
8
+ It's a common pattern to spend a non-trivial amount of time enabling and disabling
9
+ logs, metrics, or other types of debugging information on a per-customer or a
10
+ per-business-object basis. Instead, this framework provides the tooling to do this type of
11
+ observability work ahead of time (i.e. inserting the proper signalling) and be able to
12
+ enable/disable it selectively at runtime or via configuration.
13
+
14
+ ## Usage
15
+
16
+ Mixboard is designed as a Rails Engine, so usage is straightforward. A simple example
17
+ is below.
18
+
19
+ ### Signals
20
+
21
+ A signal is a payload containing information that can be routed through the mixer.
22
+
23
+ First, create a signal by extending `Mixboard::Signal` if you aren't using a generic
24
+ signal type, like so:
25
+
26
+ ```ruby
27
+ class DummySignal < Mixboard::Signal
28
+ attr_accessor :my_attribute
29
+ end
30
+ ```
31
+
32
+ #### Signal Levels
33
+
34
+ Signals have levels associated with them, similar to log messages. Using levels,
35
+ it's easy to subcategorize or delineate the granularity of signals. The provided
36
+ levels are: `verbose`, `debug`, `info`, `warn`, and `error`, similar to most
37
+ logging frameworks. These are often helpful when using filters.
38
+
39
+ ### Sources
40
+
41
+ A source is anything that emits signals of a certain type.
42
+
43
+ Create a source by extending `Mixboard::Source`, making sure the methods you
44
+ plan on using on your source are calling the `emit` method with a signal instance,
45
+ like so:
46
+
47
+ ```ruby
48
+ class DummySource < Mixboard::Source
49
+ def signal_class
50
+ DummySignal
51
+ end
52
+
53
+ def my_logging_method
54
+ signal = DummySignal.new
55
+ signal.my_attribute = 'FizzBuzz'
56
+ emit(signal)
57
+ end
58
+ end
59
+ ```
60
+
61
+ ### Sinks
62
+
63
+ A sink is anything that accepts signals of a certain type.
64
+
65
+ Create a sink by extending `Mixboard::Sink` if you aren't using a generic sink,
66
+ making sure to provide an `accept` method, like so:
67
+
68
+ ```ruby
69
+ class DummySink < Mixboard::Sink
70
+ def signal_class
71
+ DummySignal
72
+ end
73
+
74
+ def accept(signal)
75
+ MyExternalLoggingService.post(signal.my_attribute)
76
+ end
77
+ end
78
+ ```
79
+
80
+ ### Configuration
81
+
82
+ To statically configure channels, create a `mixboard.rb` initializer and
83
+ configure a channel, like so:
84
+ ```ruby
85
+ Mixboard::Mixer.configure do |c|
86
+ channel = Mixboard::Channel.new
87
+ .add_source(DummySource)
88
+ .add_sink(DummySink.new(any_config_options))
89
+
90
+ c.add_channel(channel)
91
+ end
92
+ ```
93
+
94
+ And you should be all set. You can instantiate your `DummySource` anywhere, use
95
+ `my_logging_method`, and Mixboard will connect the two components!
96
+
97
+ To dynamically configure channels... TODO!
98
+
99
+ ### Signal Processors
100
+
101
+ Signal processors accept signals of a certain type and emit signals of a certain type.
102
+ The are used for transforming signals from one type to another.
103
+
104
+ Imagine wanting to send an error message to something like Sentry, but also wanting to
105
+ track it as a metric in StatsD. You would set up two channels:
106
+
107
+ - Source: Logger; Sink: SentrySink
108
+ - Source: Logger; Sink: StatsDCountMetricSink
109
+
110
+ For the first channel, the signal types match (assuming they are both something simple,
111
+ such as `MessageSignal`, so you don't need to associate a signal processor. For the
112
+ second channel, you have a source type of `MessageSignal`, and a sink type of `CountMetric`.
113
+ You would need to add a signal processor that accepts `MessageSignal` (or a superclass),
114
+ and emits `CountMetric`. Your channels would look like:
115
+
116
+ - Source: Logger; Sink: SentrySink
117
+ - Source: Logger; Sink: StatsDCountMetricSink, SignalProcessors: MessageSignalCounter
118
+
119
+ Your signal processor might look like:
120
+
121
+ ```ruby
122
+ class MessageSignalCounter < Mixboard::SignalProcessor
123
+ def initialize(metric_name:)
124
+ @metric_name = metric_name
125
+ end
126
+
127
+ def input_signal_class
128
+ MessageSignal
129
+ end
130
+
131
+ def output_signal_class
132
+ CountMetric
133
+ end
134
+
135
+ def transform(signal)
136
+ return CountMetric.new(@metric_name, 1)
137
+ end
138
+ end
139
+ ```
140
+
141
+ ### Filters
142
+
143
+ Filters interrupt the flow of signals through the system. You can add filters before or
144
+ after channels and signal processors. Here's a simple example:
145
+
146
+ ```ruby
147
+ class DummyFilter < Mixboard::Filter
148
+ def signal_class
149
+ DummySignal
150
+ end
151
+
152
+ def filter(signal)
153
+ signal
154
+ end
155
+ end
156
+ ```
157
+
158
+ Filters' `filter` method should only return the original signal to continue processing, or
159
+ return nil to indicate to stop processing the signal further.
160
+
161
+
162
+ ## Installation
163
+ Add this line to your application's Gemfile:
164
+
165
+ ```ruby
166
+ gem 'mixboard'
167
+ ```
168
+
169
+ Or install it yourself as:
170
+ ```bash
171
+ $ gem install mixboard
172
+ ```
173
+
174
+ To take advantage of the dynamic "mixing panel" to configure channels... TODO
175
+
176
+ ## Contributing
177
+ Contribution directions go here. TODO
178
+
179
+ ## License
180
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require 'bundler/setup'
5
+ rescue LoadError
6
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
7
+ end
8
+
9
+ require 'rdoc/task'
10
+
11
+ RDoc::Task.new(:rdoc) do |rdoc|
12
+ rdoc.rdoc_dir = 'rdoc'
13
+ rdoc.title = 'Mixboard'
14
+ rdoc.options << '--line-numbers'
15
+ rdoc.rdoc_files.include('README.md')
16
+ rdoc.rdoc_files.include('lib/**/*.rb')
17
+ end
18
+
19
+ APP_RAKEFILE = File.expand_path('spec/dummy/Rakefile', __dir__)
20
+ load 'rails/tasks/engine.rake'
21
+
22
+ load 'rails/tasks/statistics.rake'
23
+
24
+ require 'bundler/gem_tasks'
25
+
26
+ begin
27
+ require 'rspec/core/rake_task'
28
+
29
+ RSpec::Core::RakeTask.new(:spec)
30
+
31
+ task default: :spec
32
+ rescue LoadError
33
+ # no rspec available
34
+ end
@@ -0,0 +1 @@
1
+ //= link_directory ../stylesheets/mixboard .css
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Probably will delete this
5
+ class ApplicationController < ActionController::Base
6
+ protect_from_forgery with: :exception
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Probably will delete this
5
+ module ApplicationHelper
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Probably will delete this
5
+ class ApplicationJob < ActiveJob::Base
6
+ end
7
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A specific end-to-end route from a source to a sink.
5
+ class Channel
6
+ include Mixboard::Filterable
7
+ include Mixboard::UtilityFunctions
8
+
9
+ attr_reader :source_class
10
+
11
+ def add_source(source_class)
12
+ assert_non_nil_of_type(source_class, Class)
13
+ @source_class = source_class
14
+ self
15
+ end
16
+
17
+ def add_sink(sink)
18
+ assert_non_nil_of_type(sink, Sink)
19
+ @sink = sink
20
+ self
21
+ end
22
+
23
+ def add_signal_processor(signal_processor)
24
+ assert_non_nil_of_type(signal_processor, SignalProcessor)
25
+ signal_processors.append(signal_processor)
26
+ self
27
+ end
28
+
29
+ def accept(signal)
30
+ signal = with_filters(signal) do
31
+ signal_processors.reduce(signal) do |current_signal, signal_processor|
32
+ signal_processor.do_transform(current_signal)
33
+ end
34
+ end
35
+ @sink.do_accept(signal) unless signal.nil?
36
+ end
37
+
38
+ private
39
+
40
+ def signal_processors
41
+ @signal_processors ||= []
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A mixer's configuration, usually set via an initializer
5
+ class Configuration
6
+ include Mixboard::UtilityFunctions
7
+
8
+ attr_reader :channel_map
9
+
10
+ def add_source(source_class)
11
+ assert_non_nil_of_type(source_class, Class)
12
+ sources.add(source_class)
13
+ self
14
+ end
15
+
16
+ def add_sink(sink)
17
+ assert_non_nil_of_type(sink, Mixboard::Sink)
18
+ sinks.add(sink)
19
+ self
20
+ end
21
+
22
+ def add_channel(channel)
23
+ assert_non_nil_of_type(channel, Channel)
24
+ channels.append(channel)
25
+ self
26
+ end
27
+
28
+ def dynamic_channel_store=(dynamic_channel_store)
29
+ assert_non_nil_of_type(dynamic_channel_store, Mixboard::DynamicChannelStore)
30
+ @dynamic_channel_store = dynamic_channel_store
31
+ end
32
+
33
+ def dynamic_channel_store
34
+ @dynamic_channel_store ||= Mixboard::InMemoryDynamicChannelStore.new
35
+ end
36
+
37
+ def setup_channel_map
38
+ @channel_map = {}
39
+ channels.each do |channel|
40
+ @channel_map[channel.source_class] ||= []
41
+ @channel_map[channel.source_class].append(channel)
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ def sources
48
+ @sources ||= Set.new
49
+ end
50
+
51
+ def sinks
52
+ @sinks ||= Set.new
53
+ end
54
+
55
+ # @return Array[Channel]
56
+ def channels
57
+ @channels ||= []
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A DynamicChannelStore is an interface for a dynamically populated set of channels. The canonical implementation
5
+ # is InMemoryDynamicChannelStore, but you could implement this with another backing store, such as Redis or Memcached.
6
+ class DynamicChannelStore
7
+ include Mixboard::UtilityFunctions
8
+
9
+ def add_channel(_id, _channel, _expiry = nil)
10
+ declare_abstract_method_body
11
+ end
12
+
13
+ def remove_channel_by_id(_id)
14
+ declare_abstract_method_body
15
+ end
16
+
17
+ def channels
18
+ declare_abstract_method_body
19
+ end
20
+
21
+ def active_channel_map
22
+ map = {}
23
+ channels.each do |id, channel, expiry|
24
+ if !expiry.nil? && Time.now.to_i > expiry
25
+ remove_channel_by_id(id)
26
+ next
27
+ end
28
+ map[channel.source_class] ||= []
29
+ map[channel.source_class].append(channel)
30
+ end
31
+ map
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ module Errors
5
+ # An error meant to indicate that a user tried to call a superclass method that the subclass did not implement.
6
+ class AbstractNotImplementedError < StandardError
7
+ def initialize(class_name:, entity_name:)
8
+ super("#{entity_name} not implemented in abstract class #{class_name}")
9
+ @class_name = class_name
10
+ @entity_name = entity_name
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ module Errors
5
+ # An error indicating that a signal was provided with an incorrect type.
6
+ class TypeMismatchError < StandardError
7
+ def initialize(expected_type:, given_type:)
8
+ super("Expected signal of type #{expected_type}, but given signal of type #{given_type}")
9
+ @expected_type = expected_type
10
+ @given_type = given_type
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Filters interrupt the flow of signals through the system. You can add filters before or
5
+ # after channels and signal processors
6
+ class Filter
7
+ include Mixboard::UtilityFunctions
8
+
9
+ # @return [Class] the Signal subclass that this filter applies to
10
+ def signal_class
11
+ declare_abstract_method_body
12
+ end
13
+
14
+ # @param [Signal]
15
+ # @return [Signal,nil] the filtered signal or nil
16
+ def filter(_signal)
17
+ declare_abstract_method_body
18
+ end
19
+
20
+ def do_filter(signal)
21
+ assert_non_nil_of_type(signal, signal_class)
22
+ signal = filter(signal)
23
+ assert_type(signal, signal_class)
24
+ signal
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A mixin for anything that can have a filter added before/after it.
5
+ module Filterable
6
+ include Mixboard::UtilityFunctions
7
+
8
+ def add_before_filter(filter)
9
+ before_filters.append(filter)
10
+ self
11
+ end
12
+
13
+ def add_after_filter(filter)
14
+ after_filters.append(filter)
15
+ self
16
+ end
17
+
18
+ def with_filters(signal, &block)
19
+ signal = run_filters(signal, before_filters)
20
+ return nil if signal.nil?
21
+
22
+ block.call(signal)
23
+ run_filters(signal, after_filters)
24
+ end
25
+
26
+ private
27
+
28
+ def run_filters(signal, filters)
29
+ filters.reduce(signal) do |current_signal, filter|
30
+ return nil if current_signal.nil?
31
+
32
+ assert_non_nil_of_type(current_signal, filter.signal_class)
33
+ filter.do_filter(current_signal)
34
+ end
35
+ end
36
+
37
+ def before_filters
38
+ @before_filters ||= []
39
+ end
40
+
41
+ def after_filters
42
+ @after_filters ||= []
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # An in-memory store of dynamic channel assignments. You probably shouldn't use this because
5
+ # it won't work right with multiple processes. Consider implementing a DynamicChannelStore with
6
+ # a fast key-value store, like Redis or Memcached.
7
+ class InMemoryDynamicChannelStore < Mixboard::DynamicChannelStore
8
+ include Mixboard::UtilityFunctions
9
+
10
+ def add_channel(id, channel, expiry = nil)
11
+ assert_non_nil_of_type(channel, Mixboard::Channel)
12
+ assert_type(expiry, Integer)
13
+ channels_by_id[id] = [channel, expiry]
14
+ end
15
+
16
+ def remove_channel_by_id(id)
17
+ channels_by_id.delete(id)
18
+ end
19
+
20
+ def channels
21
+ channels_by_id.map do |id, tuple|
22
+ [id, tuple[0], tuple[1]]
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def channels_by_id
29
+ @channels_by_id ||= {}
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # The Mixer is a singleton class that actually does the routing of signals.
5
+ class Mixer
6
+ include ::Singleton
7
+ include Mixboard::UtilityFunctions
8
+
9
+ CONFIGURATION_MUTEX = Mutex.new
10
+
11
+ attr_writer :map_refresh_timeout
12
+
13
+ # Accept a signal from a source to route it to the correct channels
14
+ # @param [Signal] signal
15
+ # @param [Source] source
16
+ def accept(signal, source)
17
+ assert_non_nil_of_type(signal, Signal)
18
+ assert_non_nil_of_type(source, Source)
19
+ (channel_map[source.class] || []).each do |channel|
20
+ channel.accept(signal)
21
+ end
22
+ end
23
+
24
+ def configure(&block)
25
+ CONFIGURATION_MUTEX.synchronize do
26
+ return unless @configuration.nil?
27
+
28
+ c = Mixboard::Configuration.new
29
+ block.call(c) if block_given?
30
+ c.setup_channel_map
31
+ @last_map_refresh = 0
32
+ @configuration = c
33
+ end
34
+ end
35
+
36
+ def clear_configuration
37
+ @configuration = nil
38
+ end
39
+
40
+ def map_refresh_timeout
41
+ @map_refresh_timeout ||= 30
42
+ end
43
+
44
+ def dynamic_channel_store
45
+ configuration.dynamic_channel_store
46
+ end
47
+
48
+ private
49
+
50
+ def channel_map
51
+ refresh_channel_map if Time.now.to_i > last_map_refresh + map_refresh_timeout
52
+ @channel_map
53
+ end
54
+
55
+ def refresh_channel_map
56
+ @last_map_refresh = Time.now.to_i
57
+ map = configuration.channel_map.dup
58
+
59
+ configuration.dynamic_channel_store.active_channel_map.each do |source, channels|
60
+ map[source] ||= []
61
+ map[source].concat(channels)
62
+ end
63
+
64
+ @channel_map = map
65
+ end
66
+
67
+ def last_map_refresh
68
+ @last_map_refresh ||= 0
69
+ end
70
+
71
+ def configuration
72
+ raise 'Mixer not configured yet!' if @configuration.nil?
73
+
74
+ @configuration
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A signal is a payload containing information that can be routed through the mixer.
5
+ class Signal
6
+ end
7
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Signal processors accept signals of a certain type and emit signals of a certain type.
5
+ # The are used for transforming signals from one type to another.
6
+ class SignalProcessor
7
+ include Mixboard::Filterable
8
+ include Mixboard::UtilityFunctions
9
+
10
+ # @return [Class] the Signal subclass that this filter applies to
11
+ def input_signal_class
12
+ declare_abstract_method_body
13
+ end
14
+
15
+ # @return [Class] the Signal subclass that this filter applies to
16
+ def output_signal_class
17
+ declare_abstract_method_body
18
+ end
19
+
20
+ # @param [Signal]
21
+ # @return [Signal,nil] the filtered signal or nil
22
+ def transform(_signal)
23
+ declare_abstract_method_body
24
+ end
25
+
26
+ def do_transform(signal)
27
+ assert_non_nil_of_type(signal, input_signal_class)
28
+ signal = with_filters(signal) do
29
+ transform(signal)
30
+ end
31
+ assert_type(signal, output_signal_class)
32
+ signal
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A sink is anything that accepts signals of a certain type.
5
+ class Sink
6
+ include Mixboard::UtilityFunctions
7
+
8
+ def signal_class
9
+ declare_abstract_method_body
10
+ end
11
+
12
+ def accept(_signal)
13
+ declare_abstract_method_body
14
+ end
15
+
16
+ def do_accept(signal)
17
+ assert_non_nil_of_type(signal, signal_class)
18
+ accept(signal)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # A source is anything that emits signals of a certain type.
5
+ class Source
6
+ include Mixboard::UtilityFunctions
7
+
8
+ def signal_class
9
+ declare_abstract_method_body
10
+ end
11
+
12
+ def emit(signal)
13
+ assert_non_nil_of_type(signal, signal_class)
14
+ Mixer.instance.accept(signal, self)
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # Various utility functions
5
+ module UtilityFunctions
6
+ def method_not_implemented_error(name)
7
+ Mixboard::Errors::AbstractNotImplementedError.new(
8
+ class_name: self.class.to_s, entity_name: "method: #{name}"
9
+ )
10
+ end
11
+
12
+ def declare_abstract_method_body
13
+ raise method_not_implemented_error(caller_locations(1, 1)[0].label)
14
+ end
15
+
16
+ def assert_type(obj, typ)
17
+ return unless !obj.nil? && !obj.is_a?(typ)
18
+
19
+ raise Mixboard::Errors::TypeMismatchError.new(expected_type: typ, given_type: obj.class)
20
+ end
21
+
22
+ def assert_non_nil_of_type(obj, typ)
23
+ raise Mixboard::Errors::TypeMismatchError.new(expected_type: typ, given_type: obj.class) if obj.nil?
24
+
25
+ assert_type(obj, typ)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,15 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <title>Mixboard</title>
5
+ <%= csrf_meta_tags %>
6
+ <%= csp_meta_tag %>
7
+
8
+ <%= stylesheet_link_tag "mixboard/application", media: "all" %>
9
+ </head>
10
+ <body>
11
+
12
+ <%= yield %>
13
+
14
+ </body>
15
+ </html>
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ Mixboard::Engine.routes.draw do
4
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'mixboard/engine'
4
+
5
+ # code is in app/mixer/mixboard
6
+ module Mixboard
7
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ # The Rails engine for Mixboard
5
+ class Engine < ::Rails::Engine
6
+ isolate_namespace Mixboard
7
+
8
+ config.generators do |g|
9
+ g.test_framework :rspec
10
+ g.fixture_replacement :factory_bot
11
+ g.factory_bot dir: 'spec/factories'
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mixboard
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+ # desc "Explaining what the task does"
3
+ # task :mixboard do
4
+ # # Task goes here
5
+ # end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mixboard
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Zach McCormick
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2020-11-11 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '5'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: factory_bot_rails
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: sqlite3
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: |
70
+ Mixboard is a framework for routing signals from sources to sinks. Signals can be metrics,
71
+ log messages, errors, or any other similar piece of information one might want to route
72
+ from a source to a sink. Conditional routing is available via filters, and transformation
73
+ of signals is possible via signal processors.
74
+ email:
75
+ - zach.mccormick@braze.com
76
+ executables: []
77
+ extensions: []
78
+ extra_rdoc_files: []
79
+ files:
80
+ - LICENSE.md
81
+ - README.md
82
+ - Rakefile
83
+ - app/assets/config/mixboard_manifest.js
84
+ - app/assets/stylesheets/mixboard/application.css
85
+ - app/controllers/mixboard/application_controller.rb
86
+ - app/helpers/mixboard/application_helper.rb
87
+ - app/jobs/mixboard/application_job.rb
88
+ - app/mixer/mixboard/channel.rb
89
+ - app/mixer/mixboard/configuration.rb
90
+ - app/mixer/mixboard/dynamic_channel_store.rb
91
+ - app/mixer/mixboard/errors/abstract_not_implemented_error.rb
92
+ - app/mixer/mixboard/errors/type_mismatch_error.rb
93
+ - app/mixer/mixboard/filter.rb
94
+ - app/mixer/mixboard/filterable.rb
95
+ - app/mixer/mixboard/in_memory_dynamic_channel_store.rb
96
+ - app/mixer/mixboard/mixer.rb
97
+ - app/mixer/mixboard/signal.rb
98
+ - app/mixer/mixboard/signal_processor.rb
99
+ - app/mixer/mixboard/sink.rb
100
+ - app/mixer/mixboard/source.rb
101
+ - app/mixer/mixboard/utility_functions.rb
102
+ - app/views/layouts/mixboard/application.html.erb
103
+ - config/routes.rb
104
+ - lib/mixboard.rb
105
+ - lib/mixboard/engine.rb
106
+ - lib/mixboard/version.rb
107
+ - lib/tasks/mixboard_tasks.rake
108
+ homepage: https://github.com/braze-inc/mixboard
109
+ licenses:
110
+ - MIT
111
+ metadata: {}
112
+ post_install_message:
113
+ rdoc_options: []
114
+ require_paths:
115
+ - lib
116
+ required_ruby_version: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - ">"
119
+ - !ruby/object:Gem::Version
120
+ version: '2.6'
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ requirements: []
127
+ rubygems_version: 3.0.3
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: Mixboard is a framework for routing signals from sources to sinks.
131
+ test_files: []