Skip to content

Commit

Permalink
Hide UI elements when Flipper is in read-only mode
Browse files Browse the repository at this point in the history
  • Loading branch information
radar committed Nov 13, 2023
1 parent b0d56f2 commit eb4b04d
Show file tree
Hide file tree
Showing 19 changed files with 138 additions and 62 deletions.
4 changes: 4 additions & 0 deletions lib/flipper/adapter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ def from(source)
end
end

def read_only?
false
end

# Public: Get all features and gate values in one call. Defaults to one call
# to features and another to get_multi. Feel free to override per adapter to
# make this more efficient.
Expand Down
4 changes: 4 additions & 0 deletions lib/flipper/adapters/read_only.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ def features
@adapter.features
end

def read_only?
true
end

def get(feature)
@adapter.get(feature)
end
Expand Down
4 changes: 4 additions & 0 deletions lib/flipper/dsl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,10 @@ def features
adapter.features.map { |name| feature(name) }.to_set
end

def read_only?
adapter.read_only?
end

# Cloud DSL method that does nothing for open source version.
def sync
end
Expand Down
10 changes: 9 additions & 1 deletion lib/flipper/ui/action.rb
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,7 @@ def valid_request_method?

# Internal: Method to call when the UI is in read only mode and you want
# to inform people of that fact.
def read_only
def render_read_only
status 403

breadcrumb 'Home', '/'
Expand All @@ -286,6 +286,14 @@ def read_only
halt view_response(:read_only)
end

def read_only?
Flipper::UI.configuration.read_only || flipper.read_only?
end

def write_allowed?
!read_only?
end

def bootstrap_css
SOURCES[:bootstrap_css]
end
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def get
end

def post
read_only if Flipper::UI.configuration.read_only
read_only if read_only?

feature = flipper[feature_name]
value = params['value'].to_s.strip
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/add_feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AddFeature < UI::Action
route %r{\A/features/new/?\Z}

def get
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

unless Flipper::UI.configuration.feature_creation_enabled
status 403
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/boolean_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class BooleanGate < UI::Action
route %r{\A/features/(?<feature_name>.*)/boolean/?\Z}

def post
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

feature = flipper[feature_name]
@feature = Decorators::Feature.new(feature)
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/feature.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def get
end

def delete
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

unless Flipper::UI.configuration.feature_removal_enabled
status 403
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/features.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def get
end

def post
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

unless Flipper::UI.configuration.feature_creation_enabled
status 403
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/groups_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def get
end

def post
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

feature = flipper[feature_name]
value = params['value'].to_s.strip
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/percentage_of_actors_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class PercentageOfActorsGate < UI::Action
route %r{\A/features/(?<feature_name>.*)/percentage_of_actors/?\Z}

def post
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

feature = flipper[feature_name]
@feature = Decorators::Feature.new(feature)
Expand Down
2 changes: 1 addition & 1 deletion lib/flipper/ui/actions/percentage_of_time_gate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class PercentageOfTimeGate < UI::Action
route %r{\A/features/(?<feature_name>.*)/percentage_of_time/?\Z}

def post
read_only if Flipper::UI.configuration.read_only
render_read_only if read_only?

