flipper-ui 0.2.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +19 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +17 -0
  5. data/Guardfile +26 -0
  6. data/LICENSE +22 -0
  7. data/README.md +101 -0
  8. data/Rakefile +7 -0
  9. data/examples/basic.ru +44 -0
  10. data/examples/flipper.html +14 -0
  11. data/examples/flipper.png +0 -0
  12. data/flipper-ui.gemspec +21 -0
  13. data/lib/flipper-ui.rb +1 -0
  14. data/lib/flipper/ui.rb +23 -0
  15. data/lib/flipper/ui/action.rb +172 -0
  16. data/lib/flipper/ui/action_collection.rb +20 -0
  17. data/lib/flipper/ui/actions/features.rb +21 -0
  18. data/lib/flipper/ui/actions/file.rb +17 -0
  19. data/lib/flipper/ui/actions/gate.rb +143 -0
  20. data/lib/flipper/ui/actions/index.rb +17 -0
  21. data/lib/flipper/ui/assets/javascripts/application.coffee +305 -0
  22. data/lib/flipper/ui/assets/javascripts/spine/ajax.coffee +223 -0
  23. data/lib/flipper/ui/assets/javascripts/spine/list.coffee +43 -0
  24. data/lib/flipper/ui/assets/javascripts/spine/local.coffee +16 -0
  25. data/lib/flipper/ui/assets/javascripts/spine/manager.coffee +83 -0
  26. data/lib/flipper/ui/assets/javascripts/spine/relation.coffee +148 -0
  27. data/lib/flipper/ui/assets/javascripts/spine/route.coffee +146 -0
  28. data/lib/flipper/ui/assets/javascripts/spine/spine.coffee +542 -0
  29. data/lib/flipper/ui/assets/javascripts/spine/version +1 -0
  30. data/lib/flipper/ui/assets/stylesheets/application.scss +237 -0
  31. data/lib/flipper/ui/decorators/feature.rb +37 -0
  32. data/lib/flipper/ui/decorators/gate.rb +36 -0
  33. data/lib/flipper/ui/error.rb +10 -0
  34. data/lib/flipper/ui/eruby.rb +11 -0
  35. data/lib/flipper/ui/middleware.rb +66 -0
  36. data/lib/flipper/ui/public/css/application.css +183 -0
  37. data/lib/flipper/ui/public/css/images/animated-overlay.gif +0 -0
  38. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_18_b81900_40x40.png +0 -0
  39. data/lib/flipper/ui/public/css/images/ui-bg_diagonals-thick_20_666666_40x40.png +0 -0
  40. data/lib/flipper/ui/public/css/images/ui-bg_flat_10_000000_40x100.png +0 -0
  41. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_f6f6f6_1x400.png +0 -0
  42. data/lib/flipper/ui/public/css/images/ui-bg_glass_100_fdf5ce_1x400.png +0 -0
  43. data/lib/flipper/ui/public/css/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  44. data/lib/flipper/ui/public/css/images/ui-bg_gloss-wave_35_f6a828_500x100.png +0 -0
  45. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_100_eeeeee_1x100.png +0 -0
  46. data/lib/flipper/ui/public/css/images/ui-bg_highlight-soft_75_ffe45c_1x100.png +0 -0
  47. data/lib/flipper/ui/public/css/images/ui-icons_222222_256x240.png +0 -0
  48. data/lib/flipper/ui/public/css/images/ui-icons_228ef1_256x240.png +0 -0
  49. data/lib/flipper/ui/public/css/images/ui-icons_ef8c08_256x240.png +0 -0
  50. data/lib/flipper/ui/public/css/images/ui-icons_ffd27a_256x240.png +0 -0
  51. data/lib/flipper/ui/public/css/images/ui-icons_ffffff_256x240.png +0 -0
  52. data/lib/flipper/ui/public/css/jquery-ui-1.10.3.slider.min.css +5 -0
  53. data/lib/flipper/ui/public/images/logo.png +0 -0
  54. data/lib/flipper/ui/public/images/remove.png +0 -0
  55. data/lib/flipper/ui/public/js/application.js +544 -0
  56. data/lib/flipper/ui/public/js/handlebars.js +1992 -0
  57. data/lib/flipper/ui/public/js/jquery-ui-1.10.3.slider.min.js +6 -0
  58. data/lib/flipper/ui/public/js/jquery.js +9555 -0
  59. data/lib/flipper/ui/public/js/jquery.min.js +4 -0
  60. data/lib/flipper/ui/public/js/jquery.min.map +1 -0
  61. data/lib/flipper/ui/public/js/spine/ajax.js +320 -0
  62. data/lib/flipper/ui/public/js/spine/list.js +72 -0
  63. data/lib/flipper/ui/public/js/spine/local.js +29 -0
  64. data/lib/flipper/ui/public/js/spine/manager.js +157 -0
  65. data/lib/flipper/ui/public/js/spine/relation.js +260 -0
  66. data/lib/flipper/ui/public/js/spine/route.js +223 -0
  67. data/lib/flipper/ui/public/js/spine/spine.js +927 -0
  68. data/lib/flipper/ui/util.rb +12 -0
  69. data/lib/flipper/ui/version.rb +5 -0
  70. data/lib/flipper/ui/views/index.erb +9 -0
  71. data/lib/flipper/ui/views/layout.erb +161 -0
  72. data/script/bootstrap +21 -0
  73. data/script/server +19 -0
  74. data/script/test +30 -0
  75. data/spec/flipper/ui/decorators/feature_spec.rb +59 -0
  76. data/spec/flipper/ui/decorators/gate_spec.rb +47 -0
  77. data/spec/flipper/ui/util_spec.rb +18 -0
  78. data/spec/flipper/ui_spec.rb +470 -0
  79. data/spec/helper.rb +35 -0
  80. metadata +168 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8e9d451a699e2b1acd1697ffea82cace549570b2
