示例#1
0
class SlackBot:
    def __init__(self, token):
        self.client = WebClient(token=token)
        self.channels_dict = dict()
        try:
            for response in self.client.conversations_list():
                self.channels_dict.update({
                    channel["name"]: channel["id"]
                    for channel in response["channels"]
                })
        except SlackApiError as e:
            print(f"Error: {e}")

    def check_channel(self, channel: str):
        return channel in self.channels_dict

    def post_in_channel(self, channel_name: str, message: str,
                        articles: [Article]):
        if articles:
            marked_articles = '>- ' + '\n>- '.join(
                [f'<{art.url}|{art.header}>' for art in articles])

            try:
                self.client.conversations_join(
                    channel=self.channels_dict[channel_name])
                self.client.chat_postMessage(
                    channel=self.channels_dict[channel_name],
                    text=f"{message}",
                    blocks=[{
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"{message}"
                        }
                    }, {
                        "type": "section",
                        "text": {
                            "type": "mrkdwn",
                            "text": f"{marked_articles}"
                        }
                    }])

            except SlackApiError as e:
                print(f"Error: {e}")
示例#2
0
def join_channel(channel):
    """
    If the app gets the 'not_in_channel' error when accessing a public channel, call this method

    :param channel: The channel to join
    :returns: Response object (Dictionary)
    """
    if not settings.SLACK_TOKEN:
        return {'ok': False, 'error': 'config_error'}

    client = WebClient(token=settings.SLACK_TOKEN)

    try:
        response = client.conversations_join(channel=channel)
        assert response['ok'] is True
        return {'ok': response['ok']}
    except SlackApiError as e:
        assert e.response['ok'] is False
        return e.response
示例#3
0
class SlackMonitor(Monitor):
    """
    Create a monitoring service that alerts on Task failures / completion in a Slack channel
    """
    def __init__(self,
                 slack_api_token,
                 channel,
                 message_prefix=None,
                 filters=None):
        # type: (str, str, Optional[str], Optional[List[Callable[[Task], bool]]]) -> ()
        """
        Create a Slack Monitoring object.
        It will alert on any Task/Experiment that failed or completed

        :param slack_api_token: Slack bot API Token. Token should start with "xoxb-"
        :param channel: Name of the channel to post alerts to
        :param message_prefix: optional message prefix to add before any message posted
            For example: message_prefix="Hey <!here>,"
        :param filters: An optional collection of callables that will be passed a Task
            object and return True/False if it should be filtered away
        """
        super(SlackMonitor, self).__init__()
        self.channel = "{}".format(channel[1:] if channel[0] ==
                                   "#" else channel)
        self.slack_client = WebClient(token=slack_api_token)
        self.min_num_iterations = 0
        self.filters = filters or list()
        self.status_alerts = [
            "failed",
        ]
        self.include_manual_experiments = False
        self.include_archived = False
        self.verbose = False
        self._channel_id = None
        self._message_prefix = "{} ".format(
            message_prefix) if message_prefix else ""
        self.check_credentials()

    def check_credentials(self):
        # type: () -> ()
        """
        Check we have the correct credentials for the slack channel
        """
        self.slack_client.api_test()

        # Find channel ID
        channels = []
        cursor = None
        while True:
            response = self.slack_client.conversations_list(cursor=cursor)
            channels.extend(response.data["channels"])
            cursor = response.data["response_metadata"].get("next_cursor")
            if not cursor:
                break
        channel_id = [
            channel_info.get("id") for channel_info in channels
            if channel_info.get("name") == self.channel
        ]
        if not channel_id:
            raise ValueError(
                "Error: Could not locate channel name '{}'".format(
                    self.channel))

        # test bot permission (join channel)
        self._channel_id = channel_id[0]
        self.slack_client.conversations_join(channel=self._channel_id)

    def post_message(self, message, retries=1, wait_period=10.0):
        # type: (str, int, float) -> ()
        """
        Post message on our slack channel

        :param message: Message to be sent (markdown style)
        :param retries: Number of retries before giving up
        :param wait_period: wait between retries in seconds
        """
        for i in range(retries):
            if i != 0:
                sleep(wait_period)

            try:
                self.slack_client.chat_postMessage(
                    channel=self._channel_id,
                    blocks=[
                        dict(type="section",
                             text={
                                 "type": "mrkdwn",
                                 "text": message
                             })
                    ],
                )
                return
            except SlackApiError as e:
                print(
                    'While trying to send message: "\n{}\n"\nGot an error: {}'.
                    format(message, e.response["error"]))

    def get_query_parameters(self):
        # type: () -> dict
        """
        Return the query parameters for the monitoring.

        :return dict: Example dictionary: {'status': ['failed'], 'order_by': ['-last_update']}
        """
        filter_tags = list() if self.include_archived else ["-archived"]
        if not self.include_manual_experiments:
            filter_tags.append("-development")
        return dict(status=self.status_alerts,
                    order_by=["-last_update"],
                    system_tags=filter_tags)

    def process_task(self, task):
        """
        # type: (Task) -> ()
        Called on every Task that we monitor.
        This is where we send the Slack alert

        :return: None
        """
        # skipping failed tasks with low number of iterations
        if self.min_num_iterations and task.get_last_iteration(
        ) < self.min_num_iterations:
            print("Skipping {} experiment id={}, number of iterations {} < {}".
                  format(task.status, task.id, task.get_last_iteration(),
                         self.min_num_iterations))
            return
        if any(f(task) for f in self.filters):
            if self.verbose:
                print("Experiment id={} {} did not pass all filters".format(
                    task.id, task.status))
            return

        print('Experiment id={} {}, raising alert on channel "{}"'.format(
            task.id, task.status, self.channel))

        console_output = task.get_reported_console_output(number_of_reports=3)
        message = "{}Experiment ID <{}|{}> *{}*\nProject: *{}*  -  Name: *{}*\n" "```\n{}\n```".format(
            self._message_prefix,
            task.get_output_log_web_page(),
            task.id,
            task.status,
            task.get_project_name(),
            task.name,
            ("\n".join(console_output))[-2048:],
        )
        self.post_message(message, retries=5)
