@sw-tsdk/plugin-connector 3.13.1 → 3.13.2-next.3dfd44a
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.
- package/README.md +18 -18
- package/lib/commands/connector/build.js +168 -44
- package/lib/commands/connector/build.js.map +1 -1
- package/lib/commands/connector/sign.js +108 -12
- package/lib/commands/connector/sign.js.map +1 -1
- package/lib/commands/connector/validate.js +110 -10
- package/lib/commands/connector/validate.js.map +1 -1
- package/lib/commands/migrator/convert.d.ts +3 -0
- package/lib/commands/migrator/convert.js +201 -20
- package/lib/commands/migrator/convert.js.map +1 -1
- package/lib/templates/migrator-runners/plugin_override.txt +76 -4
- package/lib/templates/migrator-runners/runner_override.txt +30 -0
- package/lib/templates/migrator-runners/script_override.txt +77 -5
- package/lib/templates/swimlane/__init__.py +18 -0
- package/lib/templates/swimlane/core/__init__.py +0 -0
- package/lib/templates/swimlane/core/adapters/__init__.py +10 -0
- package/lib/templates/swimlane/core/adapters/app.py +59 -0
- package/lib/templates/swimlane/core/adapters/app_revision.py +49 -0
- package/lib/templates/swimlane/core/adapters/helper.py +84 -0
- package/lib/templates/swimlane/core/adapters/record.py +468 -0
- package/lib/templates/swimlane/core/adapters/record_revision.py +43 -0
- package/lib/templates/swimlane/core/adapters/report.py +65 -0
- package/lib/templates/swimlane/core/adapters/task.py +58 -0
- package/lib/templates/swimlane/core/adapters/usergroup.py +183 -0
- package/lib/templates/swimlane/core/bulk.py +48 -0
- package/lib/templates/swimlane/core/cache.py +165 -0
- package/lib/templates/swimlane/core/client.py +466 -0
- package/lib/templates/swimlane/core/cursor.py +100 -0
- package/lib/templates/swimlane/core/fields/__init__.py +46 -0
- package/lib/templates/swimlane/core/fields/attachment.py +82 -0
- package/lib/templates/swimlane/core/fields/base/__init__.py +15 -0
- package/lib/templates/swimlane/core/fields/base/cursor.py +90 -0
- package/lib/templates/swimlane/core/fields/base/field.py +149 -0
- package/lib/templates/swimlane/core/fields/base/multiselect.py +116 -0
- package/lib/templates/swimlane/core/fields/comment.py +48 -0
- package/lib/templates/swimlane/core/fields/datetime.py +112 -0
- package/lib/templates/swimlane/core/fields/history.py +28 -0
- package/lib/templates/swimlane/core/fields/list.py +266 -0
- package/lib/templates/swimlane/core/fields/number.py +38 -0
- package/lib/templates/swimlane/core/fields/reference.py +169 -0
- package/lib/templates/swimlane/core/fields/text.py +30 -0
- package/lib/templates/swimlane/core/fields/tracking.py +10 -0
- package/lib/templates/swimlane/core/fields/usergroup.py +137 -0
- package/lib/templates/swimlane/core/fields/valueslist.py +70 -0
- package/lib/templates/swimlane/core/resolver.py +46 -0
- package/lib/templates/swimlane/core/resources/__init__.py +0 -0
- package/lib/templates/swimlane/core/resources/app.py +136 -0
- package/lib/templates/swimlane/core/resources/app_revision.py +43 -0
- package/lib/templates/swimlane/core/resources/attachment.py +64 -0
- package/lib/templates/swimlane/core/resources/base.py +55 -0
- package/lib/templates/swimlane/core/resources/comment.py +33 -0
- package/lib/templates/swimlane/core/resources/record.py +499 -0
- package/lib/templates/swimlane/core/resources/record_revision.py +44 -0
- package/lib/templates/swimlane/core/resources/report.py +259 -0
- package/lib/templates/swimlane/core/resources/revision_base.py +69 -0
- package/lib/templates/swimlane/core/resources/task.py +16 -0
- package/lib/templates/swimlane/core/resources/usergroup.py +166 -0
- package/lib/templates/swimlane/core/search.py +31 -0
- package/lib/templates/swimlane/core/wrappedsession.py +12 -0
- package/lib/templates/swimlane/exceptions.py +191 -0
- package/lib/templates/swimlane/utils/__init__.py +132 -0
- package/lib/templates/swimlane/utils/date_validator.py +4 -0
- package/lib/templates/swimlane/utils/list_validator.py +7 -0
- package/lib/templates/swimlane/utils/str_validator.py +10 -0
- package/lib/templates/swimlane/utils/version.py +101 -0
- package/lib/transformers/base-transformer.js +61 -14
- package/lib/transformers/base-transformer.js.map +1 -1
- package/lib/transformers/connector-generator.d.ts +104 -2
- package/lib/transformers/connector-generator.js +1234 -51
- package/lib/transformers/connector-generator.js.map +1 -1
- package/lib/types/migrator-types.d.ts +22 -0
- package/lib/types/migrator-types.js.map +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +6 -6
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Custom exceptions and errors"""
|
|
2
|
+
|
|
3
|
+
from difflib import get_close_matches
|
|
4
|
+
|
|
5
|
+
from requests import HTTPError
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SwimlaneException(Exception):
|
|
9
|
+
"""Base exception for Swimlane errors"""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UnknownField(SwimlaneException, KeyError):
|
|
13
|
+
"""Raised anytime access is attempted to a field that does not exist on an App or Record
|
|
14
|
+
|
|
15
|
+
Attributes:
|
|
16
|
+
app (App): App with the unknown field requested
|
|
17
|
+
field_name (str): Name of the field that was requested
|
|
18
|
+
similar_field_names (list(str)): List of strings of fields on app that are potentially similar to field_name
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def __init__(self, app, field_name, field_pool):
|
|
22
|
+
self.app = app
|
|
23
|
+
self.field_name = field_name
|
|
24
|
+
self.similar_field_names = get_close_matches(self.field_name, field_pool, 3)
|
|
25
|
+
|
|
26
|
+
message = "{!r} has no field '{}'".format(self.app, self.field_name)
|
|
27
|
+
|
|
28
|
+
if self.similar_field_names:
|
|
29
|
+
message += '. Similar fields: ' + ', '.join([repr(f) for f in self.similar_field_names])
|
|
30
|
+
|
|
31
|
+
super(UnknownField, self).__init__(message)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ValidationError(SwimlaneException, ValueError):
|
|
35
|
+
"""Raised when record's field data is invalid
|
|
36
|
+
|
|
37
|
+
Attributes:
|
|
38
|
+
record (Record): Record in context of validation failure
|
|
39
|
+
failure (str): Reason for record failure
|
|
40
|
+
"""
|
|
41
|
+
|
|
42
|
+
def __init__(self, record, failure):
|
|
43
|
+
self.record = record
|
|
44
|
+
self.failure = failure
|
|
45
|
+
|
|
46
|
+
super(ValidationError, self).__init__(
|
|
47
|
+
'Validation failed for {!r}. Reason: {}'.format(self.record, self.failure)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class _InvalidSwimlaneVersion(SwimlaneException, NotImplementedError):
|
|
52
|
+
"""Base class raised when connecting to unsupported versions of Swimlane
|
|
53
|
+
|
|
54
|
+
Attributes:
|
|
55
|
+
swimlane (Swimlane): Swimlane client failing the version check
|
|
56
|
+
min_version (str): Minimum version specified on version range
|
|
57
|
+
max_version (str): Maximum version specified on version range
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, swimlane, min_version, max_version):
|
|
61
|
+
self.swimlane = swimlane
|
|
62
|
+
self.min_version = min_version
|
|
63
|
+
self.max_version = max_version
|
|
64
|
+
|
|
65
|
+
super(_InvalidSwimlaneVersion, self).__init__(self._get_message())
|
|
66
|
+
|
|
67
|
+
def _get_range_string(self):
|
|
68
|
+
if self.min_version and self.max_version:
|
|
69
|
+
range_string = '>= {}, < {}'.format(self.min_version, self.max_version)
|
|
70
|
+
elif self.min_version:
|
|
71
|
+
range_string = '>= {}'.format(self.min_version)
|
|
72
|
+
else:
|
|
73
|
+
range_string = '<= {}'.format(self.max_version)
|
|
74
|
+
|
|
75
|
+
return range_string
|
|
76
|
+
|
|
77
|
+
def _get_message(self):
|
|
78
|
+
return 'Swimlane version {}, must be {}'.format(
|
|
79
|
+
self.swimlane.version,
|
|
80
|
+
self._get_range_string()
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class InvalidSwimlaneBuildVersion(_InvalidSwimlaneVersion):
|
|
85
|
+
"""Raised when method connected to Swimlane with build version outside a required range"""
|
|
86
|
+
|
|
87
|
+
def _get_message(self):
|
|
88
|
+
return 'Swimlane build version {}, must be {}'.format(
|
|
89
|
+
self.swimlane.build_version,
|
|
90
|
+
self._get_range_string()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class InvalidSwimlaneProductVersion(_InvalidSwimlaneVersion):
|
|
95
|
+
"""Raised when method connected to Swimlane with product version outside a required range"""
|
|
96
|
+
|
|
97
|
+
def _get_message(self):
|
|
98
|
+
return 'Swimlane product version {}, must be {}'.format(
|
|
99
|
+
self.swimlane.product_version,
|
|
100
|
+
self._get_range_string()
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class SwimlaneHTTP400Error(SwimlaneException, HTTPError):
|
|
105
|
+
"""Exception raised when receiving a 400 response with additional context
|
|
106
|
+
|
|
107
|
+
Attributes:
|
|
108
|
+
code (int): Swimlane error code
|
|
109
|
+
name (str): Human-readable Swimlane error name
|
|
110
|
+
argument (str): Optional argument included with error or None
|
|
111
|
+
http_error (HTTPError): Source requests.HTTPError caught and used to generate this exception
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
codes = {
|
|
115
|
+
-1: 'Unknown',
|
|
116
|
+
1000: 'PasswordExpired',
|
|
117
|
+
1001: 'DuplicateUserName',
|
|
118
|
+
1002: 'InvalidUserNameOrPassword',
|
|
119
|
+
1003: 'ConfirmPasswordDoesNotMatch',
|
|
120
|
+
1004: 'PasswordDoesNotMeetComplexityRequirements',
|
|
121
|
+
1005: 'PasswordResetRequired',
|
|
122
|
+
1006: 'NewPasswordCannotMatchCurrent',
|
|
123
|
+
1007: 'InvalidUser',
|
|
124
|
+
1051: 'DuplicateGroupName',
|
|
125
|
+
1061: 'DuplicateRoleName',
|
|
126
|
+
2000: 'DuplicateFieldName',
|
|
127
|
+
2001: 'FieldNameEmpty',
|
|
128
|
+
2002: 'InvalidApplicationExportFile',
|
|
129
|
+
2003: 'ApplicationNotFound',
|
|
130
|
+
2004: 'InvalidCalculation',
|
|
131
|
+
2005: 'DuplicateApplicationName',
|
|
132
|
+
2006: 'DuplicateAppletName',
|
|
133
|
+
2007: 'DuplicateAppletAcronym',
|
|
134
|
+
2008: 'DuplicateApplicationAcronym',
|
|
135
|
+
2011: 'SectionNameTooLong',
|
|
136
|
+
3000: 'DuplicateFieldValue',
|
|
137
|
+
3001: 'InvalidDateField',
|
|
138
|
+
3002: 'RecordNotFound',
|
|
139
|
+
3003: 'FieldNotFound',
|
|
140
|
+
3006: 'MaxAttachmentSize',
|
|
141
|
+
4000: 'BadStatsGroup',
|
|
142
|
+
4001: 'BadFilter',
|
|
143
|
+
5000: 'AppLimitExceeded',
|
|
144
|
+
5001: 'UserLimitExceeded',
|
|
145
|
+
5002: 'NewServerInstall',
|
|
146
|
+
5003: 'UnableToConnectToActiveDirectory',
|
|
147
|
+
5004: 'UnableToRetrieveStoredValue',
|
|
148
|
+
5005: 'UnableToConnectToMongoDb',
|
|
149
|
+
5006: 'UnableToConnectToSmtp',
|
|
150
|
+
5007: 'SwimlaneAlreadyInitialized',
|
|
151
|
+
5008: 'ModelValidationError',
|
|
152
|
+
5009: 'UpgradeInProcess',
|
|
153
|
+
5010: 'RequiredFieldMissing',
|
|
154
|
+
5011: 'UnableToRetrieveEncryptionKey',
|
|
155
|
+
5012: 'PathNotFound',
|
|
156
|
+
5013: 'WrongType',
|
|
157
|
+
5014: 'ModificationError',
|
|
158
|
+
5015: 'DatabaseError',
|
|
159
|
+
5016: 'NetworkError',
|
|
160
|
+
5017: 'InvalidOnThisOS',
|
|
161
|
+
6000: 'ConnectionDataNotProvided',
|
|
162
|
+
7000: 'RegexNotDefined',
|
|
163
|
+
7001: 'AssetNotFound',
|
|
164
|
+
9000: 'BadThreatIntelConnector',
|
|
165
|
+
9001: 'NoThreatIntel',
|
|
166
|
+
9002: 'ThreatIntelTypeNotSupportedByThisProvider',
|
|
167
|
+
10000: 'DuplicateTaskName',
|
|
168
|
+
10001: 'TaskNotFound',
|
|
169
|
+
17001: 'DuplicateAssetName',
|
|
170
|
+
19001: 'HangfireError'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def __init__(self, http_error):
|
|
174
|
+
self.http_error = http_error
|
|
175
|
+
|
|
176
|
+
try:
|
|
177
|
+
error_content = self.http_error.response.json()
|
|
178
|
+
except ValueError:
|
|
179
|
+
error_content = {'Argument': None, 'ErrorCode': '-1'}
|
|
180
|
+
|
|
181
|
+
self.code = int(error_content.get('ErrorCode', -1))
|
|
182
|
+
self.argument = error_content.get('Argument')
|
|
183
|
+
self.name = self.codes.get(self.code, self.codes[-1])
|
|
184
|
+
|
|
185
|
+
message = '{}:{}'.format(self.name, self.code)
|
|
186
|
+
if self.argument is not None:
|
|
187
|
+
message = '{message} ({argument})'.format(message=message, argument=self.argument)
|
|
188
|
+
|
|
189
|
+
super(SwimlaneHTTP400Error, self).__init__(
|
|
190
|
+
'{message}: Bad Request for url: {url}'.format(message=message, url=self.http_error.response.url)
|
|
191
|
+
)
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""Utility functions"""
|
|
2
|
+
from __future__ import absolute_import
|
|
3
|
+
|
|
4
|
+
import importlib
|
|
5
|
+
import pkgutil
|
|
6
|
+
import random
|
|
7
|
+
import string
|
|
8
|
+
import functools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def random_string(length, source=string.ascii_letters + string.digits):
|
|
12
|
+
"""Return random string of characters from source of specified length
|
|
13
|
+
|
|
14
|
+
Args:
|
|
15
|
+
length (int): Length of the returned string
|
|
16
|
+
source (str): String of characters to use as options for randomly selected characters. Defaults to alphanumeric
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: String of length number of characters composed of source characters
|
|
20
|
+
"""
|
|
21
|
+
return ''.join(random.choice(source) for _ in range(length))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_recursive_subclasses(cls):
|
|
25
|
+
"""Return list of all subclasses for a class, including subclasses of direct subclasses"""
|
|
26
|
+
return cls.__subclasses__() + [g for s in cls.__subclasses__() for g in get_recursive_subclasses(s)]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def import_submodules(package):
|
|
30
|
+
"""Return list of imported module instances from beneath root_package"""
|
|
31
|
+
|
|
32
|
+
if isinstance(package, str):
|
|
33
|
+
package = importlib.import_module(package)
|
|
34
|
+
|
|
35
|
+
results = {}
|
|
36
|
+
|
|
37
|
+
for _, full_name, is_pkg in pkgutil.walk_packages(package.__path__, package.__name__ + '.'):
|
|
38
|
+
results[full_name] = importlib.import_module(full_name)
|
|
39
|
+
|
|
40
|
+
if is_pkg:
|
|
41
|
+
results.update(import_submodules(full_name))
|
|
42
|
+
|
|
43
|
+
return results
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def one_of_keyword_only(*valid_keywords):
|
|
47
|
+
"""Decorator to help make one-and-only-one keyword-only argument functions more reusable
|
|
48
|
+
|
|
49
|
+
Notes:
|
|
50
|
+
Decorated function should take 2 arguments, the first for the key, the second the value
|
|
51
|
+
|
|
52
|
+
Examples:
|
|
53
|
+
|
|
54
|
+
::
|
|
55
|
+
|
|
56
|
+
@one_of_keyword_only('a', 'b', 'c')
|
|
57
|
+
def func(key, value):
|
|
58
|
+
if key == 'a':
|
|
59
|
+
...
|
|
60
|
+
elif key == 'b':
|
|
61
|
+
...
|
|
62
|
+
else:
|
|
63
|
+
# key = 'c'
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
...
|
|
67
|
+
|
|
68
|
+
func(a=1)
|
|
69
|
+
func(b=2)
|
|
70
|
+
func(c=3)
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
func(d=4)
|
|
74
|
+
except TypeError:
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
func(a=1, b=2)
|
|
79
|
+
except TypeError:
|
|
80
|
+
...
|
|
81
|
+
|
|
82
|
+
Args:
|
|
83
|
+
*valid_keywords (str): All allowed keyword argument names
|
|
84
|
+
|
|
85
|
+
Raises:
|
|
86
|
+
TypeError: On decorated call, if 0 or 2+ arguments are provided or kwargs contains a key not in valid_keywords
|
|
87
|
+
"""
|
|
88
|
+
|
|
89
|
+
def decorator(func):
|
|
90
|
+
|
|
91
|
+
@functools.wraps(func)
|
|
92
|
+
def wrapper(*args, **kwargs):
|
|
93
|
+
sentinel = object()
|
|
94
|
+
values = {}
|
|
95
|
+
|
|
96
|
+
for key in valid_keywords:
|
|
97
|
+
kwarg_value = kwargs.pop(key, sentinel)
|
|
98
|
+
if kwarg_value is not sentinel:
|
|
99
|
+
values[key] = kwarg_value
|
|
100
|
+
|
|
101
|
+
if kwargs:
|
|
102
|
+
raise TypeError('Unexpected arguments: {}'.format(kwargs))
|
|
103
|
+
|
|
104
|
+
if not values:
|
|
105
|
+
raise TypeError('Must provide one of {} as keyword argument'.format(', '.join(valid_keywords)))
|
|
106
|
+
|
|
107
|
+
if len(values) > 1:
|
|
108
|
+
raise TypeError('Must provide only one of {} as keyword argument. Received {}'.format(
|
|
109
|
+
', '.join(valid_keywords),
|
|
110
|
+
values
|
|
111
|
+
))
|
|
112
|
+
|
|
113
|
+
return func(*(args + values.popitem()))
|
|
114
|
+
|
|
115
|
+
return wrapper
|
|
116
|
+
|
|
117
|
+
return decorator
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def validate_type(field, value):
|
|
121
|
+
"""Type validation for filters and fields from bulk_modify, bulk_delete and filter"""
|
|
122
|
+
accepted_values_to_check = (int, str, float, list, bool, tuple)
|
|
123
|
+
should_check_value_type = not value == None and type(value) in accepted_values_to_check
|
|
124
|
+
if should_check_value_type:
|
|
125
|
+
value_list = value if isinstance(value, list) else [value]
|
|
126
|
+
for v in value_list:
|
|
127
|
+
wrong_type = not any(
|
|
128
|
+
[isinstance(v, field_type) for field_type in field.supported_types]
|
|
129
|
+
) if len(field.supported_types) > 0 else False
|
|
130
|
+
|
|
131
|
+
if wrong_type:
|
|
132
|
+
raise ValueError('Value must be one of {}'.format(", ".join([str(f) for f in field.supported_types])))
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
def validate_str_list(value, key):
|
|
2
|
+
if not value or not isinstance(value, list):
|
|
3
|
+
raise ValueError('{} must be a non-empty list value'.format(key))
|
|
4
|
+
|
|
5
|
+
for i in value:
|
|
6
|
+
if not isinstance(i, str) or i.strip() == '':
|
|
7
|
+
raise ValueError('{} must contain non-empty string values'.format(key))
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
def validate_str(value, key):
|
|
2
|
+
if not isinstance(value, str):
|
|
3
|
+
raise ValueError('{} must be a string value.'.format(key))
|
|
4
|
+
if value.strip() == '':
|
|
5
|
+
raise ValueError('{} must not be an empty string value.'.format(key))
|
|
6
|
+
|
|
7
|
+
def validate_str_format(value, key):
|
|
8
|
+
not_allowed = "@#$%^&*()"
|
|
9
|
+
if any(ch in not_allowed for ch in value):
|
|
10
|
+
raise ValueError('{} is not of the proper format.'.format(key))
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import re
|
|
3
|
+
|
|
4
|
+
from pkg_resources import get_distribution, DistributionNotFound
|
|
5
|
+
|
|
6
|
+
from swimlane.exceptions import InvalidSwimlaneBuildVersion
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def compare_versions(version_a, version_b, zerofill=False):
|
|
10
|
+
"""Return direction of version relative to provided version sections
|
|
11
|
+
|
|
12
|
+
Args:
|
|
13
|
+
version_a (str): First version to compare
|
|
14
|
+
version_b (str): Second version to compare
|
|
15
|
+
zerofill (bool): If True, treat any missing version sections as 0, otherwise ignore section. Defaults to False
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
int: 0 if equal, -1 if a > b, 1 if a < b
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
|
|
22
|
+
If a is equal to b, return 0
|
|
23
|
+
If a is greater than b, return -1
|
|
24
|
+
If a is less than b, return 1
|
|
25
|
+
|
|
26
|
+
>>> compare_versions('2', '2') == 0
|
|
27
|
+
>>> compare_versions('2', '1') == -1
|
|
28
|
+
>>> compare_versions('2', '3') == 1
|
|
29
|
+
|
|
30
|
+
If zerofill is False (default), sections not included in both versions are ignored during comparison
|
|
31
|
+
|
|
32
|
+
>>> compare_versions('2.13.2', '2.13') == 0
|
|
33
|
+
>>> compare_versions('2.13.2-1234', '3') == 1
|
|
34
|
+
|
|
35
|
+
If zerofill is True, any sections in one version not included in other version are set to 0
|
|
36
|
+
|
|
37
|
+
>>> compare_versions('2.13.2', '2.13', True) == -1
|
|
38
|
+
>>> compare_versions('2.13.2-1234', '2.13.2', True) == -1
|
|
39
|
+
>>> compare_versions('2.13.2', '2.13.2', True) == 0
|
|
40
|
+
"""
|
|
41
|
+
a_sections = list((int(match) for match in re.findall(r'\d+', version_a)))
|
|
42
|
+
b_sections = list((int(match) for match in re.findall(r'\d+', version_b)))
|
|
43
|
+
|
|
44
|
+
if zerofill:
|
|
45
|
+
max_sections = max([len(a_sections), len(b_sections)])
|
|
46
|
+
|
|
47
|
+
a_sections += [0 for _ in range(max(max_sections - len(a_sections), 0))]
|
|
48
|
+
b_sections += [0 for _ in range(max(max_sections - len(b_sections), 0))]
|
|
49
|
+
|
|
50
|
+
else:
|
|
51
|
+
min_sections = min([len(a_sections), len(b_sections)])
|
|
52
|
+
|
|
53
|
+
a_sections = a_sections[:min_sections]
|
|
54
|
+
b_sections = b_sections[:min_sections]
|
|
55
|
+
|
|
56
|
+
return (b_sections > a_sections) - (b_sections < a_sections)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def requires_swimlane_version(min_version=None, max_version=None):
|
|
60
|
+
"""Decorator for SwimlaneResolver methods verifying Swimlane server build version is within a given inclusive range
|
|
61
|
+
|
|
62
|
+
Raises:
|
|
63
|
+
InvalidVersion: Raised before decorated method call if Swimlane server version is out of provided range
|
|
64
|
+
ValueError: If neither min_version or max_version were provided, or if those values conflict (2.15 < 2.14)
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
if min_version is None and max_version is None:
|
|
68
|
+
raise ValueError('Must provide either min_version, max_version, or both')
|
|
69
|
+
|
|
70
|
+
if min_version and max_version and compare_versions(min_version, max_version) < 0:
|
|
71
|
+
raise ValueError('min_version must be <= max_version ({}, {})'.format(min_version, max_version))
|
|
72
|
+
|
|
73
|
+
def decorator(func):
|
|
74
|
+
|
|
75
|
+
@functools.wraps(func)
|
|
76
|
+
def wrapper(self, *args, **kwargs):
|
|
77
|
+
swimlane = self._swimlane
|
|
78
|
+
|
|
79
|
+
if min_version and compare_versions(min_version, swimlane.build_version, True) < 0:
|
|
80
|
+
raise InvalidSwimlaneBuildVersion(swimlane, min_version, max_version)
|
|
81
|
+
|
|
82
|
+
if max_version and compare_versions(swimlane.build_version, max_version, True) < 0:
|
|
83
|
+
raise InvalidSwimlaneBuildVersion(swimlane, min_version, max_version)
|
|
84
|
+
|
|
85
|
+
return func(self, *args, **kwargs)
|
|
86
|
+
|
|
87
|
+
return wrapper
|
|
88
|
+
|
|
89
|
+
return decorator
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def get_package_version():
|
|
93
|
+
"""Get swimlane package version
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
str: Installed swimlane lib package version, or 0.0.0.dev if not fully installed
|
|
97
|
+
"""
|
|
98
|
+
try:
|
|
99
|
+
return get_distribution(__name__.split('.')[0]).version
|
|
100
|
+
except DistributionNotFound:
|
|
101
|
+
return '0.0.0.dev'
|
|
@@ -13,21 +13,63 @@ class BaseTransformer {
|
|
|
13
13
|
}
|
|
14
14
|
return name;
|
|
15
15
|
};
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
// Infer JSON Schema type from a literal value (from InputMapping Value or Example)
|
|
17
|
+
const inferValueType = (value) => {
|
|
18
|
+
if (value === undefined || value === null)
|
|
19
|
+
return 'string';
|
|
20
|
+
if (typeof value === 'number')
|
|
21
|
+
return 'number';
|
|
22
|
+
if (typeof value === 'boolean')
|
|
23
|
+
return 'boolean';
|
|
24
|
+
if (Array.isArray(value))
|
|
25
|
+
return 'array';
|
|
26
|
+
return 'string';
|
|
27
|
+
};
|
|
28
|
+
// Separate credentials and asset inputs from regular inputs
|
|
29
|
+
const assetParameters = [];
|
|
30
|
+
const regularInputs = [];
|
|
31
|
+
for (const input of jsonFileContent?.InputMapping ?? []) {
|
|
32
|
+
const inputType = (input.type || input.Type || '').toLowerCase();
|
|
33
|
+
if (inputType === 'credentials' || inputType === 'asset') {
|
|
34
|
+
assetParameters.push({
|
|
35
|
+
Key: input.Key,
|
|
36
|
+
Type: inputType,
|
|
37
|
+
Example: input.Example,
|
|
38
|
+
SubValue: input.subValue || input.SubValue,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// Infer ValueType from Value first, then from first Example
|
|
43
|
+
const sample = input.Value !== undefined && input.Value !== null ?
|
|
44
|
+
input.Value :
|
|
45
|
+
(Array.isArray(input.Example) && input.Example.length > 0 ? input.Example[0] : (input.Example ?? undefined));
|
|
46
|
+
const valueType = inferValueType(sample);
|
|
47
|
+
regularInputs.push({
|
|
48
|
+
...input,
|
|
49
|
+
Key: input.Key,
|
|
50
|
+
Title: input.Key,
|
|
51
|
+
ValueType: valueType,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
22
55
|
const outputs = (jsonFileContent?.Outputs ?? [])
|
|
23
|
-
.flatMap((
|
|
24
|
-
.map((mapping) => ({
|
|
56
|
+
.flatMap((outputBlock) => (outputBlock.Mappings ?? []).map((mapping) => ({
|
|
25
57
|
Key: typeof mapping.Key === 'string' ? mapping.Key.replaceAll(' ', '_') : mapping.Key,
|
|
26
58
|
Title: mapping.Key,
|
|
27
59
|
ValueType: 'string',
|
|
28
60
|
Example: mapping.Example || [],
|
|
29
61
|
Description: '',
|
|
30
|
-
|
|
62
|
+
// Preserve mapping metadata for output date conversion (Value = application field id)
|
|
63
|
+
Value: mapping.Value,
|
|
64
|
+
DataFormat: mapping.DataFormat ?? mapping.dataFormat,
|
|
65
|
+
UnixEpochUnit: mapping.UnixEpochUnit ?? mapping.unixEpochUnit,
|
|
66
|
+
customDataFormat: mapping.customDataFormat ?? mapping.CustomDataFormat,
|
|
67
|
+
// Application id of the output block (e.g. InsertUpdateRecordOutput.ApplicationId); used to resolve field type
|
|
68
|
+
ApplicationId: outputBlock.ApplicationId ?? outputBlock.applicationId,
|
|
69
|
+
})));
|
|
70
|
+
// Extract task ID from common locations in the task JSON
|
|
71
|
+
const taskId = jsonFileContent?._id || jsonFileContent?.$id || jsonFileContent?.Id || jsonFileContent?.id || '';
|
|
72
|
+
const taskApplicationId = jsonFileContent?.ApplicationId ?? jsonFileContent?.applicationId;
|
|
31
73
|
return {
|
|
32
74
|
description: jsonFileContent?.Action?.Descriptor?.Description,
|
|
33
75
|
family: jsonFileContent?.Action?.Descriptor?.Family,
|
|
@@ -37,17 +79,22 @@ class BaseTransformer {
|
|
|
37
79
|
script: jsonFileContent?.Action?.Script,
|
|
38
80
|
exportName: truncateIfNeeded(jsonFileContent?.Name, 'exportName'),
|
|
39
81
|
exportUid: truncateIfNeeded(jsonFileContent?.Uid.replaceAll('-', '_'), 'exportUid'),
|
|
82
|
+
taskId,
|
|
83
|
+
taskApplicationId: typeof taskApplicationId === 'string' ? taskApplicationId : undefined,
|
|
40
84
|
forkedName: truncateIfNeeded(jsonFileContent?.Action?.ForkedFromPackage, 'forkedName'),
|
|
41
|
-
inputs,
|
|
85
|
+
inputs: regularInputs,
|
|
42
86
|
outputs,
|
|
87
|
+
assetParameters,
|
|
43
88
|
author: jsonFileContent?.CreatedByUser?.Author,
|
|
44
89
|
};
|
|
45
90
|
}
|
|
46
91
|
scriptIsValid(script) {
|
|
47
|
-
const containsMongoImports = script.includes('from pymongo import MongoClient' || 'import pymongo');
|
|
48
|
-
|
|
49
|
-
const
|
|
50
|
-
|
|
92
|
+
const containsMongoImports = script.includes('from pymongo import MongoClient') || script.includes('import pymongo');
|
|
93
|
+
// Note: These checks are currently disabled but kept for future use
|
|
94
|
+
// const containsSwimlaneDriver = script.includes('from swimlane import Swimlane') || script.includes('from swimlane import SwimlaneClient') || script.includes('import Swimlane')
|
|
95
|
+
// const containsInternalConfig = script.includes('sw_context.config')
|
|
96
|
+
// return !(containsMongoImports || containsSwimlaneDriver || containsInternalConfig)
|
|
97
|
+
return !containsMongoImports;
|
|
51
98
|
}
|
|
52
99
|
}
|
|
53
100
|
exports.BaseTransformer = BaseTransformer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-transformer.js","sourceRoot":"","sources":["../../src/transformers/base-transformer.ts"],"names":[],"mappings":";;;AAEA,MAAsB,eAAe;IAEzB,iBAAiB,CAAC,eAAoB;QAC9C,MAAM,UAAU,GAAG,EAAE,CAAA;QAErB,MAAM,gBAAgB,GAAG,CAAC,IAAwB,EAAE,KAAa,EAAU,EAAE;YAC3E,IAAI,CAAC,IAAI;gBAAE,OAAO,EAAE,CAAA;YACpB,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE;gBAC5B,OAAO,CAAC,IAAI,CAAC,YAAY,KAAK,YAAY,UAAU,gDAAgD,IAAI,EAAE,CAAC,CAAA;gBAC3G,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAA;aAC9C;YAED,OAAO,IAAI,CAAA;QACb,CAAC,CAAA;QAED,
|
|
1
|
+
{"version":3,"file":"base-transformer.js","sourceRoot":"","sources":["../../src/transformers/base-transformer.ts"],"names":[],"mappings":";;;AAEA,MAAsB,eAAe;IAEzB,iBAAiB,CAAC,eAAoB;QAC9C,MAAM,UAAU,GAAG,EAAE,CAAA;QAErB,MAAM,gBAAgB,GAAG,CAAC,IAAwB,EAAE,KAAa,EAAU,EAAE;YAC3E,IAAI,CAAC,IAAI;gBAAE,OAAO,EAAE,CAAA;YACpB,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE;gBAC5B,OAAO,CAAC,IAAI,CAAC,YAAY,KAAK,YAAY,UAAU,gDAAgD,IAAI,EAAE,CAAC,CAAA;gBAC3G,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC,CAAA;aAC9C;YAED,OAAO,IAAI,CAAA;QACb,CAAC,CAAA;QAED,mFAAmF;QACnF,MAAM,cAAc,GAAG,CAAC,KAAc,EAAU,EAAE;YAChD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI;gBAAE,OAAO,QAAQ,CAAA;YAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ;gBAAE,OAAO,QAAQ,CAAA;YAC9C,IAAI,OAAO,KAAK,KAAK,SAAS;gBAAE,OAAO,SAAS,CAAA;YAChD,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;gBAAE,OAAO,OAAO,CAAA;YACxC,OAAO,QAAQ,CAAA;QACjB,CAAC,CAAA;QAED,4DAA4D;QAC5D,MAAM,eAAe,GAAsE,EAAE,CAAA;QAC7F,MAAM,aAAa,GAAU,EAAE,CAAA;QAE/B,KAAK,MAAM,KAAK,IAAI,eAAe,EAAE,YAAY,IAAI,EAAE,EAAE;YACvD,MAAM,SAAS,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAA;YAChE,IAAI,SAAS,KAAK,aAAa,IAAI,SAAS,KAAK,OAAO,EAAE;gBACxD,eAAe,CAAC,IAAI,CAAC;oBACnB,GAAG,EAAE,KAAK,CAAC,GAAG;oBACd,IAAI,EAAE,SAAS;oBACf,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,QAAQ;iBAC3C,CAAC,CAAA;aACH;iBAAM;gBACL,4DAA4D;gBAC5D,MAAM,MAAM,GAAG,KAAK,CAAC,KAAK,KAAK,SAAS,IAAI,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC;oBAChE,KAAK,CAAC,KAAK,CAAC,CAAC;oBACb,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,IAAI,SAAS,CAAC,CAAC,CAAA;gBAC9G,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,CAAA;gBACxC,aAAa,CAAC,IAAI,CAAC;oBACjB,GAAG,KAAK;oBACR,GAAG,EAAE,KAAK,CAAC,GAAG;oBACd,KAAK,EAAE,KAAK,CAAC,GAAG;oBAChB,SAAS,EAAE,SAAS;iBACrB,CAAC,CAAA;aACH;SACF;QAED,MAAM,OAAO,GAAG,CAAC,eAAe,EAAE,OAAO,IAAI,EAAE,CAAC;aAC/C,OAAO,CAAC,CAAC,WAAgB,EAAE,EAAE,CAAC,CAAC,WAAW,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,OAAY,EAAE,EAAE,CAAC,CAAC;YACjF,GAAG,EAAE,OAAO,OAAO,CAAC,GAAG,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG;YACrF,KAAK,EAAE,OAAO,CAAC,GAAG;YAClB,SAAS,EAAE,QAAQ;YACnB,OAAO,EAAE,OAAO,CAAC,OAAO,IAAI,EAAE;YAC9B,WAAW,EAAE,EAAE;YACf,sFAAsF;YACtF,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,OAAO,CAAC,UAAU;YACpD,aAAa,EAAE,OAAO,CAAC,aAAa,IAAI,OAAO,CAAC,aAAa;YAC7D,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,IAAI,OAAO,CAAC,gBAAgB;YACtE,+GAA+G;YAC/G,aAAa,EAAE,WAAW,CAAC,aAAa,IAAI,WAAW,CAAC,aAAa;SACtE,CAAC,CAAC,CAAC,CAAA;QAEJ,yDAAyD;QACzD,MAAM,MAAM,GAAG,eAAe,EAAE,GAAG,IAAI,eAAe,EAAE,GAAG,IAAI,eAAe,EAAE,EAAE,IAAI,eAAe,EAAE,EAAE,IAAI,EAAE,CAAA;QAC/G,MAAM,iBAAiB,GAAG,eAAe,EAAE,aAAa,IAAI,eAAe,EAAE,aAAa,CAAA;QAE1F,OAAO;YACL,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW;YAC7D,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM;YACnD,WAAW,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,WAAW;YAC7D,aAAa,EAAE,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,aAAa;YACjE,UAAU,EAAE,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,YAAY,CAAC;YACrF,MAAM,EAAE,eAAe,EAAE,MAAM,EAAE,MAAM;YACvC,UAAU,EAAE,gBAAgB,CAAC,eAAe,EAAE,IAAI,EAAE,YAAY,CAAC;YACjE,SAAS,EAAE,gBAAgB,CAAC,eAAe,EAAE,GAAG,CAAC,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EAAE,WAAW,CAAC;YACnF,MAAM;YACN,iBAAiB,EAAE,OAAO,iBAAiB,KAAK,QAAQ,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,SAAS;YACxF,UAAU,EAAE,gBAAgB,CAAC,eAAe,EAAE,MAAM,EAAE,iBAAiB,EAAE,YAAY,CAAC;YACtF,MAAM,EAAE,aAAa;YACrB,OAAO;YACP,eAAe;YACf,MAAM,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM;SAC/C,CAAA;IACH,CAAC;IAEM,aAAa,CAAC,MAAc;QACjC,MAAM,oBAAoB,GAAG,MAAM,CAAC,QAAQ,CAAC,iCAAiC,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAC,CAAA;QACpH,oEAAoE;QACpE,kLAAkL;QAClL,sEAAsE;QACtE,qFAAqF;QACrF,OAAO,CAAC,oBAAoB,CAAA;IAC9B,CAAC;CACF;AAnGD,0CAmGC"}
|