8000 Merge pull request #378 from baixin137/materialize-workbooks · prnka11/server-client-python@5a35d6a · GitHub
[go: up one dir, main page]

Skip to content

Commit 5a35d6a

Browse files
authored
Merge pull request tableau#378 from baixin137/materialize-workbooks
Add materialized views settings support for workbooks/sites
2 parents a6c249b + 17921d4 commit 5a35d6a

File tree

9 files changed

+306
-16
lines changed

9 files changed

+306
-16
lines changed

samples/materialize_workbooks.py

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
import argparse
2+
import getpass
3+
import logging
4+
import tableauserverclient as TSC
5+
from collections import defaultdict
6+
7+
8+
def main():
9+
parser = argparse.ArgumentParser(description='Materialized views settings for sites/workbooks.')
10+
parser.add_argument('--server', '-s', required=True, help='Tableau server address')
11+
parser.add_argument('--username', '-u', required=True, help='username to sign into server')
12+
parser.add_argument('--password', '-p', required=False, help='password to sign into server')
13+
parser.add_argument('--mode', '-m', required=False, choices=['enable', 'disable'],
14+
help='enable/disable materialized views for sites/workbooks')
15+
parser.add_argument('--status', '-st', required=False, action='store_true',
16+
help='show materialized views enabled sites/workbooks')
17+
parser.add_argument('--site-id', '-si', required=False,
18+
help='set to Default site by default')
19+
parser.add_argument('--logging-level', '-l', choices=['debug', 'info', 'error'], default='error',
20+
help='desired logging level (set to error by default)')
21+
parser.add_argument('--type', '-t', required=False, choices=['site', 'workbook', 'project_name',
22+
'project_id', 'project_path'],
23+
help='type of content you want to update materialized views settings on')
24+
parser.add_argument('--path-list', '-pl', required=False, help='path to a list of workbook paths')
25+
parser.add_argument('--name-list', '-nl', required=False, help='path to a list of workbook names')
26+
parser.add_argument('--project-name', '-pn', required=False, help='name of the project')
27+
parser.add_argument('--project-path', '-pp', required=False, help="path of the project")
28+
29+
args = parser.parse_args()
30+
31+
if args.password:
32+
password = args.password
33+
else:
34+
password = getpass.getpass("Password: ")
35+
36+
logging_level = getattr(logging, args.logging_level.upper())
37+
logging.basicConfig(level=logging_level)
38+
39+
# site content url is the TSC term for site id
40+
site_content_url = args.site_id if args.site_id is not None else ""
41+
enable_materialized_views = args.mode == "enable"
42+
43+
if (args.type is None) != (args.mode is None):
44+
print("Use '--type <content type> --mode <enable/disable>' to update materialized views settings.")
45+
return
46+
47+
# enable/disable materialized views for site
48+
if args.type == 'site':
49+
if not update_site(args, enable_materialized_views, password, site_content_url):
50+
return
51+
52+
# enable/disable materialized views for workbook
53+
# works only when the site the workbooks belong to are enabled too
54+
elif args.type == 'workbook':
55+
if not update_workbook(args, enable_materialized_views, password, site_content_url):
56+
return
57+
58+
# enable/disable materialized views for project by project name
59+
# will show possible projects when project name is not unique
60+
elif args.type == 'project_name':
61+
if not update_project_by_name(args, enable_materialized_views, password, site_content_url):
62+
return
63+
64+
# enable/disable materialized views for proejct by project path, for example: project1/project2
65+
elif args.type == 'project_path':
66+
if not update_project_by_path(args, enable_materialized_views, password, site_content_url):
67+
return
68+
69+
# show enabled sites and workbooks
70+
if args.status:
71+
show_materialized_views_status(args, password, site_content_url)
72+
73+
74+
def find_project_path(project, all_projects, path):
75+
# project stores the id of it's parent
76+
# this method is to run recursively to find the path from root project to given project
77+
path = project.name if len(path) == 0 else project.name + '/' + path
78+
79+
if project.parent_id is None:
80+
return path
81+
else:
82+
return find_project_path(all_projects[project.parent_id], all_projects, path)
83+
84+
85+
def get_project_paths(server, projects):
86+
# most likely user won't have too many projects so we store them in a dict to search
87+
all_projects = {project.id: project for project in TSC.Pager(server.projects)}
88+
89+
result = dict()
90+
for project in projects:
91+
result[find_project_path(project, all_projects, "")] = project
92+
return result
93+
94+
95+
def print_paths(paths):
96+
for path in paths.keys():
97+
print(path)
98+
99+
100+
def show_materialized_views_status(args, password, site_content_url):
101+
tableau_auth = TSC.TableauAuth(args.username, password, site_id=site_content_url)
102+
server = TSC.Server(args.server, use_server_version=True)
103+
enabled_sites = set()
104+
with server.auth.sign_in(tableau_auth):
105+
# For server admin, this will prints all the materialized views enabled sites
106+
# For other users, this only prints the status of the site they belong to
107+
print("Materialized views is enabled on sites:")
108+
# only server admins can get all the sites in the server
109+
# other users can only get the site they are in
110+
for site in TSC.Pager(server.sites):
111+
if site.materialized_views_enabled:
112+
enabled_sites.add(site)
113+
print("Site name: {}".format(site.name))
114+
print('\n')
115+
116+
print("Materialized views is enabled on workbooks:")
117+
# Individual workbooks can be enabled only when the sites they belong to are enabled too
118+
for site in enabled_sites:
119+
site_auth = TSC.TableauAuth(args.username, password, site.content_url)
120+
with server.auth.sign_in(site_auth):
121+
for workbook in TSC.Pager(server.workbooks):
122+
if workbook.materialized_views_enabled:
123+
print("Workbook: {} from site: {}".format(workbook.name, site.name))
124+
125+
126+
def update_project_by_path(args, enable_materialized_views, password, site_content_url):
127+
if args.project_path is None:
128+
print("Use --project_path <project path> to specify the path of the project")
129+
return False
130+
tableau_auth = TSC.TableauAuth(args.username, password, site_content_url)
131+
server = TSC.Server(args.server, use_server_version=True)
132+
project_name = args.project_path.split('/')[-1]
133+
with server.auth.sign_in(tableau_auth):
134+
projects = [project for project in TSC.Pager(server.projects) if project.name == project_name]
135+
136+
possible_paths = get_project_paths(server, projects)
137+
update_project(possible_paths[args.project_path], server, enable_materialized_views)
138+
return True
139+
140+
141+
def update_project_by_name(args, enable_materialized_views, password, site_content_url):
142+
if args.project_name is None:
143+
print("Use --project-name <project name> to specify the name of the project")
144+
return False
145+
tableau_auth = TSC.TableauAuth(args.username, password, site_content_url)
146+
server = TSC.Server(args.server, use_server_version=True)
147+
with server.auth.sign_in(tableau_auth):
148+
# get all projects with given name
149+
projects = [project for project in TSC.Pager(server.projects) if project.name == args.project_name]
150+
151+
if len(projects) > 1:
152+
possible_paths = get_project_paths(server, projects)
153+
print("Project name is not unique, use '--project_path <path>'")
154+
print("Possible project paths:")
155+
print_paths(possible_paths)
156+
print('\n')
157+
return False
158+
else:
159+
update_project(projects[0], server, enable_materialized_views)
160+
return True
161+
162+
163+
def update_project(project, server, enable_materialized_views):
164+
for workbook in TSC.Pager(server.workbooks):
165+
if workbook.project_id == project.id:
166+
workbook.materialized_views_enabled = enable_materialized_views
167+
server.workbooks.update(workbook)
168+
169+
print("Updated materialized views settings for project: {}".format(project.name))
170+
print('\n')
171+
172+
173+
def parse_workbook_path(file_path):
174+
# parse the list of project path of workbooks
175+
workbook_paths = open(file_path, 'r')
176+
workbook_path_mapping = defaultdict(list)
177+
for workbook_path in workbook_paths:
178+
workbook_project = workbook_path.rstrip().split('/')
179+
workbook_path_mapping[workbook_project[-1]].append('/'.join(workbook_project[:-1]))
180+
return workbook_path_mapping
181+
182+
183+
def update_workbook(args, enable_materialized_views, password, site_content_url):
184+
if args.path_list is None and args.name_list is None:
185+
print("Use '--path-list <filename>' or '--name-list <filename>' to specify the path of a list of workbooks")
186+
print('\n')
187+
return False
188+
tableau_auth = TSC.TableauAuth(args.username, password, site_id=site_content_url)
189+
server = TSC.Server(args.server, use_server_version=True)
190+
with server.auth.sign_in(tableau_auth):
191+
if args.path_list is not None:
192+
workbook_path_mapping = parse_workbook_path(args.path_list)
193+
all_projects = {project.id: project for project in TSC.Pager(server.projects)}
194+
update_workbooks_by_paths(all_projects, enable_materialized_views, server, workbook_path_mapping)
195+
elif args.name_list is not None:
196+
update_workbooks_by_names(args.name_list, server, enable_materialized_views)
197+
return True
198+
199+
200+
def update_workbooks_by_paths(all_projects, enable_materialized_views, server, workbook_path_mapping):
201+
for workbook_name, workbook_paths in workbook_path_mapping.items():
202+
req_option = TSC.RequestOptions()
203+
req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name,
204+
TSC.RequestOptions.Operator.Equals,
205+
workbook_name))
206+
workbooks = list(TSC.Pager(server.workbooks, req_option))
207+
for workbook in workbooks:
208+
path = find_project_path(all_projects[workbook.project_id], all_projects, "")
209+
if path in workbook_paths:
210+
workbook.materialized_views_enabled = enable_materialized_views
211+
server.workbooks.update(workbook)
212+
print("Updated materialized views settings for workbook: {}".format(path + '/' + workbook.name))
213+
print('\n')
214+
215+
216+
def update_workbooks_by_names(name_list, server, enable_materialized_views):
217+
workbook_names = open(name_list, 'r')
218+
for workbook_name in workbook_names:
219+
req_option = TSC.RequestOptions()
220+
req_option.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name,
221+
TSC.RequestOptions.Operator.Equals,
222+
workbook_name.rstrip()))
223+
workbooks = list(TSC.Pager(server.workbooks, req_option))
224+
for workbook in workbooks:
225+
workbook.materialized_views_enabled = enable_materialized_views
226+
server.workbooks.update(workbook)
227+
print("Updated materialized views settings for workbook: {}".format(workbook.name))
228+
print('\n')
229+
230+
231+
def update_site(args, enable_materialized_views, password, site_content_url):
232+
tableau_auth = TSC.TableauAuth(args.username, password, site_id=site_content_url)
233+
server = TSC.Server(args.server, use_server_version=True)
234+
with server.auth.sign_in(tableau_auth):
235+
site_to_update = server.sites.get_by_content_url(site_content_url)
236+
site_to_update.materialized_views_enabled = enable_materialized_views
237+
238+
server.sites.update(site_to_update)
239+
print("Updated materialized views settings for site: {}".format(site_to_update.name))
240+
print('\n')
241+
return True
242+
243+
244+
if __name__ == "__main__":
245+
main()

