diff --git a/tableauserverclient/models/view_item.py b/tableauserverclient/models/view_item.py
index 4dfcf41c1..a8c1a6988 100644
--- a/tableauserverclient/models/view_item.py
+++ b/tableauserverclient/models/view_item.py
@@ -75,6 +75,9 @@ def csv(self):
@property
def total_views(self):
+ if self._total_views is None:
+ error = "Usage statistics must be requested when querying for view."
+ raise UnpopulatedPropertyError(error)
return self._total_views
@property
diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py
index 892abd8ce..0335ce781 100644
--- a/tableauserverclient/server/endpoint/views_endpoint.py
+++ b/tableauserverclient/server/endpoint/views_endpoint.py
@@ -24,9 +24,12 @@ def baseurl(self):
return "{0}/views".format(self.siteurl)
@api(version="2.2")
- def get(self, req_options=None):
+ def get(self, req_options=None, usage=False):
logger.info('Querying all views on site')
- server_response = self.get_request(self.baseurl, req_options)
+ url = self.baseurl
+ if usage:
+ url += "?includeUsageStatistics=true"
+ server_response = self.get_request(url, req_options)
pagination_item = PaginationItem.from_response(server_response.content, self.parent_srv.namespace)
all_view_items = ViewItem.from_response(server_response.content, self.parent_srv.namespace)
return all_view_items, pagination_item
diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py
index d7a2ae4a7..ec5cedc22 100644
--- a/tableauserverclient/server/endpoint/workbooks_endpoint.py
+++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py
@@ -118,19 +118,21 @@ def download(self, workbook_id, filepath=None, include_extract=True, no_extract=
# Get all views of workbook
@api(version="2.0")
- def populate_views(self, workbook_item):
+ def populate_views(self, workbook_item, usage=False):
if not workbook_item.id:
error = "Workbook item missing ID. Workbook must be retrieved from server first."
raise MissingRequiredFieldError(error)
def view_fetcher():
- return self._get_views_for_workbook(workbook_item)
+ return self._get_views_for_workbook(workbook_item, usage)
workbook_item._set_views(view_fetcher)
logger.info('Populated views for workbook (ID: {0}'.format(workbook_item.id))
- def _get_views_for_workbook(self, workbook_item):
+ def _get_views_for_workbook(self, workbook_item, usage):
url = "{0}/{1}/views".format(self.baseurl, workbook_item.id)
+ if usage:
+ url += "?includeUsageStatistics=true"
server_response = self.get_request(url)
views = ViewItem.from_response(server_response.content,
self.parent_srv.namespace,
diff --git a/tableauserverclient/server/request_options.py b/tableauserverclient/server/request_options.py
index 009daa6f1..37f23f54c 100644
--- a/tableauserverclient/server/request_options.py
+++ b/tableauserverclient/server/request_options.py
@@ -41,6 +41,11 @@ def page_number(self, page_number):
def apply_query_params(self, url):
params = []
+
+ if '?' in url:
+ url, existing_params = url.split('?')
+ params.append(existing_params)
+
if self.page_number:
params.append('pageNumber={0}'.format(self.pagenumber))
if self.page_size:
diff --git a/test/assets/view_get_usage.xml b/test/assets/view_get_usage.xml
new file mode 100644
index 000000000..a6844879d
--- /dev/null
+++ b/test/assets/view_get_usage.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/assets/workbook_populate_views_usage.xml b/test/assets/workbook_populate_views_usage.xml
new file mode 100644
index 000000000..a75e4037f
--- /dev/null
+++ b/test/assets/workbook_populate_views_usage.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/test_view.py b/test/test_view.py
index 7a35b3754..09ce2f3d7 100644
--- a/test/test_view.py
+++ b/test/test_view.py
@@ -7,6 +7,7 @@
ADD_TAGS_XML = os.path.join(TEST_ASSET_DIR, 'view_add_tags.xml')
GET_XML = os.path.join(TEST_ASSET_DIR, 'view_get.xml')
+GET_XML_USAGE = os.path.join(TEST_ASSET_DIR, 'view_get_usage.xml')
POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, 'Sample View Image.png')
POPULATE_PDF = os.path.join(TEST_ASSET_DIR, 'populate_pdf.pdf')
POPULATE_CSV = os.path.join(TEST_ASSET_DIR, 'populate_csv.csv')
@@ -45,6 +46,33 @@ def test_get(self):
self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_views[1].workbook_id)
self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[1].owner_id)
+ def test_get_with_usage(self):
+ with open(GET_XML_USAGE, 'rb') as f:
+ response_xml = f.read().decode('utf-8')
+ with requests_mock.mock() as m:
+ m.get(self.baseurl + "?includeUsageStatistics=true", text=response_xml)
+ all_views, pagination_item = self.server.views.get(usage=True)
+
+ self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_views[0].id)
+ self.assertEqual(7, all_views[0].total_views)
+ self.assertEqual('fd252f73-593c-4c4e-8584-c032b8022adc', all_views[1].id)
+ self.assertEqual(13, all_views[1].total_views)
+
+ def test_get_with_usage_and_filter(self):
+ with open(GET_XML_USAGE, 'rb') as f:
+ response_xml = f.read().decode('utf-8')
+ with requests_mock.mock() as m:
+ m.get(self.baseurl + "?includeUsageStatistics=true&filter=name:in:[foo,bar]", text=response_xml)
+ options = TSC.RequestOptions()
+ options.filter.add(TSC.Filter(TSC.RequestOptions.Field.Name, TSC.RequestOptions.Operator.In,
+ ["foo", "bar"]))
+ all_views, pagination_item = self.server.views.get(req_options=options, usage=True)
+
+ self.assertEqual("ENDANGERED SAFARI", all_views[0].name)
+ self.assertEqual(7, all_views[0].total_views)
+ self.assertEqual("Overview", all_views[1].name)
+ self.assertEqual(13, all_views[1].total_views)
+
def test_get_before_signin(self):
self.server._auth_token = None
self.assertRaises(TSC.NotSignedInError, self.server.views.get)
diff --git a/test/test_workbook.py b/test/test_workbook.py
index 91ca3d679..8c36f0229 100644
--- a/test/test_workbook.py
+++ b/test/test_workbook.py
@@ -13,6 +13,7 @@
POPULATE_CONNECTIONS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_connections.xml')
POPULATE_PREVIEW_IMAGE = os.path.join(TEST_ASSET_DIR, 'RESTAPISample Image.png')
POPULATE_VIEWS_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views.xml')
+POPULATE_VIEWS_USAGE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_populate_views_usage.xml')
PUBLISH_XML = os.path.join(TEST_ASSET_DIR, 'workbook_publish.xml')
UPDATE_XML = os.path.join(TEST_ASSET_DIR, 'workbook_update.xml')
@@ -220,6 +221,24 @@ def test_populate_views(self):
self.assertEqual('Interest rates', views_list[2].name)
self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url)
+ def test_populate_views_with_usage(self):
+ with open(POPULATE_VIEWS_USAGE_XML, 'rb') as f:
+ response_xml = f.read().decode('utf-8')
+ with requests_mock.mock() as m:
+ m.get(self.baseurl + '/1f951daf-4061-451a-9df1-69a8062664f2/views?includeUsageStatistics=true',
+ text=response_xml)
+ single_workbook = TSC.WorkbookItem('test')
+ single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2'
+ self.server.workbooks.populate_views(single_workbook, usage=True)
+
+ views_list = single_workbook.views
+ self.assertEqual('097dbe13-de89-445f-b2c3-02f28bd010c1', views_list[0].id)
+ self.assertEqual(2, views_list[0].total_views)
+ self.assertEqual('2c1ab9d7-8d64-4cc6-b495-52e40c60c330', views_list[1].id)
+ self.assertEqual(37, views_list[1].total_views)
+ self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id)
+ self.assertEqual(0, views_list[2].total_views)
+
def test_populate_views_missing_id(self):
single_workbook = TSC.WorkbookItem('test')
self.assertRaises(TSC.MissingRequiredFieldError, self.server.workbooks.populate_views, single_workbook)