flipper-ui 0.2.0.beta1

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.
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