pushpop 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,50 @@
1
+ require 'erb'
2
+
3
+ module Pushpop
4
+
5
+ class Step
6
+
7
+ TEMPLATES_DIRECTORY = File.expand_path('../../../templates', __FILE__)
8
+
9
+ class ERBContext
10
+ attr_accessor :response
11
+ attr_accessor :step_responses
12
+
13
+ def initialize(response, step_responses)
14
+ self.response = response
15
+ self.step_responses = step_responses
16
+ end
17
+
18
+ def get_binding
19
+ binding
20
+ end
21
+ end
22
+
23
+ attr_accessor :name
24
+ attr_accessor :plugin
25
+ attr_accessor :block
26
+
27
+ def initialize(name=nil, plugin=nil, &block)
28
+ self.name = name || plugin || Pushpop.random_name
29
+ self.plugin = plugin
30
+ self.block = block
31
+ end
32
+
33
+ def template(filename, response, step_responses={}, directory=TEMPLATES_DIRECTORY)
34
+ erb_context = ERBContext.new(response, step_responses)
35
+ ERB.new(get_template_contents(filename, directory)).result(erb_context.get_binding)
36
+ end
37
+
38
+ def run(last_response=nil, step_responses=nil)
39
+ self.instance_exec(last_response, step_responses, &block)
40
+ end
41
+
42
+ private
43
+
44
+ def get_template_contents(filename, directory)
45
+ File.read(File.join(directory, filename))
46
+ end
47
+
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ module Pushpop
2
+ VERSION = '0.1.0'
3
+ end
data/pushpop.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'pushpop/version'
4
+
5
+ Gem::Specification.new do |s|
6
+
7
+ s.name = "pushpop"
8
+ s.version = Pushpop::VERSION
9
+ s.authors = ["Josh Dzielak"]
10
+ s.email = "josh@keen.io"
11
+ s.homepage = "https://github.com/keenlabs/pushpop"
12
+ s.summary = "Automatic delivery of reports and alerts based on Keen IO events"
13
+ s.description = "Pushpop is a simple but powerful Ruby app that sends notifications based on events captured with Keen IO."
14
+
15
+ s.add_dependency "clockwork"
16
+ s.add_dependency "keen"
17
+
18
+ s.files = `git ls-files`.split("\n")
19
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
20
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
21
+ s.require_paths = ["lib"]
22
+ end
@@ -0,0 +1,9 @@
1
+ require 'pushpop'
2
+
3
+ job 'Simple Math' do
4
+ every 1.seconds
5
+ step 'return 10' do 10 end
6
+ step 'increase by 20' do |response|
7
+ 20 + response
8
+ end
9
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pushpop::Keen do
4
+
5
+ describe '#configure' do
6
+
7
+ it 'should set various params' do
8
+
9
+ step = Pushpop::Keen.new do
10
+ event_collection 'pageviews'
11
+ analysis_type 'count'
12
+ timeframe 'last_3_days'
13
+ target_property 'trinkets'
14
+ group_by 'referer'
15
+ interval 'hourly'
16
+ filters [{ property_value: 'referer',
17
+ operator: 'ne',
18
+ property_value: 'yahoo.com' }]
19
+ steps [{ event_collection: 'pageviews',
20
+ actor_property: 'user.id' }]
21
+ analyses [{ analysis_type: 'count' }]
22
+ end
23
+
24
+ step.configure
25
+
26
+ step._event_collection.should == 'pageviews'
27
+ step._analysis_type.should == 'count'
28
+ step._timeframe.should == 'last_3_days'
29
+ step._group_by.should == 'referer'
30
+ step._interval.should == 'hourly'
31
+ step._steps.should == [{
32
+ event_collection: 'pageviews',
33
+ actor_property: 'user.id'
34
+ }]
35
+ step._analyses.should == [{ analysis_type: 'count' }]
36
+ end
37
+
38
+ end
39
+
40
+ describe '#run' do
41
+ it 'should run the query based on the analysis type' do
42
+ Keen.stub(:count).with('pageviews', {
43
+ timeframe: 'last_3_days'
44
+ }).and_return(365)
45
+
46
+ step = Pushpop::Keen.new('one') do
47
+ event_collection 'pageviews'
48
+ analysis_type 'count'
49
+ timeframe 'last_3_days'
50
+ end
51
+ response = step.run
52
+ response.should == 365
53
+ end
54
+ end
55
+
56
+ describe '#to_analysis_options' do
57
+ it 'should include various options' do
58
+ step = Pushpop::Keen.new('one') do end
59
+ step._timeframe = 'last_4_days'
60
+ step._group_by = 'referer'
61
+ step._target_property = 'trinkets'
62
+ step._interval = 'hourly'
63
+ step._filters = [{ property_value: 'referer',
64
+ operator: 'ne',
65
+ property_value: 'yahoo.com' }]
66
+ step._steps = [{ event_collection: 'pageviews',
67
+ actor_property: 'user.id' }]
68
+ step._analyses = [{ analysis_type: 'count' }]
69
+ step.to_analysis_options.should == {
70
+ timeframe: 'last_4_days',
71
+ target_property: 'trinkets',
72
+ group_by: 'referer',
73
+ interval: 'hourly',
74
+ filters: [{ property_value: 'referer',
75
+ operator: 'ne',
76
+ property_value: 'yahoo.com' }],
77
+ steps: [{ event_collection: 'pageviews',
78
+ actor_property: 'user.id' }],
79
+ analyses: [{ analysis_type: 'count' }]
80
+ }
81
+ end
82
+
83
+ it 'should not include nils' do
84
+ step = Pushpop::Keen.new('one') do end
85
+ step.to_analysis_options.should == {}
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,66 @@
1
+ require 'spec_helper'
2
+
3
+ SPEC_TEMPLATES_DIRECTORY ||= File.expand_path('../../templates', __FILE__)
4
+
5
+ describe Pushpop::Sendgrid do
6
+
7
+ describe '#configure' do
8
+
9
+ it 'should set various params' do
10
+
11
+ step = Pushpop::Sendgrid.new do
12
+ to 'josh@keen.io'
13
+ from 'depths@hell.com'
14
+ subject 'time is up'
15
+ body 'use code 3:16 for high leniency'
16
+ preview true
17
+ end
18
+
19
+ step.configure
20
+
21
+ step._to.should == 'josh@keen.io'
22
+ step._from.should == 'depths@hell.com'
23
+ step._subject.should == 'time is up'
24
+ step._body.should == 'use code 3:16 for high leniency'
25
+ step._preview.should be_true
26
+
27
+ end
28
+
29
+ end
30
+
31
+ describe '#run' do
32
+
33
+ it 'should send some email' do
34
+
35
+ Mail.stub(:deliver)
36
+
37
+ step = Pushpop::Sendgrid.new do |response|
38
+ to 'josh@keen.io'
39
+ from 'alerts+pushpop@keen.io'
40
+ subject "There were #{response} Pageviews Today!"
41
+ body 'hey wats up'
42
+ end
43
+
44
+ step.run(365)
45
+
46
+ end
47
+
48
+ end
49
+
50
+ describe '#body' do
51
+
52
+ it 'should use a string if given 1 arg' do
53
+ step = Pushpop::Sendgrid.new
54
+ step.body 'hello world'
55
+ step._body.should == 'hello world'
56
+ end
57
+
58
+ it 'should use a template if more than 1 arg is passed' do
59
+ step = Pushpop::Sendgrid.new
60
+ step.body('spec.html.erb', 500, {}, SPEC_TEMPLATES_DIRECTORY)
61
+ step._body.strip.should == '<pre>500</pre>'
62
+ end
63
+
64
+ end
65
+
66
+ end
@@ -0,0 +1,147 @@
1
+ require 'spec_helper'
2
+
3
+ describe Pushpop::Job do
4
+
5
+ let (:empty_job) { Pushpop::Job.new('foo') do end }
6
+ let (:empty_step) { Pushpop::Step.new('bar') do end }
7
+
8
+ describe '#register_plugins' do
9
+ it 'should register a plugin' do
10
+ Pushpop::Job.register_plugin('blaz', Class)
11
+ Pushpop::Job.plugins['blaz'].should == Class
12
+ end
13
+ end
14
+
15
+ describe '#initialize' do
16
+ it 'should set a name and evaluate a block' do
17
+ block_ran = false
18
+ job = Pushpop::Job.new('foo') do block_ran = true end
19
+ job.name.should == 'foo'
20
+ job.every_duration.should be_nil
21
+ job.every_options.should == {}
22
+ block_ran.should be_true
23
+ end
24
+
25
+ it 'should auto-generate a name' do
26
+ job = Pushpop::Job.new do end
27
+ job.name.should_not be_nil
28
+ end
29
+ end
30
+
31
+ describe '#every' do
32
+ it 'should set duration and options' do
33
+ job = empty_job
34
+ job.every(10.seconds, at: '01:02')
35
+ job.every_duration.should == 10
36
+ job.every_options.should == { at: '01:02' }
37
+ end
38
+ end
39
+
40
+ describe '#step' do
41
+ it 'should add the step to the internal list of steps' do
42
+ empty_proc = Proc.new {}
43
+ job = empty_job
44
+ job.step('blah', &empty_proc)
45
+ job.steps.first.name.should == 'blah'
46
+ job.steps.first.block.should == empty_proc
47
+ end
48
+
49
+ context 'plugin specified' do
50
+ class FakeStep < Pushpop::Step
51
+ end
52
+
53
+ before do
54
+ Pushpop::Job.register_plugin('blaz', FakeStep)
55
+ end
56
+
57
+ it 'should use the registered plugin to instantiate the class' do
58
+ empty_proc = Proc.new {}
59
+ job = empty_job
60
+ job.step('blah', 'blaz', &empty_proc)
61
+ job.steps.first.name.should == 'blah'
62
+ job.steps.first.plugin.should == 'blaz'
63
+ job.steps.first.class.should == FakeStep
64
+ job.steps.first.block.should == empty_proc
65
+ end
66
+
67
+ it 'should throw an exception for an unregistered plugin' do
68
+ empty_proc = Proc.new {}
69
+ job = empty_job
70
+ expect {
71
+ job.step('blah', 'blaze', &empty_proc)
72
+ }.to raise_error /No plugin configured/
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#run' do
78
+ it 'should call each step with the response to the previous' do
79
+ job = Pushpop::Job.new('foo') do
80
+ step 'one' do
81
+ 10
82
+ end
83
+
84
+ step 'two' do |response|
85
+ response + 20
86
+ end
87
+ end
88
+ job.run.should == [30, { 'one' => 10, 'two' => 30 }]
89
+ end
90
+ end
91
+
92
+ describe '#schedule' do
93
+ it 'should add the job to clockwork' do
94
+ frequency = 1.seconds
95
+ simple_job = Pushpop::Job.new('foo') do
96
+ every frequency
97
+ step 'track_times_run' do
98
+ @times_run ||= 0
99
+ @times_run += 1
100
+ end
101
+ end
102
+
103
+ simple_job.schedule
104
+
105
+ Clockwork.manager.tick(Time.now)
106
+ simple_job.run.first.should == 2
107
+ Clockwork.manager.tick(Time.now + frequency)
108
+ simple_job.run.first.should == 4
109
+ end
110
+ end
111
+
112
+ describe '#method_missing' do
113
+ class FakeStep < Pushpop::Step
114
+ end
115
+
116
+ before do
117
+ end
118
+
119
+ it 'should assume its a registered plugin name and try to create a step' do
120
+ Pushpop::Job.register_plugin('blaz', FakeStep)
121
+ simple_job = job do
122
+ blaz 'hi' do end
123
+ end
124
+ simple_job.steps.first.name.should == 'hi'
125
+ simple_job.steps.first.class.should == FakeStep
126
+ end
127
+
128
+ it 'should not assume a name' do
129
+ Pushpop::Job.register_plugin('blaz', FakeStep)
130
+ simple_job = job do
131
+ blaz do end
132
+ end
133
+ simple_job.steps.first.name.should_not be_nil
134
+ simple_job.steps.first.plugin.should == 'blaz'
135
+ simple_job.steps.first.class.should == FakeStep
136
+ end
137
+
138
+ it 'should raise an exception if there is no registered plugin' do
139
+ expect {
140
+ job do
141
+ blaze do end
142
+ end
143
+ }.to raise_error /undefined method/
144
+ end
145
+ end
146
+
147
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+
3
+ SPEC_TEMPLATES_DIRECTORY ||= File.expand_path('../../templates', __FILE__)
4
+
5
+ describe Pushpop::Step do
6
+
7
+ describe 'initialize' do
8
+
9
+ it 'should set a name, a plugin, and a block' do
10
+ empty_proc = Proc.new {}
11
+ step = Pushpop::Step.new('foo', 'foopie', &empty_proc)
12
+ step.name.should == 'foo'
13
+ step.plugin.should == 'foopie'
14
+ step.block.should == empty_proc
15
+ end
16
+
17
+ it 'should auto-generate a name if not given and plugin not given' do
18
+ empty_proc = Proc.new {}
19
+ step = Pushpop::Step.new(&empty_proc)
20
+ step.name.should_not be_nil
21
+ step.plugin.should be_nil
22
+ step.block.should == empty_proc
23
+ end
24
+
25
+ it 'should set name to plugin name if not given' do
26
+ empty_proc = Proc.new {}
27
+ step = Pushpop::Step.new(nil, 'keen', &empty_proc)
28
+ step.name.should == 'keen'
29
+ step.plugin.should == 'keen'
30
+ step.block.should == empty_proc
31
+ end
32
+
33
+ it 'should not require a plugin' do
34
+ empty_proc = Proc.new {}
35
+ step = Pushpop::Step.new('foo', &empty_proc)
36
+ step.name.should == 'foo'
37
+ step.block.should == empty_proc
38
+ end
39
+
40
+ end
41
+
42
+ describe 'run' do
43
+
44
+ it 'should call the block with the same args' do
45
+ arg1, arg2 = nil
46
+ times_run = 0
47
+ empty_proc = Proc.new { |a1, a2| arg1 = a1; arg2 = a2; times_run += 1 }
48
+ step = Pushpop::Step.new('foo', &empty_proc)
49
+ step.run('foo', 'bar')
50
+ arg1.should == 'foo'
51
+ arg2.should == 'bar'
52
+ times_run.should == 1
53
+ end
54
+
55
+ it 'should execute the block bound to the step' do
56
+ _self = nil
57
+ step = Pushpop::Step.new(nil, nil) do
58
+ _self = self
59
+ end
60
+ step.run
61
+ _self.should == step
62
+ end
63
+
64
+ end
65
+
66
+ describe 'template' do
67
+ it 'should render the named template with the response binding' do
68
+ step = Pushpop::Step.new
69
+ step.template('spec.html.erb', 500, {}, SPEC_TEMPLATES_DIRECTORY).strip.should == '<pre>500</pre>'
70
+ end
71
+
72
+ it 'should render the named template with the step_response binding' do
73
+ step = Pushpop::Step.new
74
+ step.template('spec.html.erb', nil, { test: 600 }, SPEC_TEMPLATES_DIRECTORY).strip.should == '<pre>600</pre>'
75
+ end
76
+ end
77
+
78
+ end