From 91e1605f643920f1b56ffdb2ee9e23dbe53c280c Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Tue, 24 Oct 2017 13:49:22 -0700 Subject: [PATCH 1/7] added ability to get usage statistics for views --- tableauserverclient/server/endpoint/views_endpoint.py | 5 +++-- tableauserverclient/server/endpoint/workbooks_endpoint.py | 8 ++++---- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/tableauserverclient/server/endpoint/views_endpoint.py b/tableauserverclient/server/endpoint/views_endpoint.py index 892abd8ce..4e87d79eb 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -24,9 +24,10 @@ 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 = "{0}?includeUsageStatistics={1}".format(self.baseurl, usage) + 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..dd4154d9a 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -118,19 +118,19 @@ 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): - url = "{0}/{1}/views".format(self.baseurl, workbook_item.id) + def _get_views_for_workbook(self, workbook_item, usage): + url = "{0}/{1}/views?includeUsageStatistics={2}".format(self.baseurl, workbook_item.id, usage) server_response = self.get_request(url) views = ViewItem.from_response(server_response.content, self.parent_srv.namespace, From 05ea75cfe753dda2125e30b72ab97efefc11d0da Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Tue, 24 Oct 2017 14:19:28 -0700 Subject: [PATCH 2/7] fixes to address failing tests --- tableauserverclient/models/view_item.py | 3 +++ tableauserverclient/server/endpoint/views_endpoint.py | 4 +++- tableauserverclient/server/endpoint/workbooks_endpoint.py | 4 +++- 3 files changed, 9 insertions(+), 2 deletions(-) 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 4e87d79eb..0335ce781 100644 --- a/tableauserverclient/server/endpoint/views_endpoint.py +++ b/tableauserverclient/server/endpoint/views_endpoint.py @@ -26,7 +26,9 @@ def baseurl(self): @api(version="2.2") def get(self, req_options=None, usage=False): logger.info('Querying all views on site') - url = "{0}?includeUsageStatistics={1}".format(self.baseurl, usage) + 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) diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index dd4154d9a..52dbf51db 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -130,7 +130,9 @@ def view_fetcher(): logger.info('Populated views for workbook (ID: {0}'.format(workbook_item.id)) def _get_views_for_workbook(self, workbook_item, usage): - url = "{0}/{1}/views?includeUsageStatistics={2}".format(self.baseurl, workbook_item.id, usage) + url = "{0}/{1}/views".format(self.baseurl, workbook_item.id, usage) + if usage: + url += "?includeUsageStatistics=true" server_response = self.get_request(url) views = ViewItem.from_response(server_response.content, self.parent_srv.namespace, From f8e8656d7c70397731b31754a1685be3559bd7e6 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Wed, 25 Oct 2017 10:43:55 -0700 Subject: [PATCH 3/7] code clean up and added test cases for the change --- .../server/endpoint/workbooks_endpoint.py | 2 +- test/assets/view_get_usage.xml | 16 +++++++++++ test/assets/workbook_populate_views_usage.xml | 14 ++++++++++ test/test_view.py | 23 ++++++++++++++++ test/test_workbook.py | 27 +++++++++++++++++++ 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 test/assets/view_get_usage.xml create mode 100644 test/assets/workbook_populate_views_usage.xml diff --git a/tableauserverclient/server/endpoint/workbooks_endpoint.py b/tableauserverclient/server/endpoint/workbooks_endpoint.py index 52dbf51db..ec5cedc22 100644 --- a/tableauserverclient/server/endpoint/workbooks_endpoint.py +++ b/tableauserverclient/server/endpoint/workbooks_endpoint.py @@ -130,7 +130,7 @@ def view_fetcher(): logger.info('Populated views for workbook (ID: {0}'.format(workbook_item.id)) def _get_views_for_workbook(self, workbook_item, usage): - url = "{0}/{1}/views".format(self.baseurl, workbook_item.id, usage) + url = "{0}/{1}/views".format(self.baseurl, workbook_item.id) if usage: url += "?includeUsageStatistics=true" server_response = self.get_request(url) diff --git a/test/assets/view_get_usage.xml b/test/assets/view_get_usage.xml new file mode 100644 index 000000000..5c1b285d6 --- /dev/null +++ b/test/assets/view_get_usage.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ 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..7fa17475a 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,28 @@ 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_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, text=response_xml) + all_views, pagination_item = self.server.views.get(usage=True) + + self.assertEqual(2, pagination_item.total_available) + self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_views[0].id) + self.assertEqual('ENDANGERED SAFARI', all_views[0].name) + self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', all_views[0].content_url) + self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_views[0].workbook_id) + self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[0].owner_id) + self.assertEqual(7, all_views[0].total_views) + + self.assertEqual('fd252f73-593c-4c4e-8584-c032b8022adc', all_views[1].id) + self.assertEqual('Overview', all_views[1].name) + self.assertEqual('Superstore/sheets/Overview', all_views[1].content_url) + self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_views[1].workbook_id) + self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[1].owner_id) + 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..1b13261c3 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,32 @@ 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_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', text=response_xml) + single_workbook = TSC.WorkbookItem('test') + single_workbook._id = '1f951daf-4061-451a-9df1-69a8062664f2' + self.server.workbooks.populate_views(single_workbook) + + views_list = single_workbook.views + self.assertEqual('097dbe13-de89-445f-b2c3-02f28bd010c1', views_list[0].id) + self.assertEqual('GDP per capita', views_list[0].name) + self.assertEqual('RESTAPISample/sheets/GDPpercapita', views_list[0].content_url) + self.assertEqual(2, views_list[0].total_views) + + self.assertEqual('2c1ab9d7-8d64-4cc6-b495-52e40c60c330', views_list[1].id) + self.assertEqual('Country ranks', views_list[1].name) + self.assertEqual('RESTAPISample/sheets/Countryranks', views_list[1].content_url) + self.assertEqual(37, views_list[1].total_views) + + self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id) + self.assertEqual('Interest rates', views_list[2].name) + self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url) + 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) From 91affd93e99e24fe45cf0f8bf78c4bff1f2a9cae Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Wed, 25 Oct 2017 10:55:48 -0700 Subject: [PATCH 4/7] minor fix to pass travis --- test/test_workbook.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test/test_workbook.py b/test/test_workbook.py index 1b13261c3..63a5ffbbc 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -246,7 +246,6 @@ def test_populate_views_usage(self): self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url) 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) From 272afbd980b13ac19d96acb6b7fcaec495f99a28 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Wed, 25 Oct 2017 14:42:22 -0700 Subject: [PATCH 5/7] renamed the new test cases --- test/test_view.py | 2 +- test/test_workbook.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/test_view.py b/test/test_view.py index 7fa17475a..d14d5a4a6 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -46,7 +46,7 @@ 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_usage(self): + 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: diff --git a/test/test_workbook.py b/test/test_workbook.py index 63a5ffbbc..00dff3f0e 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -221,7 +221,7 @@ 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_usage(self): + 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: From ffee9bd02a27b82eb54752f68807df3c7bb0fa58 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Thu, 26 Oct 2017 14:10:08 -0700 Subject: [PATCH 6/7] implemented changes to allow request options to work with usage stats, and also added test --- tableauserverclient/server/request_options.py | 5 ++++ test/assets/view_get_usage.xml | 6 +++-- test/test_view.py | 27 +++++++++++-------- test/test_workbook.py | 13 +++------ 4 files changed, 28 insertions(+), 23 deletions(-) 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 index 5c1b285d6..914fbd6f7 100644 --- a/test/assets/view_get_usage.xml +++ b/test/assets/view_get_usage.xml @@ -2,14 +2,16 @@ - + + - + + diff --git a/test/test_view.py b/test/test_view.py index d14d5a4a6..b00fdce19 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -50,22 +50,27 @@ 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, text=response_xml) + m.get(self.baseurl + "?includeUsageStatistics=true", text=response_xml) all_views, pagination_item = self.server.views.get(usage=True) - self.assertEqual(2, pagination_item.total_available) self.assertEqual('d79634e1-6063-4ec9-95ff-50acbf609ff5', all_views[0].id) - self.assertEqual('ENDANGERED SAFARI', all_views[0].name) - self.assertEqual('SafariSample/sheets/ENDANGEREDSAFARI', all_views[0].content_url) - self.assertEqual('3cc6cd06-89ce-4fdc-b935-5294135d6d42', all_views[0].workbook_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[0].owner_id) self.assertEqual(7, all_views[0].total_views) - self.assertEqual('fd252f73-593c-4c4e-8584-c032b8022adc', all_views[1].id) - self.assertEqual('Overview', all_views[1].name) - self.assertEqual('Superstore/sheets/Overview', all_views[1].content_url) - self.assertEqual('6d13b0ca-043d-4d42-8c9d-3f3313ea3a00', all_views[1].workbook_id) - self.assertEqual('5de011f8-5aa9-4d5b-b991-f462c8dd6bb7', all_views[1].owner_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("foo", all_views[0].name) + self.assertEqual(7, all_views[0].total_views) + self.assertEqual("bar", all_views[1].name) self.assertEqual(13, all_views[1].total_views) def test_get_before_signin(self): diff --git a/test/test_workbook.py b/test/test_workbook.py index 00dff3f0e..8c36f0229 100644 --- a/test/test_workbook.py +++ b/test/test_workbook.py @@ -225,25 +225,18 @@ 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', text=response_xml) + 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) + 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('GDP per capita', views_list[0].name) - self.assertEqual('RESTAPISample/sheets/GDPpercapita', views_list[0].content_url) self.assertEqual(2, views_list[0].total_views) - self.assertEqual('2c1ab9d7-8d64-4cc6-b495-52e40c60c330', views_list[1].id) - self.assertEqual('Country ranks', views_list[1].name) - self.assertEqual('RESTAPISample/sheets/Countryranks', views_list[1].content_url) self.assertEqual(37, views_list[1].total_views) - self.assertEqual('0599c28c-6d82-457e-a453-e52c1bdb00f5', views_list[2].id) - self.assertEqual('Interest rates', views_list[2].name) - self.assertEqual('RESTAPISample/sheets/Interestrates', views_list[2].content_url) self.assertEqual(0, views_list[2].total_views) def test_populate_views_missing_id(self): From f22b51bd567f831c1f856ec5baad380d3ed00791 Mon Sep 17 00:00:00 2001 From: Chris Shin Date: Mon, 20 Nov 2017 14:49:56 -0800 Subject: [PATCH 7/7] minor text change to apply comment --- test/assets/view_get_usage.xml | 4 ++-- test/test_view.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/assets/view_get_usage.xml b/test/assets/view_get_usage.xml index 914fbd6f7..a6844879d 100644 --- a/test/assets/view_get_usage.xml +++ b/test/assets/view_get_usage.xml @@ -2,13 +2,13 @@ - + - + diff --git a/test/test_view.py b/test/test_view.py index b00fdce19..09ce2f3d7 100644 --- a/test/test_view.py +++ b/test/test_view.py @@ -68,9 +68,9 @@ def test_get_with_usage_and_filter(self): ["foo", "bar"])) all_views, pagination_item = self.server.views.get(req_options=options, usage=True) - self.assertEqual("foo", all_views[0].name) + self.assertEqual("ENDANGERED SAFARI", all_views[0].name) self.assertEqual(7, all_views[0].total_views) - self.assertEqual("bar", all_views[1].name) + self.assertEqual("Overview", all_views[1].name) self.assertEqual(13, all_views[1].total_views) def test_get_before_signin(self):