feature = flipper[feature_name]
@feature = Decorators::Feature.new(feature)
Expand Down
89 changes: 41 additions & 48 deletions lib/flipper/ui/views/feature.erb
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@
</strong>
</h6>
</div>
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="col col-auto toggle-block-when-off">
<button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add an actor</button>
</div>
Expand Down Expand Up @@ -73,7 +73,7 @@
</h6>
</div>
<div class="col col-auto">
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<form action="<%= script_name %>/features/<%= @feature.key %>/actors" method="post">
<%== csrf_input_tag %>
<input type="hidden" name="operation" value="disable">
Expand Down Expand Up @@ -102,7 +102,7 @@
</strong>
</h6>
</div>
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="col col-auto toggle-block-when-off">
<button class="btn btn-outline-secondary js-toggle-trigger" data-toggle="collapse" data-target="#add-actor">Add a group</button>
</div>
Expand Down Expand Up @@ -138,7 +138,7 @@
<h6 class="m-0"><%= item %></h6>
</div>
<div class="col col-auto">
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<form action="<%= script_name %>/features/<%= @feature.key %>/groups" method="post">
<%== csrf_input_tag %>
<input type="hidden" name="operation" value="disable">
Expand All @@ -160,7 +160,7 @@
<div class="col col-mr-auto">
<h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_actors_value > 0 %>">Enabled for <%= @feature.percentage_of_actors_value %>% of actors</strong></h6>
</div>
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="col col-auto toggle-block-when-off">
<button class="btn btn-outline-secondary js-toggle-trigger">Edit</button>
</div>
Expand All @@ -171,7 +171,7 @@
</div>
</div>

<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="card-body border-bottom toggle-block-when-on bg-lightest">
<div class="row">
<div class="col-12 col-md-6 mb-3 mb-md-0">
Expand Down Expand Up @@ -203,7 +203,7 @@
<div class="col col-mr-auto">
<h6 class="m-0"><strong class="<%= "text-muted" unless @feature.percentage_of_time_value > 0 %>">Enabled for <%= @feature.percentage_of_time_value %>% of time</strong></h6>
</div>
<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="col col-auto toggle-block-when-off">
<button class="btn btn-outline-secondary js-toggle-trigger">Edit</button>
</div>
Expand All @@ -214,7 +214,7 @@
</div>
</div>

<% unless Flipper::UI.configuration.read_only %>
<% if write_allowed? %>
<div class="card-body border-bottom toggle-block-when-on bg-lightest">
<div class="row">
<div class="col-12 col-md-6 mb-3 mb-md-0">
Expand All @@ -240,52 +240,45 @@
</div>
<% end %>

<div class="card-body">
<form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
<%== csrf_input_tag %>
<% if write_allowed? %>
<div class="card-body">
<form action="<%= script_name %>/features/<%= @feature.key %>/boolean" method="post">
<%== csrf_input_tag %>

<div class="row">
<% unless @feature.boolean_value %>
<div class="col">
<button type="submit" name="action" value="Enable" <% if Flipper::UI.configuration.confirm_fully_enable %>id="enable_feature__button"<% end %> class="btn btn-outline-success btn-block" <% if Flipper::UI.configuration.read_only %>disabled<% end %>>
<span class="d-block" data-toggle="tooltip"
<% if Flipper::UI.configuration.confirm_fully_enable %>
data-confirmation-text="<%= feature_name %>"
<% end %>
<% if Flipper::UI.configuration.read_only %>
title="Fully enable is not allowed in read only mode."
<% else %>
title="Enable for everyone"
<% end %>
>
Fully Enable
</span>
</button>
</div>
<% end %>
<div class="row">
<% unless @feature.boolean_value %>
<div class="col">
<button type="submit" name="action" value="Enable" <% if Flipper::UI.configuration.confirm_fully_enable %>id="enable_feature__button"<% end %> class="btn btn-outline-success btn-block">
<span class="d-block" data-toggle="tooltip"
<% if Flipper::UI.configuration.confirm_fully_enable %>
data-confirmation-text="<%= feature_name %>"
<% end %>
title="Enable for everyone"
>
Fully Enable
</span>
</button>
</div>
<% end %>

<% unless @feature.off? %>
<div class="col">
<button type="submit" name="action" value="Disable" class="btn btn-outline-danger btn-block" <% if Flipper::UI.configuration.read_only %>disabled<% end %>>
<span class="d-block" data-toggle="tooltip"
<% if Flipper::UI.configuration.read_only %>
title="Disable is not allowed in read only mode.">
<% else %>
title="Disable for everyone by clearing all percentages, groups and actors.">
<% end %>
Disable
</span>
</button>
</div>
<% end %>
</div>
</form>
</div>
<% unless @feature.off? %>
<div class="col">
<button type="submit" name="action" value="Disable" class="btn btn-outline-danger btn-block">
<span class="d-block" data-toggle="tooltip" title="Disable for everyone by clearing all percentages, groups and actors.">
Disable
</span>
</button>
</div>
<% end %>
</div>
</form>
</div>
<% end %>
</div>
</div>
</div>

<% if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_removal_enabled %>
<% if write_allowed? && Flipper::UI.configuration.feature_removal_enabled %>
<div class="row">
<div class="col">
<div class="card border">
Expand Down
7 changes: 4 additions & 3 deletions lib/flipper/ui/views/features.erb
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@
<% if Flipper::UI.configuration.fun %>
<h4>But I've got a blank space baby...</h4>
<p>And I'll flip your features.</p>
<%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
<%= read_only? %>
<%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
<p>
<a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
</p>
<%- end -%>
<% else %>
<h4>Getting Started</h4>
<p class="mb-1">You have not added any features to configure yet.</p>
<%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
<%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
<p class="mt-2">
<a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
</p>
Expand All @@ -26,7 +27,7 @@
<% else %>
<div class="card">
<div class="card-header">
<%- if !Flipper::UI.configuration.read_only && Flipper::UI.configuration.feature_creation_enabled -%>
<%- if write_allowed? && Flipper::UI.configuration.feature_creation_enabled -%>
<div class="float-right">
<a class="btn btn-primary btn-sm" href="<%= script_name %>/features/new">Add Feature</a>
</div>
Expand Down
10 changes: 9 additions & 1 deletion lib/flipper/ui/views/read_only.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
<div class="alert alert-danger">
The UI is currently in read only mode. To change this, you'll need to set <code>Flipper::UI.configuration.read_only = false</code> wherever flipper is running from.
<p>The UI is currently in read only mode.</p>

<p>
To change this, you'll need to set <code>Flipper::UI.configuration.read_only = false</code> wherever flipper is running from.
</p>

<p>
Alternatively, you may be using the <code>Flipper::Adapters::ReadOnly</code> adapter. This will also set Flipper into read only mode.
</p>
</div>
4 changes: 4 additions & 0 deletions spec/flipper/adapters/read_only_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
expect(subject.features).to eq(Set['stats'])
end

it 'is configured as read only' do
expect(subject.read_only?).to eq(true)
end

it 'raises error on add' do
expect { subject.add(feature) }.to raise_error(Flipper::Adapters::ReadOnly::WriteAttempted)
end
Expand Down
20 changes: 20 additions & 0 deletions spec/flipper/ui/actions/add_feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,24 @@
expect(last_response.body).to include('Feature creation is disabled.')
end
end

describe 'GET /features/new when an adpter is set to readonly' do
before do
@original_feature_creation_enabled = Flipper::UI.configuration.feature_creation_enabled
Flipper::UI.configuration.feature_creation_enabled = false
get '/features/new'
end

after do
Flipper::UI.configuration.feature_creation_enabled = @original_feature_creation_enabled
end

it 'returns 403' do
expect(last_response.status).to be(403)
end

it 'renders feature creation disabled template' do
expect(last_response.body).to include('Feature creation is disabled.')
end
end
end
14 changes: 13 additions & 1 deletion spec/flipper/ui/actions/feature_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,18 @@
expect(last_response.body).to include('Most in-depth search')
end

context "when a readonly adapter is in use" do
before do
allow(flipper).to receive(:read_only?) { true }
end

before { get '/features' }

it 'renders template with no buttons or ways to modify a feature' do
expect(last_response.body).not_to include("Fully Enable")
end
end

context 'custom actor names' do
before do
actor = Flipper::Actor.new('some_actor_name')
Expand All @@ -126,7 +138,7 @@
}
}
end

get '/features/search'
end

Expand Down
Loading

0 comments on commit eb4b04d

Please sign in to comment.