Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature flags GUI #513

Merged
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<%= form_with model: Current.tenant, url: admin_tenant_feature_flags_path(Current.tenant), title: "Toggle feature state", method: :patch do |form| %>
<%= form.hidden_field :feature_flags, value: @feature_flag[:enabled] ? @feature_flag[:features_excluding] : @feature_flag[:features_including] %>
<%= form.button name: @feature_flag[:name], class: "#{@feature_flag[:enabled] ? "bg-indigo-600" : "bg-gray-200"} relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-indigo-600 focus:ring-offset-2", role: :switch, aria: { checked: @feature_flag[:enabled] } do %>
jsuchal marked this conversation as resolved.
Show resolved Hide resolved
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="<%= @feature_flag[:enabled] ? "translate-x-5" : "translate-x-0" %> pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out"></span>
<% end %>
<% end %>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Admin::FeatureFlags::FeatureFlagToggleComponent < ViewComponent::Base
def initialize(feature_flag)
@feature_flag = feature_flag
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="w-full p-4 flex-col justify-start items-start gap-4 inline-flex">
<div class="self-stretch bg-white rounded-md border border-gray-200 flex-col justify-start items-start flex">
<div class="self-stretch p-6 border-b border-gray-200 justify-start items-center gap-4 inline-flex">
<div class="grow shrink basis-0 text-gray-900 text-xl font-semibold leading-[35px]">Feature flags</div>
</div>
<% @feature_flags.each do |flag| %>
<div class="self-stretch flex-col justify-start items-start flex">
<%= render Admin::FeatureFlags::FeatureFlagsListRowComponent.new(flag) %>
</div>
<% end %>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Admin::FeatureFlags::FeatureFlagsListComponent < ViewComponent::Base
def initialize(feature_flags:)
@feature_flags = feature_flags
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<div class="self-stretch p-6 border-b border-gray-200 items-center gap-4 inline-flex">
<div class="grow shrink basis-0 gap-1">
<div class="text-gray-900 text-lg font-medium leading-loose w-fit">
<div class="grow shrink basis-0 flex-col justify-start items-start gap-1 inline-flex">
<div class="text-center text-gray-900 text-lg font-medium leading-loose">
<%= @feature_flag[:name] %>
</div>
</div>
</div>
</div>
<%= render Admin::FeatureFlags::FeatureFlagToggleComponent.new(@feature_flag) %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class Admin::FeatureFlags::FeatureFlagsListRowComponent < ViewComponent::Base
def initialize(flag)
@feature_flag = flag
end
end
1 change: 1 addition & 0 deletions app/components/common/icon_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ class IconComponent < ViewComponent::Base
"funnel-slash" => "M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.568a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3ZM2 2l20 20",
"tag-slash" => "M9.568 3H5.25A2.25 2.25 0 0 0 3 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 0 0 5.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 0 0 9.568 3Z M6 6h.008v.008H6V6ZM3 21l18-18",
"inbox-stack" => "m7.875 14.25 1.214 1.942a2.25 2.25 0 0 0 1.908 1.058h2.006c.776 0 1.497-.4 1.908-1.058l1.214-1.942M2.41 9h4.636a2.25 2.25 0 0 1 1.872 1.002l.164.246a2.25 2.25 0 0 0 1.872 1.002h2.092a2.25 2.25 0 0 0 1.872-1.002l.164-.246A2.25 2.25 0 0 1 16.954 9h4.636M2.41 9a2.25 2.25 0 0 0-.16.832V12a2.25 2.25 0 0 0 2.25 2.25h15A2.25 2.25 0 0 0 21.75 12V9.832c0-.287-.055-.57-.16-.832M2.41 9a2.25 2.25 0 0 1 .382-.632l3.285-3.832a2.25 2.25 0 0 1 1.708-.786h8.43c.657 0 1.281.287 1.709.786l3.284 3.832c.163.19.291.404.382.632M4.5 20.25h15A2.25 2.25 0 0 0 21.75 18v-2.625c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125V18a2.25 2.25 0 0 0 2.25 2.25Z",
"flag" => "M3 3v1.5M3 21v-6m0 0 2.77-.693a9 9 0 0 1 6.208.682l.108.054a9 9 0 0 0 6.086.71l3.114-.732a48.524 48.524 0 0 1-.005-10.499l-3.11.732a9 9 0 0 1-6.085-.711l-.108-.054a9 9 0 0 0-6.208-.682L3 4.5M3 15V4.5"
}.freeze

def initialize(icon, classes: "", stroke_width: 1.5)
Expand Down
25 changes: 25 additions & 0 deletions app/controllers/admin/feature_flags_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Admin::FeatureFlagsController < ApplicationController
before_action :set_tenant

def index
authorize([:admin, :feature_flag])
@feature_flags = @tenant.list_features
jsuchal marked this conversation as resolved.
Show resolved Hide resolved
end

def update
authorize([:admin, :feature_flag])
@tenant.feature_flags = feature_flags_params["feature_flags"].split(",")
@tenant.save!
redirect_to admin_tenant_feature_flags_path
end

