recommendationSvc.py (12177B)
1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\recommendation\recommendationSvc.py 2 import collections 3 import logging 4 import uuid 5 from featuremanagement.client.flag import flag_bool 6 import uthread2 7 from carbon.common.script.sys.service import Service 8 from eve.client.script.ui.shared.recommendation.const import CURRENT_ACTIVE, COMPLETED, NUM_SLOTS, MAX_QUEUE_LENGTH 9 from eve.client.script.ui.shared.recommendation.message_bus.recommendationMessenger import RecommendationMessenger 10 from eve.client.script.ui.shared.recommendation.recommendationController import RecommendationController, LastActiveOperationInfo 11 from eve.client.script.ui.shared.recommendation.uiConst import HAS_ACCEPTED, HAS_INTERACTED_WITH 12 from launchdarkly.client.const import OPPORTUNITIES_UI 13 from operations.client.operationscontroller import GetOperationsController 14 from operations.common.const import OPERATION_CATEGORY_RECOMMENDATIONS 15 logger = logging.getLogger(__name__) 16 FEATURE_FLAG_FALLBACK = False 17 18 class RecommendationService(Service): 19 __guid__ = 'svc.recommendationSvc' 20 __displayname__ = 'Recommendation Service' 21 __startupdependencies__ = ['publicGatewaySvc'] 22 __notifyevents__ = ['OnOperationCompleted', 'OnOperationMadeActive', 'OnSessionReset'] 23 24 def __init__(self): 25 super(RecommendationService, self).__init__() 26 self.ResetVariables() 27 self._enabled = FEATURE_FLAG_FALLBACK 28 flag_bool(key=OPPORTUNITIES_UI, default_value=FEATURE_FLAG_FALLBACK, callback=self._refresh_feature_flag) 29 30 def _refresh_feature_flag(self, evaluate_flag): 31 self._enabled = evaluate_flag() 32 33 def ResetVariables(self): 34 self.availableRecommendationQueue = collections.deque(maxlen=MAX_QUEUE_LENGTH) 35 self.recommendationControllerBySlotID = {} 36 self.activeRecommendation = None 37 self.characterHasReceivedRecommendations = False 38 39 def OnSessionReset(self): 40 self.ResetVariables() 41 42 def HasRecommendations(self): 43 return any([ controller.operationID for controller in self.recommendationControllerBySlotID.values() ]) 44 45 def AddToQueue(self, operation_recommendations): 46 if not operation_recommendations: 47 return 48 for recommendation in operation_recommendations: 49 operationID, journeyID = self.UnpackOperationRecommendation(recommendation) 50 if not operationID or not journeyID: 51 logger.error('recommendation is missing a required component, operationID or journeyID is None') 52 continue 53 self.availableRecommendationQueue.append((operationID, journeyID)) 54 55 self.TryPopulateAllSlotControllers() 56 sm.GetService('infoPanel').UpdateOperationsPanel(OPERATION_CATEGORY_RECOMMENDATIONS) 57 58 def TryPopulateAllSlotControllers(self): 59 for slotIdx in range(NUM_SLOTS): 60 self.TryPopulateSlotController(slotIdx) 61 62 def TryPopulateSlotController(self, slotIdx): 63 controller = self.GetRecommendationControllerForSlot(slotIdx) 64 if not controller: 65 logger.warn('TryPopulateSlotController: could not get recommendation controller for this slot: %d' % slotIdx) 66 return 67 controller.TryPopulateController() 68 69 def GetRecommendationControllerForSlot(self, slotIdx): 70 if slotIdx in self.recommendationControllerBySlotID: 71 return self.recommendationControllerBySlotID[slotIdx] 72 return self._CreateRecommendationControllerForSlot(slotIdx) 73 74 def GetRecommendationFromQueue(self): 75 try: 76 operationRecommendation = self.availableRecommendationQueue.popleft() 77 except IndexError: 78 return (None, None) 79 80 return operationRecommendation 81 82 @staticmethod 83 def UnpackOperationRecommendation(operationRecommendation): 84 try: 85 operationID = operationRecommendation.operation.sequential 86 recommendationJourneyID = uuid.UUID(bytes=operationRecommendation.journey) 87 except AttributeError: 88 logger.exception('failed to unpack operation recommendation') 89 return (None, None) 90 91 return (operationID, recommendationJourneyID) 92 93 def _CreateRecommendationControllerForSlot(self, slotIdx): 94 if self.activeRecommendation and not self.IsActiveRecommendationInASlot(): 95 operationID = self.activeRecommendation.operationID 96 journeyID = None 97 else: 98 operationID, journeyID = self.GetRecommendationFromQueue() 99 recommendationController = RecommendationController(operationID=operationID, slotIdx=slotIdx, journeyID=journeyID) 100 self.recommendationControllerBySlotID[slotIdx] = recommendationController 101 return recommendationController 102 103 def IsActiveRecommendationInASlot(self): 104 if not self.activeRecommendation: 105 return False 106 return any((controller.operationID == self.activeRecommendation.operationID for controller in self.recommendationControllerBySlotID.values())) 107 108 def AcceptRecommendation(self, operationID, journeyID): 109 settings.char.ui.Set(HAS_ACCEPTED, True) 110 uthread2.StartTasklet(self._SendRecommendationAcceptedEvent, operationID, journeyID) 111 sm.ScatterEvent('OnRecommendationAccepted') 112 113 def _SendRecommendationAcceptedEvent(self, operationID, journeyID): 114 message_bus = RecommendationMessenger(self.publicGatewaySvc) 115 message_bus.accepted(operationID, journeyID) 116 117 def QuitRecommendation(self, operationID, slotIdx): 118 if operationID != GetOperationsController().get_active_operation_id(): 119 logger.error("Can't quit an inactive operation") 120 return 121 GetOperationsController().manager.cancel_current_treatment_operation() 122 self.activeRecommendation = None 123 self.ClearSlot(slotIdx) 124 self.RequestRecommendations(1, allowOld=False) 125 126 def DismissRecommendation(self, operationID, journeyID, slotIdx): 127 if operationID == GetOperationsController().get_active_operation_id(): 128 self.QuitRecommendation(operationID, slotIdx) 129 self.ClearSlot(slotIdx) 130 uthread2.StartTasklet(self._SendRecommendationDismissedEvent, operationID, journeyID) 131 if len(self.availableRecommendationQueue): 132 self.TryPopulateSlotController(slotIdx) 133 else: 134 uthread2.StartTasklet(self.RequestRecommendations, 1, False) 135 136 def _SendRecommendationDismissedEvent(self, operationID, journeyID): 137 if not operationID: 138 logger.error('can not dismiss this operation recommendation, operationID is None') 139 return 140 message_bus = RecommendationMessenger(self.publicGatewaySvc) 141 message_bus.dismissed(operationID, journeyID) 142 143 def ClearSlot(self, slotIdx): 144 controller = self.GetRecommendationControllerForSlot(slotIdx) 145 if not controller: 146 return 147 controller.ResetController() 148 149 def OnOperationMadeActive(self, categoryID, operationID, *args, **kwargs): 150 if categoryID != OPERATION_CATEGORY_RECOMMENDATIONS: 151 self.activeRecommendation = None 152 return 153 self.SetActiveRecommendation() 154 if self.activeRecommendation and self.IsActiveRecommendationInASlot(): 155 slotIdx = self.GetSlotIDForOperation(operationID) 156 self.SetStatusForRecommendation(slotIdx, CURRENT_ACTIVE) 157 else: 158 self.GetSlotForActiveRecommendation() 159 160 def GetSlotForActiveRecommendation(self): 161 for controller in self.recommendationControllerBySlotID.values(): 162 existingRecommendationID = controller.operationID 163 existingRecommendationJourneyID = controller.journeyID 164 self.availableRecommendationQueue.appendleft((existingRecommendationID, existingRecommendationJourneyID)) 165 controller.ResetController() 166 controller.operationID = self.activeRecommendation.operationID 167 break 168 else: 169 self.GetRecommendationControllerForSlot(0) 170 171 def SetActiveRecommendation(self): 172 activeOperation = GetOperationsController().get_active_operation() 173 if not activeOperation: 174 self.activeRecommendation = None 175 return 176 taskTuples = GetOperationsController().get_active_task_tuples() 177 self.activeRecommendation = LastActiveOperationInfo(OPERATION_CATEGORY_RECOMMENDATIONS, activeOperation, taskTuples) 178 179 def SendRecommendationDisplayedEvent(self, operationID, journeyID): 180 message_bus = RecommendationMessenger(self.publicGatewaySvc) 181 message_bus.displayed(operationID, journeyID) 182 183 def OnOperationCompleted(self, categoryID, operationID): 184 if categoryID != OPERATION_CATEGORY_RECOMMENDATIONS: 185 return 186 if not self.activeRecommendation: 187 return 188 hasCompletedActiveRecommendation = operationID == self.activeRecommendation.operationID 189 if not hasCompletedActiveRecommendation: 190 return 191 slotIDx = self.GetSlotIDForOperation(operationID) 192 self.SetStatusForRecommendation(slotIDx, COMPLETED) 193 controller = self.recommendationControllerBySlotID.get(slotIDx, None) 194 if not controller: 195 logger.warn('could not get recommendation controller for this operationID: %d, slot: %d' % (operationID, slotIDx)) 196 return 197 sm.ScatterEvent('OnOperationRecommendationCompleted', operationID) 198 199 def GetSlotIDForOperation(self, operationID): 200 slotIDx = None 201 for slotID, controller in self.recommendationControllerBySlotID.iteritems(): 202 if operationID == controller.operationID: 203 slotIDx = slotID 204 205 return slotIDx 206 207 def SetStatusForRecommendation(self, slotID, newStatus): 208 controller = self.recommendationControllerBySlotID.get(slotID, None) 209 if not controller: 210 logger.warn('could not get recommendation controller for this slotID: %d' % slotID) 211 return 212 controller.SetStatus(newStatus) 213 214 def IsActiveRecommendationComplete(self): 215 if not self.activeRecommendation: 216 return False 217 operationID = self.activeRecommendation.operationID 218 if not operationID: 219 return False 220 slotIDx = self.GetSlotIDForOperation(operationID) 221 if not slotIDx: 222 return False 223 controller = self.GetRecommendationControllerForSlot(slotIDx) 224 return controller.IsCompleted() 225 226 def ShowLastCompleted(self): 227 return self.IsActiveRecommendationComplete() and self.IsThereUnseenAndCompletedRecommendation() 228 229 def IsThereUnseenAndCompletedRecommendation(self): 230 for slotIdx in range(NUM_SLOTS): 231 controller = self.GetRecommendationControllerForSlot(slotIdx) 232 if not controller: 233 logger.warn('could not get recommendation controller for this slot: %d' % slotIdx) 234 continue 235 if controller.IsCompletedAndUnseen(): 236 return True 237 238 return False 239 240 def RequestRecommendations(self, amount, allowOld = False): 241 if not self.IsFeatureEnabled() or not self.publicGatewaySvc.is_available(): 242 return 243 message_bus = RecommendationMessenger(self.publicGatewaySvc) 244 message_bus.request_recommendations(amount, allowOld, response_function=self.AddToQueue, timeout_function=self.RequestTimeoutHandler) 245 246 def RequestTimeoutHandler(self): 247 for controller in self.recommendationControllerBySlotID.values(): 248 controller.onRequestTimeout() 249 250 def ShouldInfoPanelBlink(self): 251 if not settings.char.ui.Get(HAS_INTERACTED_WITH, False): 252 return True 253 return self.IsThereUnseenAndCompletedRecommendation() 254 255 def IsFeatureEnabled(self): 256 if not sm.GetService('publicGatewaySvc').is_available(): 257 return False 258 return self._enabled 259 260 @staticmethod 261 def ShouldBtnsBlink(): 262 return not settings.char.ui.Get(HAS_INTERACTED_WITH, False) 263 264 @staticmethod 265 def HasEverAcceptedARecommendation(): 266 return settings.char.ui.Get(HAS_ACCEPTED, False)