time_pilot 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fbeca7afffcc0db740f30a3337ad4fc3b0841586
4
- data.tar.gz: 22c08aef2488cdcdc7b85d2fb7a79c140ea3139a
3
+ metadata.gz: 8265534aac5fa5aaa1b563c34c8f1f18c6085c00
4
+ data.tar.gz: 3a4bf6338d822b708b9c85f26c462a827ab6cba2
5
5
  SHA512:
6
- metadata.gz: 374d7cf4de3ea502124796c2f45a78f2341e7d1a875683b5f0e94c12905d5dc7395f4c08a9bdf5e9547fc3bf158e196179fe899c8b48e9808d0455c12c1200cf
7
- data.tar.gz: 4307f16f7c2d6a361405b6b2b2334bd168fcc056d8f6755a5ed918120769d0bac8739af1fe7cb2c68db9c888ffada1ae036df5a82f61d92a46922040bbbd3689
6
+ metadata.gz: 757a1a9f389f4ee6db56a2aa14936ebd5a7baf29068f5c6c6db7b31c924e701f03473567fee938282acf4decf05dc6dbb179daa6fe4c963163b2c3ccac318fdb
7
+ data.tar.gz: 7d54911d42b93a522aeeb27846017dbf12588d8dc2f4a7c3d0f08cffdada7b3b26bfd92a5dca8b8963571cbc3a25c46376582284039ca196e245b4bcf697d64a
data/README.md CHANGED
@@ -79,6 +79,8 @@ TimePilot.configure do |c|
79
79
  end
80
80
  ```
81
81
 
82
+ TimePilot assumes you put in an object that responds to `#sadd`, `#srem`, `#sismember` and `#scard`.
83
+
82
84
  ## Multiple configure blocks
83
85
 
84
86
  TimePilot allows you to specify more than one configure block. This allows you to specify features at multiple levels. For example, you could specify features in a Rails Engine located in a separate gem, as well as the host application. It's easy as this:
@@ -95,7 +97,23 @@ TimePilot.configure do |c|
95
97
  end
96
98
  ```
97
99
 
98
- TimePilot assumes you put in an object that responds to `#sadd`, `#srem` and `#sismember`.
100
+ ## TimePilot Web
101
+
102
+ TimePilot provides a dashboard that is mountable inside a Rails app.
103
+ ```ruby
104
+ # in config/initializers/time_pilot.rb
105
+ require 'time_pilot/web'
106
+ TimePilot::Web.use(Rack::Auth::Basic) do |user, password|
107
+ username == ENV["TIME_PILOT_USERNAME"] && password == ENV["TIME_PILOT_PASSWORD"]
108
+ end
109
+
110
+ # in routes.rb
111
+ Application.routes.draw do
112
+ mount TimePilot::Web => '/time_pilot'
113
+ end
114
+ ```
115
+
116
+ `TimePilot::Web` is just a Rack app. You can use it outside Rails.
99
117
 
100
118
  # Usage
101
119
 
@@ -1,5 +1,4 @@
1
1
  module TimePilot
2
-
3
2
  class Configuration
4
3
  attr_reader :features, :redis_store
5
4
 
@@ -8,13 +7,12 @@ module TimePilot
8
7
  @redis_store = Redis.new
9
8
  end
10
9
 
11
- def feature feature_name
10
+ def feature(feature_name)
12
11
  @features.push(feature_name)
13
12
  end
14
13
 
15
- def redis redis_store
14
+ def redis(redis_store)
16
15
  @redis_store = redis_store
17
16
  end
18
17
  end
19
-
20
18
  end
@@ -1,22 +1,36 @@
1
1
  module TimePilot
2
2
  NAMESPACE = 'timepilot'
3
+ @group_classes = []
4
+ @mutex = Mutex.new
5
+
6
+ def self.register_class(klass)
7
+ @mutex.synchronize do
8
+ @group_classes << klass
9
+ end
10
+ end
11
+
12
+ class << self
13
+ attr_reader :group_classes
14
+ end
3
15
 
4
16
  def self.configure
5
17
  @config ||= Configuration.new
6
18
  yield @config
7
- @config.features.each do |feature_name|
8
- Features.module_eval do
9
- define_method "enable_#{feature_name}" do
10
- pilot_enable_feature(feature_name)
11
- end
19
+ @config.features.each { |f| define_feature_method(f) }
20
+ end
12
21
 
13
- define_method "disable_#{feature_name}" do
14
- pilot_disable_feature(feature_name)
15
- end
22
+ def self.define_feature_method(feature_name)
23
+ Features.module_eval do
24
+ define_method "enable_#{feature_name}" do
25
+ pilot_enable_feature(feature_name)
26
+ end
16
27
 
17
- define_method "#{feature_name}_enabled?" do
18
- pilot_feature_enabled?(feature_name)
19
- end
28
+ define_method "disable_#{feature_name}" do
29
+ pilot_disable_feature(feature_name)
30
+ end
31
+
32
+ define_method "#{feature_name}_enabled?" do
33
+ pilot_feature_enabled?(feature_name)
20
34
  end
