green_flag 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +7 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +138 -0
- data/MIT-LICENSE +20 -0
- data/README.md +43 -0
- data/Rakefile +38 -0
- data/app/assets/images/green_flag/.gitkeep +0 -0
- data/app/assets/javascripts/green_flag/admin/features.js +353 -0
- data/app/assets/javascripts/green_flag/admin/rules.js +2 -0
- data/app/assets/javascripts/green_flag/application.js +14 -0
- data/app/assets/stylesheets/green_flag/admin/features.css.scss +36 -0
- data/app/assets/stylesheets/green_flag/admin/rules.css.scss +3 -0
- data/app/assets/stylesheets/green_flag/application.css +13 -0
- data/app/controllers/green_flag/admin/feature_decision_summaries_controller.rb +33 -0
- data/app/controllers/green_flag/admin/features_controller.rb +32 -0
- data/app/controllers/green_flag/admin/rule_lists_controller.rb +28 -0
- data/app/controllers/green_flag/admin/white_list_users_controller.rb +39 -0
- data/app/controllers/green_flag/site_visitor_management.rb +51 -0
- data/app/helpers/green_flag/application_helper.rb +4 -0
- data/app/models/green_flag/feature.rb +84 -0
- data/app/models/green_flag/feature_decision.rb +77 -0
- data/app/models/green_flag/feature_event.rb +9 -0
- data/app/models/green_flag/rule.rb +66 -0
- data/app/models/green_flag/site_visitor.rb +49 -0
- data/app/models/green_flag/user_group.rb +8 -0
- data/app/models/green_flag/visitor_group.rb +60 -0
- data/app/views/green_flag/admin/features/index.html.erb +14 -0
- data/app/views/green_flag/admin/features/show.html.erb +144 -0
- data/app/views/layouts/green_flag/application.html.erb +25 -0
- data/config/routes.rb +12 -0
- data/db/migrate/20140502112602_create_green_flag_site_visitors.rb +9 -0
- data/db/migrate/20140502221059_create_green_flag_features.rb +10 -0
- data/db/migrate/20140502221423_create_green_flag_feature_decisions.rb +13 -0
- data/db/migrate/20140505204611_add_visitor_code_to_site_visitors.rb +8 -0
- data/db/migrate/20140511045110_create_green_flag_rules.rb +12 -0
- data/db/migrate/20140513203728_set_default_percentage_in_green_flag_rules.rb +10 -0
- data/db/migrate/20140514202337_require_ordering_for_green_flag_rules.rb +13 -0
- data/db/migrate/20140516214909_add_restrictions_to_green_flag_rules.rb +13 -0
- data/db/migrate/20150211214159_create_green_flag_feature_events.rb +13 -0
- data/db/migrate/20150213191101_add_rule_id_to_green_flag_feature_decisions.rb +5 -0
- data/db/migrate/20150218035000_add_version_number_to_green_flag_rules.rb +9 -0
- data/db/migrate/20150218035805_add_version_number_to_green_flag_features.rb +9 -0
- data/db/migrate/20150218171852_add_version_number_to_green_flag_rules_indices.rb +19 -0
- data/green_flag.gemspec +34 -0
- data/green_flag.png +0 -0
- data/green_flag_small.png +0 -0
- data/lib/green_flag/engine.rb +16 -0
- data/lib/green_flag/version.rb +3 -0
- data/lib/green_flag.rb +4 -0
- data/lib/tasks/green_flag_tasks.rake +4 -0
- data/script/rails +8 -0
- data/spec/controllers/admin/feature_decision_summaries_controller_spec.rb +17 -0
- data/spec/controllers/admin/features_controller_spec.rb +21 -0
- data/spec/controllers/admin/rule_lists_controller_spec.rb +25 -0
- data/spec/controllers/admin/white_list_users_controller_spec.rb +15 -0
- data/spec/controllers/site_visitor_management_spec.rb +54 -0
- data/spec/dummy/README.rdoc +261 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/assets/javascripts/application.js +15 -0
- data/spec/dummy/app/assets/stylesheets/application.css +13 -0
- data/spec/dummy/app/controllers/application_controller.rb +7 -0
- data/spec/dummy/app/controllers/feature_check_controller.rb +10 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/mailers/.gitkeep +0 -0
- data/spec/dummy/app/models/.gitkeep +0 -0
- data/spec/dummy/app/models/user.rb +3 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config/application.rb +65 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +43 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +37 -0
- data/spec/dummy/config/environments/production.rb +67 -0
- data/spec/dummy/config/environments/test.rb +37 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +15 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +6 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/db/migrate/20150726195118_create_users.rb +8 -0
- data/spec/dummy/db/migrate/20150726204409_add_email_to_users.rb +5 -0
- data/spec/dummy/db/schema.rb +78 -0
- data/spec/dummy/lib/assets/.gitkeep +0 -0
- data/spec/dummy/log/.gitkeep +0 -0
- data/spec/dummy/log/development.log +8341 -0
- data/spec/dummy/log/test.log +34578 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +25 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/dummy/tmp/cache/assets/C47/3D0/sprockets%2F8f17c33229239b023190617bf2e915a3 +0 -0
- data/spec/dummy/tmp/cache/assets/C81/770/sprockets%2Fdbcf34796b062155788f0b550808541a +0 -0
- data/spec/dummy/tmp/cache/assets/C89/D60/sprockets%2F73cd073739a0655341b7278fae57518f +0 -0
- data/spec/dummy/tmp/cache/assets/CB6/8F0/sprockets%2F5ea0f1f2583683678e122a9a9391a80f +0 -0
- data/spec/dummy/tmp/cache/assets/CD8/370/sprockets%2F357970feca3ac29060c1e3861e2c0953 +0 -0
- data/spec/dummy/tmp/cache/assets/CE7/FF0/sprockets%2Fe45f3a7675a8c5a5b064117792bf5e28 +0 -0
- data/spec/dummy/tmp/cache/assets/D07/670/sprockets%2F761d03a66b753d628feccd12072c814c +0 -0
- data/spec/dummy/tmp/cache/assets/D10/860/sprockets%2F4582878dbb5b72bfa76615d31b34ed51 +0 -0
- data/spec/dummy/tmp/cache/assets/D13/270/sprockets%2F701a30cd450ae3cfa114092bafc16004 +0 -0
- data/spec/dummy/tmp/cache/assets/D15/C10/sprockets%2F6f42f843c916d7864a0dfa912fb3194a +0 -0
- data/spec/dummy/tmp/cache/assets/D18/860/sprockets%2Fce00769800ae939cebb28501947ea96f +0 -0
- data/spec/dummy/tmp/cache/assets/D32/A10/sprockets%2F13fe41fee1fe35b49d145bcc06610705 +0 -0
- data/spec/dummy/tmp/cache/assets/D4A/970/sprockets%2F2a7d3b403cbdd8b59d57d26964ea5768 +0 -0
- data/spec/dummy/tmp/cache/assets/D4E/1B0/sprockets%2Ff7cbd26ba1d28d48de824f0e94586655 +0 -0
- data/spec/dummy/tmp/cache/assets/D59/090/sprockets%2F88788ba6a64e6279624e5c2ff7eead53 +0 -0
- data/spec/dummy/tmp/cache/assets/D5A/EA0/sprockets%2Fd771ace226fc8215a3572e0aa35bb0d6 +0 -0
- data/spec/dummy/tmp/cache/assets/D5C/330/sprockets%2Fd1b1c9a53f4a8a5827e5b02bf0e100e8 +0 -0
- data/spec/dummy/tmp/cache/assets/D68/760/sprockets%2Fea24808c41a3dff75b995b0f090e1a6a +0 -0
- data/spec/dummy/tmp/cache/assets/D6A/580/sprockets%2F18c12847aa1bb46ce9b5661f0e9e5fb0 +0 -0
- data/spec/dummy/tmp/cache/assets/DA1/210/sprockets%2F25a2979e392c8bc3adce7075cf19ab4b +0 -0
- data/spec/dummy/tmp/cache/assets/DA9/2C0/sprockets%2Ff95d82b2bbb6db8ffe1a87f67b415291 +0 -0
- data/spec/dummy/tmp/cache/assets/DB2/0C0/sprockets%2F95cf35cd3e97774df3c41ee0ef564a8d +0 -0
- data/spec/dummy/tmp/cache/assets/DDC/400/sprockets%2Fcffd775d018f68ce5dba1ee0d951a994 +0 -0
- data/spec/dummy/tmp/cache/assets/E04/890/sprockets%2F2f5173deea6c795b8fdde723bb4b63af +0 -0
- data/spec/dummy/tmp/cache/assets/E07/200/sprockets%2F82a8ce7f5bcfb07f773df4cbfeb04762 +0 -0
- data/spec/dummy/tmp/cache/assets/E34/D30/sprockets%2F99c2d0bbd78f1b867beeb3a2eefda618 +0 -0
- data/spec/factories/green_flag/feature.rb +7 -0
- data/spec/factories/green_flag/rule.rb +9 -0
- data/spec/features/admin_spec.rb +16 -0
- data/spec/features/visitor_spec.rb +36 -0
- data/spec/models/green_flag/feature_decision_spec.rb +102 -0
- data/spec/models/green_flag/feature_event_spec.rb +4 -0
- data/spec/models/green_flag/feature_spec.rb +135 -0
- data/spec/models/green_flag/rule_spec.rb +107 -0
- data/spec/models/green_flag/site_visitor_spec.rb +95 -0
- data/spec/models/green_flag/user_group_spec.rb +24 -0
- data/spec/models/green_flag/visitor_group_spec.rb +81 -0
- data/spec/spec_helper.rb +20 -0
- data/spec/support/controller_helpers.rb +7 -0
- metadata +359 -0
@@ -0,0 +1,144 @@
|
|
1
|
+
<div class="header clearfix">
|
2
|
+
<h1><%= link_to 'Green Flag', admin_features_path %> - <%= @feature.code %></h1>
|
3
|
+
</div>
|
4
|
+
|
5
|
+
<%# Document structure %>
|
6
|
+
<div class="row">
|
7
|
+
<div id='rules' class="col-md-12 "></div>
|
8
|
+
</div>
|
9
|
+
|
10
|
+
<div class="row">
|
11
|
+
<div id='decisions' class="col-md-6 panel "></div>
|
12
|
+
<div id='white-list' class="col-md-6 panel "></div>
|
13
|
+
<div id='your-status' class="col-md-6 panel "></div>
|
14
|
+
</div>
|
15
|
+
|
16
|
+
<div class="row">
|
17
|
+
</div>
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
<%# Templates - rules %>
|
22
|
+
<script id='rules-template' type='text/html'>
|
23
|
+
<h2>Rules</h2>
|
24
|
+
|
25
|
+
<div class="panel panel-default">
|
26
|
+
<table class="table">
|
27
|
+
<thead>
|
28
|
+
<th>Group Key</th>
|
29
|
+
<th>Group Description</th>
|
30
|
+
<th>Percentage</th>
|
31
|
+
<th></th>
|
32
|
+
</thead>
|
33
|
+
<tbody class="current-rules"></tbody>
|
34
|
+
<tfoot>
|
35
|
+
<th colspan="4" >
|
36
|
+
<div class="input-group">
|
37
|
+
<select class="group-keys form-control">
|
38
|
+
<option disabled>New Rule For...</option>
|
39
|
+
</select>
|
40
|
+
<span class="input-group-btn">
|
41
|
+
<input class="add-rule btn btn-primary" type="button" value="Add Rule" />
|
42
|
+
</span>
|
43
|
+
</div>
|
44
|
+
</th>
|
45
|
+
|
46
|
+
</tfoot>
|
47
|
+
</table>
|
48
|
+
|
49
|
+
<div class="invalid-message panel panel-danger">
|
50
|
+
<div class="panel-heading">These rules cannot be saved.</div>
|
51
|
+
<div class="panel-body">
|
52
|
+
There is probably a good reason.
|
53
|
+
<ul>
|
54
|
+
<li>Percentages should be between 0 and 100</li>
|
55
|
+
<li>Each group can only be included once.</li>
|
56
|
+
</ul>
|
57
|
+
</div>
|
58
|
+
</div>
|
59
|
+
|
60
|
+
<div class="save-buttons panel">
|
61
|
+
<button class="save-rules btn btn-success btn-lg">Save Rules</button>
|
62
|
+
<button class="revert-rules btn btn-default btn-lg">Revert</button>
|
63
|
+
</div>
|
64
|
+
</div>
|
65
|
+
</script>
|
66
|
+
|
67
|
+
<script id='current-rule-template' type='text/html'>
|
68
|
+
<th>{{group_key}}</th>
|
69
|
+
<td>{{group_description}}</th>
|
70
|
+
<td class="percentage">
|
71
|
+
<input type="number" class="rule-percent" value="{{percentage}}" min="0" max="100" />%
|
72
|
+
</td>
|
73
|
+
<td>
|
74
|
+
<button type="button" class="close remove-rule" aria-hidden="true">×</button>
|
75
|
+
</td>
|
76
|
+
</script>
|
77
|
+
|
78
|
+
<%# Templates - decisions %>
|
79
|
+
<script id='decisions-template' type='text/html'>
|
80
|
+
<h2>Decisions Made</h2>
|
81
|
+
<div class="panel panel-default col-md-6">
|
82
|
+
|
83
|
+
<ul class="decisions-made-summary">
|
84
|
+
<li>
|
85
|
+
<span class="decision-number">{{enabled}}</span><br>Enabled
|
86
|
+
</li>
|
87
|
+
<li>
|
88
|
+
<button class="forget-decisions-enabled btn btn-lg">Forget</button>
|
89
|
+
</li>
|
90
|
+
</ul>
|
91
|
+
|
92
|
+
</div>
|
93
|
+
|
94
|
+
<div class="panel panel-default col-md-6">
|
95
|
+
|
96
|
+
<ul class="decisions-made-summary">
|
97
|
+
<li>
|
98
|
+
<span class="decision-number">{{disabled}}</span><br>Disabled
|
99
|
+
</li>
|
100
|
+
<li>
|
101
|
+
<button class="forget-decisions-disabled btn btn-lg">Forget</button>
|
102
|
+
</li>
|
103
|
+
</ul>
|
104
|
+
|
105
|
+
</div>
|
106
|
+
|
107
|
+
|
108
|
+
</script>
|
109
|
+
|
110
|
+
<%# Templates - white list %>
|
111
|
+
<script id='white-list-template' type='text/html'>
|
112
|
+
<h2>White List</h2>
|
113
|
+
|
114
|
+
<div class="list-group">
|
115
|
+
<div class='white-list-user-list'></div>
|
116
|
+
<div class="list-group-item">
|
117
|
+
<div class="alert alert-warning error-message" style="display:none">User not found</div>
|
118
|
+
<div class="input-group">
|
119
|
+
<input class="user-email form-control" placeholder="Email" />
|
120
|
+
<span class="input-group-btn">
|
121
|
+
<input class="add-user btn btn-primary" type="button" value="Add User">
|
122
|
+
</span>
|
123
|
+
</div>
|
124
|
+
</div>
|
125
|
+
</div>
|
126
|
+
</script>
|
127
|
+
|
128
|
+
<script id='white-list-user-template' type='text/html'>
|
129
|
+
<button type="button" class="close destroy-user" aria-hidden="true">×</button>
|
130
|
+
<strong>{{name}}</strong> {{email}}
|
131
|
+
</script>
|
132
|
+
|
133
|
+
<script id='your-status-template' type='text/html'>
|
134
|
+
<h2>Your Status <span class="label {{labelClass}}">{{status}}</span></h2>
|
135
|
+
</script>
|
136
|
+
|
137
|
+
<%# Javascript kickstart %>
|
138
|
+
<%= javascript_include_tag 'green_flag/admin/features' %>
|
139
|
+
<script>
|
140
|
+
GreenFlag.FeatureEditor.start({
|
141
|
+
feature: <%= @feature.to_json.html_safe %>,
|
142
|
+
allVisitorGroups: <%= @visitor_groups.to_json.html_safe %>,
|
143
|
+
});
|
144
|
+
</script>
|
@@ -0,0 +1,25 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>GreenFlag</title>
|
5
|
+
<%= stylesheet_link_tag "green_flag/application", :media => "all" %>
|
6
|
+
<%= stylesheet_link_tag '//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css' %>
|
7
|
+
<%= stylesheet_link_tag 'green_flag/admin/features' %>
|
8
|
+
|
9
|
+
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
10
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.5.1/underscore-min.js"></script>
|
11
|
+
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.1.1/js/bootstrap.min.js"></script>
|
12
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone.js"></script>
|
13
|
+
<script src="https://cdnjs.cloudflare.com/ajax/libs/backbone.marionette/1.0.4/backbone.marionette.min.js"></script>
|
14
|
+
|
15
|
+
<%= javascript_include_tag "green_flag/application" %>
|
16
|
+
<%= csrf_meta_tags %>
|
17
|
+
</head>
|
18
|
+
<body>
|
19
|
+
|
20
|
+
<div class="container">
|
21
|
+
<%= yield %>
|
22
|
+
</div>
|
23
|
+
|
24
|
+
</body>
|
25
|
+
</html>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateGreenFlagFeatureDecisions < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :green_flag_feature_decisions do |t|
|
4
|
+
t.integer :feature_id, null: false
|
5
|
+
t.integer :site_visitor_id, null: false
|
6
|
+
t.boolean :enabled
|
7
|
+
t.boolean :manual
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :green_flag_feature_decisions, [:site_visitor_id, :feature_id],
|
11
|
+
{unique: true, name: 'index_gf_feature_decisions_on_site_visitor_id_feature_id'}
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
class AddVisitorCodeToSiteVisitors < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
add_column :green_flag_site_visitors, :visitor_code, :string
|
4
|
+
execute 'update green_flag_site_visitors set visitor_code = id'
|
5
|
+
change_column :green_flag_site_visitors, :visitor_code, :string, null: false
|
6
|
+
add_index :green_flag_site_visitors, :visitor_code, unique: true
|
7
|
+
end
|
8
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class CreateGreenFlagRules < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :green_flag_rules do |t|
|
4
|
+
t.string :group_key
|
5
|
+
t.integer :feature_id
|
6
|
+
t.integer :order_by
|
7
|
+
t.integer :percentage
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
add_index :green_flag_rules, [:feature_id, :order_by]
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class SetDefaultPercentageInGreenFlagRules < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
execute 'update green_flag_rules set percentage=0 where percentage is null'
|
4
|
+
change_column :green_flag_rules, :percentage, :integer, default: 0, null: false
|
5
|
+
end
|
6
|
+
|
7
|
+
def down
|
8
|
+
change_column :green_flag_rules, :percentage, :integer, default: nil, null: true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class RequireOrderingForGreenFlagRules < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
change_column :green_flag_rules, :order_by, :integer, null: false
|
4
|
+
remove_index :green_flag_rules, [:feature_id, :order_by]
|
5
|
+
add_index :green_flag_rules, [:feature_id, :order_by], :unique => true
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_index :green_flag_rules, [:feature_id, :order_by]
|
10
|
+
add_index :green_flag_rules, [:feature_id, :order_by]
|
11
|
+
change_column :green_flag_rules, :order_by, :integer, null: true
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class AddRestrictionsToGreenFlagRules < ActiveRecord::Migration
|
2
|
+
def up
|
3
|
+
change_column :green_flag_rules, :group_key, :string, null: false
|
4
|
+
change_column :green_flag_rules, :feature_id, :integer, null: false
|
5
|
+
add_index :green_flag_rules, [:feature_id, :group_key], :unique => true
|
6
|
+
end
|
7
|
+
|
8
|
+
def down
|
9
|
+
remove_index :green_flag_rules, [:feature_id, :group_key]
|
10
|
+
change_column :green_flag_rules, :group_key, :string, null: true
|
11
|
+
change_column :green_flag_rules, :feature_id, :integer, null: true
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
class CreateGreenFlagFeatureEvents < ActiveRecord::Migration
|
2
|
+
def change
|
3
|
+
create_table :green_flag_feature_events do |t|
|
4
|
+
t.integer :feature_id, :null => false
|
5
|
+
t.integer :event_type_code, :null => false
|
6
|
+
t.integer :count, :null => false
|
7
|
+
t.timestamps
|
8
|
+
end
|
9
|
+
|
10
|
+
add_index :green_flag_feature_events, :feature_id
|
11
|
+
add_index :green_flag_feature_events, :event_type_code
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'activerecord-concurrent-index'
|
2
|
+
|
3
|
+
class AddVersionNumberToGreenFlagRulesIndices < ActiveRecord::Migration
|
4
|
+
def up
|
5
|
+
remove_index :green_flag_rules, [:feature_id, :group_key]
|
6
|
+
remove_index :green_flag_rules, [:feature_id, :order_by]
|
7
|
+
|
8
|
+
add_index_concurrently :green_flag_rules, [:feature_id, :group_key, :version_number], :unique => true, :name => 'index_green_flag_rules_on_feature_version_and_group_key'
|
9
|
+
add_index_concurrently :green_flag_rules, [:feature_id, :order_by, :version_number], :unique => true, :name => 'index_green_flag_rules_on_feature_version_and_order_by'
|
10
|
+
end
|
11
|
+
|
12
|
+
def down
|
13
|
+
add_index_concurrently :green_flag_rules, [:feature_id, :group_key], :unique => true
|
14
|
+
add_index_concurrently :green_flag_rules, [:feature_id, :order_by], :unique => true
|
15
|
+
|
16
|
+
remove_index :green_flag_rules, [:feature_id, :group_key, :version_number]
|
17
|
+
remove_index :green_flag_rules, [:feature_id, :order_by, :version_number]
|
18
|
+
end
|
19
|
+
end
|
data/green_flag.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require "green_flag/version"
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = "green_flag"
|
9
|
+
s.version = GreenFlag::VERSION
|
10
|
+
s.authors = ["Tim Morton"]
|
11
|
+
s.email = ["tim@timothymorton.com"]
|
12
|
+
s.homepage = "https://github.com/websdotcom/green_flag"
|
13
|
+
s.summary = "A feature-flag system."
|
14
|
+
s.description = <<-EOF
|
15
|
+
A feature-flag system that makes it *really easy* for application developers
|
16
|
+
to add and manage features. You can roll out features to a percentage of
|
17
|
+
your users, or you can define groups to see the new feature.
|
18
|
+
Want to test a new feature on 50% of your premium-plan users named "Fred"?
|
19
|
+
GreenFlag can do that.
|
20
|
+
EOF
|
21
|
+
s.license = "MIT"
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split($/)
|
24
|
+
s.test_files = Dir["spec/**/*"]
|
25
|
+
|
26
|
+
s.add_dependency "rails", "~> 3.2"
|
27
|
+
s.add_dependency "pg", "~> 0.18"
|
28
|
+
s.add_dependency "activerecord-concurrent-index", "~> 0.0.1"
|
29
|
+
s.add_dependency "sass-rails", "~> 3.2"
|
30
|
+
|
31
|
+
s.add_development_dependency 'rspec-rails'
|
32
|
+
s.add_development_dependency 'factory_girl_rails'
|
33
|
+
s.add_development_dependency 'capybara'
|
34
|
+
end
|
data/green_flag.png
ADDED
Binary file
|
Binary file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module GreenFlag
|
2
|
+
class Engine < ::Rails::Engine
|
3
|
+
isolate_namespace GreenFlag
|
4
|
+
|
5
|
+
config.generators do |g|
|
6
|
+
g.test_framework :rspec, :fixture => false
|
7
|
+
g.fixture_replacement :factory_girl, :dir => 'spec/factories'
|
8
|
+
g.assets false
|
9
|
+
g.helper false
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer "green_flag.site_visitor_management" do |app|
|
13
|
+
ActionController::Base.send :include, GreenFlag::SiteVisitorManagement
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/green_flag.rb
ADDED
data/script/rails
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
|
3
|
+
|
4
|
+
ENGINE_ROOT = File.expand_path('../..', __FILE__)
|
5
|
+
ENGINE_PATH = File.expand_path('../../lib/green_flag/engine', __FILE__)
|
6
|
+
|
7
|
+
require 'rails/all'
|
8
|
+
require 'rails/engine/commands'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreenFlag::Admin::FeatureDecisionSummariesController do
|
4
|
+
|
5
|
+
let(:feature) { GreenFlag::Feature.create(code: 'asdf') }
|
6
|
+
|
7
|
+
describe '#show' do
|
8
|
+
subject { get :show, feature_id: feature.id, format: 'js' }
|
9
|
+
it { should be_successful }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#update' do
|
13
|
+
subject { post :update, feature_id: feature.id, format: 'js' }
|
14
|
+
it { should be_successful }
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreenFlag::Admin::FeaturesController do
|
4
|
+
|
5
|
+
let(:feature) { GreenFlag::Feature.create(code: 'foo') }
|
6
|
+
|
7
|
+
describe '#index' do
|
8
|
+
it 'is successful' do
|
9
|
+
get :index
|
10
|
+
expect(response).to be_success
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe '#show' do
|
15
|
+
it 'is successful' do
|
16
|
+
get :show, :id => feature.id
|
17
|
+
expect(response).to be_success
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreenFlag::Admin::RuleListsController do
|
4
|
+
|
5
|
+
let(:feature) { GreenFlag::Feature.create(code: 'asdf') }
|
6
|
+
|
7
|
+
describe '#show' do
|
8
|
+
subject { get :show, feature_id: feature.id, format: 'js' }
|
9
|
+
it { should be_successful }
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#update' do
|
13
|
+
let(:empty_rules_json) { [] }
|
14
|
+
subject do
|
15
|
+
post :update, feature_id: feature.id, format: 'js',
|
16
|
+
'_json' => empty_rules_json
|
17
|
+
end
|
18
|
+
it { should be_successful }
|
19
|
+
it 'sends the rules to a model' do
|
20
|
+
expect(GreenFlag::Rule).to receive(:set_rules!).with(feature.id, [])
|
21
|
+
subject
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreenFlag::Admin::WhiteListUsersController do
|
4
|
+
|
5
|
+
describe '#index' do
|
6
|
+
subject { get :index, feature_id: 1, format: 'js' }
|
7
|
+
context 'with no whitelisted users' do
|
8
|
+
before(:each) do
|
9
|
+
allow(GreenFlag::FeatureDecision).to receive(:whitelisted_users) { [] }
|
10
|
+
end
|
11
|
+
it { should be_successful }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GreenFlag::SiteVisitorManagement do
|
4
|
+
|
5
|
+
controller(ActionController::Base) do
|
6
|
+
include GreenFlag::SiteVisitorManagement
|
7
|
+
|
8
|
+
def index
|
9
|
+
render :text => 'foo'
|
10
|
+
end
|
11
|
+
|
12
|
+
def current_user
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:cookie_name) { GreenFlag::SiteVisitorManagement::COOKIE_NAME }
|
18
|
+
|
19
|
+
it 'does not prevent the controller from rendering' do
|
20
|
+
get :index
|
21
|
+
expect(response).to be_success
|
22
|
+
end
|
23
|
+
|
24
|
+
context '#set_site_visitor' do
|
25
|
+
context 'when there is no vistor cookie' do
|
26
|
+
it 'does not create a new visitor' do
|
27
|
+
expect{ get :index }.to_not change { GreenFlag::SiteVisitor.count }
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sets the visitor cookie' do
|
31
|
+
get :index
|
32
|
+
expect(response.cookies[cookie_name]).to be_present
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when there is a visitor cookie' do
|
37
|
+
before(:each) do
|
38
|
+
request.cookies[cookie_name] = '2d931510-d99f-494a-8c67-87feb05e1594'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'does not create a new visitor' do
|
42
|
+
expect{ get :index }.to_not change { GreenFlag::SiteVisitor.count }
|
43
|
+
end
|
44
|
+
it 'calls record_login when there is a current_user' do
|
45
|
+
user = double
|
46
|
+
controller.stub(:current_user) { user }
|
47
|
+
expect(controller).to receive(:record_login).with(user)
|
48
|
+
get :index
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
|
54
|
+
end
|