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)