Skip to content

Commit

Permalink
Merge pull request #4 from shipyardapp/sc-3280/tableau-verify-datasou…
Browse files Browse the repository at this point in the history
…rce-refresh-status

Sc 3280/tableau verify datasource refresh status
  • Loading branch information
Blake Burch authored Jun 16, 2022
2 parents dfe09cf + 5b8280f commit 4dbe228
Show file tree
Hide file tree
Showing 11 changed files with 571 additions and 3 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ coverage.xml
*.py,cover
.hypothesis/
.pytest_cache/
.DS_Store

# Translations
*.mo
Expand Down Expand Up @@ -109,6 +110,7 @@ venv/
ENV/
env.bak/
venv.bak/
.idea/

# Spyder project settings
.spyderproject
Expand Down
Binary file added tableau.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes.
49 changes: 49 additions & 0 deletions tableau_blueprints/authorization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import sys
import tableauserverclient as TSC

EXIT_CODE_INVALID_CREDENTIALS = 200


def connect_to_tableau(
username,
password,
site_id,
server_url,
sign_in_method):
"""TSC library to sign in and sign out of Tableau Server and Tableau Online.
:param username:The username or access token name of the user.
:param password:The password or access token of the user.
:param site_id: The site_id for required datasources. ex: ffc7f88a-85a7-48d5-ac03-09ef0a677280
:param server_url: This corresponds to the contentUrl attribute in the Tableau REST API.
:param sign_in_method: Whether to log in with username_password or access_token.
:return: server object, connection object
"""
if sign_in_method == 'username_password':
tableau_auth = TSC.TableauAuth(username, password, site_id=site_id)

if sign_in_method == 'access_token':
tableau_auth = TSC.PersonalAccessTokenAuth(
token_name=username,
personal_access_token=password,
site_id=site_id)

try:
# Make sure we use an updated version of the rest apis.
server = TSC.Server(
server_url,
use_server_version=True,
)
connection = server.auth.sign_in(tableau_auth)
print("Successfully authenticated with Tableau.")
except Exception as e:
print(f'Failed to connect to Tableau.')
if sign_in_method == 'username_password':
print('Invalid username or password. Please check for typos and try again.')
if sign_in_method == 'access_token':
print(
'Invalid token name or access token. Please check for typos and try again.')
print(e)
sys.exit(EXIT_CODE_INVALID_CREDENTIALS)

return server, connection
148 changes: 148 additions & 0 deletions tableau_blueprints/download_view.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import argparse
import sys
import shipyard_utils as shipyard

import tableauserverclient as TSC

try:
import authorization
import errors
import lookup
except BaseException:
from . import authorization
from . import errors
from . import lookup


def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('--username', dest='username', required=True)
parser.add_argument('--password', dest='password', required=True)
parser.add_argument(
'--sign-in-method',
dest='sign_in_method',
default='username_password',
choices={
'username_password',
'access_token'},
required=False)
parser.add_argument('--site-id', dest='site_id', required=True)
parser.add_argument('--server-url', dest='server_url', required=True)
parser.add_argument('--view-name', dest='view_name', required=True)
parser.add_argument(
'--file-type',
dest='file_type',
choices=[
'png',
'pdf',
'csv'],
type=str.lower,
required=True)
parser.add_argument(
'--destination-file-name',
dest='destination_file_name',
default='output.csv',
required=True)
parser.add_argument(
'--destination-folder-name',
dest='destination_folder_name',
default='',
required=False)
parser.add_argument('--file-options', dest='file_options', required=False)
parser.add_argument('--workbook-name', dest='workbook_name', required=True)
parser.add_argument('--project-name', dest='project_name', required=True)
args = parser.parse_args()
return args


def generate_view_content(server, view_id, file_type):
"""
Given a specific view_id, populate the view and return the bytes necessary for creating the file.
"""
view_object = server.views.get_by_id(view_id)
if file_type == 'png':
server.views.populate_image(view_object)
view_content = view_object.image
if file_type == 'pdf':
server.views.populate_pdf(view_object, req_options=None)
view_content = view_object.pdf
if file_type == 'csv':
server.views.populate_csv(view_object, req_options=None)
view_content = view_object.csv
return view_content


def write_view_content_to_file(
destination_full_path,
view_content,
file_type,
view_name):
"""
Write the byte contents to the specified file path.
"""
try:
with open(destination_full_path, 'wb') as f:
if file_type == 'csv':
f.writelines(view_content)
else:
f.write(view_content)
print(
f'Successfully downloaded {view_name} to {destination_full_path}')
except OSError as e:
print(f'Could not write file: {destination_full_path}')
print(e)
sys.exit(errors.EXIT_CODE_FILE_WRITE_ERROR)


def main():
args = get_args()
username = args.username
password = args.password
site_id = args.site_id
server_url = args.server_url
sign_in_method = args.sign_in_method
view_name = args.view_name
file_type = args.file_type
project_name = args.project_name
workbook_name = args.workbook_name