21
35
  end
22
36
  end
@@ -33,6 +47,7 @@ module TimePilot
33
47
  "#{NAMESPACE}:#{name}"
34
48
  end
35
49
 
50
+ # Including this module makes a class a TimePilot class
36
51
  module Features
37
52
  def self.included(base)
38
53
  base.send :extend, ClassMethods
@@ -40,26 +55,35 @@ module TimePilot
40
55
 
41
56
  module ClassMethods
42
57
  attr_reader :time_pilot_groups
43
- def is_pilot_group options={}
44
- @time_pilot_groups = Array(options[:overridden_by]).map { |e| e.to_s } + [self.to_s.underscore]
58
+ def is_pilot_group(options = {})
59
+ TimePilot.register_class(self)
60
+ @time_pilot_groups = Array(options[:overridden_by]).map(&:to_s) +
61
+ [to_s.underscore]
62
+ end
63
+
64
+ def pilot_feature_cardinality(feature_name)
65
+ key_name = "#{feature_name}:#{to_s.underscore}_ids"
66
+ TimePilot.redis.scard TimePilot.key(key_name)
45
67
  end
46
68
  end
47
69
 
48
70
  def pilot_enable_feature(feature_name)
49
- TimePilot.redis.sadd TimePilot.key("#{feature_name}:#{self.class.to_s.underscore}_ids"), id
71
+ key_name = "#{feature_name}:#{self.class.to_s.underscore}_ids"
72
+ TimePilot.redis.sadd TimePilot.key(key_name), id
50
73
  end
51
74
 
52
75
  def pilot_disable_feature(feature_name)
53
- TimePilot.redis.srem TimePilot.key("#{feature_name}:#{self.class.to_s.underscore}_ids"), id
76
+ key_name = "#{feature_name}:#{self.class.to_s.underscore}_ids"
77
+ TimePilot.redis.srem TimePilot.key(key_name), id
54
78
  end
55
79
 
56
80
  def pilot_feature_enabled?(feature_name)
57
- TimePilot.redis.pipelined {
81
+ TimePilot.redis.pipelined do
58
82
  self.class.time_pilot_groups.each do |group|
59
83
  method = group.to_s == self.class.to_s.underscore ? 'id' : group + '_id'
60
84
  TimePilot.redis.sismember TimePilot.key("#{feature_name}:#{group}_ids"), send(method)
61
85
  end
62
- }.include? true
86
+ end.include? true
63
87
  end
64
88
 
65
89
  end
@@ -1,3 +1,3 @@
1
1
  module TimePilot
2
- VERSION = '0.0.4'
2
+ VERSION = '0.1.0'
3
3
  end
@@ -0,0 +1,74 @@
1
+ require 'erb'
2
+
3
+ module TimePilot
4
+ # Rack application displaying member counts
5
+ class Web
6
+ def call(env)
7
+ dup.call!(env)
8
+ end
9
+
10
+ def call!(env)
11
+ req = Rack::Request.new(env)
12
+ case req.path_info
13
+ when '/'
14
+ dashboard
15
+ else
16
+ not_found
17
+ end
18
+ end
19
+
20
+ # TODO: use Redis pipelining
21
+ def dashboard
22
+ @total_count = TimePilot.features.map do |feature_name|
23
+ counts = TimePilot.group_classes.map do |klass|
24
+ [klass.to_s, klass.pilot_feature_cardinality(feature_name)]
25
+ end.to_h
26
+ [feature_name, counts]
27
+ end.to_h
28
+ erb :index
29
+ end
30
+
31
+ def not_found
32
+ [404, { 'Content-Type' => 'text/html' }, ['Not Found']]
33
+ end
34
+
35
+ def erb(view)
36
+ [
37
+ 200,
38
+ { 'Content-Type' => 'text/html' },
39
+ [ERB.new(read_template(view)).result(binding)]
40
+ ]
41
+ end
42
+
43
+ def read_template(view)
44
+ File.read(File.join(__dir__, '..', '..', 'web', "#{view}.html.erb"))
45
+ end
46
+
47
+ class << self
48
+ def app
49
+ @app ||= instance
50
+ end
51
+
52
+ def call(env)
53
+ app.call(env)
54
+ end
55
+
56
+ def use(middleware, *args, &block)
57
+ @app = nil
58
+ @middleware << [middleware, args, block]
59
+ end
60
+
61
+ def instance
62
+ builder = Rack::Builder.new
63
+ @middleware.each { |c, a, b| builder.use(c, *a, &b) }
64
+ builder.run new
65
+ builder.to_app
66
+ end
67
+
68
+ def reset!
69
+ @middleware = []
70
+ end
71
+ end
72
+ reset!
73
+ end
74
+ end
data/myapp/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ gem 'rack'
2
+ gem 'time_pilot', path: '..'
data/myapp/simple.ru ADDED
@@ -0,0 +1,42 @@
1
+ require 'time_pilot'
2
+
3
+ TimePilot.configure do |c|
4
+ c.feature :planning
5
+ c.feature :private_messaging
6
+ end
7
+
8
+ class Team
9
+ attr_accessor :id
10
+ include TimePilot::Features
11
+ is_pilot_group
12
+ end
13
+
14
+ class Employee
15
+ attr_accessor :id, :team_id
16
+ include TimePilot::Features
17
+ is_pilot_group overridden_by: [:team]
18
+ end
19
+
20
+ healthcare = Team.new
21
+ healthcare.id = 1
22
+ healthcare.enable_planning
23
+
24
+ john = Employee.new
25
+ john.id = 1
26
+ john.team_id = healthcare.id
27
+ john.enable_planning
28
+ john.enable_private_messaging
29
+
30
+ jane = Employee.new
31
+ jane.id = 2
32
+ jane.team_id = healthcare.id
33
+ jane.enable_planning
34
+ jane.disable_planning
35
+ jane.enable_private_messaging
36
+
37
+ require 'time_pilot/web'
38
+ TimePilot::Web.use(Rack::Auth::Basic) do
39
+ true
40
+ end
41
+
42
+ run TimePilot::Web
data/myapp/start.sh ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env sh
2
+ rerun -d '..' 'rackup simple.ru'
@@ -107,6 +107,21 @@ describe TimePilot do
107
107
  @jane.planning_enabled?.must_equal false
108
108
  end
109
109
 
110
+ it 'defines a cardinality count on the classes' do
111
+ @nedap.enable_planning
112
+ @john.enable_planning
113
+ @jane.enable_planning
114
+ Company.pilot_feature_cardinality(:planning).must_equal 1
115
+ Team.pilot_feature_cardinality(:planning).must_equal 0
116
+ Employee.pilot_feature_cardinality(:planning).must_equal 2
117
+ end
118
+
119
+ it 'registers classes that include TimePilot::Features' do
120
+ TimePilot.group_classes.must_include Company
121
+ TimePilot.group_classes.must_include Team
122
+ TimePilot.group_classes.must_include Employee
123
+ end
124
+
110
125
  specify 'company overrides team' do
111
126
  @nedap.enable_planning
112
127
  @retail.planning_enabled?.must_equal true
@@ -0,0 +1,47 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="utf8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Time Pilot Web</title>
7
+ <link rel="stylesheet" href="http://yui.yahooapis.com/pure/0.6.0/pure-min.css">
8
+ <style type="text/css">
9
+ h1 {
10
+ text-align: center;
11
+ }
12
+ body {
13
+ font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
14
+ font-weight: 300;
15
+ }
16
+ .centered {
17
+ margin-left: auto;
18
+ margin-right: auto;
19
+ }
20
+ </style>
21
+ </head>
22
+ <body>
23
+ <div class="pure-g-r">
24
+ <div class="pure-u-1-1">
25
+ <h1>Time Pilot Web</h1>
26
+ <table class="pure-table pure-table-horizontal pure-table-striped centered">
27
+ <thead>
28
+ <tr>
29
+ <th>Feature</th><th>Group</th><th>Count</th>
30
+ </tr>
31
+ </thead>
32
+ <tbody>
33
+ <% @total_count.each do |feature, groups| %>
34
+ <% groups.each do |group, count| %>
35
+ <tr>
36
+ <td><%= feature %></td>
37
+ <td><%= group %></td>
38
+ <td><%= count %></td>
39
+ </tr>
40
+ <% end %>
41
+ <% end %>
42
+ </tbody>
43
+ </table>
44
+ </div>
45
+ </div>
46
+ </body>
47
+ </html>
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: time_pilot
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - "@mlangenberg"
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-01-13 00:00:00.000000000 Z
12
+ date: 2015-07-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
@@ -72,8 +72,13 @@ files:
72
72
  - lib/time_pilot/configuration.rb
73
73
  - lib/time_pilot/time_pilot.rb
74
74
  - lib/time_pilot/version.rb
75
+ - lib/time_pilot/web.rb
76
+ - myapp/Gemfile
77
+ - myapp/simple.ru
78
+ - myapp/start.sh
75
79
  - test/time_pilot_test.rb
76
80
  - time_pilot.gemspec
81
+ - web/index.html.erb
77
82
  homepage: https://github.com/nedap/time_pilot
78
83
  licenses:
79
84
  - MIT
@@ -94,9 +99,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
94
99
  version: '0'
95
100
  requirements: []
96
101
  rubyforge_project:
97
- rubygems_version: 2.2.2
102
+ rubygems_version: 2.4.6
98
103
  signing_key:
99
104
  specification_version: 4
100
105
  summary: Configure enabled features for a specific set of users.
101
106
  test_files: []
102
- has_rdoc: