Example #1
0
    def update_element_caption(self, element: Element,
                               update_with_data: str) -> None:
        """
        Sets the new caption for the element
        "<agent_name>: <new_data>[, <old_query>]"
        :param element: Element to update the caption on
        :param update_with_data: the data (sentence, or a keyword) to update the element with.
        :return: Nothing
        """
        # Save the old caption
        old_caption = element.get_name()

        # Form the new caption
        new_caption = f'{self.name}: {update_with_data}'

        # Do nothing if they are the same
        if old_caption == new_caption:
            return

        # Check if the old caption matches the pattern
        pattern = re.compile(f'{self.name}: [^\n\r]+')
        if pattern.match(old_caption):
            # If it is, extract the query itself, leaving the sender
            old_caption_queries = old_caption.replace(f'{self.name}: ', '')

            # Check if last query wasn't the same (i.e., it's not a periodical update
            last_old_query = old_caption_queries.split(', ')[0]
            if last_old_query != update_with_data:
                new_caption += f', {old_caption_queries}'

        element.set_name(new_caption)
Example #2
0
    def __get_cached_elements(
            self,
            elements: List[Element],
            connection: Optional[redis.StrictRedis] = None
    ) -> Dict[str, Element]:
        """
        Returns cached elements from the given ones

        :param elements: Filters the given elements with those that are already cached
        :param connection: Redis connection (like `get_redis_connection()`)
                            -- a new one will be created in case of None

        :return: Element ID - Element mapping (dict)
        """
        if connection is None:
            connection = get_redis_connection()

        redis_elements = {}
        for element in elements:
            element_dict = connection.hgetall(
                f'{self.__redis_name}:elements:{element.get_id()}')
            if element_dict:
                redis_element = Element.from_dictionary(
                    self.__platform_access, element_dict)
                redis_elements[redis_element.get_id()] = redis_element
        return redis_elements
Example #3
0
    def __create_elem_from_response(self,
                                    elem_response: Dict,
                                    regexp: str = None) -> Optional[Element]:
        """
        Creates element from a response dictionary.
        If regexp is given, also checks if name/caption matches regular expression

        :param elem_response: Raw data from a platform's response for a single Element
        :param regexp: Regular expression to a caption-match check

        :return: object: New Element, if every field persist.
                If regexp is given, also filters element's name/caption
                Any other case -- None
        """
        name = ''
        agent_task_id = elem_response.get('agentTaskId', None)
        agent_data_id = None
        if agent_task_id:
            for link in elem_response['_links']:
                if link['rel'] == 'agentTask':
                    # Extract AgentData's ID
                    agent_data_id = int(
                        link['href'].split('agentdata/')[1].split('/')[0])

        # Caption
        if elem_response['caption'] is not None:
            name = elem_response['caption']

            if regexp and not re.match(regexp, name):
                return None
        elif not agent_task_id:
            return None

        # Identifier
        if elem_response['_links'][0]['href']:
            id_href = elem_response['_links'][0]['href']
        else:
            return None

        # Position and size
        if elem_response['posX'] and elem_response['posY'] \
                and elem_response['sizeX'] and elem_response['sizeY']:
            pos_x = elem_response['posX']
            pos_y = elem_response['posY']
            size_x = elem_response['sizeX']
            size_y = elem_response['sizeY']
        else:
            return None

        element = Element(platform_access=self.__platform_access,
                          name=name,
                          identifier=id_href,
                          board=self,
                          pos_x=pos_x,
                          pos_y=pos_y,
                          size_x=size_x,
                          size_y=size_y,
                          agent_task_id=agent_task_id,
                          agent_data_id=agent_data_id)
        return element
Example #4
0
    def get_file_from_agent_and_send_to_element(self, file_url: str,
                                                filename: str,
                                                element: Element) -> bool:
        """
        Downloads a file from agent and uploads it to an element
        :param file_url: a url of the file
        :param filename: a name of the file to display in the element
        :param element: the element where file should be placed
        :return: True if files was uploaded, False otherwise
        """
        if not (file_url and filename and element):
            return False
        try:
            file_bytes = requests.get(file_url).content
        except Exception as e:
            logger.error(
                self.name,
                f'Agent can\'t download file by url {file_url}. Error {e}')
            return False

        returned_code = element.put_file(filename, file_bytes)
        if returned_code not in [
                HTTPStatus.CREATED, AdapterStatus.CONNECTION_ABORTED
        ]:
            logger.error(
                self.name,
                f'Agent can\'t upload file to element {element.get_id()}.')
            return False
        else:
            return True
