# frozen_string_literal: true module API class Features < ::API::Base before { authenticated_as_admin! } feature_category :feature_flags urgency :low # TODO: remove these helpers with feature flag set_feature_flag_service helpers do def gate_value(params) case params[:value] when 'true' true when '0', 'false' false else # https://github.com/jnunemaker/flipper/blob/master/lib/flipper/typecast.rb#L47 if params[:value].to_s.include?('.') params[:value].to_f else params[:value].to_i end end end def gate_key(params) case params[:key] when 'percentage_of_actors' :percentage_of_actors else :percentage_of_time end end def gate_targets(params) Feature::Target.new(params).targets end def gate_specified?(params) Feature::Target.new(params).gate_specified? end end resource :features do desc 'List all features' do detail 'Get a list of all persisted features, with its gate values.' success Entities::Feature is_array true tags %w[features] end get do features = Feature.all present features, with: Entities::Feature, current_user: current_user end desc 'List all feature definitions' do detail 'Get a list of all feature definitions.' success Entities::Feature::Definition is_array true tags %w[features] end get :definitions do definitions = ::Feature::Definition.definitions.values.map(&:to_h) present definitions, with: Entities::Feature::Definition, current_user: current_user end desc 'Set or create a feature' do detail "Set a feature's gate value. If a feature with the given name doesn't exist yet, it's created. " \ "The value can be a boolean, or an integer to indicate percentage of time." success Entities::Feature tags %w[features] end params do requires :value, types: [String, Integer], desc: '`true` or `false` to enable/disable, or an integer for percentage of time' optional :key, type: String, desc: '`percentage_of_actors` or `percentage_of_time` (default)' optional :feature_group, type: String, desc: 'A Feature group name' optional :user, type: String, desc: 'A GitLab username or comma-separated multiple usernames' optional :group, type: String, desc: "A GitLab group's path, for example `gitlab-org`, or comma-separated multiple group paths" optional :namespace, type: String, desc: "A GitLab group or user namespace's path, for example `john-doe`, or comma-separated " \ "multiple namespace paths. Introduced in GitLab 15.0." optional :project, type: String, desc: "A projects path, for example `gitlab-org/gitlab-foss`, or comma-separated multiple project paths" optional :force, type: Boolean, desc: 'Skip feature flag validation checks, such as a YAML definition' mutually_exclusive :key, :feature_group mutually_exclusive :key, :user mutually_exclusive :key, :group mutually_exclusive :key, :namespace mutually_exclusive :key, :project end post ':name' do if Feature.enabled?(:set_feature_flag_service) flag_params = declared_params(include_missing: false) response = ::Admin::SetFeatureFlagService .new(feature_flag_name: params[:name], params: flag_params) .execute if response.success? present response.payload[:feature_flag], with: Entities::Feature, current_user: current_user else bad_request!(response.message) end else validate_feature_flag_name!(params[:name]) unless params[:force] targets = gate_targets(params) value = gate_value(params) key = gate_key(params) case value when true if gate_specified?(params) targets.each { |target| Feature.enable(params[:name], target) } else Feature.enable(params[:name]) end when false if gate_specified?(params) targets.each { |target| Feature.disable(params[:name], target) } else Feature.disable(params[:name]) end else if key == :percentage_of_actors Feature.enable_percentage_of_actors(params[:name], value) else Feature.enable_percentage_of_time(params[:name], value) end end present Feature.get(params[:name]), # rubocop:disable Gitlab/AvoidFeatureGet with: Entities::Feature, current_user: current_user end rescue Feature::Target::UnknowTargetError => e bad_request!(e.message) end desc 'Delete a feature' do detail "Removes a feature gate. Response is equal when the gate exists, or doesn't." tags %w[features] end delete ':name' do Feature.remove(params[:name]) no_content! end end # TODO: remove this helper with feature flag set_feature_flag_service helpers do def validate_feature_flag_name!(name) # no-op end end end end API::Features.prepend_mod_with('API::Features')