示例#4
0
class ChannelManager(object):

    def __init__(self, token=Path('SLACK_OAUTH_TOKEN').read_text()):
        self.token = token
        self.client = WebClient(token=self.token)
        self.channels = [ ]

    # Sets the internal channels field
    def list(self):
        resp = self.client.conversations_list(exclude_archived=True, types="public_channel,private_channel", limit=500)
        assert resp.data['ok']
        self.channels = []
        for channel in resp.data['channels']:
            self.channels.append({
                'ID': channel['id'],
                'Channel Name': channel['name'],
                'Purpose': channel['purpose']['value']
            })
    
    def exists(self, name):
        for channel in self.channels:
            if channel['Channel Name'] == name:
                return True
        return False

    def get_id(self, name):
        for channel in self.channels:
            if channel['Channel Name'] == name:
                return channel['ID']
        return False
    
    def get_purpose(self, name):
        for channel in self.channels:
            if channel['Channel Name'] == name:
                return channel['Purpose']
        return False


    def create(self, name, is_private):
        print(f'Creating channel {name} ...')
        resp = self.client.conversations_create(name=name, is_private=is_private)
        return resp
    
    def set_purpose(self, name, purpose):
        chan_id = self.get_id(name)
        # TODO(arjun): Hack. The name that comes back has HTML entities for
        # special characters. God help us all.
        if self.get_purpose(name) != "":
            return
        print(f'Setting purpose for {name} ...')
        self.client.conversations_setPurpose(channel=chan_id, purpose=purpose)

    def post(self, name, message):
        channel_id = self.get_id(name)
        if not channel_id:
            return False

        try:
            resp = self.client.chat_postMessage(channel=channel_id, text=message)
            if resp.data['ok']:
                return True
            else:
                print(f"Response was {resp.data}")
                return False    
        except SlackApiError as e:
            if e.response["error"] != "not_in_channel":
                raise e
            self.client.conversations_join(channel=channel_id)
            self.client.chat_postMessage(channel=channel_id, text=message)
            return True