client/#75 - Added Slack module

Added collector
Added integrator
This commit is contained in:
Petr Kalis 2021-06-08 11:48:32 +02:00
parent 9d055e9adb
commit 841124e87c
7 changed files with 214 additions and 1 deletions

View file

@ -39,6 +39,7 @@ from .deadline import DeadlineModule
from .project_manager_action import ProjectManagerAction
from .standalonepublish_action import StandAlonePublishAction
from .sync_server import SyncServerModule
from .slack import SlackIntegrationModule
__all__ = (
@ -77,5 +78,7 @@ __all__ = (
"ProjectManagerAction",
"StandAlonePublishAction",
"SyncServerModule"
"SyncServerModule",
"SlackIntegrationModule"
)

View file

@ -0,0 +1,46 @@
Slack notification for publishing
---------------------------------
This module allows configuring profiles(when to trigger, for which combination of task, host and family)
and templates(could contain {} placeholder, as "{asset} published").
These need to be configured in
```Project settings > Slack > Publish plugins > Notification to Slack```
Slack module must be enabled in System Setting, could be configured per Project.
## App installation
Slack app needs to be installed to company's workspace. Attached .yaml file could be
used, follow instruction https://api.slack.com/reference/manifests#using
## Settings
### Token
Most important for module to work is to fill authentication token
```Project settings > Slack > Publish plugins > Token```
This token should be available after installation of app in Slack dashboard.
It is possible to create multiple tokens and configure different scopes for them.
### Profiles
Profiles are used to select when to trigger notification. One or multiple profiles
could be configured, 'family', 'task name' (regex available) and host combination is needed.
Eg. If I want to be notified when render is published from Maya, setting is:
- family: 'render'
- host: 'Maya'
### Channel
Message could be delivered to one or multiple channels, by default app allows Slack bot
to send messages to 'public' channels (eg. bot doesn't need to join channel first).
This could be configured in Slack dashboard and scopes might be modified.
### Message
Placeholders {} could be used in message content which will be filled during runtime.
Only keys available in 'anatomyData' are currently implemented.
Example of message content:
```{SUBSET} for {Asset} was published.```

View file

@ -0,0 +1,7 @@
from .slack_module import (
SlackIntegrationModule
)
__all__ = (
"SlackIntegrationModule"
)

View file

@ -0,0 +1,22 @@
_metadata:
major_version: 1
minor_version: 1
display_information:
name: OpenPypeNotifier
features:
app_home:
home_tab_enabled: false
messages_tab_enabled: true
messages_tab_read_only_enabled: true
bot_user:
display_name: OpenPypeNotifier
always_online: false
oauth_config:
scopes:
bot:
- chat:write
- chat:write.public
settings:
org_deploy_enabled: false
socket_mode_enabled: false
is_hosted: false

View file

@ -0,0 +1,55 @@
from avalon import io
import pyblish.api
from openpype.lib.profiles_filtering import filter_profiles
class CollectSlackFamilies(pyblish.api.InstancePlugin):
"""Collect family for Slack notification
Expects configured profile in
Project settings > Slack > Publish plugins > Notification to Slack
Add Slack family to those instance that should be messaged to Slack
"""
order = pyblish.api.CollectorOrder + 0.4999
label = 'Collect Slack family'
profiles = None
def process(self, instance):
task_name = io.Session.get("AVALON_TASK")
family = self.main_family_from_instance(instance)
key_values = {
"families": family,
"tasks": task_name,
"hosts": instance.data["anatomyData"]["app"],
}
self.log.debug("key_values {}".format(key_values))
profile = filter_profiles(self.profiles, key_values,
logger=self.log)
# make slack publishable
if profile:
if instance.data.get('families'):
instance.data['families'].append('slack')
else:
instance.data['families'] = ['slack']
instance.data["slack_channel"] = profile["channel"]
instance.data["slack_message"] = profile["message"]
slack_token = (instance.context.data["project_settings"]
["slack"]
["publish"]
["CollectSlackFamilies"]
["token"])
instance.data["slack_token"] = slack_token
def main_family_from_instance(self, instance): # TODO yank from integrate
"""Returns main family of entered instance."""
family = instance.data.get("family")
if not family:
family = instance.data["families"][0]
return family

View file

@ -0,0 +1,54 @@
from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError
import pyblish.api
from openpype.lib.plugin_tools import prepare_template_data
class IntegrateSlackAPI(pyblish.api.InstancePlugin):
""" Send message notification to a channel.
Triggers on instances with "slack" family, filled by
'collect_slack_family'.
Expects configured profile in
Project settings > Slack > Publish plugins > Notification to Slack
Message template can contain {} placeholders from anatomyData.
"""
order = pyblish.api.IntegratorOrder+0.499
label = "Integrate Slack Api"
families = ["slack"]
optional = True
def process(self, instance):
message_templ = instance.data["slack_message"]
fill_pairs = set()
for key, value in instance.data["anatomyData"].items():
if not isinstance(value, str):
continue
fill_pairs.add((key, value))
self.log.debug("fill_pairs:: {}".format(fill_pairs))
message = message_templ.format(**prepare_template_data(fill_pairs))
self.log.debug("message:: {}".format(message))
if '{' in message:
self.log.warning(
"Missing values to fill message properly {}".format(message))
return
for channel in instance.data["slack_channel"]:
try:
client = WebClient(token=instance.data["slack_token"])
_response = client.chat_postMessage(
channel=channel,
text=message
)
except SlackApiError as e:
# You will get a SlackApiError if "ok" is False
self.log.warning("Error happened {}".format(e.response[
"error"]))

View file

@ -0,0 +1,26 @@
import os
from openpype.modules import (
PypeModule, IPluginPaths)
class SlackIntegrationModule(PypeModule, IPluginPaths):
"""Allows sending notification to Slack channels during publishing."""
name = "slack"
def initialize(self, modules_settings):
slack_settings = modules_settings[self.name]
self.enabled = slack_settings["enabled"]
def connect_with_modules(self, _enabled_modules):
"""Nothing special."""
return
def get_plugin_paths(self):
"""Deadline plugin paths."""
current_dir = os.path.dirname(os.path.abspath(__file__))
return {
"publish": [os.path.join(current_dir, "plugins", "publish")]
}