tableauserverclient/models/site_item.py

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ class State:
1717

1818
def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_quota=None,
1919
disable_subscriptions=False, subscribe_others_enabled=True, revision_history_enabled=False,
20-
revision_limit=None):
20+
revision_limit=None, materialized_views_enabled=False):
2121
self._admin_mode = None
2222
self._id = None
2323
self._num_users = None
@@ -33,6 +33,7 @@ def __init__(self, name, content_url, admin_mode=None, user_quota=None, storage_
3333
self.revision_history_enabled = revision_history_enabled
3434
self.subscribe_others_enabled = subscribe_others_enabled
3535
self.admin_mode = admin_mode
36+
self.materialized_views_enabled = materialized_views_enabled
3637

3738
@property
3839
def admin_mode(self):
@@ -123,6 +124,15 @@ def subscribe_others_enabled(self):
123124
def subscribe_others_enabled(self, value):
124125
self._subscribe_others_enabled = value
125126

127+
@property
128+
def materialized_views_enabled(self):
129+
return self._materialized_views_enabled
130+
131+
@materialized_views_enabled.setter
132+
@property_is_boolean
133+
def materialized_views_enabled(self, value):
134+
self._materialized_views_enabled = value
135+
126136
def is_default(self):
127137
return self.name.lower() == 'default'
128138

@@ -132,16 +142,17 @@ def _parse_common_tags(self, site_xml, ns):
132142
if site_xml is not None:
133143
(_, name, content_url, _, admin_mode, state,
134144
subscribe_others_enabled, disable_subscriptions, revision_history_enabled,
135-
user_quota, storage_quota, revision_limit, num_users, storage) = self._parse_element(site_xml, ns)
145+
user_quota, storage_quota, revision_limit, num_users, storage,
146+
materialized_views_enabled) = self._parse_element(site_xml, ns)
136147

