Class: Magick::AdminUI::FeaturesController

Inherits:
ActionController::Base
  • Object
show all
Includes:
ActionController::RequestForgeryProtection, Authentication
Defined in:
app/controllers/magick/adminui/features_controller.rb

Constant Summary collapse

FORM_UNMANAGED_TARGETING_KEYS =

Targeting rules the edit form has no fields for. They are carried over from the current state on every submit so a form save can never silently destroy them. (:variants is preserved by replace_targeting itself.)

%i[
  group excluded_groups ip_address excluded_ip_addresses
  date_range custom_attributes complex_conditions
].freeze

Instance Method Summary collapse

Methods included from Authentication

decision, included

Instance Method Details

#available_roles ⇒ Object



39
40
41
# File 'app/controllers/magick/adminui/features_controller.rb', line 39

def available_roles
  Magick::AdminUI.config.available_roles || []
end

#available_tags ⇒ Object



43
44
45
# File 'app/controllers/magick/adminui/features_controller.rb', line 43

def available_tags
  Magick::AdminUI.config.tags
end

#disable ⇒ Object



106
107
108
109
# File 'app/controllers/magick/adminui/features_controller.rb', line 106

def disable
  @feature.disable
  redirect_to magick_admin_ui.features_path, notice: 'Feature disabled'
end

#disable_for_role ⇒ Object



126
127
128
129
130
131
132
133
134
# File 'app/controllers/magick/adminui/features_controller.rb', line 126

def disable_for_role
  role = params[:role]
  if role.present?
    @feature.disable_for_role(role)
    redirect_to magick_admin_ui.feature_path(@feature.name), notice: "Feature disabled for role: #{role}"
  else
    redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Role is required'
  end
end

#edit ⇒ Object



76
77
# File 'app/controllers/magick/adminui/features_controller.rb', line 76

def edit
end

#enable ⇒ Object



101
102
103
104
# File 'app/controllers/magick/adminui/features_controller.rb', line 101

def enable
  @feature.enable
  redirect_to magick_admin_ui.features_path, notice: 'Feature enabled'
end

#enable_for_role ⇒ Object



116
117
118
119
120
121
122
123
124
# File 'app/controllers/magick/adminui/features_controller.rb', line 116

def enable_for_role
  role = params[:role]
  if role.present?
    @feature.enable_for_role(role)
    redirect_to magick_admin_ui.feature_path(@feature.name), notice: "Feature enabled for role: #{role}"
  else
    redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Role is required'
  end
end

#enable_for_user ⇒ Object



111
112
113
114
# File 'app/controllers/magick/adminui/features_controller.rb', line 111

def enable_for_user
  @feature.enable_for_user(params[:user_id])
  redirect_to magick_admin_ui.feature_path(@feature.name), notice: 'Feature enabled for user'
end

#index ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# File 'app/controllers/magick/adminui/features_controller.rb', line 51

def index
  @features = Magick.features.values

  # Filter by group if provided
  if params[:group].present?
    @features = @features.select { |f| f.group == params[:group] }
  end

  # Filter by search query (name or description)
  if params[:search].present?
    search_term = params[:search].downcase
    @features = @features.select do |f|
      f.name.downcase.include?(search_term) ||
      (f.display_name && f.display_name.downcase.include?(search_term)) ||
      (f.description && f.description.downcase.include?(search_term))
    end
  end

  # Get all available groups for filter dropdown
  @available_groups = Magick.features.values.map(&:group).compact.uniq.sort
end

#magick_admin_ui ⇒ Object



35
36
37
# File 'app/controllers/magick/adminui/features_controller.rb', line 35

def magick_admin_ui
  Magick::AdminUI::Engine.routes.url_helpers
end

#partially_enabled?(feature) ⇒ Boolean

Returns:

  • (Boolean)


47
48
49
# File 'app/controllers/magick/adminui/features_controller.rb', line 47

def partially_enabled?(feature)
  (feature.targeting || {}).any?
end

#show ⇒ Object



73
74
# File 'app/controllers/magick/adminui/features_controller.rb', line 73

def show
end

#update ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# File 'app/controllers/magick/adminui/features_controller.rb', line 79

def update
  # Update group if provided
  if params.key?(:group)
    @feature.set_group(params[:group])
  end

  if @feature.type == :boolean
    # For boolean features, checkbox sends 'true' when checked, nothing when unchecked
    # Rails form helpers handle this - if checkbox is unchecked, params[:value] will be nil
    value = params[:value] == 'true'
    @feature.set_value(value)
  elsif params[:value].present?
    # For string/number features, convert to appropriate type
    value = params[:value]
    if @feature.type == :number
      value = value.include?('.') ? value.to_f : value.to_i
    end
    @feature.set_value(value)
  end
  redirect_to magick_admin_ui.feature_path(@feature.name), notice: 'Feature updated successfully'
end

#update_targeting ⇒ Object



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'app/controllers/magick/adminui/features_controller.rb', line 136

def update_targeting
  targeting_params = params[:targeting] || {}
  unless hash_like?(targeting_params)
    redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Invalid targeting payload.'
    return
  end

  # Ensure we're using the registered feature instance
  feature_name = @feature.name.to_s
  @feature = Magick.features[feature_name] if Magick.features.key?(feature_name)

  # One declarative write: one audit entry + one version per submit.
  @feature.replace_targeting(desired_targeting_from_form(targeting_params))
  @feature.reload

  redirect_to magick_admin_ui.feature_path(@feature.name), notice: 'Targeting updated successfully'
rescue Magick::InvalidTargetingError => e
  redirect_to magick_admin_ui.feature_path(@feature.name), alert: "Invalid targeting: #{e.message}"
rescue StandardError => e
  ::Rails.logger.error "Magick: Error updating targeting for #{@feature.name}: #{e.class}: #{e.message}\n#{e.backtrace.first(5).join("\n")}" if defined?(::Rails)
  redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Could not update targeting — see server logs for details.'
end

#update_variants ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'app/controllers/magick/adminui/features_controller.rb', line 159

def update_variants
  variants_data = []

  if params[:variants].present? && !hash_like?(params[:variants])
    redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Invalid variants payload.'
    return
  end

  if params[:variants].present?
    params[:variants].each do |_index, variant_params|
      next if variant_params[:name].blank?

      variants_data << {
        name: variant_params[:name].strip,
        value: variant_params[:value].to_s.strip,
        weight: (variant_params[:weight].presence || 0).to_f
      }
    end
  end

  if variants_data.any?
    @feature.set_variants(variants_data)
  else
    # Clear variants if all removed
    @feature.instance_variable_get(:@targeting)&.delete(:variants)
    @feature.save_targeting
  end

  redirect_to magick_admin_ui.feature_path(@feature.name), notice: 'Variants updated successfully'
rescue StandardError => e
  ::Rails.logger.error "Magick: Error updating variants for #{@feature.name}: #{e.class}: #{e.message}" if defined?(::Rails)
  redirect_to magick_admin_ui.feature_path(@feature.name), alert: 'Could not update variants — see server logs for details.'
end