-
Notifications
You must be signed in to change notification settings - Fork 5
/
Tableau2Slack.py
164 lines (147 loc) · 6.5 KB
/
Tableau2Slack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
import logging
import traceback
import os
import requests
from datetime import datetime, timezone
import pytz
import json
from dotenv import load_dotenv
import tableauserverclient as TSC
from slack import WebClient
from slack.errors import SlackApiError
import nest_asyncio
nest_asyncio.apply()
def Tableau2Slack():
try:
# Import Tableau environment variables
load_dotenv() # EXAMPLE VALUES:
tabAccount = os.getenv("tabAcct") # email@email.com
tabPass = os.getenv("tabPwd") # password
tabDomain = os.getenv("tabDomain") # site-id
tabServer = os.getenv("tabServer") # https://10ay.online.tableau.com
tabView = os.getenv("tabView") # InvestorUpdate
tabPath = os.getenv("tabPath") # ./InvestorUpdate.png
# For further explanation of Tableau environment variables visit:
# https://tableau.github.io/server-client-python/docs/api-ref#tableauauth-class
# Full list of Tableau Online Server Domains for tabServer variable:
# https://help.tableau.com/current/online/en-us/to_keep_data_fresh.htm#safelist
print("Talking to Tableau...\n")
tableau_auth = TSC.TableauAuth(tabAccount, tabPass, tabDomain)
server = TSC.Server(tabServer)
# Searching Tableau Online account for the View we declared in env variables
with server.auth.sign_in(tableau_auth):
server.use_server_version()
req_option = TSC.RequestOptions()
req_option.filter.add(
TSC.Filter(
TSC.RequestOptions.Field.Name,
TSC.RequestOptions.Operator.Equals,
tabView,
)
)
all_views, pagination_item = server.views.get(req_option)
# Error catching for bad View names
if not all_views:
raise LookupError("View with the specified name was not found.")
view_item = all_views[0]
# Force refresh of screenshot if cached image more than one hour old
max_age = "1"
if not max_age:
max_age = "1"
image_req_option = TSC.ImageRequestOptions(
imageresolution=TSC.ImageRequestOptions.Resolution.High, maxage=max_age
)
server.views.populate_image(view_item, image_req_option)
# Save bytes as image
with open(tabPath, "wb") as image_file:
image_file.write(view_item.image)
print("Tableau image successfully saved to {0}".format(tabPath), "\n")
try:
# Upload and send customized Slack message
# Import Slack environment variables
load_dotenv() # EXAMPLE VALUES:
slackBotToken = os.getenv(
"slackBotToken"
) # xoxb-1111111111-111111111-111111111
slackChannel = os.getenv("slackChannel") # #random
# for further explanation of Slack environment variables visit:
# https://slack.dev/python-slackclient/
# Preparing date to format filename and caption. Change "Sales Analytics" ect. to whatever you like.
date = datetime.utcnow().strftime("%Y-%m-%d")
utc_dt = datetime.now(timezone.utc)
your_tz = pytz.timezone(
"US/Pacific"
) # Replace with your timezone to localize
imagetitle = "SalesUpdate_{}".format(
utc_dt.astimezone(your_tz).strftime("%Y-%m-%d_%I:%M%p_%Z")
)
caption = "Sales Update as of {}".format(
utc_dt.astimezone(your_tz).strftime("%Y-%m-%d %I:%M%p %Z")
)
captionA = "Sales Analytics Update"
captionB = " as of {}".format(
utc_dt.astimezone(your_tz).strftime("%Y-%m-%d %I:%M%p %Z")
)
captionC = "\n\n*" + captionA + "*" + captionB
# Connect to Slack
print("Talking to Slack...\n")
sc = WebClient(slackBotToken)
# Upload Tableau image to burner channel so that we can securely generate a URL from Tableau image.
img = sc.files_upload(
channels="#tableau-test", # You will need to make this channel or another like it.
file=tabPath,
title=imagetitle,
filetype="png",
)
# Store private URL to post in message.
url_private = img["file"]["url_private"]
# Construct "builder" and "attachments" json payloads.
blocksjson = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": ":level_slider: Queuing up your Daily Dose of Data :control_knobs:",
},
},
{
"type": "image",
"image_url": "https://i.imgur.com/Eg4TjJq.png",
"alt_text": "Nina Kravisualization",
},
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": "Want to see more? <https://www.tableau.com/ Insert Your Link to All Dashboards Here>",
}
],
},
{"type": "section", "text": {"type": "mrkdwn", "text": captionC}},
]
# Note: the use of "attachments" is a work around and may be deprecated in the future.
attachmentsjson = [
{"title": "", "text": "", "image_url": url_private, "alt_text": caption}
]
# Post images with custom icon and username
msg = sc.chat_postMessage(
channel=slackChannel,
icon_url="https://i.imgur.com/WpMvRvn.jpg",
username="Nina Kravisualization",
blocks=json.dumps(blocksjson),
attachments=json.dumps(attachmentsjson),
)
# Remove the local image file once it is posted
fileRemoval = "{0}".format(tabPath)
os.remove(fileRemoval)
print("Removing image from local machine.")
# Out of the box Slack error handling
except SlackApiError as e:
assert e.response["ok"] is False
assert e.response["error"]
print(f"Got an error: {e.response['error']}")
# Tableau try statement error handling
except:
traceback.print_exc()
Tableau2Slack()