137148
self._set_values(None, name, content_url, None, admin_mode, state, subscribe_others_enabled,
138149
disable_subscriptions, revision_history_enabled, user_quota, storage_quota,
139-
revision_limit, num_users, storage)
150+
revision_limit, num_users, storage, materialized_views_enabled)
140151
return self
141152

142153
def _set_values(self, id, name, content_url, status_reason, admin_mode, state,
143154
subscribe_others_enabled, disable_subscriptions, revision_history_enabled,
144-
user_quota, storage_quota, revision_limit, num_users, storage):
155+
user_quota, storage_quota, revision_limit, num_users, storage, materialized_views_enabled):
145156
if id is not None:
146157
self._id = id
147158
if name:
@@ -170,6 +181,8 @@ def _set_values(self, id, name, content_url, status_reason, admin_mode, state,
170181
self._num_users = num_users
171182
if storage:
172183
self._storage = storage
184+
if materialized_views_enabled:
185+
self._materialized_views_enabled = materialized_views_enabled
173186

174187
@classmethod
175188
def from_response(cls, resp, ns):
@@ -179,12 +192,13 @@ def from_response(cls, resp, ns):
179192
for site_xml in all_site_xml:
180193
(id, name, content_url, status_reason, admin_mode, state, subscribe_others_enabled,
181194
disable_subscriptions, revision_history_enabled, user_quota, storage_quota,
182-
revision_limit, num_users, storage) = cls._parse_element(site_xml, ns)
195+
revision_limit, num_users, storage, materialized_views_enabled) = cls._parse_element(site_xml, ns)
183196

184197
site_item = cls(name, content_url)
185198
site_item._set_values(id, name, content_url, status_reason, admin_mode, state,
186199
subscribe_others_enabled, disable_subscriptions, revision_history_enabled,
187-
user_quota, storage_quota, revision_limit, num_users, storage)
200+
user_quota, storage_quota, revision_limit, num_users, storage,
201+
materialized_views_enabled)
188202
all_site_items.append(site_item)
189203
return all_site_items
190204

@@ -219,9 +233,11 @@ def _parse_element(site_xml, ns):
219233
num_users = usage_elem.get('numUsers', None)
220234
storage = usage_elem.get('storage', None)
221235

236+
materialized_views_enabled = string_to_bool(site_xml.get('materializedViewsEnabled', ''))
237+
222238
return id, name, content_url, status_reason, admin_mode, state, subscribe_others_enabled,\
223239
disable_subscriptions, revision_history_enabled, user_quota, storage_quota,\
224-
revision_limit, num_users, storage
240+
revision_limit, num_users, storage, materialized_views_enabled
225241

226242

227243
# Used to convert string represented boolean to a boolean type

0 commit comments

Comments
 (0)
0