Skip to content
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
5 changes: 5 additions & 0 deletions .changeset/symbol-flag-keys.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'posthog-ruby': patch
---

Accept symbol feature flag keys in flag APIs.
12 changes: 7 additions & 5 deletions lib/posthog/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -364,15 +364,15 @@ def is_feature_enabled( # rubocop:disable Naming/PredicateName
!!response
end

# @param [String] flag_key The unique flag key of the feature flag
# @param [String, Symbol] flag_key The unique flag key of the feature flag
Comment thread
marandaneto marked this conversation as resolved.
# @return [String] The decrypted value of the feature flag payload
def get_remote_config_payload(flag_key)
@feature_flags_poller.get_remote_config_payload(flag_key)
@feature_flags_poller.get_remote_config_payload(flag_key.to_s)
end

# Returns whether the given feature flag is enabled for the given user or not
#
# @param [String] key The key of the feature flag
# @param [String, Symbol] key The key of the feature flag
# @param [String] distinct_id The distinct id of the user
# @param [Hash] groups
# @param [Hash] person_properties key-value pairs of properties to associate with the user.
Expand Down Expand Up @@ -455,7 +455,7 @@ def get_feature_flag_result(
# @param [Hash] group_properties
# @param [Boolean] only_evaluate_locally Skip the remote /flags call entirely
# @param [Boolean] disable_geoip Stamped on captured access events
# @param [Array<String>] flag_keys When set, scopes the underlying /flags
# @param [Array<String, Symbol>] flag_keys When set, scopes the underlying /flags
# request to only these flag keys (sent as `flag_keys_to_evaluate`).
# Distinct from {FeatureFlagEvaluations#only}, which filters the
# already-fetched snapshot in memory.
Expand Down Expand Up @@ -598,7 +598,7 @@ def get_all_flags(
# @deprecated Use {#get_feature_flag_result} instead, which returns both the flag value and payload
# and properly raises the $feature_flag_called event.
#
# @param [String] key The key of the feature flag
# @param [String, Symbol] key The key of the feature flag
# @param [String] distinct_id The distinct id of the user
# @option [String or boolean] match_value The value of the feature flag to be matched
# @option [Hash] groups
Expand All @@ -623,6 +623,7 @@ def get_feature_flag_payload(
'instead — this consolidates flag evaluation into a single `/flags` request per ' \
'incoming request.'
)
key = key.to_s
person_properties, group_properties = add_local_person_and_group_properties(distinct_id, groups,
person_properties, group_properties)
@feature_flags_poller.get_feature_flag_payload(key, distinct_id, match_value, groups, person_properties,
Expand Down Expand Up @@ -725,6 +726,7 @@ def _get_feature_flag_result(
only_evaluate_locally: false,
send_feature_flag_events: true
)
key = key.to_s
person_properties, group_properties = add_local_person_and_group_properties(
distinct_id, groups, person_properties, group_properties
)
Expand Down
8 changes: 7 additions & 1 deletion lib/posthog/feature_flags.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def get_feature_payloads(

def get_flags(distinct_id, groups = {}, person_properties = {}, group_properties = {}, flag_keys = nil,
disable_geoip = nil)
flag_keys = flag_keys.map(&:to_s) if flag_keys.is_a?(Array)
request_data = {
distinct_id: distinct_id,
groups: groups,
Expand Down Expand Up @@ -160,7 +161,7 @@ def get_flags(distinct_id, groups = {}, person_properties = {}, group_properties
end

def get_remote_config_payload(flag_key)
_request_remote_config_payload(flag_key)
_request_remote_config_payload(flag_key.to_s)
end

def get_feature_flag(
Expand All @@ -171,6 +172,8 @@ def get_feature_flag(
group_properties = {},
only_evaluate_locally = false
)
key = key.to_s

# make sure they're loaded on first run
load_feature_flags

Expand Down Expand Up @@ -363,6 +366,8 @@ def get_feature_flag_payload(
group_properties = {},
only_evaluate_locally = false
)
key = key.to_s

if match_value.nil?
match_value = get_feature_flag(
key,
Expand Down Expand Up @@ -923,6 +928,7 @@ def _compute_flag_locally(flag, distinct_id, groups = {}, person_properties = {}
def _compute_flag_payload_locally(key, match_value)
return nil if @feature_flags_by_key.nil?

key = key.to_s
response = nil
if [true, false].include? match_value
response = @feature_flags_by_key.dig(key, :filters, :payloads, match_value.to_s.to_sym)
Expand Down
18 changes: 18 additions & 0 deletions spec/posthog/feature_flag_evaluations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,16 @@ def capture_stderr
expect(unknown[:properties]['$feature_flag_error']).to eq('flag_missing')
end

it 'accepts symbol flag keys when reading a snapshot' do
stub_flags(flags_response)
snapshot = client.evaluate_flags('user-1')

expect(snapshot.enabled?(:'boolean-flag')).to be(true)
expect(snapshot.get_flag(:'variant-flag')).to eq('variant-value')
expect(snapshot.get_flag_payload(:'variant-flag')).to eq('key' => 'value')
expect(snapshot.only(:'boolean-flag').keys).to eq(['boolean-flag'])
end

it 'enabled? returns false for unknown flags' do
stub_flags(flags_response)
snapshot = client.evaluate_flags('user-1')
Expand Down Expand Up @@ -156,6 +166,14 @@ def capture_stderr
)
end

it 'accepts symbol keys in the flag_keys filter array' do
stub_flags(flags_response)
client.evaluate_flags('user-1', flag_keys: [:'boolean-flag'])
expect(WebMock).to have_requested(:post, FLAGS_ENDPOINT).with(
body: hash_including(flag_keys_to_evaluate: %w[boolean-flag])
)
end

it 'returns a usable empty snapshot for empty distinct_id and does not call /flags' do
stub_flags(flags_response)
snapshot = client.evaluate_flags('')
Expand Down
36 changes: 36 additions & 0 deletions spec/posthog/feature_flag_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,42 @@ module PostHog
person_properties: { 'region' => 'Canada' })).to eq(false)
end

it 'accepts symbol flag keys when evaluating locally' do
api_feature_flag_res = {
'flags' => [
{
'id' => 1,
'name' => 'Symbol Flag',
'key' => 'symbol-flag',
'is_simple_flag' => true,
'active' => true,
'filters' => {
'groups' => [{ 'rollout_percentage' => 100 }],
'payloads' => { 'true' => '{"source":"local"}' }
}
}
]
}
stub_request(
:get,
'https://us.i.posthog.com/flags/definitions?token=testsecret&send_cohorts=true'
).to_return(status: 200, body: api_feature_flag_res.to_json)
stub_request(:post, flags_endpoint).to_return(status: 400)

c = Client.new(api_key: API_KEY, personal_api_key: API_KEY, test_mode: true)

expect(c.get_feature_flag(:'symbol-flag', 'some-distinct-id',
send_feature_flag_events: false)).to eq(true)
expect(c.is_feature_enabled(:'symbol-flag', 'some-distinct-id',
send_feature_flag_events: false)).to eq(true)

result = c.get_feature_flag_result(:'symbol-flag', 'some-distinct-id', send_feature_flag_events: false)
expect(result.key).to eq('symbol-flag')
expect(result.value).to eq(true)
expect(c.get_feature_flag_payload(:'symbol-flag', 'some-distinct-id')).to eq('{"source":"local"}')
assert_not_requested :post, flags_endpoint
end

it 'evaluates group properties' do
api_feature_flag_res = {
'flags' => [
Expand Down
20 changes: 20 additions & 0 deletions spec/posthog/flags_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,15 @@ module PostHog
})
)
end

it 'accepts symbol flag keys' do
stub_request(:post, flags_endpoint)
.to_return(status: 200, body: flags_v4_response.to_json)

expect(client.get_feature_flag(:'enabled-flag', 'test-distinct-id',
send_feature_flag_events: false)).to eq(true)
expect(client.get_feature_flag_payload(:'enabled-flag', 'test-distinct-id')).to eq('{"foo": 1}')
end
end
end

Expand Down Expand Up @@ -400,6 +409,17 @@ module PostHog
# Verify the request was made to the correct URL with token parameter
expect(WebMock).to have_requested(:get, remote_config_endpoint)
end

it 'accepts a symbol flag key' do
remote_config_response = { test: 'payload' }
stub_request(:get, remote_config_endpoint)
.to_return(status: 200, body: remote_config_response.to_json)

result = poller.get_remote_config_payload(:'test-flag')

expect(result[:test]).to eq('payload')
expect(WebMock).to have_requested(:get, remote_config_endpoint)
end
end

describe 'Cohort evaluation' do
Expand Down
Loading