Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions tabcmd/commands/datasources_and_workbooks/export_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ def define_args(export_parser):
group.add_argument(
"--filter",
metavar="COLUMN=VALUE",
action="append",
help=_("tabcmd.export.help.filter"),
)
group.add_argument(
Expand Down Expand Up @@ -143,11 +144,19 @@ def run_command(cls, args):

@staticmethod
def apply_filters_from_args(request_options: RequestOptionsType, args, logger=None) -> None:
if args.filter:
logger.debug("filter = {}".format(args.filter))
params = args.filter.split("&")
for value in params:
ExportCommand.apply_filter_value(logger, request_options, value)
if not args.filter:
return
logger.debug("filter = {}".format(args.filter))
# Back-compat: a single --filter flag historically joined multiple pairs
# with '&' (e.g. `--filter "a=1&b=2"`), so that one case still splits on '&'.
# Repeated --filter flags each carry exactly one pair — no split — so a
# literal '&' or '=' in a value is passed through untouched.
if len(args.filter) == 1:
values = args.filter[0].split("&")
else:
values = args.filter
for value in values:
ExportCommand.apply_filter_value(logger, request_options, value)

@staticmethod
def download_wb_pdf(server, workbook_item, args, logger):
Expand Down
2 changes: 1 addition & 1 deletion tabcmd/locales/en/tabcmd_messages_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ tabcmd.error.server_connection_failed=Failed to connect to server
tabcmd.errors.parent.not.found=Containing project could not be found
tabcmd.errors.user_already_exists=A user with the name {0} already exists. Try a different name.
tabcmd.export.help.filename=Filename to store the exported data
tabcmd.export.help.filter=Data filter to apply to the view
tabcmd.export.help.filter=Data filter to apply to the view (COLUMN=VALUE). Repeat the flag for multiple filters (--filter "A=1" --filter "B=2"); repeated flags allow '&' inside a value. For back-compat, a single --filter may still join pairs with '&' (--filter "A=1&B=2").
tabcmd.export.help.orientation=Page orientation (landscape or portrait) of the exported PDF
tabcmd.export.help.page_size=Set the page size of the exported PDF
tabcmd.export.help.url=URL of the workbook or view to export
Expand Down
27 changes: 27 additions & 0 deletions tests/commands/test_datasources_and_workbooks_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from unittest.mock import MagicMock

from tabcmd.commands.datasources_and_workbooks.datasources_and_workbooks_command import DatasourcesAndWorkbooks
from tabcmd.commands.datasources_and_workbooks.export_command import ExportCommand
import tableauserverclient as tsc
import unittest
from unittest import mock
Expand Down Expand Up @@ -189,6 +190,32 @@ def test_apply_csv_options_with_language(self):
DatasourcesAndWorkbooks.apply_csv_options(mock_logger, request_options, mock_args)
assert request_options.language == "de"

def test_apply_filters_from_args_none(self):
args = argparse.Namespace(filter=None)
request_options = tsc.PDFRequestOptions()
ExportCommand.apply_filters_from_args(request_options, args, mock_logger)
assert request_options.view_filters == []

def test_apply_filters_from_args_single(self):
args = argparse.Namespace(filter=["Region=West"])
request_options = tsc.PDFRequestOptions()
ExportCommand.apply_filters_from_args(request_options, args, mock_logger)
assert request_options.view_filters == [("Region", "West")]

def test_apply_filters_from_args_repeated(self):
# repeated --filter flags each carry one pair, so '&' in a value is safe.
args = argparse.Namespace(filter=["Region=West", "Product=AT&T"])
request_options = tsc.PDFRequestOptions()
ExportCommand.apply_filters_from_args(request_options, args, mock_logger)
assert request_options.view_filters == [("Region", "West"), ("Product", "AT&T")]

def test_apply_filters_from_args_backcompat_ampersand_join(self):
# Old-style single flag joining pairs with '&' still parses.
args = argparse.Namespace(filter=["Region=West&Product=Widget"])
request_options = tsc.PDFRequestOptions()
ExportCommand.apply_filters_from_args(request_options, args, mock_logger)
assert request_options.view_filters == [("Region", "West"), ("Product", "Widget")]


@mock.patch("tableauserverclient.Server")
class MockedServerTests(unittest.TestCase):
Expand Down
14 changes: 14 additions & 0 deletions tests/parsers/test_parser_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,17 @@ def test_export_parser_missing_all_args(self):
mock_args = [commandname]
with self.assertRaises(SystemExit):
args = self.parser_under_test.parse_args(mock_args)

def test_export_parser_single_filter(self):
args = self.parser_under_test.parse_args([commandname, "helloworld", "--pdf", "--filter", "Region=West"])
assert args.filter == ["Region=West"]

def test_export_parser_repeated_filter(self):
args = self.parser_under_test.parse_args(
[commandname, "helloworld", "--pdf", "--filter", "Region=West", "--filter", "Product=AT&T"]
)
assert args.filter == ["Region=West", "Product=AT&T"]

def test_export_parser_no_filter_defaults_to_none(self):
args = self.parser_under_test.parse_args([commandname, "helloworld", "--pdf"])
assert args.filter is None