Example #5
0
 def _run_update_for_task(self, doc_ref: firestore.firestore.DocumentReference, task_name: str) \
         -> Optional[Element]:
     """
     Run a periodical update for agent task stored in Firestore
     :param doc_ref: a reference to Firestore document with info about the task
     :param task_name: a name of the task
     :return: Nothing
     """
     doc_id = None
     try:
         task_content = self.db.get_doc_content(doc_ref)
         platform_access = self.__get_platform_access()
         board = Board(platform_access,
                       identifier=task_content[BOARD_IDENTIFIER_KEY])
         element = Element(platform_access,
                           identifier=task_content[ELEMENT_ID_KEY],
                           board=board)
         agent_task = task_content[AGENT_TASK_KEY]
         doc_id = self.db.get_doc_id(doc_ref)
         self.task_running_updaters[doc_id] = doc_ref
     except Exception as e:
         logger.exception(
             self.name,
             f'Could not perform periodical update for task {task_name}. Error: {e}'
         )
         return None
     else:
         self._run_on_element(element=element,
                              agent_task=agent_task,
                              update=True,
                              doc_id=doc_id)
     finally:
         self.db.finish_monitoring_task(doc_ref, update=True)
         if doc_id:
             self.task_running_updaters.pop(doc_id)
Example #6
0
    def _run_on_element(self,
                        element: Element,
                        agent_task: Dict = None,
                        update: bool = False,
                        doc_id: str = None) -> Optional[Element]:
        """
        Running on a target element...
        :param element: own_adapter.element.Element on which the agent should run...
        :param agent_task: an agent_task with answers for an agent to process
        :param update: if an element is being updated
        :param doc_id: optional id of a document in Firestore to send messages from Agent
        :return: Target element or None if board or board.element or task are not defined
        """
        if not element:
            logger.exception(self.name, 'Element was not provided')
            return None

        board = element.get_board()
        if not board:
            logger.exception(self.name, f'Board is undefined.')
            return None

        if agent_task:
            return self.identify_and_pass_task(element, agent_task, update)

        return None
Example #7
0
    def __upload_jokes(self, element: Element, query: str) -> Optional[List]:
        """
        Uploads the metadata for the given query
        __it uses REST-API__
        :param query: Query to find jokes about
        :return: Plain or None
        """
        logger.debug(self.name, 'Privately started to upload jokes')

        board = element.get_board()
        if not board:
            logger.error(self.name,
                         f'Couldn\'t get the board of element [{element}].')
            return None

        response = None
        try:
            logger.debug(
                self.name,
                f'Connecting to {self.redis_name} via REST for [{query}]')

            self.update_element_caption(element, query)

            # Compose the data to be sent to the agent.
            data = {
                REQ_QUERY_KEY: query,
            }

            # Get the result-data from Jokes-agent's REST-API.
            request_method = 'POST'
            request_flask_endpoint = ''
            jokes_response = self.send_request_to_agent_handler(
                method=request_method,
                url=request_flask_endpoint,
                params={},
                data=data)

            # Check if we get successful response.
            if not jokes_response or jokes_response.status_code != HTTPStatus.OK:
                logger.exception(
                    self.name, f'Error: upload jokes about {query} failed.'
                    f' Skipped. Response: {response.status_code if response else None}',
                    response)
                return None

            # Parse the received data.
            logger.debug(self.name, 'Success. Parsing the data')
            response_data = jokes_response.json()

            # Return the results.
            logger.info(self.name, 'Success, returning the data', response)
            return response_data

        except Exception as excpt:
            logger.exception(
                self.name,
                f'Error: upload of query [{query}; {year}] is failed. '
                f'Skipped. Error type: {excpt}', response)
            return None