private

def set_tenant
@tenant = policy_scope([:admin, :feature_flag]).find(params[:tenant_id])
end

def feature_flags_params
params.require(:tenant).permit(:feature_flags)
end
end
5 changes: 3 additions & 2 deletions app/lib/sidebar_menu.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def initialize(controller, action, parameters = nil)
private

def initial_structure(controller, _action)
return admin_menu + site_admin_menu if controller.in? %w[groups users tags tag_groups automation_rules boxes api_connections filters automation_webhooks]
return admin_menu + site_admin_menu if controller.in? %w[groups users tags tag_groups automation_rules boxes api_connections filters automation_webhooks feature_flags]

default_main_menu
end
Expand Down Expand Up @@ -40,7 +40,8 @@ def admin_menu
TW::SidebarMenuItemComponent.new(name: 'API Prepojenia', url: admin_tenant_api_connections_path(Current.tenant), icon: Icons::RectangleStackComponent.new),
TW::SidebarMenuItemComponent.new(name: 'Skupiny', url: admin_tenant_groups_path(Current.tenant), icon: Icons::UserGroupsComponent.new),
TW::SidebarMenuItemComponent.new(name: 'Štítky', url: admin_tenant_tags_path(Current.tenant), icon: Icons::TagComponent.new),
TW::SidebarMenuItemComponent.new(name: 'Integrácie', url: admin_tenant_automation_webhooks_path(Current.tenant), icon: Common::IconComponent.new("code-bracket"))
TW::SidebarMenuItemComponent.new(name: 'Integrácie', url: admin_tenant_automation_webhooks_path(Current.tenant), icon: Common::IconComponent.new("code-bracket")),
TW::SidebarMenuItemComponent.new(name: 'Feature flags', url: admin_tenant_feature_flags_path(Current.tenant), icon: Common::IconComponent.new("flag"))
jsuchal marked this conversation as resolved.
Show resolved Hide resolved
]
end

Expand Down
11 changes: 10 additions & 1 deletion app/models/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def draft_tag!
end

def signed_externally_tag!
signed_externally_tag || raise(ActiveRecord::RecordNotFound, "`SignedExternallyTag` not found in tenant: #{self.id}")
signed_externally_tag || raise(ActiveRecord::RecordNotFound, "`SignedExternallyTag` not found in tenant: #{id}")
end

def signature_requested_tag!
Expand Down Expand Up @@ -88,6 +88,15 @@ def disable_feature(feature)
save!
end

def list_features
AVAILABLE_FEATURE_FLAGS.map do |feature|
{ name: feature.to_s,
enabled: feature_enabled?(feature),
features_including: (feature_flags + [feature.to_s]).join(","),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Toto je interna vec view - cize tam by som to takto rozdeloval, plus teda hladas partition metodu.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luciajanikova vies tu napisat flags ktore nema byt vidiet - resp. len v ?labs

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Viditelne flagy: :audit_log, :archive, :api, :fs_sync
Schovane flagy: :message_draft_import, :fs_api
podla mna takto @jsuchal

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@richardlences vies takto nastavit?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ano nastavil som to tak

features_excluding: (feature_flags - [feature.to_s]).join(",") }
end
end

def make_admins_see_everything!
everything_tag.groups << admin_group
end
Expand Down
24 changes: 24 additions & 0 deletions app/policies/admin/feature_flag_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# frozen_string_literal: true

class Admin::FeatureFlagPolicy < ApplicationPolicy
attr_reader :user, :tenant

def initialize(user, tenant)
@user = user
@tenant = tenant
end

class Scope < Scope
def resolve
Tenant.where(id: @user.tenant)
end
end

def index?
@user.admin?
end

def update?
@user.admin?
end
end
1 change: 1 addition & 0 deletions app/views/admin/feature_flags/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<%= render Admin::FeatureFlags::FeatureFlagsListComponent.new(feature_flags: @feature_flags) %>
jsuchal marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@

resources :users

get "/feature_flags/", to: "feature_flags#index"
patch "/feature_flags/", to: "feature_flags#update"
jsuchal marked this conversation as resolved.
Show resolved Hide resolved

resources :boxes, only: :index
namespace :boxes do
resources :upvs_boxes, except: [:index, :destroy]
Expand Down
19 changes: 19 additions & 0 deletions test/system/admin/feature_flags_management_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
require "application_system_test_case"

class FeatureFlagsManagementTest < ApplicationSystemTestCase
setup do
sign_in_as(:admin)
visit root_path
click_link "Nastavenia"
click_link "Feature flags"
jsuchal marked this conversation as resolved.
Show resolved Hide resolved
end

test "admin can enable and disable a feature" do
available_features = users(:admin).tenant.list_features.pluck(:name)
enabled = users(:admin).tenant.feature_enabled?(available_features[0].to_sym)
click_button available_features[0]
assert_button available_features[0]
users(:admin).tenant.reload
assert_not_equal enabled, users(:admin).tenant.feature_enabled?(available_features[0].to_sym)
end
end