pushpop 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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