Example #8
0
    def on_websocket_message(self, ws: websocket.WebSocketApp,
                             message: str) -> None:
        """Processes websocket messages"""
        message_dict = json.loads(message)
        content_type = message_dict['contentType']
        message_type = content_type.replace('application/vnd.uberblik.', '')
        debug(self.name, message)

        if message_type == 'liveUpdateAgentTaskElementAnswersSaved+json':
            # Get the data from the LiveUpdate's message
            agent_data_id = int(message_dict['agentDataId'])
            agent_task_id = int(message_dict['agentTaskId'])
            element_id = int(message_dict['elementId'])
            board_id = int(message_dict['boardId'])
            query_id = str(message_dict['agentQueryId'])

            agent = self.get_agent()
            agent_task = get_agent_task_answers_by_id(
                agent.get_platform_access(),
                agent_data_id=agent_data_id,
                agent_task_id=agent_task_id,
                board_id=board_id,
                element_id=element_id)

            board = Board.get_board_by_id(board_id,
                                          agent.get_platform_access(),
                                          need_name=False)

            element = Element.get_element_by_id(element_id,
                                                agent.get_platform_access(),
                                                board)

            if element:
                # Run AgentTask on the element
                updated_element = self._run_on_element_default(
                    element, query_id, agent_task)
                if updated_element:
                    element = updated_element
                element.set_last_processing_time(datetime.datetime.now())
                agent.cache_element_to_redis(element)

        elif message_type in [
                'liveUpdateElementPermanentlyDeleted+json',
                'liveUpdateElementDeleted+json'
        ]:
            element_id = f'/{"/".join(message_dict["path"].split("/")[-4:])}'
            self.db.delete_old_agent_tasks(element_id)

        elif message_type == 'liveUpdateAgentTaskElementDeleted+json':
            # Get the data from the LiveUpdate's message
            element_id = int(message_dict['elementId'])
            board_id = int(message_dict['boardId'])

            full_element_id = f'/boards/{board_id}/elements/{element_id}'
            self.db.delete_old_agent_tasks(full_element_id)

        elif message_type == 'liveUpdateBoardDeleted+json':
            board_id = int(message_dict['path'].split('/')[-1])
            self.db.delete_old_agent_tasks('', board_id)
Example #9
0
 def _run_on_element_and_save_task(self, element: Element, task_name: str, query_id: str,
                                   agent_task: Dict = None, start_listener: bool = False,
                                   update: bool = False, constant_monitoring: bool = False) \
         -> Optional[Element]:
     """
     Create document to communicate with agent, run on a target element
     
     :param query_id: an id of agent task query
     :param task_name: a name of task which is being executed
     :param update: if an element is being updated
     :param element: own_adapter.element.Element on which the agent should run
     :param agent_task: an agent_task with answers for an agent to process
     :param constant_monitoring: whether this task should run in constant monitoring mode
     :param start_listener: if a communication listener should be started for this task
     :return: Target element
     """
     self.db.delete_old_agent_tasks(element.get_id())
     doc_ref = self.db.create_new_doc_for_task(
         element.get_board().get_id(),
         element.get_id(),
         query_id,
         task_name,
         update_period=self.updating_time_interval,
         constant_monitoring=constant_monitoring,
         agent_task=agent_task)
     doc_id = self.db.get_doc_id(doc_ref)
     try:
         self.task_running_updaters[doc_id] = doc_ref
         if start_listener or constant_monitoring:
             threading.Thread(
                 target=lambda: self.communication_handling(doc_ref),
                 daemon=True).start()
         res = self._run_on_element(element, agent_task, update, doc_id)
         if not res:
             self.db.delete_doc(doc_ref)
         return res
     except Exception as e:
         logger.exception(
             self.name,
             f'Couldn\'t run updates on element for task: {task_name}. Error: {e}'
         )
     finally:
         self.db.finish_monitoring_task(doc_ref, update=True)
         self.task_running_updaters.pop(doc_id)
Example #10
0
    def cache_element_to_redis(self, element: Element) -> None:
        """
        Caches an element to Redis

        :param element: Element to be cached

        :return: Nothing
        """
        connection = get_redis_connection()
        connection.hmset(f'{self.__redis_name}:elements:{element.get_id()}',
                         element.to_dictionary())
Example #11
0
    def get_jokes(self, element: Element,
                  agent_task: Dict) -> Optional[Element]:
        """
        Run on element which requested jokes.

        :param element: own_adapter.element.Element on which Jokes-agent should run.
        :param agent_task: An agent task to get details from.

        :return: Target element or None if something gone wrong
        """
        board = element.get_board()

        # Extract the topic for the joke.
        topic = get_answer_from_agent_task_answers(agent_task, answer_index=1)
        topic = str(topic[0]) if topic else ''

        # Put start-message on the board.
        start_msg = f'Time to find a joke for thee.'
        if topic:
            start_msg += f' "{topic}" you say? Let\'s see...'
        board.put_message(start_msg)

        try:
            # Get the jokes from the agent.
            uploaded_jokes = self.__upload_jokes(element=element, query=topic)
            if not uploaded_jokes:
                # No jokes for you.
                message = f'I could not find any jokes on "{topic}".' \
                    if topic \
                    else 'There are no more jokes in this world.'
                board.put_message(message)
                return None

            # Compose successful report message.
            message = f'"{uploaded_jokes}"'
            # Make a joke in a joke from time to time.
            if randint(-3, 10) < 0:
                prefix = 'I haven\'t found a thing. Just joking, here you go:\n'
                message = prefix + message
            board.put_message(message)

        except AttributeError as attr_err:
            logger.exception(
                self.name,
                f'Some attribute was incorrect while running Jokes-agent on element: {attr_err}'
            )
            return None

        return element