pyxecm 1.4__py3-none-any.whl → 1.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of pyxecm might be problematic. Click here for more details.
- pyxecm/__init__.py +5 -0
- pyxecm/avts.py +1065 -0
- pyxecm/coreshare.py +2532 -0
- pyxecm/customizer/__init__.py +4 -0
- pyxecm/customizer/browser_automation.py +164 -54
- pyxecm/customizer/customizer.py +588 -231
- pyxecm/customizer/k8s.py +143 -29
- pyxecm/customizer/m365.py +1434 -1323
- pyxecm/customizer/payload.py +15073 -5933
- pyxecm/customizer/pht.py +926 -0
- pyxecm/customizer/salesforce.py +866 -351
- pyxecm/customizer/sap.py +4 -4
- pyxecm/customizer/servicenow.py +1467 -0
- pyxecm/customizer/successfactors.py +1056 -0
- pyxecm/helper/__init__.py +2 -0
- pyxecm/helper/assoc.py +44 -1
- pyxecm/helper/data.py +1731 -0
- pyxecm/helper/web.py +170 -46
- pyxecm/helper/xml.py +170 -34
- pyxecm/otac.py +309 -23
- pyxecm/otawp.py +1810 -0
- pyxecm/otcs.py +5308 -2985
- pyxecm/otds.py +1909 -1954
- pyxecm/otmm.py +928 -0
- pyxecm/otpd.py +13 -10
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/METADATA +5 -1
- pyxecm-1.6.dist-info/RECORD +32 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/WHEEL +1 -1
- pyxecm-1.4.dist-info/RECORD +0 -24
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/LICENSE +0 -0
- {pyxecm-1.4.dist-info → pyxecm-1.6.dist-info}/top_level.txt +0 -0
pyxecm/customizer/pht.py
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
"""
|
|
2
|
+
PHT stands for Product Hierarchy Tracker and is an OpenText internal application aiming at creating a common naming reference for Engineering Products and
|
|
3
|
+
track all product-related data. It also provides an approved reporting hierarchy.
|
|
4
|
+
See: https://pht.opentext.com
|
|
5
|
+
|
|
6
|
+
Class: PHT
|
|
7
|
+
Methods:
|
|
8
|
+
|
|
9
|
+
__init__ : class initializer
|
|
10
|
+
config : Returns config data set
|
|
11
|
+
get_data: Get the Data object that holds all processed PHT products
|
|
12
|
+
request_header: Returns the request header for ServiceNow API calls
|
|
13
|
+
do_request: Call an PHT REST API in a safe way
|
|
14
|
+
parse_request_response: Parse the REST API responses and convert
|
|
15
|
+
them to Python dict in a safe way
|
|
16
|
+
|
|
17
|
+
authenticate : Authenticates at ServiceNow API
|
|
18
|
+
|
|
19
|
+
get_attributes: Get a list of all product attributes (schema) of PHT
|
|
20
|
+
get_business_units: Get the list of PHT Business Units
|
|
21
|
+
get_product_families: Get the list of PHT product families
|
|
22
|
+
|
|
23
|
+
get_products: Get the list of PHT products
|
|
24
|
+
get_products_filtered: Get a list of filtered PHT products
|
|
25
|
+
get_product: Get a specific product in PHT
|
|
26
|
+
search_products: Search products in PHT
|
|
27
|
+
|
|
28
|
+
get_master_products: Get the list of PHT master products
|
|
29
|
+
get_master_products_filtered: Get a list of filtered PHT master products
|
|
30
|
+
get_master_product: Get a specific product in PHT
|
|
31
|
+
|
|
32
|
+
get_teams: Get a list of all teams in PHT.
|
|
33
|
+
get_team: Get a specific team in PHT.
|
|
34
|
+
|
|
35
|
+
get_componets: Get a list of all components in PHT.
|
|
36
|
+
get_components_filtered: Get a list of filtered PHT components.
|
|
37
|
+
get_component: Get a specific component in PHT.
|
|
38
|
+
search_components: Search components in PHT
|
|
39
|
+
|
|
40
|
+
load_products: Load products into a data frame.
|
|
41
|
+
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
__author__ = "Dr. Marc Diefenbruch"
|
|
45
|
+
__copyright__ = "Copyright 2024, OpenText"
|
|
46
|
+
__credits__ = ["Kai-Philip Gatzweiler"]
|
|
47
|
+
__maintainer__ = "Dr. Marc Diefenbruch"
|
|
48
|
+
__email__ = "mdiefenb@opentext.com"
|
|
49
|
+
|
|
50
|
+
import json
|
|
51
|
+
import logging
|
|
52
|
+
import time
|
|
53
|
+
|
|
54
|
+
import requests
|
|
55
|
+
from requests.auth import HTTPBasicAuth
|
|
56
|
+
from pyxecm.helper.data import Data
|
|
57
|
+
|
|
58
|
+
logger = logging.getLogger("pyxecm.customizer.pht")
|
|
59
|
+
|
|
60
|
+
REQUEST_HEADERS = {"Accept": "application/json", "Content-Type": "application/json"}
|
|
61
|
+
|
|
62
|
+
REQUEST_TIMEOUT = 60
|
|
63
|
+
REQUEST_RETRY_DELAY = 20
|
|
64
|
+
REQUEST_MAX_RETRIES = 2
|
|
65
|
+
|
|
66
|
+
class PHT(object):
|
|
67
|
+
"""Used to retrieve data from OpenText PHT."""
|
|
68
|
+
|
|
69
|
+
_config: dict
|
|
70
|
+
_session = None
|
|
71
|
+
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
base_url: str,
|
|
75
|
+
username: str,
|
|
76
|
+
password: str,
|
|
77
|
+
):
|
|
78
|
+
"""Initialize the PHT object
|
|
79
|
+
|
|
80
|
+
Args:
|
|
81
|
+
base_url (str): base URL of the ServiceNow tenant
|
|
82
|
+
username (str): user name in Saleforce
|
|
83
|
+
password (str): password of the user
|
|
84
|
+
"""
|
|
85
|
+
|
|
86
|
+
pht_config = {}
|
|
87
|
+
|
|
88
|
+
# Store the credentials and parameters in a config dictionary:
|
|
89
|
+
pht_config["baseUrl"] = base_url
|
|
90
|
+
pht_config["username"] = username
|
|
91
|
+
pht_config["password"] = password
|
|
92
|
+
|
|
93
|
+
pht_config["restUrl"] = pht_config["baseUrl"] + "/api"
|
|
94
|
+
pht_config["attributeUrl"] = pht_config["restUrl"] + "/attribute"
|
|
95
|
+
pht_config["businessUnitUrl"] = pht_config["restUrl"] + "/business-unit"
|
|
96
|
+
pht_config["productFamilyUrl"] = pht_config["restUrl"] + "/product-family"
|
|
97
|
+
pht_config["productUrl"] = pht_config["restUrl"] + "/product"
|
|
98
|
+
pht_config["productFilteredUrl"] = pht_config["productUrl"] + "/filtered"
|
|
99
|
+
pht_config["productSearchUrl"] = pht_config["productUrl"] + "/search"
|
|
100
|
+
pht_config["productUsersUrl"] = pht_config["productUrl"] + "/users"
|
|
101
|
+
pht_config["teamUrl"] = pht_config["restUrl"] + "/team"
|
|
102
|
+
pht_config["masterProductUrl"] = pht_config["restUrl"] + "/master-product"
|
|
103
|
+
pht_config["masterProductFilteredUrl"] = (
|
|
104
|
+
pht_config["masterProductUrl"] + "/filtered"
|
|
105
|
+
)
|
|
106
|
+
pht_config["componentUrl"] = pht_config["restUrl"] + "/component"
|
|
107
|
+
pht_config["componentFilteredUrl"] = pht_config["componentUrl"] + "/filtered"
|
|
108
|
+
pht_config["componentSearchUrl"] = pht_config["componentUrl"] + "/search"
|
|
109
|
+
pht_config["componentUsersUrl"] = pht_config["componentUrl"] + "/users"
|
|
110
|
+
|
|
111
|
+
self._config = pht_config
|
|
112
|
+
|
|
113
|
+
self._session = requests.Session()
|
|
114
|
+
|
|
115
|
+
self._data = Data()
|
|
116
|
+
|
|
117
|
+
# end method definition
|
|
118
|
+
|
|
119
|
+
def config(self) -> dict:
|
|
120
|
+
"""Returns the configuration dictionary
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
dict: Configuration dictionary
|
|
124
|
+
"""
|
|
125
|
+
return self._config
|
|
126
|
+
|
|
127
|
+
# end method definition
|
|
128
|
+
|
|
129
|
+
def get_data(self) -> Data:
|
|
130
|
+
"""Get the Data object that holds all processed PHT products
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Data: Datastructure with all processed PHT product data.
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
return self._data
|
|
137
|
+
|
|
138
|
+
# end method definition
|
|
139
|
+
|
|
140
|
+
def request_header(self, content_type: str = "") -> dict:
|
|
141
|
+
"""Returns the request header used for Application calls.
|
|
142
|
+
Consists of Bearer access token and Content Type
|
|
143
|
+
|
|
144
|
+
Args:
|
|
145
|
+
content_type (str, optional): custom content type for the request
|
|
146
|
+
Return:
|
|
147
|
+
dict: request header values
|
|
148
|
+
"""
|
|
149
|
+
|
|
150
|
+
request_header = {}
|
|
151
|
+
|
|
152
|
+
request_header = REQUEST_HEADERS
|
|
153
|
+
|
|
154
|
+
if content_type:
|
|
155
|
+
request_header["Content-Type"] = content_type
|
|
156
|
+
|
|
157
|
+
return request_header
|
|
158
|
+
|
|
159
|
+
# end method definition
|
|
160
|
+
|
|
161
|
+
def do_request(
|
|
162
|
+
self,
|
|
163
|
+
url: str,
|
|
164
|
+
method: str = "GET",
|
|
165
|
+
headers: dict | None = None,
|
|
166
|
+
data: dict | None = None,
|
|
167
|
+
files: dict | None = None,
|
|
168
|
+
timeout: int | None = REQUEST_TIMEOUT,
|
|
169
|
+
show_error: bool = True,
|
|
170
|
+
failure_message: str = "",
|
|
171
|
+
success_message: str = "",
|
|
172
|
+
max_retries: int = REQUEST_MAX_RETRIES,
|
|
173
|
+
retry_forever: bool = False,
|
|
174
|
+
) -> dict | None:
|
|
175
|
+
"""Call an PHT REST API in a safe way
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
url (str): URL to send the request to.
|
|
179
|
+
method (str, optional): HTTP method (GET, POST, etc.). Defaults to "GET".
|
|
180
|
+
headers (dict | None, optional): Request Headers. Defaults to None.
|
|
181
|
+
json (dict | None, optional): Request payload. Defaults to None.
|
|
182
|
+
files (dict | None, optional): Dictionary of {"name": file-tuple} for multipart encoding upload.
|
|
183
|
+
file-tuple can be a 2-tuple ("filename", fileobj) or a 3-tuple ("filename", fileobj, "content_type")
|
|
184
|
+
timeout (int | None, optional): Timeout for the request in seconds. Defaults to REQUEST_TIMEOUT.
|
|
185
|
+
show_error (bool, optional): Whether or not an error should be logged in case of a failed REST call.
|
|
186
|
+
If False, then only a warning is logged. Defaults to True.
|
|
187
|
+
failure_message (str, optional): Specific error message. Defaults to "".
|
|
188
|
+
max_retries (int, optional): How many retries on Connection errors? Default is REQUEST_MAX_RETRIES.
|
|
189
|
+
retry_forever (bool, optional): Eventually wait forever - without timeout. Defaults to False.
|
|
190
|
+
|
|
191
|
+
Returns:
|
|
192
|
+
dict | None: Response of PHT REST API or None in case of an error.
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
retries = 0
|
|
196
|
+
while True:
|
|
197
|
+
try:
|
|
198
|
+
response = self._session.request(
|
|
199
|
+
method=method,
|
|
200
|
+
url=url,
|
|
201
|
+
json=data,
|
|
202
|
+
files=files,
|
|
203
|
+
headers=headers,
|
|
204
|
+
timeout=timeout,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if response.ok:
|
|
208
|
+
if success_message:
|
|
209
|
+
logger.debug(success_message)
|
|
210
|
+
return self.parse_request_response(response)
|
|
211
|
+
# Check if Session has expired - then re-authenticate and try once more
|
|
212
|
+
elif response.status_code == 401 and retries == 0:
|
|
213
|
+
logger.debug("Session has expired - try to re-authenticate...")
|
|
214
|
+
self.authenticate()
|
|
215
|
+
retries += 1
|
|
216
|
+
else:
|
|
217
|
+
# Handle plain HTML responses to not pollute the logs
|
|
218
|
+
content_type = response.headers.get("content-type", None)
|
|
219
|
+
if content_type == "text/html":
|
|
220
|
+
response_text = "HTML content (see debug log)"
|
|
221
|
+
else:
|
|
222
|
+
response_text = response.text
|
|
223
|
+
|
|
224
|
+
if show_error:
|
|
225
|
+
logger.error(
|
|
226
|
+
"%s; status -> %s; error -> %s",
|
|
227
|
+
failure_message,
|
|
228
|
+
response.status_code,
|
|
229
|
+
response_text,
|
|
230
|
+
)
|
|
231
|
+
else:
|
|
232
|
+
logger.warning(
|
|
233
|
+
"%s; status -> %s; warning -> %s",
|
|
234
|
+
failure_message,
|
|
235
|
+
response.status_code,
|
|
236
|
+
response_text,
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
if content_type == "text/html":
|
|
240
|
+
logger.debug(
|
|
241
|
+
"%s; status -> %s; warning -> %s",
|
|
242
|
+
failure_message,
|
|
243
|
+
response.status_code,
|
|
244
|
+
response.text,
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
return None
|
|
248
|
+
except requests.exceptions.Timeout:
|
|
249
|
+
if retries <= max_retries:
|
|
250
|
+
logger.warning(
|
|
251
|
+
"Request timed out. Retrying in %s seconds...",
|
|
252
|
+
str(REQUEST_RETRY_DELAY),
|
|
253
|
+
)
|
|
254
|
+
retries += 1
|
|
255
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
256
|
+
else:
|
|
257
|
+
logger.error(
|
|
258
|
+
"%s; timeout error",
|
|
259
|
+
failure_message,
|
|
260
|
+
)
|
|
261
|
+
if retry_forever:
|
|
262
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
263
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
264
|
+
timeout = None
|
|
265
|
+
else:
|
|
266
|
+
return None
|
|
267
|
+
except requests.exceptions.ConnectionError:
|
|
268
|
+
if retries <= max_retries:
|
|
269
|
+
logger.warning(
|
|
270
|
+
"Connection error. Retrying in %s seconds...",
|
|
271
|
+
str(REQUEST_RETRY_DELAY),
|
|
272
|
+
)
|
|
273
|
+
retries += 1
|
|
274
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
275
|
+
else:
|
|
276
|
+
logger.error(
|
|
277
|
+
"%s; connection error",
|
|
278
|
+
failure_message,
|
|
279
|
+
)
|
|
280
|
+
if retry_forever:
|
|
281
|
+
# If it fails after REQUEST_MAX_RETRIES retries we let it wait forever
|
|
282
|
+
logger.warning("Turn timeouts off and wait forever...")
|
|
283
|
+
timeout = None
|
|
284
|
+
time.sleep(REQUEST_RETRY_DELAY) # Add a delay before retrying
|
|
285
|
+
else:
|
|
286
|
+
return None
|
|
287
|
+
|
|
288
|
+
# end method definition
|
|
289
|
+
|
|
290
|
+
def parse_request_response(
|
|
291
|
+
self,
|
|
292
|
+
response_object: requests.Response,
|
|
293
|
+
additional_error_message: str = "",
|
|
294
|
+
show_error: bool = True,
|
|
295
|
+
) -> list | None:
|
|
296
|
+
"""Converts the request response (JSon) to a Python list in a safe way
|
|
297
|
+
that also handles exceptions. It first tries to load the response.text
|
|
298
|
+
via json.loads() that produces a dict output. Only if response.text is
|
|
299
|
+
not set or is empty it just converts the response_object to a dict using
|
|
300
|
+
the vars() built-in method.
|
|
301
|
+
|
|
302
|
+
Args:
|
|
303
|
+
response_object (object): this is reponse object delivered by the request call
|
|
304
|
+
additional_error_message (str, optional): use a more specific error message
|
|
305
|
+
in case of an error
|
|
306
|
+
show_error (bool): True: write an error to the log file
|
|
307
|
+
False: write a warning to the log file
|
|
308
|
+
Returns:
|
|
309
|
+
list: response information or None in case of an error
|
|
310
|
+
"""
|
|
311
|
+
|
|
312
|
+
if not response_object:
|
|
313
|
+
return None
|
|
314
|
+
|
|
315
|
+
try:
|
|
316
|
+
if response_object.text:
|
|
317
|
+
list_object = json.loads(response_object.text)
|
|
318
|
+
else:
|
|
319
|
+
list_object = vars(response_object)
|
|
320
|
+
except json.JSONDecodeError as exception:
|
|
321
|
+
if additional_error_message:
|
|
322
|
+
message = "Cannot decode response as JSON. {}; error -> {}".format(
|
|
323
|
+
additional_error_message, exception
|
|
324
|
+
)
|
|
325
|
+
else:
|
|
326
|
+
message = "Cannot decode response as JSON; error -> {}".format(
|
|
327
|
+
exception
|
|
328
|
+
)
|
|
329
|
+
if show_error:
|
|
330
|
+
logger.error(message)
|
|
331
|
+
else:
|
|
332
|
+
logger.warning(message)
|
|
333
|
+
return None
|
|
334
|
+
else:
|
|
335
|
+
return list_object
|
|
336
|
+
|
|
337
|
+
# end method definition
|
|
338
|
+
|
|
339
|
+
def authenticate(self) -> str | None:
|
|
340
|
+
"""Authenticate at PHT with basic authentication."""
|
|
341
|
+
|
|
342
|
+
self._session.headers.update(self.request_header())
|
|
343
|
+
|
|
344
|
+
username = self.config()["username"]
|
|
345
|
+
password = self.config()["password"]
|
|
346
|
+
if not self._session:
|
|
347
|
+
self._session = requests.Session()
|
|
348
|
+
self._session.auth = HTTPBasicAuth(username, password)
|
|
349
|
+
|
|
350
|
+
return self._session.auth
|
|
351
|
+
|
|
352
|
+
# end method definition
|
|
353
|
+
|
|
354
|
+
def get_attributes(self) -> list | None:
|
|
355
|
+
"""Get a list of all product attributes (schema) of PHT
|
|
356
|
+
|
|
357
|
+
Returns:
|
|
358
|
+
list | None: list of product attributes
|
|
359
|
+
|
|
360
|
+
Example:
|
|
361
|
+
[
|
|
362
|
+
{
|
|
363
|
+
'id': 28,
|
|
364
|
+
'uuid': '43ba5852-eb83-11ed-a752-00505682262c',
|
|
365
|
+
'name': 'UBM SCM Migration JIRA/ValueEdge',
|
|
366
|
+
'description': 'Identifies the Issue to track work for the SCM migration for this project.\nIts a free text field and no validation with JIRA/ValueEdge will take place',
|
|
367
|
+
'type': 'TEXT',
|
|
368
|
+
'attributeCategory': {
|
|
369
|
+
'id': 2,
|
|
370
|
+
'name': 'Auxiliary assignment'
|
|
371
|
+
},
|
|
372
|
+
'showDefault': False,
|
|
373
|
+
'restricted': True,
|
|
374
|
+
'allowScopeChain': True,
|
|
375
|
+
'visibleToAll': False,
|
|
376
|
+
'deleted': False,
|
|
377
|
+
'attributeOptions': [],
|
|
378
|
+
'attributeScopes': [],
|
|
379
|
+
'allowedTeams': []
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
"""
|
|
383
|
+
|
|
384
|
+
request_header = self.request_header()
|
|
385
|
+
request_url = self.config()["attributeUrl"]
|
|
386
|
+
|
|
387
|
+
return self.do_request(
|
|
388
|
+
url=request_url,
|
|
389
|
+
method="GET",
|
|
390
|
+
headers=request_header,
|
|
391
|
+
timeout=None,
|
|
392
|
+
failure_message="Failed to get PHT attributes!",
|
|
393
|
+
)
|
|
394
|
+
|
|
395
|
+
# end method definition
|
|
396
|
+
|
|
397
|
+
def get_business_units(self) -> list | None:
|
|
398
|
+
"""Get the list of PHT Business Units
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
list | None: list of the known business units.
|
|
402
|
+
|
|
403
|
+
Example:
|
|
404
|
+
[
|
|
405
|
+
{
|
|
406
|
+
'id': 1,
|
|
407
|
+
'name': 'Content Services',
|
|
408
|
+
'leaderModel': {
|
|
409
|
+
'id': 219,
|
|
410
|
+
'domain': 'mcybala',
|
|
411
|
+
'email': 'mcybala@opentext.com',
|
|
412
|
+
'name': 'Michael Cybala',
|
|
413
|
+
'role': None,
|
|
414
|
+
'status': 'ACTIVE',
|
|
415
|
+
'location': 'Kempten, DEU',
|
|
416
|
+
'title': 'VP, Software Engineering',
|
|
417
|
+
'type': 'OTHERS'
|
|
418
|
+
},
|
|
419
|
+
'pmLeaderModel': {
|
|
420
|
+
'id': 350,
|
|
421
|
+
'domain': 'mdiefenb',
|
|
422
|
+
'email': 'mdiefenb@opentext.com',
|
|
423
|
+
'name': 'Marc Diefenbruch',
|
|
424
|
+
'role': None,
|
|
425
|
+
'status': 'ACTIVE',
|
|
426
|
+
'location': 'Virtual, DEU',
|
|
427
|
+
'title': 'VP, Product Management',
|
|
428
|
+
'type': 'OTHERS'
|
|
429
|
+
},
|
|
430
|
+
'sltOwnerModel': {
|
|
431
|
+
'id': 450,
|
|
432
|
+
'domain': 'jradko',
|
|
433
|
+
'email': 'jradko@opentext.com',
|
|
434
|
+
'name': 'John Radko',
|
|
435
|
+
'role': None,
|
|
436
|
+
'status': 'ACTIVE',
|
|
437
|
+
'location': 'Gaithersburg, MD, USA',
|
|
438
|
+
'title': 'SVP, Software Engineering',
|
|
439
|
+
'type': 'OTHERS'
|
|
440
|
+
},
|
|
441
|
+
'status': 'ACTIVE',
|
|
442
|
+
'engineering': True,
|
|
443
|
+
'attributes': [{...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}, {...}],
|
|
444
|
+
'leader': 'Michael Cybala',
|
|
445
|
+
'leaderDomain': 'mcybala',
|
|
446
|
+
'pmLeader': 'Marc Diefenbruch',
|
|
447
|
+
'pmLeaderDomain': 'mdiefenb',
|
|
448
|
+
'sltOwner': 'John Radko',
|
|
449
|
+
'sltOwnerDomain': 'jradko'
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
"""
|
|
453
|
+
|
|
454
|
+
request_header = self.request_header()
|
|
455
|
+
request_url = self.config()["businessUnitUrl"]
|
|
456
|
+
|
|
457
|
+
return self.do_request(
|
|
458
|
+
url=request_url,
|
|
459
|
+
method="GET",
|
|
460
|
+
headers=request_header,
|
|
461
|
+
timeout=None,
|
|
462
|
+
failure_message="Failed to get PHT business units!",
|
|
463
|
+
)
|
|
464
|
+
|
|
465
|
+
# end method definition
|
|
466
|
+
|
|
467
|
+
def get_product_families(self) -> list | None:
|
|
468
|
+
"""Get the list of PHT product families
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
list | None: list of the known product families.
|
|
472
|
+
"""
|
|
473
|
+
|
|
474
|
+
request_header = self.request_header()
|
|
475
|
+
request_url = self.config()["productFamilyUrl"]
|
|
476
|
+
|
|
477
|
+
return self.do_request(
|
|
478
|
+
url=request_url,
|
|
479
|
+
method="GET",
|
|
480
|
+
headers=request_header,
|
|
481
|
+
timeout=None,
|
|
482
|
+
failure_message="Failed to get PHT product families!",
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
# end method definition
|
|
486
|
+
|
|
487
|
+
def get_products(self) -> list | None:
|
|
488
|
+
"""Get the list of PHT products
|
|
489
|
+
|
|
490
|
+
Returns:
|
|
491
|
+
list | None: list of the known products.
|
|
492
|
+
"""
|
|
493
|
+
|
|
494
|
+
request_header = self.request_header()
|
|
495
|
+
request_url = self.config()["productUrl"]
|
|
496
|
+
|
|
497
|
+
return self.do_request(
|
|
498
|
+
url=request_url,
|
|
499
|
+
method="GET",
|
|
500
|
+
headers=request_header,
|
|
501
|
+
timeout=None,
|
|
502
|
+
failure_message="Failed to get PHT products!",
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# end method definition
|
|
506
|
+
|
|
507
|
+
def get_products_filtered(
|
|
508
|
+
self, filter_definition: dict | None = None
|
|
509
|
+
) -> list | None:
|
|
510
|
+
"""Get a list of filtered PHT products
|
|
511
|
+
|
|
512
|
+
Args:
|
|
513
|
+
filter_definition (dict): a dictionary of filter conditions.
|
|
514
|
+
Example filters:
|
|
515
|
+
{
|
|
516
|
+
businessUnitName: <String>,
|
|
517
|
+
productFamilyName: <String>,
|
|
518
|
+
productName: <String>,
|
|
519
|
+
productSyncId: <String>,
|
|
520
|
+
productStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
521
|
+
productManager: <String>,
|
|
522
|
+
developmentManager: <String>,
|
|
523
|
+
attributeOperator: AND | OR,
|
|
524
|
+
attributes: {
|
|
525
|
+
"<AttributeName>": {
|
|
526
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
527
|
+
"values": List<String>
|
|
528
|
+
},
|
|
529
|
+
...
|
|
530
|
+
},
|
|
531
|
+
includeAttributes: true | false
|
|
532
|
+
}
|
|
533
|
+
Returns:
|
|
534
|
+
list | None: list of matching products.
|
|
535
|
+
"""
|
|
536
|
+
|
|
537
|
+
if not filter_definition:
|
|
538
|
+
return self.get_products()
|
|
539
|
+
|
|
540
|
+
request_header = self.request_header()
|
|
541
|
+
request_url = self.config()["productFilteredUrl"]
|
|
542
|
+
request_data = filter_definition
|
|
543
|
+
|
|
544
|
+
return self.do_request(
|
|
545
|
+
url=request_url,
|
|
546
|
+
method="POST",
|
|
547
|
+
headers=request_header,
|
|
548
|
+
data=request_data,
|
|
549
|
+
timeout=None,
|
|
550
|
+
failure_message="Failed to get filtered PHT products!",
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# end method definition
|
|
554
|
+
|
|
555
|
+
def get_product(self, sync_id: int) -> dict | None:
|
|
556
|
+
"""Get a specific product in PHT.
|
|
557
|
+
|
|
558
|
+
Returns:
|
|
559
|
+
dict | None: product data matching the sync ID
|
|
560
|
+
"""
|
|
561
|
+
|
|
562
|
+
request_header = self.request_header()
|
|
563
|
+
request_url = self.config()["productUrl"] + "/" + str(sync_id)
|
|
564
|
+
|
|
565
|
+
return self.do_request(
|
|
566
|
+
url=request_url,
|
|
567
|
+
method="GET",
|
|
568
|
+
headers=request_header,
|
|
569
|
+
timeout=None,
|
|
570
|
+
failure_message="Failed to retrieve PHT product with sync ID -> {}!".format(
|
|
571
|
+
sync_id
|
|
572
|
+
),
|
|
573
|
+
)
|
|
574
|
+
|
|
575
|
+
# end method definition
|
|
576
|
+
|
|
577
|
+
def search_products(
|
|
578
|
+
self, query: str, business_unit: str | None = None, family: str | None = None
|
|
579
|
+
) -> list | None:
|
|
580
|
+
"""Search for specific product in PHT by the product name.
|
|
581
|
+
|
|
582
|
+
Returns:
|
|
583
|
+
str: search term matches any part of the component name
|
|
584
|
+
"""
|
|
585
|
+
|
|
586
|
+
request_header = self.request_header()
|
|
587
|
+
request_url = self.config()["componentSearchUrl"] + "?q=" + query
|
|
588
|
+
if business_unit:
|
|
589
|
+
request_url += "&businessUnit=" + business_unit
|
|
590
|
+
if family:
|
|
591
|
+
request_url += "&family=" + family
|
|
592
|
+
|
|
593
|
+
return self.do_request(
|
|
594
|
+
url=request_url,
|
|
595
|
+
method="GET",
|
|
596
|
+
headers=request_header,
|
|
597
|
+
timeout=None,
|
|
598
|
+
failure_message="Failed to retrieve PHT components matching -> {}!".format(
|
|
599
|
+
query
|
|
600
|
+
),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
# end method definition
|
|
604
|
+
|
|
605
|
+
def get_master_products(self) -> list | None:
|
|
606
|
+
"""Get the list of PHT master products
|
|
607
|
+
|
|
608
|
+
Returns:
|
|
609
|
+
list | None: list of the known master products.
|
|
610
|
+
"""
|
|
611
|
+
|
|
612
|
+
request_header = self.request_header()
|
|
613
|
+
request_url = self.config()["masterProductUrl"]
|
|
614
|
+
|
|
615
|
+
return self.do_request(
|
|
616
|
+
url=request_url,
|
|
617
|
+
method="GET",
|
|
618
|
+
headers=request_header,
|
|
619
|
+
timeout=None,
|
|
620
|
+
failure_message="Failed to get PHT master products!",
|
|
621
|
+
)
|
|
622
|
+
|
|
623
|
+
# end method definition
|
|
624
|
+
|
|
625
|
+
def get_master_products_filtered(
|
|
626
|
+
self, filter_definition: dict | None = None
|
|
627
|
+
) -> list | None:
|
|
628
|
+
"""Get a list of filtered PHT master products
|
|
629
|
+
|
|
630
|
+
Args:
|
|
631
|
+
filter_definition (dict): a dictionary of filter conditions.
|
|
632
|
+
Example filters:
|
|
633
|
+
{
|
|
634
|
+
businessUnitName: <String>,
|
|
635
|
+
productFamilyName: <String>,
|
|
636
|
+
masterproductName: <String>,
|
|
637
|
+
masterproductSyncId: <String>,
|
|
638
|
+
masterproductStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
639
|
+
productManagerDomain: <String>,
|
|
640
|
+
attributeOperator: AND | OR,
|
|
641
|
+
attributes: {
|
|
642
|
+
"<AttributeName>": {
|
|
643
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
644
|
+
"values": List<String>
|
|
645
|
+
},
|
|
646
|
+
...
|
|
647
|
+
},
|
|
648
|
+
includeAttributes: true | false
|
|
649
|
+
includeLinkedProducts: true | false
|
|
650
|
+
}
|
|
651
|
+
Returns:
|
|
652
|
+
list | None: list of matching products.
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
if not filter_definition:
|
|
656
|
+
return self.get_products()
|
|
657
|
+
|
|
658
|
+
request_header = self.request_header()
|
|
659
|
+
request_url = self.config()["masterProductFilteredUrl"]
|
|
660
|
+
request_data = filter_definition
|
|
661
|
+
|
|
662
|
+
return self.do_request(
|
|
663
|
+
url=request_url,
|
|
664
|
+
method="POST",
|
|
665
|
+
headers=request_header,
|
|
666
|
+
data=request_data,
|
|
667
|
+
timeout=None,
|
|
668
|
+
failure_message="Failed to get filtered PHT master products!",
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
# end method definition
|
|
672
|
+
|
|
673
|
+
def get_master_product(self, sync_id: int) -> dict | None:
|
|
674
|
+
"""Get a specific product in PHT.
|
|
675
|
+
|
|
676
|
+
Returns:
|
|
677
|
+
dict | None: product data matching the sync ID
|
|
678
|
+
"""
|
|
679
|
+
|
|
680
|
+
request_header = self.request_header()
|
|
681
|
+
request_url = self.config()["productUrl"] + "/" + str(sync_id)
|
|
682
|
+
|
|
683
|
+
return self.do_request(
|
|
684
|
+
url=request_url,
|
|
685
|
+
method="GET",
|
|
686
|
+
headers=request_header,
|
|
687
|
+
timeout=None,
|
|
688
|
+
failure_message="Failed to retrieve PHT product with sync ID -> {}!".format(
|
|
689
|
+
sync_id
|
|
690
|
+
),
|
|
691
|
+
)
|
|
692
|
+
|
|
693
|
+
# end method definition
|
|
694
|
+
|
|
695
|
+
def get_teams(self) -> list:
|
|
696
|
+
"""Get a list of all teams in PHT.
|
|
697
|
+
|
|
698
|
+
Returns:
|
|
699
|
+
list: list of PHT teams
|
|
700
|
+
"""
|
|
701
|
+
|
|
702
|
+
request_header = self.request_header()
|
|
703
|
+
request_url = self.config()["teamUrl"]
|
|
704
|
+
|
|
705
|
+
return self.do_request(
|
|
706
|
+
url=request_url,
|
|
707
|
+
method="GET",
|
|
708
|
+
headers=request_header,
|
|
709
|
+
timeout=None,
|
|
710
|
+
failure_message="Failed to retrieve PHT teams!",
|
|
711
|
+
)
|
|
712
|
+
|
|
713
|
+
# end method definition
|
|
714
|
+
|
|
715
|
+
def get_team(self, team_id: int) -> dict | None:
|
|
716
|
+
"""Get a specific team in PHT.
|
|
717
|
+
|
|
718
|
+
Returns:
|
|
719
|
+
dict | None: dict of the PHT team
|
|
720
|
+
"""
|
|
721
|
+
|
|
722
|
+
request_header = self.request_header()
|
|
723
|
+
request_url = self.config()["teamUrl"] + "/" + str(team_id)
|
|
724
|
+
|
|
725
|
+
return self.do_request(
|
|
726
|
+
url=request_url,
|
|
727
|
+
method="GET",
|
|
728
|
+
headers=request_header,
|
|
729
|
+
timeout=None,
|
|
730
|
+
failure_message="Failed to retrieve PHT teams!",
|
|
731
|
+
)
|
|
732
|
+
|
|
733
|
+
# end method definition
|
|
734
|
+
|
|
735
|
+
def get_components(self) -> list:
|
|
736
|
+
"""Get a list of all components in PHT.
|
|
737
|
+
|
|
738
|
+
Returns:
|
|
739
|
+
list: list of PHT components
|
|
740
|
+
|
|
741
|
+
Example result:
|
|
742
|
+
[
|
|
743
|
+
{
|
|
744
|
+
'id': 468,
|
|
745
|
+
'syncId': '6380c3da-8ded-40cd-8071-61f6721956f5',
|
|
746
|
+
'name': 'XOTE',
|
|
747
|
+
'developmentManager': {
|
|
748
|
+
'id': 237,
|
|
749
|
+
'domain': 'kurt',
|
|
750
|
+
'email': 'kurt.junker@opentext.com',
|
|
751
|
+
'name': 'Kurt Junker',
|
|
752
|
+
'role': None,
|
|
753
|
+
'status': 'ACTIVE',
|
|
754
|
+
'location': 'Grasbrunn, DEU',
|
|
755
|
+
'title': 'Sr. Manager, Software Engineering',
|
|
756
|
+
'type': 'OTHERS'
|
|
757
|
+
},
|
|
758
|
+
'componentCategory': {
|
|
759
|
+
'id': 2,
|
|
760
|
+
'name': 'Testing scripts',
|
|
761
|
+
'shortName': 'Testing scripts'
|
|
762
|
+
},
|
|
763
|
+
'comment': 'Test Framework maintained and used by Core Archive Team',
|
|
764
|
+
'status': 'MAINTENANCE',
|
|
765
|
+
'attributes': [
|
|
766
|
+
{
|
|
767
|
+
'id': 409,
|
|
768
|
+
'attribute': {
|
|
769
|
+
'id': 4,
|
|
770
|
+
'uuid': '03e228b5-9eae-11ea-96ab-00505682bce9',
|
|
771
|
+
'name': 'Build Advocate',
|
|
772
|
+
'description': 'Primary contact for build items.',
|
|
773
|
+
'type': 'USER'
|
|
774
|
+
},
|
|
775
|
+
'value': 'burkhard',
|
|
776
|
+
'textAttributeValue': None,
|
|
777
|
+
'userAttributeValue': {
|
|
778
|
+
'id': 414,
|
|
779
|
+
'domain': 'burkhard',
|
|
780
|
+
'email': 'burkhard.meier@opentext.com',
|
|
781
|
+
'name': 'Burkhard Meier',
|
|
782
|
+
'role': None,
|
|
783
|
+
'status': 'ACTIVE',
|
|
784
|
+
'location': 'Virtual, DEU',
|
|
785
|
+
'title': 'Principal Software Engineer',
|
|
786
|
+
'type': 'DEV'
|
|
787
|
+
},
|
|
788
|
+
'listAttributeValue': None
|
|
789
|
+
},
|
|
790
|
+
...
|
|
791
|
+
],
|
|
792
|
+
'sourceRepos': [],
|
|
793
|
+
'artifacts': [],
|
|
794
|
+
'products': [],
|
|
795
|
+
'teams': [],
|
|
796
|
+
'users': [],
|
|
797
|
+
'guestTeams': [],
|
|
798
|
+
'guestUsers': [],
|
|
799
|
+
'relatedLOBS': []
|
|
800
|
+
}
|
|
801
|
+
]
|
|
802
|
+
"""
|
|
803
|
+
|
|
804
|
+
request_header = self.request_header()
|
|
805
|
+
request_url = self.config()["componentUrl"]
|
|
806
|
+
|
|
807
|
+
return self.do_request(
|
|
808
|
+
url=request_url,
|
|
809
|
+
method="GET",
|
|
810
|
+
headers=request_header,
|
|
811
|
+
timeout=None,
|
|
812
|
+
failure_message="Failed to retrieve PHT components!",
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
# end method definition
|
|
816
|
+
|
|
817
|
+
def get_components_filtered(
|
|
818
|
+
self, filter_definition: dict | None = None
|
|
819
|
+
) -> list | None:
|
|
820
|
+
"""Get a list of filtered PHT components
|
|
821
|
+
|
|
822
|
+
Args:
|
|
823
|
+
filter_definition (dict): a dictionary of filter conditions.
|
|
824
|
+
Example filters:
|
|
825
|
+
{
|
|
826
|
+
componentName: <String>,
|
|
827
|
+
componentSyncId: <String>,
|
|
828
|
+
componentStatus: ACTIVE | INACTIVE | MAINTENANCE,
|
|
829
|
+
developmentManager: <String>,
|
|
830
|
+
attributeOperator: AND | OR,
|
|
831
|
+
attributes: {
|
|
832
|
+
"<AttributeName>": {
|
|
833
|
+
"compare": CONTAINS | EXISTS | DOES_NOT_EXISTS,
|
|
834
|
+
"values": List<String>
|
|
835
|
+
},
|
|
836
|
+
...
|
|
837
|
+
},
|
|
838
|
+
includeAttributes: true | false
|
|
839
|
+
}
|
|
840
|
+
Returns:
|
|
841
|
+
list | None: list of matching components.
|
|
842
|
+
"""
|
|
843
|
+
|
|
844
|
+
if not filter_definition:
|
|
845
|
+
return self.get_products()
|
|
846
|
+
|
|
847
|
+
request_header = self.request_header()
|
|
848
|
+
request_url = self.config()["masterProductFilteredUrl"]
|
|
849
|
+
request_data = filter_definition
|
|
850
|
+
|
|
851
|
+
return self.do_request(
|
|
852
|
+
url=request_url,
|
|
853
|
+
method="POST",
|
|
854
|
+
headers=request_header,
|
|
855
|
+
data=request_data,
|
|
856
|
+
timeout=None,
|
|
857
|
+
failure_message="Failed to get filtered PHT master products!",
|
|
858
|
+
)
|
|
859
|
+
|
|
860
|
+
# end method definition
|
|
861
|
+
|
|
862
|
+
def get_component(self, sync_id: int) -> dict | None:
|
|
863
|
+
"""Get a specific component in PHT.
|
|
864
|
+
|
|
865
|
+
Returns:
|
|
866
|
+
dict | None: PHT component
|
|
867
|
+
"""
|
|
868
|
+
|
|
869
|
+
request_header = self.request_header()
|
|
870
|
+
request_url = self.config()["componentUrl"] + "/" + str(sync_id)
|
|
871
|
+
|
|
872
|
+
return self.do_request(
|
|
873
|
+
url=request_url,
|
|
874
|
+
method="GET",
|
|
875
|
+
headers=request_header,
|
|
876
|
+
timeout=None,
|
|
877
|
+
failure_message="Failed to retrieve PHT component with sync ID -> {}!".format(
|
|
878
|
+
sync_id
|
|
879
|
+
),
|
|
880
|
+
)
|
|
881
|
+
|
|
882
|
+
# end method definition
|
|
883
|
+
|
|
884
|
+
def search_components(self, query: str) -> list | None:
|
|
885
|
+
"""Search for specific components in PHT by the component name.
|
|
886
|
+
|
|
887
|
+
Returns:
|
|
888
|
+
str: search term matches any part of the component name
|
|
889
|
+
"""
|
|
890
|
+
|
|
891
|
+
request_header = self.request_header()
|
|
892
|
+
request_url = self.config()["componentSearchUrl"] + "?q=" + query
|
|
893
|
+
|
|
894
|
+
return self.do_request(
|
|
895
|
+
url=request_url,
|
|
896
|
+
method="GET",
|
|
897
|
+
headers=request_header,
|
|
898
|
+
timeout=None,
|
|
899
|
+
failure_message="Failed to retrieve PHT components matching -> {}!".format(
|
|
900
|
+
query
|
|
901
|
+
),
|
|
902
|
+
)
|
|
903
|
+
|
|
904
|
+
# end method definition
|
|
905
|
+
|
|
906
|
+
def load_products(self, product_list: list = None) -> bool:
|
|
907
|
+
"""Load products into a data frame in the self._data object
|
|
908
|
+
|
|
909
|
+
Args:
|
|
910
|
+
product_list (list, optional): listn of products - if already avaiable. Defaults to None.
|
|
911
|
+
|
|
912
|
+
Returns:
|
|
913
|
+
bool: True if successful, False otherwise.
|
|
914
|
+
"""
|
|
915
|
+
|
|
916
|
+
if not product_list:
|
|
917
|
+
product_list = self.get_products()
|
|
918
|
+
|
|
919
|
+
self._data = Data(product_list)
|
|
920
|
+
|
|
921
|
+
if self._data:
|
|
922
|
+
return True
|
|
923
|
+
|
|
924
|
+
return False
|
|
925
|
+
|
|
926
|
+
# end method definition
|