From e026a27b9380467eed8ae2d2ecc082394f77cc2d Mon Sep 17 00:00:00 2001 From: Jakub Jezek Date: Mon, 12 Dec 2022 12:11:33 +0100 Subject: [PATCH 01/23] hiero: fixing thumbnail if multillayer exr --- openpype/hosts/hiero/plugins/publish/extract_thumbnail.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py index e64aa89b26..5ca79dc1dc 100644 --- a/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py +++ b/openpype/hosts/hiero/plugins/publish/extract_thumbnail.py @@ -41,7 +41,7 @@ class ExtractThumnail(publish.Extractor): track_item_name, thumb_frame, ".png") thumb_path = os.path.join(staging_dir, thumb_file) - thumbnail = track_item.thumbnail(thumb_frame).save( + thumbnail = track_item.thumbnail(thumb_frame, "colour").save( thumb_path, format='png' ) From ae709afaaf85ca6bd1d6d74476ea8c561d550eec Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Dec 2022 12:25:01 +0100 Subject: [PATCH 02/23] Added dynamic message to Slack notification Artist can now add additional message, specific per instance and publish, if they are using Publisher. --- .../plugins/publish/collect_slack_family.py | 23 +++++++++++++++++-- .../plugins/publish/integrate_slack_api.py | 11 +++++---- website/docs/module_slack.md | 6 +++++ 3 files changed, 34 insertions(+), 6 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/collect_slack_family.py b/openpype/modules/slack/plugins/publish/collect_slack_family.py index 27e899d59a..b3e7bbdcec 100644 --- a/openpype/modules/slack/plugins/publish/collect_slack_family.py +++ b/openpype/modules/slack/plugins/publish/collect_slack_family.py @@ -1,10 +1,12 @@ import pyblish.api from openpype.lib.profiles_filtering import filter_profiles -from openpype.pipeline import legacy_io +from openpype.lib import attribute_definitions +from openpype.pipeline import OpenPypePyblishPluginMixin -class CollectSlackFamilies(pyblish.api.InstancePlugin): +class CollectSlackFamilies(pyblish.api.InstancePlugin, + OpenPypePyblishPluginMixin): """Collect family for Slack notification Expects configured profile in @@ -17,6 +19,18 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): profiles = None + @classmethod + def get_attribute_defs(cls): + return [ + attribute_definitions.TextDef( + # Key under which it will be stored + "additional_message", + # Use plugin label as label for attribute + label="Additional Slack message", + placeholder="" + ) + ] + def process(self, instance): task_data = instance.data["anatomyData"].get("task", {}) family = self.main_family_from_instance(instance) @@ -55,6 +69,11 @@ class CollectSlackFamilies(pyblish.api.InstancePlugin): ["token"]) instance.data["slack_token"] = slack_token + attribute_values = self.get_attr_values_from_data(instance.data) + additional_message = attribute_values.get("additional_message") + if additional_message: + instance.data["slack_additional_message"] = additional_message + def main_family_from_instance(self, instance): # TODO yank from integrate """Returns main family of entered instance.""" family = instance.data.get("family") diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 0cd5ec9de8..d94ecb02e4 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -31,11 +31,14 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): review_path = self._get_review_path(instance) publish_files = set() + message = '' + additional_message = instance.data.get("slack_additional_message") + if additional_message: + message = "{} \n".format(additional_message) for message_profile in instance.data["slack_channel_message_profiles"]: - message = self._get_filled_message(message_profile["message"], - instance, - review_path) - self.log.debug("message:: {}".format(message)) + message += self._get_filled_message(message_profile["message"], + instance, + review_path) if not message: return diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 3a2842da63..2bfd7cb562 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -94,6 +94,12 @@ Few keys also have Capitalized and UPPERCASE format. Values will be modified acc Here you can find review {review_filepath} ``` +##### Dynamic message for artists +If artists uses host with implemented Publisher (new UI for publishing, implemented in Tray Publisher, Adobe products etc), it is possible for +them to add additional message (notification for specific users for example, artists must provide proper user id with '@'). +Additional message will be sent only if at least one profile, eg. one target channel is configured. +All available template keys (see higher) could be used here as a placeholder too. + #### Message retention Currently no purging of old messages is implemented in Openpype. Admins of Slack should set their own retention of messages and files per channel. (see https://slack.com/help/articles/203457187-Customize-message-and-file-retention-policies) From c16a5289e49286bb7c65be04a9629f846cab58ce Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Dec 2022 14:09:03 +0100 Subject: [PATCH 03/23] OP-4470 - better handle missing keys Message might contain {placeholder} which are not collected. Previously it would fail without sending message. Now missing keys are double escaped {{}}. --- .../plugins/publish/integrate_slack_api.py | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 0cd5ec9de8..9122c1c5ed 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -1,4 +1,5 @@ import os +import re import six import pyblish.api import copy @@ -132,14 +133,14 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): fill_key = "task[{}]".format(key) fill_pairs.append((fill_key, value)) - self.log.debug("fill_pairs ::{}".format(fill_pairs)) multiple_case_variants = prepare_template_data(fill_pairs) fill_data.update(multiple_case_variants) - - message = None + message = '' try: - message = message_templ.format(**fill_data) + message = self._escape_missing_keys(message_templ, fill_data).\ + format(**fill_data) except Exception: + # shouldn't happen self.log.warning( "Some keys are missing in {}".format(message_templ), exc_info=True) @@ -263,3 +264,22 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): msg = " - application must added to channel '{}'.".format(channel) error_str += msg + " Ask Slack admin." return error_str + + def _escape_missing_keys(self, message, fill_data): + """Double escapes placeholder which are missing in 'fill_data'""" + placeholder_keys = re.findall("\{([^}]+)\}", message) + + fill_keys = [] + for key, value in fill_data.items(): + fill_keys.append(key) + if isinstance(value, dict): + for child_key in value.keys(): + fill_keys.append("{}[{}]".format(key, child_key)) + + not_matched = set(placeholder_keys) - set(fill_keys) + + for not_matched_item in not_matched: + message = message.replace("{}".format(not_matched_item), + "{{{}}}".format(not_matched_item)) + + return message From 3a41d6a72158af0707d58f146b834076ed386b13 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Fri, 23 Dec 2022 14:13:00 +0100 Subject: [PATCH 04/23] OP-4470 - safer handling of review path 'published_path' might be missing. Thumbnail path was fixed previously, this one was missed. --- .../slack/plugins/publish/integrate_slack_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 9122c1c5ed..c4d6b27726 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -163,17 +163,21 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _get_review_path(self, instance): """Returns abs url for review if present in instance repres""" - published_path = None + review_path = None for repre in instance.data.get("representations", []): tags = repre.get('tags', []) if (repre.get("review") or "review" in tags or "burnin" in tags): - if os.path.exists(repre["published_path"]): - published_path = repre["published_path"] + repre_review_path = ( + repre.get("published_path") or + os.path.join(repre["stagingDir"], repre["files"]) + ) + if os.path.exists(repre_review_path): + review_path = repre_review_path if "burnin" in tags: # burnin has precedence if exists break - return published_path + return review_path def _python2_call(self, token, channel, message, publish_files): from slackclient import SlackClient From f5842d91bd49cc955f49ed53388efd565b84f0a6 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Dec 2022 12:18:11 +0100 Subject: [PATCH 05/23] rename variable 'max_len' to 'message_len' --- openpype/widgets/message_window.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/widgets/message_window.py b/openpype/widgets/message_window.py index 94e51f5d4f..8301f5e8f2 100644 --- a/openpype/widgets/message_window.py +++ b/openpype/widgets/message_window.py @@ -105,16 +105,18 @@ class ScrollMessageBox(QtWidgets.QDialog): content_widget = QtWidgets.QWidget(self) scroll_widget.setWidget(content_widget) - max_len = 0 + message_len = 0 content_layout = QtWidgets.QVBoxLayout(content_widget) for message in messages: label_widget = QtWidgets.QLabel(message, content_widget) content_layout.addWidget(label_widget) - max_len = max(max_len, len(message)) + message_len = max(message_len, len(message)) # guess size of scrollable area max_width = QtWidgets.QApplication.desktop().availableGeometry().width - scroll_widget.setMinimumWidth(min(max_width, max_len * 6)) + scroll_widget.setMinimumWidth( + min(max_width, message_len * 6) + ) layout.addWidget(scroll_widget) if not cancelable: # if no specific buttons OK only From a941aabc049ee3ec59ef829dbd58c1abca937868 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Dec 2022 12:21:05 +0100 Subject: [PATCH 06/23] call the width method to get the value --- openpype/widgets/message_window.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/widgets/message_window.py b/openpype/widgets/message_window.py index 8301f5e8f2..2b186475ee 100644 --- a/openpype/widgets/message_window.py +++ b/openpype/widgets/message_window.py @@ -113,7 +113,7 @@ class ScrollMessageBox(QtWidgets.QDialog): message_len = max(message_len, len(message)) # guess size of scrollable area - max_width = QtWidgets.QApplication.desktop().availableGeometry().width + max_width = QtWidgets.QApplication.desktop().availableGeometry().width() scroll_widget.setMinimumWidth( min(max_width, message_len * 6) ) From f3c13e7669c149ef4b652820a2f72f4553484ea2 Mon Sep 17 00:00:00 2001 From: iLLiCiTiT Date: Wed, 28 Dec 2022 12:26:25 +0100 Subject: [PATCH 07/23] fix too long line --- openpype/widgets/message_window.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/openpype/widgets/message_window.py b/openpype/widgets/message_window.py index 2b186475ee..a44df2ec8e 100644 --- a/openpype/widgets/message_window.py +++ b/openpype/widgets/message_window.py @@ -113,7 +113,8 @@ class ScrollMessageBox(QtWidgets.QDialog): message_len = max(message_len, len(message)) # guess size of scrollable area - max_width = QtWidgets.QApplication.desktop().availableGeometry().width() + desktop = QtWidgets.QApplication.desktop() + max_width = desktop.availableGeometry().width() scroll_widget.setMinimumWidth( min(max_width, message_len * 6) ) From efab124e0f721e03367710f1800bb6b0a8f9ca1e Mon Sep 17 00:00:00 2001 From: OpenPype Date: Sat, 31 Dec 2022 03:27:31 +0000 Subject: [PATCH 08/23] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 40abb9e9fd..4fbe5a3608 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.10-nightly.4" +__version__ = "3.14.10-nightly.5" From 7a372d1b1cd481610a044a552e155db62ae8adbb Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 3 Jan 2023 17:05:47 +0100 Subject: [PATCH 09/23] Added possibility to mention users or groups --- .../plugins/publish/integrate_slack_api.py | 271 ++++++++++++++---- 1 file changed, 213 insertions(+), 58 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index d94ecb02e4..8d34521194 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -3,6 +3,9 @@ import six import pyblish.api import copy from datetime import datetime +import re +from abc import ABCMeta, abstractmethod +import time from openpype.client import OpenPypeMongoConnection from openpype.lib.plugin_tools import prepare_template_data @@ -33,6 +36,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): publish_files = set() message = '' additional_message = instance.data.get("slack_additional_message") + token = instance.data["slack_token"] if additional_message: message = "{} \n".format(additional_message) for message_profile in instance.data["slack_channel_message_profiles"]: @@ -52,18 +56,16 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): project = instance.context.data["anatomyData"]["project"]["code"] for channel in message_profile["channels"]: if six.PY2: - msg_id, file_ids = \ - self._python2_call(instance.data["slack_token"], - channel, - message, - publish_files) + client = SlackPython2Operations(token, self.log) else: - msg_id, file_ids = \ - self._python3_call(instance.data["slack_token"], - channel, - message, - publish_files) + client = SlackPython3Operations(token, self.log) + users, groups = client.get_users_and_groups() + message = self._translate_users(message, users, groups) + + msg_id, file_ids = client.send_message(channel, + message, + publish_files) if not msg_id: return @@ -177,15 +179,211 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): break return published_path - def _python2_call(self, token, channel, message, publish_files): - from slackclient import SlackClient + def _get_user_id(self, users, user_name): + """Returns internal slack id for user name""" + user_id = None + for user in users: + if (not user.get("deleted") and + (user_name.lower() == user["name"].lower() or + user_name.lower() == user["real_name"])): + user_id = user["id"] + break + return user_id + + def _get_group_id(self, groups, group_name): + """Returns internal group id for string name""" + group_id = None + for group in groups: + if (not group.get("date_delete") and + (group_name.lower() == group["name"].lower() or + group_name.lower() == group["handle"])): + group_id = group["id"] + break + return group_id + + def _translate_users(self, message, users, groups): + matches = re.findall("(?".format(slack_id) + else: + slack_id = self._get_group_id(groups, user_name) + if slack_id: + mention = "".format(slack_id) + if mention: + message = message.replace(orig_user, mention) + + return message + + +@six.add_metaclass(ABCMeta) +class AbstractSlackOperations: + + @abstractmethod + def _get_users_list(self): + """Return response with user list, different methods Python 2 vs 3""" + raise NotImplementedError + + @abstractmethod + def _get_usergroups_list(self): + """Return response with user list, different methods Python 2 vs 3""" + raise NotImplementedError + + @abstractmethod + def get_users_and_groups(self): + """Return users and groups, different retry in Python 2 vs 3""" + raise NotImplementedError + + @abstractmethod + def send_message(self, channel, message, publish_files): + """Sends message to channel, different methods in Python 2 vs 3""" + pass + + def _get_users(self): + """Parse users.list response into list of users (dicts)""" + first = True + next_page = None + users = [] + while first or next_page: + response = self._get_users_list() + first = False + next_page = response.get("response_metadata").get("next_cursor") + for user in response.get("members"): + users.append(user) + + return users + + def _get_groups(self): + """Parses usergroups.list response into list of groups (dicts)""" + response = self._get_usergroups_list() + groups = [] + for group in response.get("usergroups"): + groups.append(group) + return groups + + def _enrich_error(self, error_str, channel): + """Enhance known errors with more helpful notations.""" + if 'not_in_channel' in error_str: + # there is no file.write.public scope, app must be explicitly in + # the channel + msg = " - application must added to channel '{}'.".format(channel) + error_str += msg + " Ask Slack admin." + return error_str + + +class SlackPython3Operations(AbstractSlackOperations): + + def __init__(self, token, log): + from slack_sdk import WebClient + + self.client = WebClient(token=token) + self.log = log + + def _get_users_list(self): + return self.client.users_list() + + def _get_usergroups_list(self): + return self.client.usergroups_list() + + def get_users_and_groups(self): + from slack_sdk.errors import SlackApiError + while True: + try: + users = self._get_users() + groups = self._get_groups() + break + except SlackApiError as e: + retry_after = e.response.headers.get("Retry-After") + if retry_after: + print( + "Rate limit hit, sleeping for {}".format(retry_after)) + time.sleep(int(retry_after)) + else: + raise e + + return users, groups + + def send_message(self, channel, message, publish_files): + from slack_sdk.errors import SlackApiError + try: + attachment_str = "\n\n Attachment links: \n" + file_ids = [] + for published_file in publish_files: + response = self.client.files_upload( + file=published_file, + filename=os.path.basename(published_file)) + attachment_str += "\n<{}|{}>".format( + response["file"]["permalink"], + os.path.basename(published_file)) + file_ids.append(response["file"]["id"]) + + if publish_files: + message += attachment_str + + message = self.translate_users(message) + + response = self.client.chat_postMessage( + channel=channel, + text=message + ) + return response.data["ts"], file_ids + except SlackApiError as e: + # # You will get a SlackApiError if "ok" is False + error_str = self._enrich_error(str(e.response["error"]), channel) + self.log.warning("Error happened {}".format(error_str)) + except Exception as e: + error_str = self._enrich_error(str(e), channel) + self.log.warning("Not SlackAPI error", exc_info=True) + + return None, [] + + +class SlackPython2Operations(AbstractSlackOperations): + + def __init__(self, token, log): + from slackclient import SlackClient + + self.client = SlackClient(token=token) + self.log = log + + def _get_users_list(self): + return self.client.api_call("users.list") + + def _get_usergroups_list(self): + return self.client.api_call("usergroups.list") + + def get_users_and_groups(self): + while True: + try: + users = self._get_users() + groups = self._get_groups() + break + except Exception as e: + retry_after = e.response.headers.get("Retry-After") + if retry_after: + print( + "Rate limit hit, sleeping for {}".format(retry_after)) + time.sleep(int(retry_after)) + else: + raise e + + return users, groups + + def send_message(self, channel, message, publish_files): try: - client = SlackClient(token) attachment_str = "\n\n Attachment links: \n" file_ids = [] for p_file in publish_files: with open(p_file, 'rb') as pf: - response = client.api_call( + response = self.client.api_call( "files.upload", file=pf, channel=channel, @@ -206,7 +404,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if publish_files: message += attachment_str - response = client.api_call( + response = self.client.api_call( "chat.postMessage", channel=channel, text=message @@ -223,46 +421,3 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): self.log.warning("Error happened: {}".format(error_str)) return None, [] - - def _python3_call(self, token, channel, message, publish_files): - from slack_sdk import WebClient - from slack_sdk.errors import SlackApiError - try: - client = WebClient(token=token) - attachment_str = "\n\n Attachment links: \n" - file_ids = [] - for published_file in publish_files: - response = client.files_upload( - file=published_file, - filename=os.path.basename(published_file)) - attachment_str += "\n<{}|{}>".format( - response["file"]["permalink"], - os.path.basename(published_file)) - file_ids.append(response["file"]["id"]) - - if publish_files: - message += attachment_str - - response = client.chat_postMessage( - channel=channel, - text=message - ) - return response.data["ts"], file_ids - except SlackApiError as e: - # You will get a SlackApiError if "ok" is False - error_str = self._enrich_error(str(e.response["error"]), channel) - self.log.warning("Error happened {}".format(error_str)) - except Exception as e: - error_str = self._enrich_error(str(e), channel) - self.log.warning("Not SlackAPI error", exc_info=True) - - return None, [] - - def _enrich_error(self, error_str, channel): - """Enhance known errors with more helpful notations.""" - if 'not_in_channel' in error_str: - # there is no file.write.public scope, app must be explicitly in - # the channel - msg = " - application must added to channel '{}'.".format(channel) - error_str += msg + " Ask Slack admin." - return error_str From 1ee50975c0e61acac51f7f76b905a420e95487c6 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 3 Jan 2023 17:14:41 +0100 Subject: [PATCH 10/23] Fix wrong position of method --- .../plugins/publish/integrate_slack_api.py | 38 +++++++++---------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index a010d08a82..fc5342177d 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -228,6 +228,25 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return message + def _escape_missing_keys(self, message, fill_data): + """Double escapes placeholder which are missing in 'fill_data'""" + placeholder_keys = re.findall("\{([^}]+)\}", message) + + fill_keys = [] + for key, value in fill_data.items(): + fill_keys.append(key) + if isinstance(value, dict): + for child_key in value.keys(): + fill_keys.append("{}[{}]".format(key, child_key)) + + not_matched = set(placeholder_keys) - set(fill_keys) + + for not_matched_item in not_matched: + message = message.replace("{}".format(not_matched_item), + "{{{}}}".format(not_matched_item)) + + return message + @six.add_metaclass(ABCMeta) class AbstractSlackOperations: @@ -283,25 +302,6 @@ class AbstractSlackOperations: error_str += msg + " Ask Slack admin." return error_str - def _escape_missing_keys(self, message, fill_data): - """Double escapes placeholder which are missing in 'fill_data'""" - placeholder_keys = re.findall("\{([^}]+)\}", message) - - fill_keys = [] - for key, value in fill_data.items(): - fill_keys.append(key) - if isinstance(value, dict): - for child_key in value.keys(): - fill_keys.append("{}[{}]".format(key, child_key)) - - not_matched = set(placeholder_keys) - set(fill_keys) - - for not_matched_item in not_matched: - message = message.replace("{}".format(not_matched_item), - "{{{}}}".format(not_matched_item)) - - return message - class SlackPython3Operations(AbstractSlackOperations): From f80fe3fb938488009974cd931b285ea8564d7b2b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 3 Jan 2023 17:14:57 +0100 Subject: [PATCH 11/23] Fix obsolete call of method --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index fc5342177d..803a07f5d2 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -352,8 +352,6 @@ class SlackPython3Operations(AbstractSlackOperations): if publish_files: message += attachment_str - message = self.translate_users(message) - response = self.client.chat_postMessage( channel=channel, text=message From 394c678299f26967ba64b87a7c8684c1d0419191 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 3 Jan 2023 17:19:50 +0100 Subject: [PATCH 12/23] Do not throw exception if user or group list error Skip notification, publish shouldn't fail because of this. --- .../modules/slack/plugins/publish/integrate_slack_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 803a07f5d2..f18b927c98 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -331,7 +331,9 @@ class SlackPython3Operations(AbstractSlackOperations): "Rate limit hit, sleeping for {}".format(retry_after)) time.sleep(int(retry_after)) else: - raise e + self.log.warning("Cannot pull user info, " + "mentions won't work", exc_info=True) + return [], [] return users, groups @@ -395,7 +397,9 @@ class SlackPython2Operations(AbstractSlackOperations): "Rate limit hit, sleeping for {}".format(retry_after)) time.sleep(int(retry_after)) else: - raise e + self.log.warning("Cannot pull user info, " + "mentions won't work", exc_info=True) + return [], [] return users, groups From 61ef7479e8bbb842378229e87b2187924a368c7a Mon Sep 17 00:00:00 2001 From: Ondrej Samohel Date: Tue, 3 Jan 2023 17:21:14 +0100 Subject: [PATCH 13/23] =?UTF-8?q?=F0=9F=94=A7=20pass=20mongo=20url=20as=20?= =?UTF-8?q?default?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- openpype/settings/defaults/project_settings/deadline.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/settings/defaults/project_settings/deadline.json b/openpype/settings/defaults/project_settings/deadline.json index 6e1c0f3540..527f5c0d24 100644 --- a/openpype/settings/defaults/project_settings/deadline.json +++ b/openpype/settings/defaults/project_settings/deadline.json @@ -2,7 +2,7 @@ "deadline_servers": [], "publish": { "CollectDefaultDeadlineServer": { - "pass_mongo_url": false + "pass_mongo_url": true }, "CollectDeadlinePools": { "primary_pool": "", From 68fe82323883c8035f3831820681174980649802 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Tue, 3 Jan 2023 17:53:00 +0100 Subject: [PATCH 14/23] Fix resolving of user_id Display name or real_name could be used also. --- .../slack/plugins/publish/integrate_slack_api.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index f18b927c98..577ead9667 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -187,10 +187,13 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _get_user_id(self, users, user_name): """Returns internal slack id for user name""" user_id = None + user_name_lower = user_name.lower() for user in users: if (not user.get("deleted") and - (user_name.lower() == user["name"].lower() or - user_name.lower() == user["real_name"])): + (user_name_lower == user["name"].lower() or + # bots dont have display_name + user_name_lower == user.get("display_name", '').lower() or + user_name_lower == user.get("real_name", '').lower())): user_id = user["id"] break return user_id @@ -208,8 +211,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _translate_users(self, message, users, groups): matches = re.findall("(? Date: Tue, 3 Jan 2023 17:58:28 +0100 Subject: [PATCH 15/23] Updated documentation --- website/docs/module_slack.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index 2bfd7cb562..1999912fdc 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -100,6 +100,10 @@ them to add additional message (notification for specific users for example, art Additional message will be sent only if at least one profile, eg. one target channel is configured. All available template keys (see higher) could be used here as a placeholder too. +#### User or group notifications +Message template or dynamic data could contain user or group notification, it must be in format @artist.name, '@John Doe' or "@admin group" for display name containing space. +If value prefixed with @ is not resolved and Slack user is not found, message will contain same value (not translated by Slack into link and proper mention.) + #### Message retention Currently no purging of old messages is implemented in Openpype. Admins of Slack should set their own retention of messages and files per channel. (see https://slack.com/help/articles/203457187-Customize-message-and-file-retention-policies) From f1111a99bda950cb79ff36675f12c34146f05ddf Mon Sep 17 00:00:00 2001 From: OpenPype Date: Wed, 4 Jan 2023 03:28:26 +0000 Subject: [PATCH 16/23] [Automated] Bump version --- openpype/version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/version.py b/openpype/version.py index 4fbe5a3608..ae514e371e 100644 --- a/openpype/version.py +++ b/openpype/version.py @@ -1,3 +1,3 @@ # -*- coding: utf-8 -*- """Package declaring Pype version.""" -__version__ = "3.14.10-nightly.5" +__version__ = "3.14.10-nightly.6" From b2e8ea6fb80faeef67cbae6db212422c43cb0b58 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Jan 2023 11:36:12 +0100 Subject: [PATCH 17/23] Hound --- .../modules/slack/plugins/publish/integrate_slack_api.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 577ead9667..97182ffd9b 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -4,7 +4,6 @@ import six import pyblish.api import copy from datetime import datetime -import re from abc import ABCMeta, abstractmethod import time @@ -210,8 +209,9 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return group_id def _translate_users(self, message, users, groups): - matches = re.findall("(? format.""" + matches = re.findall(r"(? Date: Wed, 4 Jan 2023 12:44:07 +0100 Subject: [PATCH 18/23] Fix - search pattern Updated to use user profile --- .../modules/slack/plugins/publish/integrate_slack_api.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 97182ffd9b..02197a6d01 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -191,8 +191,10 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): if (not user.get("deleted") and (user_name_lower == user["name"].lower() or # bots dont have display_name - user_name_lower == user.get("display_name", '').lower() or - user_name_lower == user.get("real_name", '').lower())): + user_name_lower == user["profile"].get("display_name", + '').lower() or + user_name_lower == user["profile"].get("real_name", + '').lower())): user_id = user["id"] break return user_id @@ -210,7 +212,7 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): def _translate_users(self, message, users, groups): """Replace all occurences of @mentions with proper <@name> format.""" - matches = re.findall(r"(? Date: Wed, 4 Jan 2023 12:51:47 +0100 Subject: [PATCH 19/23] Fix - cannot pull response from ordinary exception --- .../slack/plugins/publish/integrate_slack_api.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 02197a6d01..bb5cd40936 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -397,15 +397,9 @@ class SlackPython2Operations(AbstractSlackOperations): groups = self._get_groups() break except Exception as e: - retry_after = e.response.headers.get("Retry-After") - if retry_after: - print( - "Rate limit hit, sleeping for {}".format(retry_after)) - time.sleep(int(retry_after)) - else: - self.log.warning("Cannot pull user info, " - "mentions won't work", exc_info=True) - return [], [] + self.log.warning("Cannot pull user info, " + "mentions won't work", exc_info=True) + return [], [] return users, groups From dff87d4e1cfabafb4ec40c46a1cbc48c851f661f Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Jan 2023 12:52:32 +0100 Subject: [PATCH 20/23] Hound --- openpype/modules/slack/plugins/publish/integrate_slack_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index bb5cd40936..21069e0b13 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -396,7 +396,7 @@ class SlackPython2Operations(AbstractSlackOperations): users = self._get_users() groups = self._get_groups() break - except Exception as e: + except Exception: self.log.warning("Cannot pull user info, " "mentions won't work", exc_info=True) return [], [] From e44f585aa63c32ebb6913426626fdb4c3fd0a008 Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Jan 2023 15:05:36 +0100 Subject: [PATCH 21/23] OP-4490 - safer resolving if site is active --- openpype/modules/sync_server/providers/dropbox.py | 2 +- openpype/modules/sync_server/providers/gdrive.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/providers/dropbox.py b/openpype/modules/sync_server/providers/dropbox.py index 3515aee93f..a517e7d847 100644 --- a/openpype/modules/sync_server/providers/dropbox.py +++ b/openpype/modules/sync_server/providers/dropbox.py @@ -165,7 +165,7 @@ class DropboxHandler(AbstractProvider): Returns: (boolean) """ - return self.presets["enabled"] and self.dbx is not None + return self.presets.get("enabled") and self.dbx is not None @classmethod def get_configurable_items(cls): diff --git a/openpype/modules/sync_server/providers/gdrive.py b/openpype/modules/sync_server/providers/gdrive.py index 297a5c9fec..4e24fe41d2 100644 --- a/openpype/modules/sync_server/providers/gdrive.py +++ b/openpype/modules/sync_server/providers/gdrive.py @@ -119,7 +119,7 @@ class GDriveHandler(AbstractProvider): Returns: (boolean) """ - return self.presets["enabled"] and self.service is not None + return self.presets.get("enabled") and self.service is not None @classmethod def get_system_settings_schema(cls): From 8527554c2d3bafd1a7eb796219fe7cefebcf749b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Jan 2023 15:07:36 +0100 Subject: [PATCH 22/23] OP-4490 - fixed unnecessary checks Configured sites were checked all the time even if they weren't used. Now it checks only sites that are set for project. --- openpype/modules/sync_server/sync_server.py | 67 ++++++--------------- 1 file changed, 17 insertions(+), 50 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index d0a40a60ff..d1ca69a31c 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -169,7 +169,7 @@ def resolve_paths(module, file_path, project_name, return local_file_path, remote_file_path -def site_is_working(module, project_name, site_name): +def _site_is_working(module, project_name, site_name, site_config): """ Confirm that 'site_name' is configured correctly for 'project_name'. @@ -179,54 +179,17 @@ def site_is_working(module, project_name, site_name): module (SyncServerModule) project_name(string): site_name(string): + site_config (dict): configuration for site from Settings Returns (bool) """ - if _get_configured_sites(module, project_name).get(site_name): - return True - return False + provider = module.get_provider_for_site(site=site_name) + handler = lib.factory.get_provider(provider, + project_name, + site_name, + presets=site_config) - -def _get_configured_sites(module, project_name): - """ - Loops through settings and looks for configured sites and checks - its handlers for particular 'project_name'. - - Args: - project_setting(dict): dictionary from Settings - only_project_name(string, optional): only interested in - particular project - Returns: - (dict of dict) - {'ProjectA': {'studio':True, 'gdrive':False}} - """ - settings = module.get_sync_project_setting(project_name) - return _get_configured_sites_from_setting(module, project_name, settings) - - -def _get_configured_sites_from_setting(module, project_name, project_setting): - if not project_setting.get("enabled"): - return {} - - initiated_handlers = {} - configured_sites = {} - all_sites = module._get_default_site_configs() - all_sites.update(project_setting.get("sites")) - for site_name, config in all_sites.items(): - provider = module.get_provider_for_site(site=site_name) - handler = initiated_handlers.get((provider, site_name)) - if not handler: - handler = lib.factory.get_provider(provider, - project_name, - site_name, - presets=config) - initiated_handlers[(provider, site_name)] = \ - handler - - if handler.is_active(): - configured_sites[site_name] = True - - return configured_sites + return handler.is_active() class SyncServerThread(threading.Thread): @@ -288,7 +251,8 @@ class SyncServerThread(threading.Thread): for project_name in enabled_projects: preset = self.module.sync_project_settings[project_name] - local_site, remote_site = self._working_sites(project_name) + local_site, remote_site = self._working_sites(project_name, + preset) if not all([local_site, remote_site]): continue @@ -464,7 +428,7 @@ class SyncServerThread(threading.Thread): self.timer.cancel() self.timer = None - def _working_sites(self, project_name): + def _working_sites(self, project_name, sync_config): if self.module.is_project_paused(project_name): self.log.debug("Both sites same, skipping") return None, None @@ -476,9 +440,12 @@ class SyncServerThread(threading.Thread): local_site, remote_site)) return None, None - configured_sites = _get_configured_sites(self.module, project_name) - if not all([local_site in configured_sites, - remote_site in configured_sites]): + local_site_config = sync_config.get('sites')[local_site] + remote_site_config = sync_config.get('sites')[remote_site] + if not all([_site_is_working(self.module, project_name, local_site, + local_site_config), + _site_is_working(self.module, project_name, remote_site, + remote_site_config)]): self.log.debug( "Some of the sites {} - {} is not working properly".format( local_site, remote_site From d3f09c075badd52f3faa9d4fe41678dd1abf1d9b Mon Sep 17 00:00:00 2001 From: Petr Kalis Date: Wed, 4 Jan 2023 15:39:13 +0100 Subject: [PATCH 23/23] OP-4490 - Hound --- openpype/modules/sync_server/sync_server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openpype/modules/sync_server/sync_server.py b/openpype/modules/sync_server/sync_server.py index d1ca69a31c..85b0774e90 100644 --- a/openpype/modules/sync_server/sync_server.py +++ b/openpype/modules/sync_server/sync_server.py @@ -443,9 +443,9 @@ class SyncServerThread(threading.Thread): local_site_config = sync_config.get('sites')[local_site] remote_site_config = sync_config.get('sites')[remote_site] if not all([_site_is_working(self.module, project_name, local_site, - local_site_config), + local_site_config), _site_is_working(self.module, project_name, remote_site, - remote_site_config)]): + remote_site_config)]): self.log.debug( "Some of the sites {} - {} is not working properly".format( local_site, remote_site