Commit d2686fe9 authored by Kamil Trzciński's avatar Kamil Trzciński

Add feature flags controller specs

parent 7b2af27a
class Projects::FeatureFlagsController < Projects::ApplicationController
respond_to :html
before_action :authorize_read_feature_flags!
before_action :authorize_update_feature_flags!, only: [:edit, :update]
before_action :authorize_admin_feature_flags!, only: [:destroy]
before_action :authorize_read_feature_flag!
before_action :authorize_update_feature_flag!, only: [:edit, :update]
before_action :authorize_destroy_feature_flag!, only: [:destroy]
before_action :feature_flag, only: [:edit, :update, :destroy]
def index
@feature_flags = project.operations_feature_flags
.order(:name)
.ordered
.page(params[:page]).per(30)
end
......
......@@ -25,7 +25,7 @@ module EE
nav_tabs << :packages
end
if can?(current_user, :read_feature_flags, project) && !nav_tabs.include?(:operations)
if can?(current_user, :read_feature_flag, project) && !nav_tabs.include?(:operations)
nav_tabs << :operations
end
......@@ -35,7 +35,7 @@ module EE
override :tab_ability_map
def tab_ability_map
tab_ability_map = super
tab_ability_map[:feature_flags] = :read_feature_flags
tab_ability_map[:feature_flags] = :read_feature_flag
tab_ability_map
end
......
......@@ -14,5 +14,7 @@ module Operations
}
validates :name, uniqueness: { scope: :project_id }
validates :description, allow_blank: true, length: 0..255
scope :ordered, -> { order(:name) }
end
end
......@@ -99,9 +99,11 @@ module EE
enable :admin_board
enable :admin_vulnerability_feedback
enable :create_package
enable :read_feature_flags
enable :create_feature_flags
enable :update_feature_flags
enable :read_feature_flag
enable :create_feature_flag
enable :update_feature_flag
enable :destroy_feature_flag
enable :admin_feature_flag
end
rule { can?(:public_access) }.enable :read_package
......@@ -125,7 +127,6 @@ module EE
enable :admin_path_locks
enable :update_approvers
enable :destroy_package
enable :admin_feature_flags
end
rule { license_management_enabled & can?(:maintainer_access) }.enable :admin_software_license_policy
......
%button.btn.btn-primary.btn-inverted.append-right-8{ type: 'button', data: { toggle: 'modal', target: '#configure-feature-flags-modal' } }>
= s_('FeatureFlags|Configure')
- if can?(current_user, :admin_feature_flag, @project)
%button.btn.btn-primary.btn-inverted.append-right-8{ type: 'button', data: { toggle: 'modal', target: '#configure-feature-flags-modal' } }>
= s_('FeatureFlags|Configure')
#configure-feature-flags-modal.modal{ tabindex: -1,
role: 'dialog' }
.modal-dialog{ role: 'document' }
.modal-content
.modal-header
%h5.modal-title
= s_('FeatureFlags|Configure feature flags')
%button.close{ type: 'button', data: { dismiss: 'modal' }, aria: { label: _('Close') } }
%span{ "aria-hidden": true } &times;
.modal-body
%p
- client_libraries_url = help_page_path("user/project/operations/feature_flags", anchor: "client-libraries")
= s_('FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup.').html_safe % { docs_link_start: %Q{<a href="#{client_libraries_url}">}.html_safe,
docs_link_end: '</a>'.html_safe }
= link_to s_('FeatureFlags|More information'), help_page_path("user/project/operations/feature_flags")
- if can?(current_user, :admin_feature_flag, @project)
#configure-feature-flags-modal.modal{ tabindex: -1,
role: 'dialog' }
.modal-dialog{ role: 'document' }
.modal-content
.modal-header
%h5.modal-title
= s_('FeatureFlags|Configure feature flags')
%button.close{ type: 'button', data: { dismiss: 'modal' }, aria: { label: _('Close') } }
%span{ "aria-hidden": true } &times;
.modal-body
%p
- client_libraries_url = help_page_path("user/project/operations/feature_flags", anchor: "client-libraries")
= s_('FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup.').html_safe % { docs_link_start: %Q{<a href="#{client_libraries_url}">}.html_safe,
docs_link_end: '</a>'.html_safe }
= link_to s_('FeatureFlags|More information'), help_page_path("user/project/operations/feature_flags")
.form-group
= label_tag :api_url, s_('FeatureFlags|API URL'), class: 'label-bold'
.input-group
= text_field_tag :api_url,
unleash_api_url(@project),
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#api_url',
title: _("Copy URL to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.form-group
= label_tag :api_url, s_('FeatureFlags|API URL'), class: 'label-bold'
.input-group
= text_field_tag :api_url,
unleash_api_url(@project),
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#api_url',
title: _("Copy URL to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.form-group
= label_tag :instance_id, s_('FeatureFlags|Instance ID'), class: 'label-bold'
.input-group
= text_field_tag :instance_id,
unleash_api_instanceid(@project),
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#instance_id',
title: _("Copy ID to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.form-group
= label_tag :instance_id, s_('FeatureFlags|Instance ID'), class: 'label-bold'
.input-group
= text_field_tag :instance_id,
unleash_api_instanceid(@project),
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#instance_id',
title: _("Copy ID to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.form-group
= label_tag :application_name, s_('FeatureFlags|Application name'), class: 'label-bold'
.input-group
= text_field_tag :application_name,
"production",
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#application_name',
title: _("Copy name to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.form-group
= label_tag :application_name, s_('FeatureFlags|Application name'), class: 'label-bold'
.input-group
= text_field_tag :application_name,
"production",
readonly: true,
class: "form-control js-select-on-focus"
%span.input-group-append
= clipboard_button(target: '#application_name',
title: _("Copy name to clipboard"),
placement: "left",
container: '#configure-feature-flags-modal',
class: "input-group-text btn btn-default")
.modal{ id: "delete-feature-flag-modal-#{feature_flag.id}",
tabindex: -1,
role: 'dialog' }
.modal-dialog{ role: 'document' }
.modal-content
.modal-header
%h5.modal-title.d-flex.mw-100
- truncated_feature_flag_name = capture do
%span.text-truncate.prepend-left-4.append-right-4= feature_flag.name
= s_('FeatureFlags|Delete %{feature_flag_name}?').html_safe % { feature_flag_name: truncated_feature_flag_name }
%button.close{ type: 'button', data: { dismiss: 'modal' }, aria: { label: _('Close') } }
%span{ "aria-hidden": true } &times;
.modal-body
%p
- monospace_feature_flag_name = capture do
%span.text-monospace= feature_flag.name
= s_('FeatureFlags|Feature flag %{feature_flag_name} will be removed. Are you sure?').html_safe % { feature_flag_name: monospace_feature_flag_name }
.modal-footer
%button{ type: 'button', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
- if can?(current_user, :destroy_feature_flag, @project)
.modal{ id: "delete-feature-flag-modal-#{feature_flag.id}",
tabindex: -1,
role: 'dialog' }
.modal-dialog{ role: 'document' }
.modal-content
.modal-header
%h5.modal-title.d-flex.mw-100
- truncated_feature_flag_name = capture do
%span.text-truncate.prepend-left-4.append-right-4= feature_flag.name
= s_('FeatureFlags|Delete %{feature_flag_name}?').html_safe % { feature_flag_name: truncated_feature_flag_name }
%button.close{ type: 'button', data: { dismiss: 'modal' }, aria: { label: _('Close') } }
%span{ "aria-hidden": true } &times;
.modal-body
%p
- monospace_feature_flag_name = capture do
%span.text-monospace= feature_flag.name
= s_('FeatureFlags|Feature flag %{feature_flag_name} will be removed. Are you sure?').html_safe % { feature_flag_name: monospace_feature_flag_name }
.modal-footer
%button{ type: 'button', data: { dismiss: 'modal' }, class: 'btn btn-default' } Cancel
= button_to 'Delete',
project_feature_flag_path(@project, feature_flag),
title: 'Delete',
method: :delete,
class: 'btn btn-remove'
= button_to 'Delete',
project_feature_flag_path(@project, feature_flag),
title: 'Delete',
method: :delete,
class: 'btn btn-remove'
#error_explanation
.alert.alert-danger
- @feature_flag.errors.full_messages.each do |msg|
%p= msg
- if @feature_flag.errors.any?
#error_explanation
.alert.alert-danger
- @feature_flag.errors.full_messages.each do |msg|
%p= msg
= render 'errors'
%fieldset
.row
......
- if can?(current_user, :create_feature_flags, @project)
- if can?(current_user, :create_feature_flag, @project)
= link_to new_project_feature_flag_path(@project), class: 'btn btn-success' do
= s_('FeatureFlags|New Feature Flag')
......@@ -24,16 +24,18 @@
.table-section.section-40.table-button-footer{ role: 'gridcell' }
.table-action-buttons.btn-group
= link_to edit_project_feature_flag_path(@project, feature_flag),
class: 'btn btn-default has-tooltip',
type: 'button',
title: _('Edit') do
= sprite_icon('pencil', size: 16)
- if can?(current_user, :update_feature_flag, @project)
= link_to edit_project_feature_flag_path(@project, feature_flag),
class: 'btn btn-default has-tooltip',
type: 'button',
title: _('Edit') do
= sprite_icon('pencil', size: 16)
%button.btn.btn-danger.has-tooltip{ type: 'button',
data: { toggle: 'modal',
target: "#delete-feature-flag-modal-#{feature_flag.id}" },
title: _('Delete') }
= sprite_icon('remove', size: 16)
- if can?(current_user, :destroy_feature_flag, @project)
%button.btn.btn-danger.has-tooltip{ type: 'button',
data: { toggle: 'modal',
target: "#delete-feature-flag-modal-#{feature_flag.id}" },
title: _('Delete') }
= sprite_icon('remove', size: 16)
= paginate @feature_flags, theme: "gitlab"
require 'spec_helper'
describe Projects::FeatureFlagsController do
include Rails.application.routes.url_helpers
set(:user) { create(:user) }
set(:project) { create(:project) }
before do
project.add_developer(user)
sign_in(user)
end
describe 'GET index' do
render_views
subject { get(:index, view_params) }
context 'when there is no feature flags' do
before do
subject
end
it 'shows an empty state with buttons' do
expect(response).to be_ok
expect(response).to render_template('_empty_state')
expect(response).to render_template('_configure_feature_flags_button')
expect(response).to render_template('_new_feature_flag_button')
end
end
context 'for a list of feature flags' do
let!(:feature_flags) { create_list(:operations_feature_flag, 50, project: project) }
before do
subject
end
it 'shows an list of feature flags with buttons' do
expect(response).to be_ok
expect(response).to render_template('_table')
expect(response).to render_template('_configure_feature_flags_button')
expect(response).to render_template('_new_feature_flag_button')
end
end
end
describe 'GET new' do
render_views
subject { get(:new, view_params) }
it 'renders the form' do
subject
expect(response).to be_ok
expect(response).to render_template('new')
expect(response).to render_template('_form')
end
end
describe 'POST create' do
render_views
subject { post(:create, params) }
context 'when creating a new feature flag' do
let(:params) do
view_params.merge(operations_feature_flag: { name: 'my_feature_flag', active: true })
end
it 'creates and redirects to list' do
subject
expect(response).to redirect_to(project_feature_flags_path(project))
end
end
context 'when a feature flag already exists' do
let!(:feature_flag) { create(:operations_feature_flag, project: project, name: 'my_feature_flag') }
let(:params) do
view_params.merge(operations_feature_flag: { name: 'my_feature_flag', active: true })
end
it 'shows an error' do
subject
expect(response).to render_template('new')
expect(response).to render_template('_errors')
end
end
end
describe 'PUT update' do
let!(:feature_flag) { create(:operations_feature_flag, project: project, name: 'my_feature_flag') }
render_views
subject { post(:create, params) }
context 'when updating an existing feature flag' do
let(:params) do
view_params.merge(
id: feature_flag.id,
operations_feature_flag: { name: 'my_feature_flag_v2', active: true }
)
end
it 'updates and redirects to list' do
subject
expect(response).to redirect_to(project_feature_flags_path(project))
end
end
context 'when using existing name of the feature flag' do
let!(:other_feature_flag) { create(:operations_feature_flag, project: project, name: 'other_feature_flag') }
let(:params) do
view_params.merge(operations_feature_flag: { name: 'other_feature_flag', active: true })
end
it 'shows an error' do
subject
expect(response).to render_template('new')
expect(response).to render_template('_errors')
end
end
end
private
def view_params
{ namespace_id: project.namespace, project_id: project }
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment