Add feature flag support to Pages (#8149)
* Add 'feature_flag' field to pages table, form * Add 'exist?' to FeatureFlag * Test for feature flag when loading custom pages * Refactor FeatureFlag.enabled? to check both boolean and user gates * Ugly test fixes * PR feedback and refactoring * PR refactor: don't require database table!
This commit is contained in:
parent
3233558c5d
commit
f165c47b34
6 changed files with 102 additions and 6 deletions
|
|
@ -4,6 +4,8 @@ class PagesController < ApplicationController
|
|||
|
||||
def show
|
||||
@page = Page.find_by!(slug: params[:slug])
|
||||
not_found unless FeatureFlag.accessible?(@page.feature_flag_name, current_user)
|
||||
|
||||
set_surrogate_key_header "show-page-#{params[:slug]}"
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -111,8 +111,12 @@ class StoriesController < ApplicationController
|
|||
Honeycomb.add_field("stories_route", "org")
|
||||
handle_organization_index
|
||||
elsif @page
|
||||
Honeycomb.add_field("stories_route", "page")
|
||||
handle_page_display
|
||||
if FeatureFlag.accessible?(@page.feature_flag_name, current_user)
|
||||
Honeycomb.add_field("stories_route", "page")
|
||||
handle_page_display
|
||||
else
|
||||
not_found
|
||||
end
|
||||
else
|
||||
Honeycomb.add_field("stories_route", "user")
|
||||
handle_user_index
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
module FeatureFlag
|
||||
extend self # rubocop:disable Style/ModuleFunction
|
||||
class << self
|
||||
delegate :enabled?, :exist?, to: Flipper
|
||||
|
||||
def enabled?(feature_name, *args)
|
||||
Flipper[feature_name].enabled?(*args)
|
||||
def accessible?(feature_flag_name, *args)
|
||||
feature_flag_name.blank? || !exist?(feature_flag_name) || enabled?(feature_flag_name, *args)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -18,6 +18,10 @@ class Page < ApplicationRecord
|
|||
is_top_level_path ? "/#{slug}" : "/page/#{slug}"
|
||||
end
|
||||
|
||||
def feature_flag_name
|
||||
"page_#{slug}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def evaluate_markdown
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
</div>
|
||||
<div class="form-group">
|
||||
<% if @page.social_image_url %>
|
||||
<img src="<%= @page.social_image_url%>" style="max-width:500px;display:block" />
|
||||
<img src="<%= @page.social_image_url %>" style="max-width:500px;display:block" />
|
||||
<% end %>
|
||||
<%= form.label :social_image %>
|
||||
<%= form.file_field :social_image, class: "form-control" %>
|
||||
|
|
@ -38,6 +38,24 @@
|
|||
<%= form.check_box :is_top_level_path %>
|
||||
<p>(Determines if it is accessible by <code>/page-slug</code> vs <code>/page/page-slug</code>) Be careful! ⚠️</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<p>
|
||||
<b><%= link_to "Feature Flag", "/internal/feature_flags" %></b>
|
||||
<span class="badge badge-<%= FeatureFlag.exist?(@page.feature_flag_name) ? "success" : "warning" %>">
|
||||
<%= FeatureFlag.exist?(@page.feature_flag_name) ? "Present" : "Not Present" %>
|
||||
</span>
|
||||
</br>
|
||||
<% if FeatureFlag.exist?(@page.feature_flag_name) %>
|
||||
Access to this page is being guarded by the feature flag <code><%= @page.feature_flag_name %></code>.
|
||||
<%= link_to "Modify flag here", "/internal/feature_flags/features/#{@page.feature_flag_name}" %>
|
||||
<% else %>
|
||||
Everyone has access. Optionally guard access to this page by creating feature <code><%= @page.feature_flag_name %></code>
|
||||
<%= link_to "here", "/internal/feature_flags/features/" %>
|
||||
<% end %>
|
||||
</br>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
<%= form.submit class: "btn btn-primary float-right" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
|||
66
spec/helpers/feature_flag_spec.rb
Normal file
66
spec/helpers/feature_flag_spec.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
require "rails_helper"
|
||||
|
||||
UserStruct = Struct.new(:flipper_id)
|
||||
|
||||
describe FeatureFlag, type: :helper do
|
||||
describe ".enabled?" do
|
||||
it "calls Flipper's enabled? method" do
|
||||
allow(Flipper).to receive(:enabled?).with("foo")
|
||||
|
||||
described_class.enabled?("foo")
|
||||
|
||||
expect(Flipper).to have_received(:enabled?).with("foo")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".exist?" do
|
||||
it "calls Flipper's exist? method" do
|
||||
allow(Flipper).to receive(:exist?).with("foo")
|
||||
|
||||
described_class.exist?("foo")
|
||||
|
||||
expect(Flipper).to have_received(:exist?).with("foo")
|
||||
end
|
||||
end
|
||||
|
||||
describe ".accessible?" do
|
||||
let(:user) { UserStruct.new(flipper_id: 1) }
|
||||
|
||||
it "returns false when flag doesn't exist" do
|
||||
expect(described_class.accessible?("missing_flag")).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
|
||||
it "returns true when flag is empty" do
|
||||
expect(described_class.accessible?("")).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
|
||||
it "returns true when flag is nil" do
|
||||
expect(described_class.accessible?(nil)).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
|
||||
context "when flag exists and is set to off" do
|
||||
before { Flipper.disable("flag") }
|
||||
|
||||
it "returns false" do
|
||||
expect(described_class.accessible?("flag")).to be_falsy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
|
||||
it "returns true when flag is on for user" do
|
||||
Flipper.enable_actor("flag", user)
|
||||
expect(described_class.accessible?("flag", user)).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
end
|
||||
|
||||
context "when flag exists and is set to on" do
|
||||
before { Flipper.enable("flag") }
|
||||
|
||||
it "returns true" do
|
||||
expect(described_class.accessible?("flag")).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
|
||||
it "returns true for a user" do
|
||||
expect(described_class.accessible?("flag", user)).to be_truthy # rubocop:disable Rspec/PredicateMatcher
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Loading…
Add table
Reference in a new issue