flipper-ui 0.16.2 → 0.22.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (171) hide show
  1. checksums.yaml +5 -5
  2. data/docs/ui/README.md +35 -25
  3. data/docs/ui/images/banner.png +0 -0
  4. data/docs/ui/images/description.png +0 -0
  5. data/docs/ui/images/feature.png +0 -0
  6. data/docs/ui/images/features.png +0 -0
  7. data/examples/ui/authorization.ru +46 -0
  8. data/examples/ui/basic.ru +39 -32
  9. data/flipper-ui.gemspec +2 -3
  10. data/lib/flipper/ui/action.rb +51 -7
  11. data/lib/flipper/ui/actions/actors_gate.rb +11 -8
  12. data/lib/flipper/ui/actions/feature.rb +5 -2
  13. data/lib/flipper/ui/actions/features.rb +16 -3
  14. data/lib/flipper/ui/actions/file.rb +1 -1
  15. data/lib/flipper/ui/actions/groups_gate.rb +1 -1
  16. data/lib/flipper/ui/actions/percentage_of_actors_gate.rb +1 -1
  17. data/lib/flipper/ui/actions/percentage_of_time_gate.rb +1 -1
  18. data/lib/flipper/ui/assets/javascripts/application.coffee +5 -3
  19. data/lib/flipper/ui/configuration.rb +45 -10
  20. data/lib/flipper/ui/decorators/feature.rb +42 -16
  21. data/lib/flipper/ui/middleware.rb +2 -1
  22. data/lib/flipper/ui/public/css/application.css +26 -6492
  23. data/lib/flipper/ui/public/images/logo.png +0 -0
  24. data/lib/flipper/ui/public/js/application.js +5 -5
  25. data/lib/flipper/ui/util.rb +40 -0
  26. data/lib/flipper/ui/views/add_actor.erb +3 -3
  27. data/lib/flipper/ui/views/add_feature.erb +2 -2
  28. data/lib/flipper/ui/views/add_group.erb +1 -1
  29. data/lib/flipper/ui/views/feature.erb +199 -180
  30. data/lib/flipper/ui/views/features.erb +56 -37
  31. data/lib/flipper/ui/views/layout.erb +22 -18
  32. data/lib/flipper/ui.rb +5 -8
  33. data/lib/flipper/version.rb +1 -1
  34. data/spec/flipper/ui/actions/actors_gate_spec.rb +76 -15
  35. data/spec/flipper/ui/actions/boolean_gate_spec.rb +18 -0
  36. data/spec/flipper/ui/actions/feature_spec.rb +32 -16
  37. data/spec/flipper/ui/actions/features_spec.rb +65 -17
  38. data/spec/flipper/ui/actions/file_spec.rb +0 -20
  39. data/spec/flipper/ui/actions/groups_gate_spec.rb +20 -9
  40. data/spec/flipper/ui/actions/percentage_of_actors_gate_spec.rb +18 -3
  41. data/spec/flipper/ui/actions/percentage_of_time_gate_spec.rb +18 -3
  42. data/spec/flipper/ui/configuration_spec.rb +80 -34
  43. data/spec/flipper/ui/decorators/feature_spec.rb +2 -32
  44. data/spec/flipper/ui_spec.rb +1 -14
  45. metadata +22 -141
  46. data/docs/ui/images/configured-ui.png +0 -0
  47. data/docs/ui/images/environment-banner.png +0 -0
  48. data/lib/flipper/ui/assets/stylesheets/.DS_Store +0 -0
  49. data/lib/flipper/ui/assets/stylesheets/application.scss +0 -19
  50. data/lib/flipper/ui/assets/stylesheets/bootstrap/_alert.scss +0 -51
  51. data/lib/flipper/ui/assets/stylesheets/bootstrap/_badge.scss +0 -47
  52. data/lib/flipper/ui/assets/stylesheets/bootstrap/_breadcrumb.scss +0 -38
  53. data/lib/flipper/ui/assets/stylesheets/bootstrap/_button-group.scss +0 -166
  54. data/lib/flipper/ui/assets/stylesheets/bootstrap/_buttons.scss +0 -143
  55. data/lib/flipper/ui/assets/stylesheets/bootstrap/_card.scss +0 -270
  56. data/lib/flipper/ui/assets/stylesheets/bootstrap/_carousel.scss +0 -191
  57. data/lib/flipper/ui/assets/stylesheets/bootstrap/_close.scss +0 -34
  58. data/lib/flipper/ui/assets/stylesheets/bootstrap/_code.scss +0 -56
  59. data/lib/flipper/ui/assets/stylesheets/bootstrap/_custom-forms.scss +0 -297
  60. data/lib/flipper/ui/assets/stylesheets/bootstrap/_dropdown.scss +0 -131
  61. data/lib/flipper/ui/assets/stylesheets/bootstrap/_forms.scss +0 -333
  62. data/lib/flipper/ui/assets/stylesheets/bootstrap/_functions.scss +0 -86
  63. data/lib/flipper/ui/assets/stylesheets/bootstrap/_grid.scss +0 -52
  64. data/lib/flipper/ui/assets/stylesheets/bootstrap/_images.scss +0 -42
  65. data/lib/flipper/ui/assets/stylesheets/bootstrap/_input-group.scss +0 -159
  66. data/lib/flipper/ui/assets/stylesheets/bootstrap/_jumbotron.scss +0 -16
  67. data/lib/flipper/ui/assets/stylesheets/bootstrap/_list-group.scss +0 -115
  68. data/lib/flipper/ui/assets/stylesheets/bootstrap/_media.scss +0 -8
  69. data/lib/flipper/ui/assets/stylesheets/bootstrap/_mixins.scss +0 -42
  70. data/lib/flipper/ui/assets/stylesheets/bootstrap/_modal.scss +0 -168
  71. data/lib/flipper/ui/assets/stylesheets/bootstrap/_nav.scss +0 -118
  72. data/lib/flipper/ui/assets/stylesheets/bootstrap/_navbar.scss +0 -311
  73. data/lib/flipper/ui/assets/stylesheets/bootstrap/_pagination.scss +0 -77
  74. data/lib/flipper/ui/assets/stylesheets/bootstrap/_popover.scss +0 -183
  75. data/lib/flipper/ui/assets/stylesheets/bootstrap/_print.scss +0 -124
  76. data/lib/flipper/ui/assets/stylesheets/bootstrap/_progress.scss +0 -33
  77. data/lib/flipper/ui/assets/stylesheets/bootstrap/_reboot.scss +0 -482
  78. data/lib/flipper/ui/assets/stylesheets/bootstrap/_root.scss +0 -19
  79. data/lib/flipper/ui/assets/stylesheets/bootstrap/_tables.scss +0 -180
  80. data/lib/flipper/ui/assets/stylesheets/bootstrap/_tooltip.scss +0 -115
  81. data/lib/flipper/ui/assets/stylesheets/bootstrap/_transitions.scss +0 -36
  82. data/lib/flipper/ui/assets/stylesheets/bootstrap/_type.scss +0 -125
  83. data/lib/flipper/ui/assets/stylesheets/bootstrap/_utilities.scss +0 -14
  84. data/lib/flipper/ui/assets/stylesheets/bootstrap/_variables.scss +0 -894
  85. data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap-grid.scss +0 -32
  86. data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap-reboot.scss +0 -12
  87. data/lib/flipper/ui/assets/stylesheets/bootstrap/bootstrap.scss +0 -42
  88. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_alert.scss +0 -13
  89. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_background-variant.scss +0 -21
  90. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_badge.scss +0 -12
  91. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_border-radius.scss +0 -35
  92. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_box-shadow.scss +0 -5
  93. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_breakpoints.scss +0 -123
  94. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_buttons.scss +0 -109
  95. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_caret.scss +0 -65
  96. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_clearfix.scss +0 -7
  97. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_float.scss +0 -11
  98. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_forms.scss +0 -137
  99. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_gradients.scss +0 -45
  100. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_grid-framework.scss +0 -67
  101. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_grid.scss +0 -52
  102. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_hover.scss +0 -39
  103. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_image.scss +0 -36
  104. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_list-group.scss +0 -21
  105. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_lists.scss +0 -7
  106. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_nav-divider.scss +0 -10
  107. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_navbar-align.scss +0 -10
  108. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_pagination.scss +0 -22
  109. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_reset-text.scss +0 -17
  110. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_resize.scss +0 -6
  111. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_screen-reader.scss +0 -35
  112. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_size.scss +0 -6
  113. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_table-row.scss +0 -30
  114. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-emphasis.scss +0 -14
  115. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-hide.scss +0 -9
  116. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_text-truncate.scss +0 -8
  117. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_transition.scss +0 -9
  118. data/lib/flipper/ui/assets/stylesheets/bootstrap/mixins/_visibility.scss +0 -7
  119. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_align.scss +0 -8
  120. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_background.scss +0 -19
  121. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_borders.scss +0 -59
  122. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_clearfix.scss +0 -3
  123. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_display.scss +0 -38
  124. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_embed.scss +0 -52
  125. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_flex.scss +0 -46
  126. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_float.scss +0 -9
  127. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_position.scss +0 -36
  128. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_screenreaders.scss +0 -11
  129. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_sizing.scss +0 -12
  130. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_spacing.scss +0 -51
  131. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_text.scss +0 -52
  132. data/lib/flipper/ui/assets/stylesheets/bootstrap/utilities/_visibility.scss +0 -11
  133. data/lib/flipper/ui/assets/stylesheets/primer/.scss-lint.yml +0 -446
  134. data/lib/flipper/ui/assets/stylesheets/primer/_alerts.scss +0 -106
  135. data/lib/flipper/ui/assets/stylesheets/primer/_avatars.scss +0 -36
  136. data/lib/flipper/ui/assets/stylesheets/primer/_base.scss +0 -40
  137. data/lib/flipper/ui/assets/stylesheets/primer/_blankslate.scss +0 -96
  138. data/lib/flipper/ui/assets/stylesheets/primer/_buttons.scss +0 -404
  139. data/lib/flipper/ui/assets/stylesheets/primer/_counter.scss +0 -10
  140. data/lib/flipper/ui/assets/stylesheets/primer/_filter-list.scss +0 -68
  141. data/lib/flipper/ui/assets/stylesheets/primer/_flex-table.scss +0 -20
  142. data/lib/flipper/ui/assets/stylesheets/primer/_forms.scss +0 -756
  143. data/lib/flipper/ui/assets/stylesheets/primer/_layout.scss +0 -69
  144. data/lib/flipper/ui/assets/stylesheets/primer/_menu.scss +0 -113
  145. data/lib/flipper/ui/assets/stylesheets/primer/_mixins.scss +0 -53
  146. data/lib/flipper/ui/assets/stylesheets/primer/_normalize.scss +0 -425
  147. data/lib/flipper/ui/assets/stylesheets/primer/_states.scss +0 -32
  148. data/lib/flipper/ui/assets/stylesheets/primer/_tabnav.scss +0 -65
  149. data/lib/flipper/ui/assets/stylesheets/primer/_tooltips.scss +0 -255
  150. data/lib/flipper/ui/assets/stylesheets/primer/_truncate.scss +0 -27
  151. data/lib/flipper/ui/assets/stylesheets/primer/_type.scss +0 -92
  152. data/lib/flipper/ui/assets/stylesheets/primer/_utility.scss +0 -73
  153. data/lib/flipper/ui/assets/stylesheets/primer/_variables.scss +0 -34
  154. data/lib/flipper/ui/assets/stylesheets/primer/primer.scss +0 -39
  155. data/lib/flipper/ui/eruby.rb +0 -11
  156. data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  157. data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.svg +0 -288
  158. data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  159. data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  160. data/lib/flipper/ui/public/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
  161. data/lib/flipper/ui/public/images/remove.png +0 -0
  162. data/lib/flipper/ui/public/octicons/LICENSE.txt +0 -9
  163. data/lib/flipper/ui/public/octicons/README.md +0 -1
  164. data/lib/flipper/ui/public/octicons/octicons-local.ttf +0 -0
  165. data/lib/flipper/ui/public/octicons/octicons.css +0 -236
  166. data/lib/flipper/ui/public/octicons/octicons.eot +0 -0
  167. data/lib/flipper/ui/public/octicons/octicons.less +0 -235
  168. data/lib/flipper/ui/public/octicons/octicons.svg +0 -200
  169. data/lib/flipper/ui/public/octicons/octicons.ttf +0 -0
  170. data/lib/flipper/ui/public/octicons/octicons.woff +0 -0
  171. data/lib/flipper/ui/public/octicons/sprockets-octicons.scss +0 -232
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 71eccacf760968860d08a08de30d86c435314c3a
4
- data.tar.gz: 9e6a0d87e9158c108dbf7c80fbf663576899fcf3
2
+ SHA256:
3
+ metadata.gz: b17af1c2cd05d9026a457941c46e7c497460e4289bd8446b90d2f0342e53f34c
4
+ data.tar.gz: 4adae31beca41aebaae69c9c3a0efe51bb024d0acc54eadf3982ddfff4a16748
5
5
  SHA512:
6
- metadata.gz: 5790f438fba84b3a34301fc004fab83f6609c249026438639395781b474848e2c57f38aa612a5864e6365b5e2e853b00a7c72c96c69e6b9074e37fd5e55da44b
7
- data.tar.gz: ed1011c894f34b0ebd3ff87a06ed5687428c7ba8d76466d8e18a49ccfc595716eae65afdb6f2b54786ebde372f8ad367b6bea079773da00977d155d06d9b2d64
6
+ metadata.gz: a8e0d113cb89d77c9e8cd0ec83d8deb7a26f3f6e00452aa81ab6a308843dfe45e069b0e1303374ece24e4a5bd79718958e160249db9195ea22249bab2df66a5d
7
+ data.tar.gz: cc5f6567f690c2bd48b762083046b19e868f4885384885667f20d808cd09f0533df5d3df5239e4a21cc649babbe231e494af984b5c1261b4ef9358b68581784a
data/docs/ui/README.md CHANGED
@@ -5,9 +5,11 @@ UI for the [Flipper](https://github.com/jnunemaker/flipper) gem.
5
5
  ## Screenshots
6
6
 
7
7
  Viewing list of features:
8
+
8
9
  ![features](images/features.png)
9
10
 
10
11
  Viewing an individual feature:
12
+
11
13
  ![feature](images/feature.png)
12
14
 
13
15
  ## Installation
@@ -37,14 +39,15 @@ YourRailsApp::Application.routes.draw do
37
39
  end
38
40
  ```
39
41
 
40
- If you'd like to lazy load flipper, you can pass a block instead:
42
+ If you'd like to lazy load flipper, you can instead pass a block to initialize it:
41
43
 
42
44
  ```ruby
43
45
  # config/routes.rb
44
46
  YourRailsApp::Application.routes.draw do
45
47
  flipper_block = lambda {
46
48
  # some flipper initialization here, for example:
47
- # YourRailsApp.flipper
49
+ adapter = Flipper::Adapters::Memory.new
50
+ Flipper.new(adapter)
48
51
  }
49
52
  mount Flipper::UI.app(flipper_block) => '/flipper'
50
53
  end
@@ -95,7 +98,7 @@ end
95
98
  # config/routes.rb
96
99
 
97
100
  constraints CanAccessFlipperUI do
98
- mount Flipper::UI.app(flipper) => '/flipper'
101
+ mount Flipper::UI.app(Flipper) => '/flipper'
99
102
  end
100
103
  ```
101
104
 
@@ -106,7 +109,7 @@ Minimal example for Rack:
106
109
  ```ruby
107
110
  # config.ru
108
111
 
109
- require 'flipper-ui'
112
+ require 'flipper/ui'
110
113
 
111
114
  adapter = Flipper::Adapters::Memory.new
112
115
  flipper = Flipper.new(adapter)
@@ -126,32 +129,30 @@ See [examples/ui/basic.ru](https://github.com/jnunemaker/flipper/blob/master/exa
126
129
 
127
130
  ### Configuration
128
131
 
129
- Flipper UI can be customized via `configure`, which yields a configuration instance for setting the text on the five main sections of the UI feature view.
132
+ Flipper UI can be customized via `configure`, which yields a configuration instance.
130
133
 
131
- * `config.actors`
132
- * `config.groups`
133
- * `config.percentage_of_actors`
134
- * `config.percentage_of_time`
135
- * `config.delete`
134
+ #### Description
136
135
 
137
- Each of these methods returns a [Flipper::UI::Option](https://github.com/jnunemaker/flipper/blob/master/lib/flipper/ui/configuration/option.rb) that responds to `title=`, `description=` as seen below.
136
+ We can associate a `description` for each `feature` by providing a descriptions source:
138
137
 
139
- *e.g. customzing the percentage_of_actors and delete sections' titles and descriptions*
140
138
  ```ruby
141
139
  Flipper::UI.configure do |config|
142
- config.percentage_of_actors.title = "My Custom Title"
143
- config.percentage_of_actors.description = "My custom description"
140
+ config.descriptions_source = ->(keys) do
141
+ # descriptions loaded from YAML file or database (postgres, mysql, etc)
142
+ # return has to be hash of {String key => String description}
143
+ end
144
144
 
145
- config.delete.title = "BE VERY CAREFUL!"
146
- config.delete.description = "YOU'VE BEEN WARNED!"
145
+ # Defaults to false. Set to true to show feature descriptions on the list
146
+ # page as well as the view page.
147
+ # config.show_feature_description_in_list = true
147
148
  end
148
149
  ```
149
150
 
150
- results in:
151
+ Descriptions show up in the UI like so:
151
152
 
152
- ![configure](images/configured-ui.png)
153
+ ![description](images/description.png)
153
154
 
154
- ### Banner
155
+ #### Banner
155
156
 
156
157
  Flipper UI can display a banner across the top of the page. The `banner_text` and `banner_class` can be configured by using the `Flipper::UI.configure` block as seen below.
157
158
 
@@ -166,15 +167,24 @@ By default the `environment` is set to an empty string so no banner will show. I
166
167
 
167
168
  The above configuration results in:
168
169
 
169
- ![configure](images/environment-banner.png)
170
+ ![banner](images/banner.png)
171
+
172
+ #### Fun mode
173
+
174
+ By default, Flipper UI displays a videoclip when there are no flags. The `fun` mode can be configured by using the `Flipper::UI.configure` block as seen below.
175
+
176
+ ```ruby
177
+ Flipper::UI.configure do |config|
178
+ config.fun = false
179
+ end
180
+ ```
170
181
 
171
182
  ## Contributing
172
183
 
173
184
  1. Fork it
174
185
  2. Create your feature branch (`git checkout -b my-new-feature`)
175
186
  3. **Fire up the app** (`script/server`)
176
- 4. **Start up guard** (`bundle exec guard` for automatic coffeescript/sass compilation and such).
177
- 5. Run the tests `bundle exec rake`
178
- 6. Commit your changes (`git commit -am 'Added some feature'`)
179
- 7. Push to the branch (`git push origin my-new-feature`)
180
- 8. Create new Pull Request
187
+ 4. Run the tests `bundle exec rake`
188
+ 5. Commit your changes (`git commit -am 'Added some feature'`)
189
+ 6. Push to the branch (`git push origin my-new-feature`)
190
+ 7. Create new Pull Request
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,46 @@
1
+ #
2
+ # Usage:
3
+ # bundle exec rackup examples/ui/authorization.ru -p 9999
4
+ # bundle exec shotgun examples/ui/authorization.ru -p 9999
5
+ # http://localhost:9999/
6
+ #
7
+ require 'bundler/setup'
8
+ require "flipper/ui"
9
+ require "flipper/adapters/pstore"
10
+
11
+ Flipper.register(:admins) { |actor|
12
+ actor.respond_to?(:admin?) && actor.admin?
13
+ }
14
+
15
+ # Example middleware to allow reading the Flipper UI but nothing else.
16
+ class FlipperReadOnlyMiddleware
17
+ def initialize(app)
18
+ @app = app
19
+ end
20
+
21
+ def call(env)
22
+ request = Rack::Request.new(env)
23
+
24
+ if request.get?
25
+ @app.call(env)
26
+ else
27
+ [401, {}, ["You can only look"]]
28
+ end
29
+ end
30
+ end
31
+
32
+ # You can uncomment these to get some default data:
33
+ # Flipper.enable(:search_performance_another_long_thing)
34
+ # Flipper.disable(:gauges_tracking)
35
+ # Flipper.disable(:unused)
36
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
37
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
38
+ # Flipper.enable_group(:secrets, :admins)
39
+ # Flipper.enable_percentage_of_time(:logging, 5)
40
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
41
+ # Flipper.add("a/b")
42
+
43
+ run Flipper::UI.app { |builder|
44
+ builder.use Rack::Session::Cookie, secret: "_super_secret"
45
+ builder.use FlipperReadOnlyMiddleware
46
+ }
data/examples/ui/basic.ru CHANGED
@@ -1,20 +1,16 @@
1
1
  #
2
2
  # Usage:
3
- # bundle exec rackup examples/ui/basic.ru -p 9999
4
- # bundle exec shotgun examples/ui/basic.ru -p 9999
3
+ # # if you want it to not reload and be really fast
4
+ # bin/rackup examples/ui/basic.ru -p 9999
5
+ #
6
+ # # if you want reloading
7
+ # bin/shotgun examples/ui/basic.ru -p 9999
8
+ #
5
9
  # http://localhost:9999/
6
10
  #
7
- require "pp"
8
- require "logger"
9
- require "pathname"
10
-
11
- root_path = Pathname(__FILE__).dirname.join("..").expand_path
12
- lib_path = root_path.join("lib")
13
- $:.unshift(lib_path)
14
-
15
- require "flipper-ui"
11
+ require 'bundler/setup'
12
+ require "flipper/ui"
16
13
  require "flipper/adapters/pstore"
17
- require "active_support/notifications"
18
14
 
19
15
  Flipper.register(:admins) { |actor|
20
16
  actor.respond_to?(:admin?) && actor.admin?
@@ -24,28 +20,39 @@ Flipper.register(:early_access) { |actor|
24
20
  actor.respond_to?(:early?) && actor.early?
25
21
  }
26
22
 
27
- # Setup logging of flipper calls.
28
- if ENV["LOG"] == "1"
29
- $logger = Logger.new(STDOUT)
30
- require "flipper/instrumentation/log_subscriber"
31
- Flipper::Instrumentation::LogSubscriber.logger = $logger
23
+ Flipper::UI.configure do |config|
24
+ # config.banner_text = 'Production Environment'
25
+ # config.banner_class = 'danger'
26
+ config.feature_creation_enabled = true
27
+ config.feature_removal_enabled = true
28
+ config.cloud_recommendation = true
29
+ # config.show_feature_description_in_list = true
30
+ config.descriptions_source = lambda do |_keys|
31
+ {
32
+ "search_performance_another_long_thing" => "Just to test feature name length.",
33
+ "gauges_tracking" => "Should we track page views with gaug.es.",
34
+ "unused" => "Not used.",
35
+ "suits" => "Are suits necessary in business?",
36
+ "secrets" => "Secrets are lies.",
37
+ "logging" => "Log all the things.",
38
+ "new_cache" => "Like the old cache but newer.",
39
+ "a/b" => "Why would someone use a slash? I don't know but someone did. Let's make this really long so they regret using slashes. Please don't use slashes.",
40
+ }
41
+ end
32
42
  end
33
43
 
34
- adapter = Flipper::Adapters::PStore.new
35
- flipper = Flipper.new(adapter, instrumenter: ActiveSupport::Notifications)
36
-
37
44
  # You can uncomment these to get some default data:
38
- # flipper[:search_performance_another_long_thing].enable
39
- # flipper[:gauges_tracking].enable
40
- # flipper[:unused].disable
41
- # flipper[:suits].enable_actor Flipper::Actor.new('1')
42
- # flipper[:suits].enable_actor Flipper::Actor.new('6')
43
- # flipper[:secrets].enable_group :admins
44
- # flipper[:secrets].enable_group :early_access
45
- # flipper[:logging].enable_percentage_of_time 5
46
- # flipper[:new_cache].enable_percentage_of_actors 15
47
- # flipper["a/b"].add
48
-
49
- run Flipper::UI.app(flipper) { |builder|
45
+ # Flipper.enable(:search_performance_another_long_thing)
46
+ # Flipper.disable(:gauges_tracking)
47
+ # Flipper.disable(:unused)
48
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('1'))
49
+ # Flipper.enable_actor(:suits, Flipper::Actor.new('6'))
50
+ # Flipper.enable_group(:secrets, :admins)
51
+ # Flipper.enable_group(:secrets, :early_access)
52
+ # Flipper.enable_percentage_of_time(:logging, 5)
53
+ # Flipper.enable_percentage_of_actors(:new_cache, 15)
54
+ # Flipper.add("a/b")
55
+
56
+ run Flipper::UI.app { |builder|
50
57
  builder.use Rack::Session::Cookie, secret: "_super_secret"
51
58
  }
data/flipper-ui.gemspec CHANGED
@@ -10,7 +10,6 @@ Gem::Specification.new do |gem|
10
10
  gem.authors = ['John Nunemaker']
11
11
  gem.email = ['nunemaker@gmail.com']
12
12
  gem.summary = 'UI for the Flipper gem'
13
- gem.description = 'Rack middleware that provides a fully featured web interface for the flipper gem.'
14
13
  gem.license = 'MIT'
15
14
  gem.homepage = 'https://github.com/jnunemaker/flipper'
16
15
 
@@ -22,7 +21,7 @@ Gem::Specification.new do |gem|
22
21
  gem.metadata = Flipper::METADATA
23
22
 
24
23
  gem.add_dependency 'rack', '>= 1.4', '< 3'
25
- gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.1.0'
24
+ gem.add_dependency 'rack-protection', '>= 1.5.3', '< 2.2.0'
26
25
  gem.add_dependency 'flipper', "~> #{Flipper::VERSION}"
27
- gem.add_dependency 'erubis', '~> 2.7.0'
26
+ gem.add_dependency 'erubi', '>= 1.0.0', '< 2.0.0'
28
27
  end
@@ -1,6 +1,7 @@
1
1
  require 'forwardable'
2
+ require 'flipper/ui/configuration'
2
3
  require 'flipper/ui/error'
3
- require 'flipper/ui/eruby'
4
+ require 'erubi'
4
5
  require 'json'
5
6
 
6
7
  module Flipper
@@ -25,6 +26,36 @@ module Flipper
25
26
  'delete'.freeze,
26
27
  ]).freeze
27
28
 
29
+ SOURCES = {
30
+ bootstrap_css: {
31
+ src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css".freeze,
32
+ hash: "sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm".freeze
33
+ }.freeze,
34
+ jquery_js: {
35
+ src: "https://code.jquery.com/jquery-3.2.1.slim.min.js".freeze,
36
+ hash: "sha384-KJ3o2DKtIkvYIK3UENzmM7KCkRr/rE9/Qpg6aAZGJwFDMVNA/GpGFF93hXpG5KkN".freeze
37
+ }.freeze,
38
+ popper_js: {
39
+ src: "https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.12.9/umd/popper.min.js".freeze,
40
+ hash: "sha384-ApNbgh9B+Y1QKtv3Rn7W3mgPxhU9K/ScQsAP7hUibX39j7fakFPskvXusvfa0b4Q".freeze
41
+ }.freeze,
42
+ bootstrap_js: {
43
+ src: "https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/js/bootstrap.min.js".freeze,
44
+ hash: "sha384-JZR6Spejh4U02d8jOt6vLEHfe/JQGiRRSQQxSfFWpi1MquVdAyjUar5+76PVCmYl".freeze
45
+ }.freeze
46
+ }.freeze
47
+ SCRIPT_SRCS = SOURCES.values_at(:jquery_js, :popper_js, :bootstrap_js).map { |s| s[:src] }
48
+ STYLE_SRCS = SOURCES.values_at(:bootstrap_css).map { |s| s[:src] }
49
+ CONTENT_SECURITY_POLICY = <<-CSP.delete("\n")
50
+ default-src 'none';
51
+ img-src 'self';
52
+ font-src 'self';
53
+ script-src 'report-sample' 'self' #{SCRIPT_SRCS.join(' ')};
54
+ style-src 'self' 'unsafe-inline' #{STYLE_SRCS.join(' ')};
55
+ style-src-attr 'unsafe-inline' ;
56
+ style-src-elem 'self' #{STYLE_SRCS.join(' ')};
57
+ CSP
58
+
28
59
  # Public: Call this in subclasses so the action knows its route.
29
60
  #
30
61
  # regex - The Regexp that this action should run for.
@@ -129,6 +160,7 @@ module Flipper
129
160
  # Returns a response.
130
161
  def view_response(name)
131
162
  header 'Content-Type', 'text/html'
163
+ header 'Content-Security-Policy', CONTENT_SECURITY_POLICY
132
164
  body = view_with_layout { view_without_layout name }
133
165
  halt [@code, @headers, [body]]
134
166
  end
@@ -150,7 +182,7 @@ module Flipper
150
182
  # location - The String location to set the Location header to.
151
183
  def redirect_to(location)
152
184
  status 302
153
- header 'Location', "#{script_name}#{location}"
185
+ header 'Location', "#{script_name}#{Rack::Utils.escape_path(location)}"
154
186
  halt [@code, @headers, ['']]
155
187
  end
156
188
 
@@ -205,12 +237,8 @@ module Flipper
205
237
  # Private
206
238
  def view(name)
207
239
  path = views_path.join("#{name}.erb")
208
-
209
240
  raise "Template does not exist: #{path}" unless path.exist?
210
-
211
- contents = path.read
212
- compiled = Eruby.new(contents)
213
- compiled.result proc {}.binding
241
+ eval(Erubi::Engine.new(path.read, escape: true).src)
214
242
  end
215
243
 
216
244
  # Internal: The path the app is mounted at.
@@ -240,6 +268,22 @@ module Flipper
240
268
  def valid_request_method?
241
269
  VALID_REQUEST_METHOD_NAMES.include?(request_method_name)
242
270
  end
271
+
272
+ def bootstrap_css
273
+ SOURCES[:bootstrap_css]
274
+ end
275
+
276
+ def bootstrap_js
277
+ SOURCES[:bootstrap_js]
278
+ end
279
+
280
+ def popper_js
281
+ SOURCES[:popper_js]
282
+ end
283
+
284
+ def jquery_js
285
+ SOURCES[:jquery_js]
286
+ end
243
287
  end
244
288
  end
245
289
  end
@@ -25,19 +25,22 @@ module Flipper
25
25
  def post
26
26
  feature = flipper[feature_name]
27
27
  value = params['value'].to_s.strip
28
+ values = value.split(UI.configuration.actors_separator).map(&:strip).uniq
28
29
 
29
- if Util.blank?(value)
30
- error = Rack::Utils.escape("#{value.inspect} is not a valid actor value.")
30
+ if values.empty?
31
+ error = "#{value.inspect} is not a valid actor value."
31
32
  redirect_to("/features/#{feature.key}/actors?error=#{error}")
32
33
  end
33
34
 
34
- actor = Flipper::Actor.new(value)
35
+ values.each do |value|
36
+ actor = Flipper::Actor.new(value)
35
37
 
36
- case params['operation']
37
- when 'enable'
38
- feature.enable_actor actor
39
- when 'disable'
40
- feature.disable_actor actor
38
+ case params['operation']
39
+ when 'enable'
40
+ feature.enable_actor actor
41
+ when 'disable'
42
+ feature.disable_actor actor
43
+ end
41
44
  end
42
45
 
43
46
  redirect_to("/features/#{feature.key}")
@@ -10,9 +10,12 @@ module Flipper
10
10
  route %r{\A/features/(?<feature_name>.*)\Z}
11
11
 
12
12
  def get
13
- @feature = Decorators::Feature.new(flipper[feature_name])
13
+ flipper_feature = flipper[feature_name]
14
+ @feature = Decorators::Feature.new(flipper_feature)
15
+ descriptions = Flipper::UI.configuration.descriptions_source.call([flipper_feature.key])
16
+ @feature.description = descriptions[@feature.key]
14
17
  @page_title = "#{@feature.key} // Features"
15
- @percentages = [0, 1, 5, 10, 15, 25, 50, 75, 100]
18
+ @percentages = [0, 1, 5, 10, 25, 50, 100]
16
19
 
17
20
  breadcrumb 'Home', '/'
18
21
  breadcrumb 'Features', '/features'
@@ -10,8 +10,21 @@ module Flipper
10
10
 
11
11
  def get
12
12
  @page_title = 'Features'
13
+ keys = flipper.features.map(&:key)
14
+ descriptions = if Flipper::UI.configuration.show_feature_description_in_list?
15
+ Flipper::UI.configuration.descriptions_source.call(keys)
16
+ else
17
+ {}
18
+ end
19
+
13
20
  @features = flipper.features.map do |feature|
14
- Decorators::Feature.new(feature)
21
+ decorated_feature = Decorators::Feature.new(feature)
22
+
23
+ if Flipper::UI.configuration.show_feature_description_in_list?
24
+ decorated_feature.description = descriptions[feature.key]
25
+ end
26
+
27
+ decorated_feature
15
28
  end.sort
16
29
 
17
30
  @show_blank_slate = @features.empty?
@@ -36,14 +49,14 @@ module Flipper
36
49
  value = params['value'].to_s.strip
37
50
 
38
51
  if Util.blank?(value)
39
- error = Rack::Utils.escape("#{value.inspect} is not a valid feature name.")
52
+ error = "#{value.inspect} is not a valid feature name."
40
53
  redirect_to("/features/new?error=#{error}")
41
54
  end
42
55
 
43
56
  feature = flipper[value]
44
57
  feature.add
45
58
 
46
- redirect_to "/features/#{Rack::Utils.escape_path(value)}"
59
+ redirect_to "/features/#{value}"
47
60
  end
48
61
  end
49
62
  end
@@ -5,7 +5,7 @@ module Flipper
5
5
  module UI
6
6
  module Actions
7
7
  class File < UI::Action
8
- route %r{(images|css|js|octicons|fonts)/.*\Z}
8
+ route %r{(images|css|js)/.*\Z}
9
9
 
10
10
  def get
11
11
  Rack::File.new(public_path).call(request.env)
@@ -35,7 +35,7 @@ module Flipper
35
35
 
36
36
  redirect_to("/features/#{feature.key}")
37
37
  else
38
- error = Rack::Utils.escape("The group named #{value.inspect} has not been registered.")
38
+ error = "The group named #{value.inspect} has not been registered."
39
39
  redirect_to("/features/#{feature.key}/groups?error=#{error}")
40
40
  end
41
41
  end
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_actors params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of actors value: #{exception.message}")
19
+ error = "Invalid percentage of actors value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -16,7 +16,7 @@ module Flipper
16
16
  begin
17
17
  feature.enable_percentage_of_time params['value']
18
18
  rescue ArgumentError => exception
19
- error = Rack::Utils.escape("Invalid percentage of time value: #{exception.message}")
19
+ error = "Invalid percentage of time value: #{exception.message}"
20
20
  redirect_to("/features/#{@feature.key}?error=#{error}")
21
21
  end
22
22
 
@@ -1,3 +1,5 @@
1
- $(() ->
2
- $('[data-toggle="tooltip"]').tooltip();
3
- );
1
+ $ ->
2
+ $(document).on('click', '.js-toggle-trigger', ->
3
+ $container = $(this).closest('.js-toggle-container')
4
+ $container.toggleClass('toggle-on')
5
+ )
@@ -3,11 +3,7 @@ require 'flipper/ui/configuration/option'
3
3
  module Flipper
4
4
  module UI
5
5
  class Configuration
6
- attr_reader :actors,
7
- :delete,
8
- :groups,
9
- :percentage_of_actors,
10
- :percentage_of_time
6
+ attr_reader :delete
11
7
 
12
8
  attr_accessor :banner_text,
13
9
  :banner_class
@@ -26,6 +22,33 @@ module Flipper
26
22
  # set to false, users won't be able to delete features from the UI.
27
23
  attr_accessor :feature_removal_enabled
28
24
 
25
+ # Public: Are you feeling lucky? Defaults to true. If set to false, users
26
+ # won't see a videoclip of Taylor Swift when there aren't any features
27
+ attr_accessor :fun
28
+
29
+ # Public: Tired of seeing the awesome message about Cloud? Set this to
30
+ # false and it will go away. Defaults to true.
31
+ attr_accessor :cloud_recommendation
32
+
33
+ # Public: What should show up in the form to add actors. This can be
34
+ # different per application since flipper_id's can be whatever an
35
+ # application needs. Defaults to "a flipper id".
36
+ attr_accessor :add_actor_placeholder
37
+
38
+ # Public: If you set this, Flipper::UI will fetch descriptions
39
+ # from your external source. Descriptions for `features` will be shown on `feature`
40
+ # page, and optionally the `features` pages. Defaults to empty block.
41
+ attr_accessor :descriptions_source
42
+
43
+ # Public: Should feature descriptions be show on the `features` list page.
44
+ # Default false. Only works when using descriptions.
45
+ attr_accessor :show_feature_description_in_list
46
+
47
+ # Public: What should be used to denote you are trying to add multiple
48
+ # actors at once (instead of just a single actor).
49
+ # Default is comma ",".
50
+ attr_accessor :actors_separator
51
+
29
52
  VALID_BANNER_CLASS_VALUES = %w(
30
53
  danger
31
54
  dark
@@ -37,16 +60,28 @@ module Flipper
37
60
  warning
38
61
  ).freeze
39
62
 
63
+ DEFAULT_DESCRIPTIONS_SOURCE = ->(_keys) { {} }
64
+
40
65
  def initialize
41
- @actors = Option.new("Actors", "Enable actors using the form above.")
42
- @groups = Option.new("Groups", "Enable groups using the form above.")
43
- @percentage_of_actors = Option.new("Percentage of Actors", "Percentage of actors functions independently of percentage of time. If you enable 50% of Actors and 25% of Time then the feature will always be enabled for 50% of users and occasionally enabled 25% of the time for everyone.") # rubocop:disable Metrics/LineLength
44
- @percentage_of_time = Option.new("Percentage of Time", "Percentage of actors functions independently of percentage of time. If you enable 50% of Actors and 25% of Time then the feature will always be enabled for 50% of users and occasionally enabled 25% of the time for everyone.") # rubocop:disable Metrics/LineLength
45
- @delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.") # rubocop:disable Metrics/LineLength
66
+ @delete = Option.new("Danger Zone", "Deleting a feature removes it from the list of features and disables it for everyone.")
46
67
  @banner_text = nil
47
68
  @banner_class = 'danger'
48
69
  @feature_creation_enabled = true
49
70
  @feature_removal_enabled = true
71
+ @fun = true
72
+ @cloud_recommendation = true
73
+ @add_actor_placeholder = "a flipper id"
74
+ @descriptions_source = DEFAULT_DESCRIPTIONS_SOURCE
75
+ @show_feature_description_in_list = false
76
+ @actors_separator = ','
77
+ end
78
+
79
+ def using_descriptions?
80
+ @descriptions_source != DEFAULT_DESCRIPTIONS_SOURCE
81
+ end
82
+
83
+ def show_feature_description_in_list?
84
+ using_descriptions? && @show_feature_description_in_list
50
85
  end
51
86
 
52
87
  def banner_class=(value)