Validate flow trigger filter against allowlist
What does this MR do and why?
This MR addresses #601346 (closed)
As part of the triggers work, we introduced actions in the workers that are passed as filter_data, which FilterEvaluator then compares against the trigger's stored filter rules (set on the frontend).
There was a gap where a trigger saved with an invalid filter value would save cleanly but silently never fire. This MR validates the flow trigger filter against an allowlist, so an invalid filter action now fails with an explicit error rather than failing silently.
Verification (rails console)
- Start rails console:
gdk rails c
# Check the flag is off by default
project = Project.first
Feature.enabled?(:validate_flow_trigger_filter_actions, project)
# => false
# Build a trigger with an invalid action — should be valid (flag off)
trigger = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:merge_request]],
filter: {
'merge_request' => {
'match' => 'all',
'rules' => [{ 'field' => 'action', 'operator' => 'eq', 'value' => 'bad_value' }]
}
}
)
trigger.valid?
trigger.errors[:filter]
# => [] (no filter errors)
# Enable the flag
Feature.enable(:validate_flow_trigger_filter_actions)
# Now the same trigger should fail validation
trigger.valid?
trigger.errors[:filter]
# => ["contains invalid action values for merge_request: bad_value. Allowed values are: approved"]
# --- Valid cases ---
# merge_request with 'approved' action
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:merge_request]],
filter: { 'merge_request' => { 'match' => 'all', 'rules' => [{ 'field' => 'action', 'operator' => 'eq', 'value' => 'approved' }] } }
)
t.valid?
puts "Valid approved action: #{t.errors[:filter].empty?}" # expect true
# work_item with 'created' action
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:work_item]],
filter: { 'work_item' => { 'match' => 'all', 'rules' => [{ 'field' => 'action', 'operator' => 'eq', 'value' => 'created' }] } }
)
t.valid?
puts "Valid created action: #{t.errors[:filter].empty?}" # expect true
# --- Invalid cases ---
# merge_request Typo: 'approveed'
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:merge_request]],
filter: { 'merge_request' => { 'match' => 'all', 'rules' => [{ 'field' => 'action', 'operator' => 'eq', 'value' => 'approveed' }] } }
)
t.valid?
puts "Typo rejected: #{t.errors[:filter].join}" # expect error message
# Invalid work_item action
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:work_item]],
filter: { 'work_item' => { 'match' => 'all', 'rules' => [{ 'field' => 'action', 'operator' => 'in', 'value' => %w[created invalid_action] }] } }
)
t.valid?
puts "invalid_action in array rejected: #{t.errors[:filter].join}" # expect error message
# Invalid action in nested group
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:merge_request]],
filter: { 'merge_request' => { 'match' => 'all', 'rules' => [{ 'type' => 'group', 'match' => 'any', 'rules' => [{ 'field' => 'action', 'operator' => 'eq', 'value' => 'invalid_action' }] }] } }
)
t.valid?
puts "Nested group rejected: #{t.errors[:filter].join}" # expect error message
# --- No-op cases (should not interfere) ---
# Empty filter
t = Ai::FlowTrigger.new(
project: project,
event_types: [Ai::FlowTrigger::EVENT_TYPES[:merge_request]],
filter: {}
)
t.valid?
puts "Empty filter OK: #{t.errors[:filter].empty?}" # expect trueFeature flag rollout issue
References
Screenshots or screen recordings
| Before | After |
|---|---|
How to set up and validate locally
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
Edited by Shola Quadri