time_pilot 0.0.4 → 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.
- checksums.yaml +4 -4
- data/README.md +19 -1
- data/lib/time_pilot/configuration.rb +2 -4
- data/lib/time_pilot/time_pilot.rb +41 -17
- data/lib/time_pilot/version.rb +1 -1
- data/lib/time_pilot/web.rb +74 -0
- data/myapp/Gemfile +2 -0
- data/myapp/simple.ru +42 -0
- data/myapp/start.sh +2 -0
- data/test/time_pilot_test.rb +15 -0
- data/web/index.html.erb +47 -0
- metadata +8 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8265534aac5fa5aaa1b563c34c8f1f18c6085c00
|
4
|
+
data.tar.gz: 3a4bf6338d822b708b9c85f26c462a827ab6cba2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
10
|
+
def feature(feature_name)
|
12
11
|
@features.push(feature_name)
|
13
12
|
end
|
14
13
|
|
15
|
-
def redis
|
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
|
8
|
-
|
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
|
-
|
14
|
-
|
15
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
44
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
86
|
+
end.include? true
|
63
87
|
end
|
64
88
|
|
65
89
|
end
|
data/lib/time_pilot/version.rb
CHANGED
@@ -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
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
data/test/time_pilot_test.rb
CHANGED
@@ -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
|
data/web/index.html.erb
ADDED
@@ -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
|
+
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-
|
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.
|
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:
|