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 all 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,21 @@
<div class="self-stretch p-4 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">
<%= t "feature_flags.#{@feature_flag}.name"%>
</div>
<div class="text-center text-gray-500 text-base font-normal leading-normal">
<%= t "feature_flags.#{@feature_flag}.description"%>
</div>
</div>
</div>
</div>
<%= form_with model: Current.tenant, url: admin_tenant_feature_flag_path(Current.tenant, @feature_flag), title: "Toggle feature state", method: :patch do |form| %>
<%= form.hidden_field :enabled, value: !@enabled %>
<%= form.button name: @feature_flag, class: "#{@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: @enabled } do %>
<span class="sr-only">Use setting</span>
<span aria-hidden="true" class="<%= @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 %>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
class Admin::FeatureFlags::FeatureFlagsListRowComponent < ViewComponent::Base
def initialize(flag, enabled_features)
@feature_flag = flag
@enabled = enabled_features.include?(flag)
end
end
3 changes: 3 additions & 0 deletions app/components/common/icon_component.rb
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ 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",
"puzzle-piece" => "M14.25 6.087c0-.355.186-.676.401-.959.221-.29.349-.634.349-1.003 0-1.036-1.007-1.875-2.25-1.875s-2.25.84-2.25 1.875c0 .369.128.713.349 1.003.215.283.401.604.401.959v0a.64.64 0 0 1-.657.643 48.39 48.39 0 0 1-4.163-.3c.186 1.613.293 3.25.315 4.907a.656.656 0 0 1-.658.663v0c-.355 0-.676-.186-.959-.401a1.647 1.647 0 0 0-1.003-.349c-1.036 0-1.875 1.007-1.875 2.25s.84 2.25 1.875 2.25c.369 0 .713-.128 1.003-.349.283-.215.604-.401.959-.401v0c.31 0 .555.26.532.57a48.039 48.039 0 0 1-.642 5.056c1.518.19 3.058.309 4.616.354a.64.64 0 0 0 .657-.643v0c0-.355-.186-.676-.401-.959a1.647 1.647 0 0 1-.349-1.003c0-1.035 1.008-1.875 2.25-1.875 1.243 0 2.25.84 2.25 1.875 0 .369-.128.713-.349 1.003-.215.283-.4.604-.4.959v0c0 .333.277.599.61.58a48.1 48.1 0 0 0 5.427-.63 48.05 48.05 0 0 0 .582-4.717.532.532 0 0 0-.533-.57v0c-.355 0-.676.186-.959.401-.29.221-.634.349-1.003.349-1.035 0-1.875-1.007-1.875-2.25s.84-2.25 1.875-2.25c.37 0 .713.128 1.003.349.283.215.604.401.96.401v0a.656.656 0 0 0 .658-.663 48.422 48.422 0 0 0-.37-5.36c-1.886.342-3.81.574-5.766.689a.578.578 0 0 1-.61-.58v0Z"

}.freeze

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

def index
authorize([:admin, :feature_flag])
if params.include?(:labs)
@feature_flags = @tenant.list_all_features
else
@feature_flags = @tenant.list_available_features
end
@enabled_features = @tenant.feature_flags
end

def update
authorize([:admin, :feature_flag])
if feature_flags_params[:enabled] == "true"
@tenant.feature_flags << params[:id]
else
@tenant.feature_flags.delete(params[:id])
end
@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(:enabled)
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: 'Funkcie', url: admin_tenant_feature_flags_path(Current.tenant), icon: Common::IconComponent.new("puzzle-piece"))
]
end

Expand Down
15 changes: 12 additions & 3 deletions app/models/tenant.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,15 @@ class Tenant < ApplicationRecord

validates_presence_of :name

AVAILABLE_FEATURE_FLAGS = [:audit_log, :archive, :api, :message_draft_import, :fs_api, :fs_sync]
AVAILABLE_FEATURE_FLAGS = [:audit_log, :archive, :api, :fs_sync]
ALL_FEATURE_FLAGS = [:audit_log, :archive, :api, :message_draft_import, :fs_api, :fs_sync]

def draft_tag!
draft_tag || raise(ActiveRecord::RecordNotFound, "`DraftTag` not found in tenant: #{id}")
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 All @@ -67,7 +68,7 @@ def user_signature_tags
end

def feature_enabled?(feature)
raise "Unknown feature #{feature}" unless feature.in? AVAILABLE_FEATURE_FLAGS
raise "Unknown feature #{feature}" unless feature.in? ALL_FEATURE_FLAGS

feature.to_s.in? feature_flags
end
Expand All @@ -88,6 +89,14 @@ def disable_feature(feature)
save!
end

def list_available_features
AVAILABLE_FEATURE_FLAGS
end

def list_all_features
ALL_FEATURE_FLAGS
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
12 changes: 12 additions & 0 deletions app/views/admin/feature_flags/index.html.erb
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]">Aktivácia rozšírení</div>
</div>
<% @feature_flags.each do |flag| %>
<div class="self-stretch flex-col justify-start items-start flex">
<%= render Admin::FeatureFlags::FeatureFlagsListRowComponent.new(flag.to_s, @enabled_features) %>
</div>
<% end %>
</div>
</div>
19 changes: 19 additions & 0 deletions config/locales/sk.yml
Original file line number Diff line number Diff line change
Expand Up @@ -341,3 +341,22 @@ sk:
notifications:
header: "Žiadne notifikácie"
description: "Nemáte žiadne notifikácie."
feature_flags:
api:
name: "API"
description: "Prístup k vybraným funkciám systému a dátam cez API"
archive:
name: "Archive"
description: "Archivácia správ"
audit_log:
name: "Audit log"
description: "Zaznamenávanie a prezeranie auditných záznamov o činnosti používateľov"
fs_api:
name: "API finančnej správy"
description: "Funkčnosť prepojenia s finančnou správou"
fs_sync:
name: "Synchronizácia schránky z finančnej správy"
description: "Synchronizácia schránky z finančnej správy"
message_draft_import:
name: "Import správ"
description: "Funkcionalita pre hromadné zasielanie správ"
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
end

resources :users
resources :feature_flags, only: [:index, :update]

resources :boxes, only: :index
namespace :boxes do
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 "Funkcie"
end

test "admin can enable and disable a feature" do
available_features = users(:admin).tenant.list_available_features
enabled = users(:admin).tenant.feature_enabled?(available_features[0])
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])
end
end
Loading