# Set all file parameters
destination_file_name = args.destination_file_name
destination_folder_name = shipyard.files.clean_folder_name(
args.destination_folder_name)
destination_full_path = shipyard.files.combine_folder_and_file_name(
folder_name=destination_folder_name, file_name=destination_file_name)

server, connection = authorization.connect_to_tableau(
username,
password,
site_id,
server_url,
sign_in_method)

with connection:
project_id = lookup.get_project_id(
server=server, project_name=project_name)
workbook_id = lookup.get_workbook_id(
server=server,
project_id=project_id,
workbook_name=workbook_name)
view_id = lookup.get_view_id(
server=server,
project_id=project_id,
workbook_id=workbook_id,
view_name=view_name)

view_content = generate_view_content(
server=server, view_id=view_id, file_type=file_type)
shipyard.files.create_folder_if_dne(
destination_folder_name=destination_folder_name)
write_view_content_to_file(
destination_full_path=destination_full_path,
view_content=view_content,
file_type=file_type,
view_name=view_name)


if __name__ == '__main__':
main()
16 changes: 16 additions & 0 deletions tableau_blueprints/errors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
EXIT_CODE_FINAL_STATUS_SUCCESS = 0
EXIT_CODE_UNKNOWN_ERROR = 3

EXIT_CODE_FILE_WRITE_ERROR = 100

EXIT_CODE_INVALID_CREDENTIALS = 200
EXIT_CODE_INVALID_PROJECT = 201
EXIT_CODE_INVALID_WORKBOOK = 202
EXIT_CODE_INVALID_VIEW = 203
EXIT_CODE_INVALID_JOB = 204
EXIT_CODE_INVALID_DATASOURCE = 205
EXIT_CODE_REFRESH_ERROR = 206

EXIT_CODE_FINAL_STATUS_CANCELLED = 210
EXIT_CODE_FINAL_STATUS_ERRORED = 211
EXIT_CODE_STATUS_INCOMPLETE = 212
104 changes: 104 additions & 0 deletions tableau_blueprints/job_status.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import tableauserverclient as TSC
import argparse
import sys
import shipyard_utils as shipyard

try:
import errors
import authorization
except BaseException:
from . import errors
from . import authorization


def get_args():
parser = argparse.ArgumentParser()
parser.add_argument('--username', dest='username', required=True)
parser.add_argument('--password', dest='password', required=True)
parser.add_argument('--site-id', dest='site_id', required=True)
parser.add_argument('--server-url', dest='server_url', required=True)
parser.add_argument(
'--sign-in-method',
dest='sign_in_method',
default='username_password',
choices={
'username_password',
'access_token'},
required=False)
parser.add_argument('--job-id', dest='job_id', required=False)
args = parser.parse_args()
return args


def get_job_info(server, job_id):
"""
Gets information about the specified job_id.
"""
try:
job_info = server.jobs.get_by_id(job_id)
except Exception as e:
print(f'Job {job_id} was not found.')
print(e)
sys.exit(errors.EXIT_CODE_INVALID_JOB)
return job_info


def determine_job_status(server, job_id):
"""
Job status response handler.
The finishCode indicates the status of the job: -1 for incomplete, 0 for success, 1 for error, or 2 for cancelled.
"""
job_info = get_job_info(server, job_id)
if job_info.finish_code == -1:
if job_info.started_at is None:
print(
f'Tableau reports that the job {job_id} has not yet started.')
else:
print(
f'Tableau reports that the job {job_id} is not yet complete.')
exit_code = errors.EXIT_CODE_STATUS_INCOMPLETE
elif job_info.finish_code == 0:
print(f'Tableau reports that job {job_id} was successful.')
exit_code = errors.EXIT_CODE_FINAL_STATUS_SUCCESS
elif job_info.finish_code == 1:
print(f'Tableau reports that job {job_id} errored.')
exit_code = errors.EXIT_CODE_FINAL_STATUS_ERRORED
elif job_info.finish_code == 2:
print(f'Tableau reports that job {job_id} was cancelled.')
exit_code = errors.EXIT_CODE_FINAL_STATUS_CANCELLED
else:
print(f'Something went wrong when fetching status for job {job_id}')
exit_code = errors.EXIT_CODE_UNKNOWN_ERROR
return exit_code


def main():
args = get_args()
username = args.username
password = args.password
site_id = args.site_id
server_url = args.server_url
sign_in_method = args.sign_in_method

base_folder_name = shipyard.logs.determine_base_artifact_folder(
'tableau')
artifact_subfolder_paths = shipyard.logs.determine_artifact_subfolders(
base_folder_name)
shipyard.logs.create_artifacts_folders(artifact_subfolder_paths)

if args.job_id:
job_id = args.job_id
else:
job_id = shipyard.logs.read_pickle_file(
artifact_subfolder_paths, 'job_id')

server, connection = authorization.connect_to_tableau(
username, password, site_id, server_url, sign_in_method)

with connection:
sys.exit(determine_job_status(server, job_id))


if __name__ == '__main__':
main()
Loading

0 comments on commit 4dbe228

Please sign in to comment.