Skip to content

Commit 1a51e52

Browse files
authored
Merge branch 'master' into pv/taco-version-name
2 parents 31a1e1c + 6c6bcf8 commit 1a51e52

File tree

201 files changed

+7958
-2877
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

201 files changed

+7958
-2877
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ labels: ''
66
assignees: ''
77

88
---
9+
Thanks for filing a bug report! Please check out our docs on [Filing a Bug with the SDK](https://tableau.github.io/connector-plugin-sdk/docs/bug-artifacts) for guidance on what to include with your report.
910

1011
**Describe the bug**
1112
A clear and concise description of what the bug is.

.github/workflows/packaging.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ jobs:
1111

1212
steps:
1313
- uses: actions/checkout@v2
14-
- name: Set up Python 3.7
14+
- name: Set up Python 3.9
1515
uses: actions/setup-python@v1
1616
with:
17-
python-version: 3.7
17+
python-version: 3.9
1818
- name: Set up Java
1919
uses: actions/setup-java@v1
2020
with:

.github/workflows/tdvt.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@ jobs:
1010

1111
steps:
1212
- uses: actions/checkout@v2
13-
- name: Set up Python 3.7
13+
- name: Set up Python 3.9
1414
uses: actions/setup-python@v1
1515
with:
16-
python-version: 3.7
16+
python-version: 3.9
1717
- name: Install TDVT
1818
run: |
1919
python -m pip install --upgrade pip
2020
pip install -e tdvt/
2121
- name: Run TDVT unit tests
2222
run: |
2323
cd tdvt/test
24-
python tdvt_test.py -v CommandLineTest ConfigTest DiffTest PrintConfigurationsTest ResultsTest ResultsExceptionTest
24+
python tdvt_test.py -v CommandLineTest ConfigTest DiffTest PrintConfigurationsTest ResultsTest ResultsExceptionTest TestCreatorTest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ dmypy.json
138138
packager_tests_logs.txt
139139
packaging_logs.txt
140140

141+
# MacOS
142+
.DS_Store
143+
141144
# TDVT logs
142145
tabquery_logs.zip
143146
tdvt_actuals_combined.zip

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,19 @@
11
# Tableau Connector SDK Changelog
2+
## 2023-04-10
3+
### Changed
4+
- Update `oauth_config.xsd` to include `configLabel` field and update `min-version-tableau` to be 2023.2 if present
5+
## 2023-04-04
6+
### Changed
7+
- Update `oauth_config.xsd` to include `instanceUrlSuffix` field and update `min-version-tableau` to be 2023.1 if present
8+
## 2022-12-16
9+
### Changed
10+
- Convert database impersonation sample to Connection Dialog V2
11+
## 2022-11-21
12+
### Changed
13+
- Update `connector_plugin_manifest_latest.xsd` to allow multple `oauth-config` fields and update `min-version-tableau` to be 2021.4 if present
14+
## 2022-11-16
15+
### Changed
16+
- Update `oauth_config.xsd` to include `oauthConfigId` field and update `min-version-tableau` to be 2021.4 if present
217
## 2021-09-02
318
### Changed
419
- Validate max length of `name` in Company-G in `connector_plugin_manifest_latest.xsd`

README.md

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,28 @@
44

55
This project consists of documentation, example files, the Tableau Datasource Verification Tool (TDVT) test harness, and a packaging tool that you can use to build and customize a Tableau Connector that uses an ODBC or JDBC driver.
66

7-
The latest version of the SDK is always targeted towards the latest, non-beta version of Tableau. Right now, this is **Tableau Desktop/Server 2021.3**. This means that the samples may not work on older versions of Tableau, and connectors packaged with newer versions of the SDK may not work in older versions of Tableau. You can download past releases of the SDK to work with older versions of Tableau if necessary.
8-
9-
| Tool | Latest Version |
10-
|--------------------------------------------------|--------------------|
11-
| Connector SDK for Tableau 2021.2 | 09-09-2021 |
12-
| Connector SDK for Tableau 2021.1 | 07-14-2021 |
13-
| Connector SDK for Tableau 2020.4 | 03-29-2021 |
14-
| Connector SDK for Tableau 2020.3 | 12-07-2020 |
15-
| Connector SDK for Tableau 2020.2 | 8-12-2020 |
16-
| Connector SDK for Tableau 2020.1 | 5-08-2020 |
17-
| Connector SDK for Tableau 2019.4 | 3-13-2020 |
18-
| Connector Packager SDK (Beta) for Tableau 2019.3 | 12-11-2019 |
19-
| TDVT | 2.5.0 (12-01-2021) |
20-
| | 1.5.24 (04-13-2020)|
21-
| Connector Packager | 2.1.0 (05-08-2020) |
7+
The latest version of the SDK is always targeted towards the latest, non-beta version of Tableau. Right now, this is **Tableau Desktop/Server 2023.2**. This means that the samples may not work on older versions of Tableau, and connectors packaged with newer versions of the SDK may not work in older versions of Tableau. You can download past releases of the SDK to work with older versions of Tableau if necessary.
8+
9+
| Tool | Latest Version |
10+
|--------------------------------------------------|---------------------|
11+
| Connector SDK for Tableau 2023.1 | 06-15-2023 |
12+
| Connector SDK for Tableau 2022.4 | 03-15-2023 |
13+
| Connector SDK for Tableau 2022.3 | 12-14-2022 |
14+
| Connector SDK for Tableau 2022.2 | 10-18-2022 |
15+
| Connector SDK for Tableau 2022.1 | 06-29-2022 |
16+
| Connector SDK for Tableau 2021.4 | 03-31-2022 |
17+
| Connector SDK for Tableau 2021.3 | 12-15-2021 |
18+
| Connector SDK for Tableau 2021.2 | 09-09-2021 |
19+
| Connector SDK for Tableau 2021.1 | 07-14-2021 |
20+
| Connector SDK for Tableau 2020.4 | 03-29-2021 |
21+
| Connector SDK for Tableau 2020.3 | 12-07-2020 |
22+
| Connector SDK for Tableau 2020.2 | 8-12-2020 |
23+
| Connector SDK for Tableau 2020.1 | 5-08-2020 |
24+
| Connector SDK for Tableau 2019.4 | 3-13-2020 |
25+
| Connector Packager SDK (Beta) for Tableau 2019.3 | 12-11-2019 |
26+
| TDVT | 2.7.0 (10-28-2022) |
27+
| | 1.5.24 (04-13-2020) |
28+
| Connector Packager | 2.1.0 (05-08-2020) |
2229

2330
* [Why Connectors?](#why-connectors)
2431
* [Get started](#get-started)
@@ -48,14 +55,13 @@ The SDK includes several [standalone example connectors](https://github.com/tabl
4855
To work with connectors, you need the following:
4956

5057
* Windows or Mac
51-
* Tableau Desktop or Server 2019.1 Beta 2 or higher
58+
* Tableau Desktop or Server 2020.4
5259
* Python 3.7 or higher
5360
* An ODBC or JDBC data source and driver
5461
* The provided test data loaded in your data source
5562

5663
To package the connector into a .taco file, you will also need:
5764

58-
* Tableau Desktop or Server 2019.4 Beta 1 or higher
5965
* JDK 8 or higher
6066

6167
For a JDBC connector, your driver must fulfill the following requirements:

connector-packager/connector_packager/jar_jdk_packager.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,22 @@ def get_min_support_version(file_list: List[ConnectorFile], cur_min_version_tabl
5555
min_version_tableau = "2020.3"
5656
reasons.append("Connector uses Connection Dialogs V2, which was added in the 2020.3 release")
5757
elif connector_file.file_type == "oauth-config":
58-
if 2021.1 > float(min_version_tableau):
58+
# Check to see if we're using oauthConfigId, which needs 2021.4+
59+
pluginOAuthConfigRoot = ET.parse(input_dir / connector_file.file_name).getroot()
60+
61+
oauthConfigId = pluginOAuthConfigRoot.find('.//oauthConfigId')
62+
instanceUrlSuffix = pluginOAuthConfigRoot.find('.//instanceUrlSuffix')
63+
configLabel = pluginOAuthConfigRoot.find('.//configLabel')
64+
if (configLabel is not None and 2023.2 > float(min_version_tableau)):
65+
min_version_tableau = "2023.2"
66+
reasons.append("Connector uses configLabel field, which was added in the 2023.2 release")
67+
elif (instanceUrlSuffix is not None and 2023.1 > float(min_version_tableau)):
68+
min_version_tableau = "2023.1"
69+
reasons.append("Connector uses instanceUrlSuffix field, which was added in the 2023.1 release")
70+
elif (oauthConfigId is not None and 2021.4 > float(min_version_tableau)):
71+
min_version_tableau = "2021.4"
72+
reasons.append("Connector contains an oauthConfigId field, which was added in the 2021.4 release")
73+
elif 2021.1 > float(min_version_tableau):
5974
min_version_tableau = "2021.1"
6075
reasons.append("Connector uses OAuth, which was added in the 2021.1 release")
6176
elif connector_file.file_type == "connection-resolver":
@@ -67,6 +82,17 @@ def get_min_support_version(file_list: List[ConnectorFile], cur_min_version_tabl
6782
if 2021.1 > float(min_version_tableau):
6883
min_version_tableau = "2021.1"
6984
reasons.append("Connector uses inferred connection resolver, which was added in the 2021.1 release")
85+
elif connector_file.file_type == "manifest":
86+
manifestRoot = ET.parse(input_dir / connector_file.file_name).getroot()
87+
oauthConfigs = manifestRoot.findall('.//oauth-config')
88+
if (oauthConfigs is not None and len(oauthConfigs) > 1 and 2023.1 > float(min_version_tableau)):
89+
min_version_tableau = "2023.1"
90+
reasons.append("Support for multiple OAuth configs was added in the 2023.1 release")
91+
elif (oauthConfigs is not None and len(oauthConfigs) == 1):
92+
firstConfig = oauthConfigs[0]
93+
if firstConfig.attrib['file'] == "null_config" and 2024.1 > float(min_version_tableau):
94+
min_version_tableau = "2024.1"
95+
reasons.append("Connector uses Null OAuth Config, which was added in the 2024.1 release")
7096

7197
if version.parse(cur_min_version_tableau) > version.parse(min_version_tableau):
7298
reasons.append("min-tableau-version set to " + cur_min_version_tableau + ", since that is higher than calculated version of " + min_version_tableau)

connector-packager/connector_packager/xml_parser.py

Lines changed: 61 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,11 @@
1616
MAX_FILE_SIZE = 1024 * 256 # This is based on the max file size we will load on the Tableau side
1717
HTTPS_STRING = "https://"
1818
TRANSLATABLE_STRING_PREFIX = "@string/"
19-
TABLEAU_FALLBACK_LANGUAGE = "en_US" # If localizing a connector, US English must be translated since we'll fall back to the English strings if we can't find one for the correct language
19+
# If localizing a connector, US English must be translated since we'll fall back to the English strings if we can't
20+
# find one for the correct language
21+
TABLEAU_FALLBACK_LANGUAGE = "en_US"
2022
TABLEAU_SUPPORTED_LANGUAGES = ["de_DE", "en_GB", "es_ES", "fr_CA", "fr_FR", "ga_IE", "it_IT", "ja_JP", "ko_KR",
21-
"pt_BR", "zh_CN", "zh_TW"]
23+
"pt_BR", "sv_SE", "th_TH", "zh_CN", "zh_TW"]
2224

2325

2426
class XMLParser:
@@ -38,6 +40,8 @@ def __init__(self, path_to_folder: Path):
3840
self.file_list = [] # list of files to package
3941
self.loc_strings = [] # list of loc strings so we can make sure they are covered in the resource files.
4042
self.connector_version = None # Get ths from the plugin-version attribute in the manifest
43+
self.null_oauth_config_found = False # whether or not we found a null oauth config
44+
self.num_oauth_configs_found = 0 # number of oauth configs found, so we can fail the connector if there are non-null cconfigs and a null config
4145

4246
def generate_file_list(self) -> Optional[List[ConnectorFile]]:
4347
"""
@@ -87,7 +91,9 @@ def generate_file_list(self) -> Optional[List[ConnectorFile]]:
8791
self.file_list.append(ConnectorFile(fallback_resource_file_name, "resource"))
8892
logging.debug("Adding file to list (name = " + fallback_resource_file_name + ", type = resource)")
8993
else:
90-
logger.error("Error: Found localized strings but " + fallback_resource_file_name + " does not exist. US English translations are required to fall back on if other languages are not translated.")
94+
logger.error("Error: Found localized strings but " + fallback_resource_file_name +
95+
" does not exist. US English translations are required to fall back on if other languages"
96+
" are not translated.")
9197
return None
9298

9399
# Check for files for each of the languages we suport
@@ -125,7 +131,7 @@ def parse_file(self, file_to_parse: ConnectorFile) -> bool:
125131

126132
# If the file is too big, we shouldn't try and parse it, just log the violation and move on
127133
if path_to_file.stat().st_size > MAX_FILE_SIZE:
128-
logging.error(file_to_parse.file_name + " exceeds maximum size of " + str(int(MAX_FILE_SIZE / 1024)) + " KB")
134+
logging.error(file_to_parse.file_name + " exceeds maximum size of " + str(int(MAX_FILE_SIZE / 1024)) + "KB")
129135
return False
130136

131137
# Get XML file ready for parsing
@@ -144,7 +150,8 @@ def parse_file(self, file_to_parse: ConnectorFile) -> bool:
144150
for child in root.iter():
145151

146152
# Check the tag
147-
# Oauth config file uses dbclass tag instead of class attribute. We need to make sure that matches the class name as well.
153+
# Oauth config file uses dbclass tag instead of class attribute. We need to make sure that matches the
154+
# class name as well.
148155
if child.tag == "dbclass":
149156
if child.text != self.class_name:
150157
logging.error("Error: dbclass in file " + file_to_parse.file_name +
@@ -153,46 +160,65 @@ def parse_file(self, file_to_parse: ConnectorFile) -> bool:
153160
file_to_parse.file_name)
154161
return False
155162

163+
# If oauth-config attribute, keep track of how many we find. Enforce that if null oauth config only one config is defined
164+
if child.tag == "oauth-config":
165+
if self.null_oauth_config_found:
166+
logger.error("Error: cannot declare a null OAuth config in connector with non-null configs")
167+
return False
168+
169+
if 'file' in child.attrib and child.attrib['file'] == "null_config":
170+
171+
# If we already found oauth configs and then found a null config, reject the connector
172+
if self.num_oauth_configs_found > 0:
173+
logger.error("Error: cannot declare a null OAuth config in connector with non-null configs")
174+
return False
175+
else:
176+
logger.debug("Null OAuth config found")
177+
self.null_oauth_config_found = True
178+
179+
self.num_oauth_configs_found+=1
180+
181+
182+
156183
# Check the attributes
157184
# If xml element has file attribute, add it to the file list. If it's not a script, parse that file too.
158-
if 'file' in child.attrib:
185+
if 'file' in child.attrib and not (child.tag == "oauth-config" and child.attrib['file'] == "null_config"):
186+
# Check to make sure the file actually exists
187+
new_file_path = str(self.path_to_folder / child.attrib['file'])
159188

160-
# Check to make sure the file actually exists
161-
new_file_path = str(self.path_to_folder / child.attrib['file'])
189+
if not os.path.isfile(new_file_path):
190+
logger.error("Error: " + new_file_path + " does not exist but is referenced in " +
191+
str(file_to_parse.file_name))
192+
return False
162193

163-
if not os.path.isfile(new_file_path):
164-
logger.debug("Error: " + new_file_path + " does not exist but is referenced in " +
165-
str(file_to_parse.file_name))
166-
return False
194+
# Make new connector file object
195+
logging.debug("Adding file to list (name = " + child.attrib['file'] + ", type = " + child.tag + ")")
196+
new_file = ConnectorFile(child.attrib['file'], child.tag)
167197

168-
# Make new connector file object
169-
logging.debug("Adding file to list (name = " + child.attrib['file'] + ", type = " + child.tag + ")")
170-
new_file = ConnectorFile(child.attrib['file'], child.tag)
198+
# figure out if new file is in the list
199+
already_in_list = new_file in self.file_list
171200

172-
# figure out if new file is in the list
173-
already_in_list = new_file in self.file_list
201+
# add new file to list
202+
self.file_list.append(new_file)
174203

175-
# add new file to list
176-
self.file_list.append(new_file)
204+
# If connection-metadata, make sure that connection-fields file exists
205+
if child.tag == 'connection-metadata':
206+
connection_fields_exists = False
177207

178-
# If connection-metadata, make sure that connection-fields file exists
179-
if child.tag == 'connection-metadata':
180-
connection_fields_exists = False
208+
for xml_file in self.file_list:
209+
if xml_file.file_type == 'connection-fields':
210+
connection_fields_exists = True
211+
break
181212

182-
for xml_file in self.file_list:
183-
if xml_file.file_type == 'connection-fields':
184-
connection_fields_exists = True
185-
break
213+
if not connection_fields_exists:
214+
logger.debug("Error: connection-metadata file requires a connection-fields file")
215+
return False
186216

187-
if not connection_fields_exists:
188-
logger.debug("Error: connection-metadata file requires a connection-fields file")
189-
return False
190-
191-
# If not a script and not in list, parse the file for more files to include
192-
if child.tag != 'script' and not already_in_list:
193-
children_valid = self.parse_file(new_file)
194-
if not children_valid:
195-
return False
217+
# If not a script and not in list, parse the file for more files to include
218+
if child.tag != 'script' and not already_in_list:
219+
children_valid = self.parse_file(new_file)
220+
if not children_valid:
221+
return False
196222

197223
if 'url' in child.attrib:
198224

connector-packager/connector_packager/xsd_validator.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def validate_file_specific_rules_connection_fields(file_to_test: ConnectorFile,
210210
if field_name in field_names:
211211
xml_violations_buffer.append("A field with the field name = " + field_name +
212212
" already exists. Cannot have multiple fields with the same name.")
213-
213+
214214
return False
215215
if field_name == 'instanceurl':
216216
used_for_oauth = False
@@ -354,11 +354,18 @@ def warn_file_specific_rules_tdr(path_to_file: Path):
354354
return
355355

356356
authentication_attr_exists = False
357+
server_attr_exists = False
357358
for attr in attribute_list.iter('attr'):
358359
if attr.text == 'authentication':
359360
authentication_attr_exists = True
360-
break
361361

362+
if attr.text == 'server':
363+
server_attr_exists = True
364+
362365
if not authentication_attr_exists:
363366
logger.warning("Warning: 'authentication' attribute is missing from "
364-
"<connection-normalizer>/<required-attributes>/<attribute-list> in " + str(path_to_file) + ".")
367+
"<connection-normalizer>/<required-attributes>/<attribute-list> in " + str(path_to_file) + ".")
368+
369+
if not server_attr_exists:
370+
logger.warning("Warning: 'server' attribute is missing from "
371+
"<connection-normalizer>/<required-attributes>/<attribute-list> in " + str(path_to_file) + ".")

0 commit comments

Comments
 (0)