4
+ data.tar.gz: 012aa58d86f51d10b433a48ad0a7503d601b948d
5
+ SHA512:
6
+ metadata.gz: a2911695881ddf0c229fbf06bd1249e0f6439d0703c8783c702bbf7bf4715070ed48b61b902ef9fc4c6d97f0d17ebea4ec7172048f1ca586c0a7ddeb89623d47
7
+ data.tar.gz: 5d110e6a66dac9408c36ae2a3222598893a57254e88e11cddce3977dadb70f10f74139464ef76340623973354ccc0172ba9e9a4a3003abbe9ed69ffe0b27ca01
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ .sass-cache
19
+ log
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'shotgun'
6
+ gem 'rspec'
7
+ gem 'rack-test'
8
+ gem 'activesupport'
9
+
10
+ group(:guard) do
11
+ gem 'guard'
12
+ gem 'guard-rspec'
13
+ gem 'guard-bundler'
14
+ gem 'guard-coffeescript'
15
+ gem 'guard-sass'
16
+ gem 'rb-fsevent'
17
+ end
data/Guardfile ADDED
@@ -0,0 +1,26 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'bundler' do
5
+ watch('Gemfile')
6
+ watch(/^.+\.gemspec/)
7
+ end
8
+
9
+ guard 'rspec' do
10
+ watch(%r{^spec/.+_spec\.rb$}) { "spec" }
11
+ watch(%r{^lib/(.+)\.rb$}) { "spec" }
12
+ watch('spec/helper.rb') { "spec" }
13
+ end
14
+
15
+ coffee_options = {
16
+ :input => 'lib/flipper/ui/assets/javascripts',
17
+ :output => 'lib/flipper/ui/public/js',
18
+ :all_on_start => false,
19
+ }
20
+ guard 'coffeescript', coffee_options
21
+
22
+ sass_options = {
23
+ :input => 'lib/flipper/ui/assets/stylesheets',
24
+ :output => 'lib/flipper/ui/public/css',
25
+ }
26
+ guard 'sass', sass_options
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 John Nunemaker
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,101 @@
1
+ # Flipper::UI
2
+
3
+ UI for the [Flipper](https://github.com/jnunemaker/flipper) gem. __Note__: This is not fully functional yet. The end product will look like this:
4
+
5
+ ![flipper web](./examples/flipper.png)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'flipper-ui'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install flipper-ui
20
+
21
+ ## Usage
22
+
23
+ ### Rails
24
+
25
+ Given that you've already initialized `Flipper` as per the [flipper](https://github.com/jnunemaker/flipper) readme:
26
+
27
+ ```ruby
28
+ # config/initializers/flipper.rb
29
+ $flipper = Flipper.new(...)
30
+ ```
31
+
32
+ you can mount `Flipper::UI` to a route of your choice:
33
+ ```ruby
34
+ # config/routes.rb
35
+
36
+ YourRailsApp::Application.routes.draw do
37
+ mount Flipper::UI.app($flipper) => '/flipper'
38
+ end
39
+ ```
40
+
41
+ #### Security
42
+
43
+ You almost certainly want to limit access when using Flipper::UI in production. Using [routes constraints](http://guides.rubyonrails.org/routing.html#request-based-constraints) is one way to achieve this:
44
+
45
+ ```ruby
46
+ # config/routes.rb
47
+
48
+ flipper_constraint = lambda { |request| request.remote_ip == '127.0.0.1' }
49
+ constraints flipper_constraint do
50
+ mount Flipper::UI.app($flipper) => '/flipper'
51
+ end
52
+ ```
53
+
54
+ Another example of a route constrain using the current_user when using Devise or another warden based authentication system:
55
+
56
+ ```ruby
57
+ # initializers/admin_access.rb
58
+
59
+ class CanAccessFlipperUI
60
+ def self.matches?(request)
61
+ current_user = request.env['warden'].user
62
+
63
+ return current_user.present? && current_user.respond_to?(:is_admin?) && current_user.is_admin?
64
+ end
65
+ end
66
+
67
+ # config/routes.rb
68
+
69
+ constraints CanAccessFlipperUI do
70
+ mount Flipper::UI.app($flipper) => '/flipper'
71
+ end
72
+ ```
73
+
74
+
75
+ ### Standalone
76
+
77
+ Minimal example for Rack:
78
+
79
+ ```ruby
80
+ # config.ru
81
+
82
+ require 'flipper-ui'
83
+ require 'flipper/adapters/memory'
84
+
85
+ adapter = Flipper::Adapters::Memory.new
86
+ flipper = Flipper.new(adapter)
87
+
88
+ run Flipper::UI.app(flipper)
89
+ ```
90
+
91
+ See [examples/basic.ru](https://github.com/jnunemaker/flipper-ui/blob/master/examples/basic.ru) for a more full example
92
+
93
+ ## Contributing
94
+
95
+ 1. Fork it
96
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
97
+ 3. **Fire up the app** (`script/server`)
98
+ 4. **Start up guard** (`bundle exec guard` for automatic coffeescript/sass compilation and such).
99
+ 5. Commit your changes (`git commit -am 'Added some feature'`)
100
+ 6. Push to the branch (`git push origin my-new-feature`)
101
+ 7. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new
6
+
7
+ task :default => :spec
data/examples/basic.ru ADDED
@@ -0,0 +1,44 @@
1
+ #
2
+ # Usage:
3
+ # bundle exec rackup examples/basic.ru
4
+ # http://localhost:9292/
5
+ #
6
+ require 'pp'
7
+ require 'logger'
8
+ require 'pathname'
9
+
10
+ root_path = Pathname(__FILE__).dirname.join('..').expand_path
11
+ lib_path = root_path.join('lib')
12
+ $:.unshift(lib_path)
13
+
14
+ require 'flipper-ui'
15
+ require 'flipper/adapters/memory'
16
+
17
+ Flipper.register(:admins) { |actor|
18
+ actor.respond_to?(:admin?) && actor.admin?
19
+ }
20
+
21
+ Flipper.register(:early_access) { |actor|
22
+ actor.respond_to?(:early?) && actor.early?
23
+ }
24
+
25
+ # Setup logging of flipper calls.
26
+ require 'flipper/instrumentation/log_subscriber'
27
+ Flipper::Instrumentation::LogSubscriber.logger = Logger.new(STDOUT)
28
+
29
+ adapter = Flipper::Adapters::Memory.new({})
30
+ flipper = Flipper.new(adapter, :instrumenter => ActiveSupport::Notifications)
31
+
32
+ Actor = Struct.new(:flipper_id)
33
+
34
+ flipper[:search_performance_another_long_thing].enable
35
+ flipper[:gauges_tracking].enable
36
+ flipper[:unused].disable
37
+ flipper[:suits].enable Actor.new('1')
38
+ flipper[:suits].enable Actor.new('6')
39
+ flipper[:secrets].enable flipper.group(:admins)
40
+ flipper[:secrets].enable flipper.group(:early_access)
41
+ flipper[:logging].enable flipper.random(5)
42
+ flipper[:new_cache].enable flipper.actors(15)
43
+
44
+ run Flipper::UI.app(flipper)
@@ -0,0 +1,14 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <title>Flipper</title>
5
+ <link rel="stylesheet" type="text/css" href="/css/application.css">
6
+ <style type="text/css">
7
+ * { margin:0; padding:0; }
8
+ body {text-align:center;}
9
+ </style>
10
+ </head>
11
+ <body>
12
+ <img src="flipper.png" />
13
+ </body>
14
+ </html>
Binary file
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/flipper/ui/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["John Nunemaker"]
6
+ gem.email = ["nunemaker@gmail.com"]
7
+ gem.description = %q{UI for the Flipper gem}
8
+ gem.summary = %q{UI for the Flipper gem}
9
+ gem.homepage = "https://github.com/jnunemaker/flipper-ui"
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
12
+ gem.files = `git ls-files`.split("\n")
13
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
14
+ gem.name = "flipper-ui"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Flipper::UI::VERSION
17
+
18
+ gem.add_dependency 'rack'
19
+ gem.add_dependency 'flipper', '~> 0.7.0.beta1'
20
+ gem.add_dependency 'erubis'
21
+ end
data/lib/flipper-ui.rb ADDED
@@ -0,0 +1 @@
1
+ require 'flipper/ui'
data/lib/flipper/ui.rb ADDED
@@ -0,0 +1,23 @@
1
+ require 'pathname'
2
+ require 'rack'
3
+ require 'flipper'
4
+ require 'multi_json'
5
+
6
+ module Flipper
7
+ module UI
8
+ def self.root
9
+ @root ||= Pathname(__FILE__).dirname.expand_path.join('ui')
10
+ end
11
+
12
+ def self.app(flipper)
13
+ app = lambda { |env| [200, {'Content-Type' => 'text/html'}, ['']] }
14
+ builder = Rack::Builder.new
15
+ yield builder if block_given?
16
+ builder.use Middleware, flipper
17
+ builder.run app
18
+ builder
19
+ end
20
+ end
21
+ end
22
+
23
+ require 'flipper/ui/middleware'
@@ -0,0 +1,172 @@
1
+ require 'forwardable'
2
+ require 'flipper/ui/error'
3
+ require 'flipper/ui/eruby'
4
+ require 'json'
5
+
6
+ module Flipper
7
+ module UI
8
+ class Action
9
+ extend Forwardable
10
+
11
+ # Public: Call this in subclasses so the action knows its route.
12
+ #
13
+ # regex - The Regexp that this action should run for.
14
+ #
15
+ # Returns nothing.
16
+ def self.route(regex)
17
+ @regex = regex
18
+ end
19
+
20
+ # Internal: Initializes and runs an action for a given request.
21
+ #
22
+ # flipper - The Flipper::DSL instance.
23
+ # request - The Rack::Request that was sent.
24
+ #
25
+ # Returns result of Action#run.
26
+ def self.run(flipper, request)
27
+ new(flipper, request).run
28
+ end
29
+
30
+ # Internal: The regex that matches which routes this action will work for.
31
+ def self.regex
32
+ @regex || raise("#{name}.route is not set")
33
+ end
34
+
35
+ # Private: The path to the views folder.
36
+ def self.views_path
37
+ @views_path ||= Flipper::UI.root.join('views')
38
+ end
39
+
40
+ # Private: The path to the public folder.
41
+ def self.public_path
42
+ @public_path ||= Flipper::UI.root.join('public')
43
+ end
44
+
45
+ # Public: The instance of the Flipper::DSL the middleware was
46
+ # initialized with.
47
+ attr_reader :flipper
48
+
49
+ # Public: The Rack::Request to provide a response for.
50
+ attr_reader :request
51
+
52
+ # Public: The params for the request.
53
+ def_delegator :@request, :params
54
+
55
+ def initialize(flipper, request)
56
+ @flipper, @request = flipper, request
57
+ @code = 200
58
+ @headers = {}
59
+ end
60
+
61
+ # Public: Runs the request method for the provided request.
62
+ #
63
+ # Returns whatever the request method returns in the action.
64
+ def run
65
+ if respond_to?(request_method_name)
66
+ catch(:halt) { send(request_method_name) }
67
+ else
68
+ raise UI::RequestMethodNotSupported, "#{self.class} does not support request method #{request_method_name.inspect}"
69
+ end
70
+ end
71
+
72
+ # Public: Runs another action from within the request method of a
73
+ # different action.
74
+ #
75
+ # action_class - The class of the other action to run.
76
+ #
77
+ # Examples
78
+ #
79
+ # run_other_action Index
80
+ # # => result of running Index action
81
+ #
82
+ # Returns result of other action.
83
+ def run_other_action(action_class)
84
+ action_class.new(flipper, request).run
85
+ end
86
+
87
+ # Public: Call this with a response to immediately stop the current action
88
+ # and respond however you want.
89
+ #
90
+ # response - The response you would like to return.
91
+ def halt(response)
92
+ throw :halt, response
93
+ end
94
+
95
+ # Public: Compiles a view and returns rack response with that as the body.
96
+ #
97
+ # name - The Symbol name of the view.
98
+ #
99
+ # Returns a response.
100
+ def view_response(name)
101
+ header 'Content-Type', 'text/html'
102
+ body = view_with_layout { view_without_layout name }
103
+ [@code, @headers, [body]]
104
+ end
105
+
106
+ # Public: Dumps an object as json and returns rack response with that as
107
+ # the body. Automatically sets Content-Type to "application/json".
108
+ #
109
+ # object - The Object that should be dumped as json.
110
+ #
111
+ # Returns a response.
112
+ def json_response(object)
113
+ header 'Content-Type', 'application/json'
114
+ body = JSON.dump(object)
115
+ [@code, @headers, [body]]
116
+ end
117
+
118
+ # Public: Set the status code for the response.
119
+ #
120
+ # code - The Integer code you would like the response to return.
121
+ def status(code)
122
+ @code = code.to_i
123
+ end
124
+
125
+ # Public: Set a header.
126
+ #
127
+ # name - The String name of the header.
128
+ # value - The value of the header.
129
+ def header(name, value)
130
+ @headers[name] = value
131
+ end
132
+
133
+ # Private
134
+ def view_with_layout(&block)
135
+ view :layout, &block
136
+ end
137
+
138
+ # Private
139
+ def view_without_layout(name)
140
+ view name
141
+ end
142
+
143
+ # Private
144
+ def view(name)
145
+ path = views_path.join("#{name}.erb")
146
+ contents = path.read
147
+ compiled = Eruby.new(contents)
148
+ compiled.result Proc.new {}.binding
149
+ end
150
+
151
+ # Internal: The path the app is mounted at.
152
+ def script_name
153
+ request.env['SCRIPT_NAME']
154
+ end
155
+
156
+ # Private
157
+ def views_path
158
+ self.class.views_path
159
+ end
160
+
161
+ # Private
162
+ def public_path
163
+ self.class.public_path
164
+ end
165
+
166
+ # Private: Returns the request method converted to an action method.
167
+ def request_method_name
168
+ @request_method_name ||= @request.request_method.downcase
169
+ end
170
+ end
171
+ end
172
+ end