11import time
22from threading import Lock , Thread
3- from typing import Dict , List
3+ from typing import Dict , List , Union
44
55import attr
66
1111from .api .default .post_metrics import sync_detailed as post_metrics
1212from .config import Config
1313from .evaluations .target import Target
14+ from .evaluations .target_attributes import TargetAttributes
1415from .evaluations .variation import Variation
1516from .models .key_value import KeyValue
1617from .models .metrics import Metrics
2324FEATURE_IDENTIFIER_ATTRIBUTE = 'featureIdentifier'
2425FEATURE_NAME_ATTRIBUTE = 'featureName'
2526VARIATION_IDENTIFIER_ATTRIBUTE = 'variationIdentifier'
26- VARIATION_VALUE_ATTRIBUTE = 'featureValue '
27+ VARIATION_VALUE_ATTRIBUTE = 'variationValue '
2728TARGET_ATTRIBUTE = 'target'
2829SDK_VERSION_ATTRIBUTE = 'SDK_VERSION'
2930SDK_VERSION = '1.0.0'
@@ -42,6 +43,13 @@ class AnalyticsEvent(object):
4243 count : int = 0
4344
4445
46+ @attr .s (auto_attribs = True )
47+ class MetricTargetData (object ):
48+ identifier : str
49+ name : str
50+ attributes : Union [Unset , TargetAttributes ] = Unset
51+
52+
4553class AnalyticsService (object ):
4654
4755 def __init__ (self , config : Config , client : AuthenticatedClient ,
@@ -51,6 +59,7 @@ def __init__(self, config: Config, client: AuthenticatedClient,
5159 self ._client = client
5260 self ._environment = environment
5361 self ._data : Dict [str , AnalyticsEvent ] = {}
62+ self ._target_data : Dict [str , MetricTargetData ] = {}
5463
5564 self ._running = False
5665 self ._runner = Thread (target = self ._sync )
@@ -67,15 +76,33 @@ def enqueue(self, target: Target, identifier: str,
6776
6877 self ._lock .acquire ()
6978 try :
70- key = self .get_key (event )
71- if key in self ._data :
72- self ._data [key ].count += 1
79+ # Store unique evaluation events. We map a unique evaluation
80+ # event to its count.
81+ unique_evaluation_key = self .get_key (event )
82+ if unique_evaluation_key in self ._data :
83+ self ._data [unique_evaluation_key ].count += 1
7384 else :
7485 event .count = 1
75- self ._data [key ] = event
86+ self ._data [unique_evaluation_key ] = event
87+
88+ # Store unique targets. If the target already exists
89+ # just ignore it.
90+ if event .target is not None and not event .target .anonymous :
91+ unique_target_key = self .get_target_key (event )
92+ if unique_target_key not in self ._target_data :
93+ target_name = event .target .name
94+ # If the target has no name use the identifier
95+ if not target_name :
96+ target_name = event .target .identifier
97+ self ._target_data [unique_target_key ] = MetricTargetData (
98+ identifier = event .target .identifier ,
99+ name = target_name ,
100+ attributes = event .target .attributes
101+ )
76102 finally :
77103 self ._lock .release ()
78104
105+ # Returns a key for unique evaluations events.
79106 def get_key (self , event : AnalyticsEvent ) -> str :
80107 return '{feature}-{variation}-{value}-{target}' .format (
81108 feature = event .flag_identifier ,
@@ -84,6 +111,11 @@ def get_key(self, event: AnalyticsEvent) -> str:
84111 target = GLOBAL_TARGET ,
85112 )
86113
114+ # Returns a key for unique targets. Targets are considered unique
115+ # if they have different identifiers.
116+ def get_target_key (self , event : AnalyticsEvent ) -> str :
117+ return event .target .identifier
118+
87119 def _sync (self ) -> None :
88120 if not self ._running :
89121 log .info ("Starting AnalyticsService with request interval: %d" ,
@@ -103,22 +135,6 @@ def _send_data(self) -> None:
103135 metrics_data : List [MetricsData ] = []
104136 try :
105137 for _ , event in self ._data .items ():
106- if event .target is not None and not event .target .anonymous :
107- target_attributes : List [KeyValue ] = []
108- if not isinstance (event .target .attributes , Unset ):
109- for key , value in event .target .attributes .items ():
110- target_attributes .append (KeyValue (key , value ))
111- target_name = event .target .identifier
112- if event .target .name :
113- target_name = event .target .name
114-
115- td = TargetData (
116- identifier = event .target .identifier ,
117- name = target_name ,
118- attributes = target_attributes
119- )
120- target_data .append (td )
121-
122138 metric_attributes : List [KeyValue ] = [
123139 KeyValue (FEATURE_IDENTIFIER_ATTRIBUTE ,
124140 event .flag_identifier ),
@@ -140,8 +156,20 @@ def _send_data(self) -> None:
140156 attributes = metric_attributes
141157 )
142158 metrics_data .append (md )
159+ for _ , unique_target in self ._target_data .items ():
160+ target_attributes : List [KeyValue ] = []
161+ if not isinstance (unique_target .attributes , Unset ):
162+ for key , value in unique_target .attributes .items ():
163+ target_attributes .append (KeyValue (key , value ))
164+ td = TargetData (
165+ identifier = unique_target .identifier ,
166+ name = unique_target .name ,
167+ attributes = target_attributes
168+ )
169+ target_data .append (td )
143170 finally :
144171 self ._data = {}
172+ self ._target_data = {}
145173 self ._lock .release ()
146174 body : Metrics = Metrics (target_data = target_data ,
147175 metrics_data = metrics_data )
0 commit comments