Class: Mixpanel::Flags::RemoteFlagsProvider

Inherits:
FlagsProvider show all
Defined in:
lib/mixpanel-ruby/flags/remote_flags_provider.rb

Overview

Remote feature flags provider Evaluates flags on the server-side via HTTP API calls

Constant Summary collapse

DEFAULT_CONFIG =
{
  api_host: 'api.mixpanel.com',
  request_timeout_in_seconds: 10,
  exposure_executor: nil
}.freeze

Instance Method Summary collapse

Methods inherited from FlagsProvider

#call_flags_endpoint, #shutdown, #track_exposure_event

Constructor Details

#initialize(token, config, tracker_callback, error_handler, credentials = nil) ⇒ RemoteFlagsProvider

Returns a new instance of RemoteFlagsProvider.

Parameters:

  • token (String)

    Mixpanel project token

  • config (Hash)

    Remote flags configuration

  • tracker_callback (Proc)

    Callback to track events

  • error_handler (Mixpanel::ErrorHandler)

    Error handler

  • credentials (ServiceAccountCredentials, nil) (defaults to: nil)

    Optional service account credentials



19
20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/mixpanel-ruby/flags/remote_flags_provider.rb', line 19

def initialize(token, config, tracker_callback, error_handler, credentials = nil)
  merged_config = DEFAULT_CONFIG.merge(config || {})

  provider_config = {
    token: token,
    api_host: merged_config[:api_host],
    request_timeout_in_seconds: merged_config[:request_timeout_in_seconds],
    exposure_executor: merged_config[:exposure_executor],
    credentials: credentials
  }

  super(provider_config, '/flags', tracker_callback, 'remote', error_handler)
end

Instance Method Details

#get_all_variants(context) ⇒ Hash?

Get all variants for user context Exposure events NOT tracked automatically

Parameters:

  • context (Hash)

    Evaluation context

Returns:

  • (Hash, nil)

    Map of flag_key => SelectedVariant, or nil on error



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/mixpanel-ruby/flags/remote_flags_provider.rb', line 112

def get_all_variants(context)
  response = fetch_flags(context)

  variants = {}
  (response['flags'] || {}).each do |flag_key, variant_data|
    variants[flag_key] = SelectedVariant.new(
      variant_key: variant_data['variant_key'],
      variant_value: variant_data['variant_value'],
      experiment_id: variant_data['experiment_id'],
      is_experiment_active: variant_data['is_experiment_active'],
      variant_source: VariantSource::REMOTE
    )
  end

  variants
rescue MixpanelError => e
  @error_handler.handle(e)
  nil
end

#get_variant(flag_key, fallback_variant, context, report_exposure: true) ⇒ SelectedVariant

Get complete variant information

Parameters:

  • flag_key (String)

    Feature flag key

  • fallback_variant (SelectedVariant)

    Fallback variant

  • context (Hash)

    Evaluation context

  • report_exposure (Boolean) (defaults to: true)

    Whether to track exposure

Returns:



58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/mixpanel-ruby/flags/remote_flags_provider.rb', line 58

def get_variant(flag_key, fallback_variant, context, report_exposure: true)
  start_time = Time.now
  response = fetch_flags(context, flag_key)
  latency_ms = ((Time.now - start_time) * 1000).to_i

  flags = response['flags'] || {}
  selected_variant_data = flags[flag_key]

  # The /flags endpoint only returns variants the user is enrolled in,
  # so a missing key could mean the flag doesn't exist OR the user
  # isn't in any rollout. The remote SDK can't tell them apart without
  # server-side help — surface as FLAG_NOT_FOUND for now.
  return fallback_variant.as_fallback(FallbackReason.flag_not_found) unless selected_variant_data

  selected_variant = SelectedVariant.new(
    variant_key: selected_variant_data['variant_key'],
    variant_value: selected_variant_data['variant_value'],
    experiment_id: selected_variant_data['experiment_id'],
    is_experiment_active: selected_variant_data['is_experiment_active'],
    variant_source: VariantSource::REMOTE
  )

  track_exposure_event(flag_key, selected_variant, context, latency_ms) if report_exposure

  return selected_variant
rescue MixpanelError => e
  @error_handler.handle(e)
  # Attach the backend's message so the OpenFeature wrapper can forward
  # it into ResolutionDetails#error_message — without this the caller
  # sees a bare GENERAL error and has to dig through logs to find out
  # the backend rejected the request (e.g. "distinct_id must be
  # provided in evalContext as a string"). SDK-83.
  return fallback_variant.as_fallback(FallbackReason.backend_error(e.message))
end

#get_variant_value(flag_key, fallback_value, context, report_exposure: true) ⇒ Object

Get variant value for a flag

Parameters:

  • flag_key (String)

    Feature flag key

  • fallback_value (Object)

    Fallback value

  • context (Hash)

    Evaluation context

  • report_exposure (Boolean) (defaults to: true)

    Whether to track exposure

Returns:

  • (Object)

    Variant value



39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/mixpanel-ruby/flags/remote_flags_provider.rb', line 39

def get_variant_value(flag_key, fallback_value, context, report_exposure: true)
  selected_variant = get_variant(
    flag_key,
    SelectedVariant.new(variant_value: fallback_value),
    context,
    report_exposure: report_exposure
  )
  selected_variant.variant_value
rescue MixpanelError => e
  @error_handler.handle(e)
  fallback_value
end

#is_enabled?(flag_key, context) ⇒ Boolean

Check if flag is enabled (for boolean flags) This method is intended only for flags defined as Mixpanel Feature Gates (boolean flags) This checks that the variant value of a selected variant is concretely the boolean 'true' It does not coerce other truthy values.

Parameters:

  • flag_key (String)

    Feature flag key

  • context (Hash)

    Evaluation context

Returns:

  • (Boolean)


100
101
102
103
104
105
106
# File 'lib/mixpanel-ruby/flags/remote_flags_provider.rb', line 100

def is_enabled?(flag_key, context)
  value = get_variant_value(flag_key, false, context)
  value == true
rescue MixpanelError => e
  @error_handler.handle(e)
  false
end