@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.
Files changed (74) hide show
  1. package/README.md +18 -18
  2. package/lib/commands/connector/build.js +168 -44
  3. package/lib/commands/connector/build.js.map +1 -1
  4. package/lib/commands/connector/sign.js +108 -12
  5. package/lib/commands/connector/sign.js.map +1 -1
  6. package/lib/commands/connector/validate.js +110 -10
  7. package/lib/commands/connector/validate.js.map +1 -1
  8. package/lib/commands/migrator/convert.d.ts +3 -0
  9. package/lib/commands/migrator/convert.js +201 -20
  10. package/lib/commands/migrator/convert.js.map +1 -1
  11. package/lib/templates/migrator-runners/plugin_override.txt +76 -4
  12. package/lib/templates/migrator-runners/runner_override.txt +30 -0
  13. package/lib/templates/migrator-runners/script_override.txt +77 -5
  14. package/lib/templates/swimlane/__init__.py +18 -0
  15. package/lib/templates/swimlane/core/__init__.py +0 -0
  16. package/lib/templates/swimlane/core/adapters/__init__.py +10 -0
  17. package/lib/templates/swimlane/core/adapters/app.py +59 -0
  18. package/lib/templates/swimlane/core/adapters/app_revision.py +49 -0
  19. package/lib/templates/swimlane/core/adapters/helper.py +84 -0
  20. package/lib/templates/swimlane/core/adapters/record.py +468 -0
  21. package/lib/templates/swimlane/core/adapters/record_revision.py +43 -0
  22. package/lib/templates/swimlane/core/adapters/report.py +65 -0
  23. package/lib/templates/swimlane/core/adapters/task.py +58 -0
  24. package/lib/templates/swimlane/core/adapters/usergroup.py +183 -0
  25. package/lib/templates/swimlane/core/bulk.py +48 -0
  26. package/lib/templates/swimlane/core/cache.py +165 -0
  27. package/lib/templates/swimlane/core/client.py +466 -0
  28. package/lib/templates/swimlane/core/cursor.py +100 -0
  29. package/lib/templates/swimlane/core/fields/__init__.py +46 -0
  30. package/lib/templates/swimlane/core/fields/attachment.py +82 -0
  31. package/lib/templates/swimlane/core/fields/base/__init__.py +15 -0
  32. package/lib/templates/swimlane/core/fields/base/cursor.py +90 -0
  33. package/lib/templates/swimlane/core/fields/base/field.py +149 -0
  34. package/lib/templates/swimlane/core/fields/base/multiselect.py +116 -0
  35. package/lib/templates/swimlane/core/fields/comment.py +48 -0
  36. package/lib/templates/swimlane/core/fields/datetime.py +112 -0
  37. package/lib/templates/swimlane/core/fields/history.py +28 -0
  38. package/lib/templates/swimlane/core/fields/list.py +266 -0
  39. package/lib/templates/swimlane/core/fields/number.py +38 -0
  40. package/lib/templates/swimlane/core/fields/reference.py +169 -0
  41. package/lib/templates/swimlane/core/fields/text.py +30 -0
  42. package/lib/templates/swimlane/core/fields/tracking.py +10 -0
  43. package/lib/templates/swimlane/core/fields/usergroup.py +137 -0
  44. package/lib/templates/swimlane/core/fields/valueslist.py +70 -0
  45. package/lib/templates/swimlane/core/resolver.py +46 -0
  46. package/lib/templates/swimlane/core/resources/__init__.py +0 -0
  47. package/lib/templates/swimlane/core/resources/app.py +136 -0
  48. package/lib/templates/swimlane/core/resources/app_revision.py +43 -0
  49. package/lib/templates/swimlane/core/resources/attachment.py +64 -0
  50. package/lib/templates/swimlane/core/resources/base.py +55 -0
  51. package/lib/templates/swimlane/core/resources/comment.py +33 -0
  52. package/lib/templates/swimlane/core/resources/record.py +499 -0
  53. package/lib/templates/swimlane/core/resources/record_revision.py +44 -0
  54. package/lib/templates/swimlane/core/resources/report.py +259 -0
  55. package/lib/templates/swimlane/core/resources/revision_base.py +69 -0
  56. package/lib/templates/swimlane/core/resources/task.py +16 -0
  57. package/lib/templates/swimlane/core/resources/usergroup.py +166 -0
  58. package/lib/templates/swimlane/core/search.py +31 -0
  59. package/lib/templates/swimlane/core/wrappedsession.py +12 -0
  60. package/lib/templates/swimlane/exceptions.py +191 -0
  61. package/lib/templates/swimlane/utils/__init__.py +132 -0
  62. package/lib/templates/swimlane/utils/date_validator.py +4 -0
  63. package/lib/templates/swimlane/utils/list_validator.py +7 -0
  64. package/lib/templates/swimlane/utils/str_validator.py +10 -0
  65. package/lib/templates/swimlane/utils/version.py +101 -0
  66. package/lib/transformers/base-transformer.js +61 -14
  67. package/lib/transformers/base-transformer.js.map +1 -1
  68. package/lib/transformers/connector-generator.d.ts +104 -2
  69. package/lib/transformers/connector-generator.js +1234 -51
  70. package/lib/transformers/connector-generator.js.map +1 -1
  71. package/lib/types/migrator-types.d.ts +22 -0
  72. package/lib/types/migrator-types.js.map +1 -1
  73. package/oclif.manifest.json +1 -1
  74. package/package.json +6 -6
@@ -0,0 +1,259 @@
1
+ import pendulum
2
+ import json
3
+
4
+
5
+ from swimlane.core.cursor import PaginatedCursor
6
+ from swimlane.core.fields.list import ListField
7
+ from swimlane.core.resources.base import APIResource
8
+ from swimlane.core.resources.record import Record, record_factory
9
+ from swimlane.core.search import CONTAINS, EQ, EXCLUDES, NOT_EQ, LT, GT, LTE, GTE, ASC, DESC
10
+ from swimlane.utils import validate_type
11
+
12
+ ALLOWED_OPERATORS = ['Or', 'And']
13
+
14
+
15
+ class Report(APIResource, PaginatedCursor):
16
+ """A report class used for searching
17
+
18
+ Can be iterated over to retrieve results
19
+
20
+ Notes:
21
+ Record retrieval is lazily evaluated and cached internally, adding a filter and attempting to iterate again will
22
+ not respect the additional filter and will return the same set of records each time
23
+
24
+ Examples:
25
+
26
+ Lazy retrieval of records with direct iteration over report
27
+
28
+ ::
29
+
30
+ report = app.reports.build('new-report')
31
+ report.filter('field_1', 'equals', 'value')
32
+
33
+ for record in report:
34
+ do_thing(record)
35
+
36
+ Full immediate retrieval of all records
37
+
38
+ ::
39
+
40
+ report = app.reports.build('new-report')
41
+ report.filter('field_1', 'doesNotEqual', 'value')
42
+
43
+ records = list(report)
44
+
45
+
46
+ Attributes:
47
+ name (str): Report name
48
+
49
+ Keyword Args:
50
+ limit (int): Max number of records to return from report/search
51
+ page_size (int): Max number of records per page
52
+ keywords (list(str)): List of keywords to use in report/search
53
+ """
54
+
55
+ _type = "Core.Models.Search.StatsReport, Core"
56
+
57
+ _FILTER_OPERANDS = (
58
+ EQ,
59
+ NOT_EQ,
60
+ CONTAINS,
61
+ EXCLUDES,
62
+ LT,
63
+ GT,
64
+ LTE,
65
+ GTE
66
+ )
67
+
68
+ _SORT_ORDERS = (
69
+ ASC,
70
+ DESC
71
+ )
72
+
73
+ default_limit = 50
74
+ default_page_start = None
75
+ default_page_end = None
76
+
77
+ def __init__(self, app, raw, **kwargs):
78
+ APIResource.__init__(self, app._swimlane, raw)
79
+ PaginatedCursor.__init__(self,
80
+ limit=kwargs.pop('limit', self.default_limit),
81
+ page_size=kwargs.pop('page_size', self.default_page_size),
82
+ page_start=kwargs.pop('page_start', self.default_page_start),
83
+ page_end=kwargs.pop('page_end', self.default_page_end)
84
+ )
85
+
86
+ self.name = self._raw['name']
87
+ self.keywords = kwargs.pop('keywords', [])
88
+
89
+ self._app = app
90
+
91
+ for field_id in self._app._fields_by_id.keys():
92
+ self._raw['columns'].append(field_id)
93
+
94
+ def filter_type(self, filter_type):
95
+ filter_type = filter_type.capitalize()
96
+ self.validateOperator(filter_type)
97
+ self.filter_type = filter_type
98
+
99
+ def validateOperator(self, operator):
100
+ if operator not in ALLOWED_OPERATORS:
101
+ raise ValueError('filter_type value not allowed')
102
+
103
+ def __str__(self):
104
+ return self.name
105
+
106
+ def _retrieve_raw_elements(self, page):
107
+ body = self._raw.copy()
108
+
109
+ body['pageSize'] = self.page_size
110
+ body['offset'] = page
111
+ if(type(self.filter_type) is str):
112
+ body['filterType'] = self.filter_type
113
+ body['keywords'] = ', '.join(self.keywords)
114
+
115
+ response = self._swimlane.request('post', 'search', json=body)
116
+ return response.json()['results'].get(self._app.id, [])
117
+
118
+ def _parse_raw_element(self, raw_element):
119
+ return Record(self._app, raw_element)
120
+
121
+ def filter(self, field_name, operand, value):
122
+ """Adds a filter to report
123
+
124
+ Notes:
125
+ 1. All filters are currently AND'ed together.
126
+ 2. None values work like a wildcard and will skip type verification.
127
+
128
+ Args:
129
+ field_name (str): Target field name to filter on
130
+ operand (str): Operand used in comparison. See `swimlane.core.search` for options
131
+ value: Target value used in comparison
132
+ """
133
+ if operand not in self._FILTER_OPERANDS:
134
+ raise ValueError('Operand must be one of {}'.format(', '.join(self._FILTER_OPERANDS)))
135
+
136
+ field = self._get_stub_field(field_name)
137
+
138
+ validate_type(field, value)
139
+
140
+ value = self.parse_field_value(field, value)
141
+
142
+ self._raw['filters'].append({
143
+ "fieldId": field.id,
144
+ "filterType": operand,
145
+ "value": field.get_report(value)
146
+ })
147
+
148
+ def sort(self, field_name, order):
149
+ """Adds a sort to report
150
+
151
+ Args:
152
+ field_name (str): Target field name to sort by
153
+ order (str): Sort order
154
+ """
155
+ if (order not in self._SORT_ORDERS):
156
+ raise ValueError('Order must be one of {}'.format(', '.join(self._SORT_ORDERS)))
157
+
158
+ field = self._get_stub_field(field_name)
159
+
160
+ self._raw['sorts'][field.id] = order
161
+
162
+ def set_columns(self, *field_names):
163
+ """Set specified columns for report
164
+
165
+ Notes:
166
+ The Tracking Id column is always included
167
+
168
+ Args:
169
+ *field_names (str): Zero or more column names
170
+ """
171
+ self._raw['columns'] = []
172
+ for field_name in field_names:
173
+ field = self._get_stub_field(field_name)
174
+
175
+ self._raw['columns'].append(field.id)
176
+
177
+ if self._app.tracking_id not in self._raw['columns']:
178
+ self._raw['columns'].append(self._app.tracking_id)
179
+
180
+ def _get_stub_field(self, field_name):
181
+ if not field_name or not isinstance(field_name, str):
182
+ raise ValueError('field_name is of an invalid format, expected non-empty string')
183
+
184
+ # Use temp Record instance for target app to translate values into expected API format
185
+ record_stub = record_factory(self._app)
186
+ return record_stub.get_field(field_name)
187
+
188
+ def parse_field_value(self, field, value):
189
+ if isinstance(field, ListField):
190
+ type = self.get_field_list_type(field.input_type)
191
+ value = self.get_default_value(value, field.input_type)
192
+ if isinstance(field, ListField) and not isinstance(value, list) and value is not None:
193
+ self.validate_type(value, type, field.input_type)
194
+ return [value]
195
+ elif isinstance(field, ListField) and isinstance(value, list) and any(not isinstance(elem, type) for elem in value):
196
+ raise TypeError('Field item must be a {}.'.format(field.input_type))
197
+ return value
198
+
199
+ def validate_type(self, value, type, type_name=None):
200
+ if(not type_name):
201
+ type_name = type
202
+ if not isinstance(value, type):
203
+ raise TypeError('Field must be a {}.'.format(type_name))
204
+
205
+ def get_default_value(self, value, field_type):
206
+ if(value == '' and field_type == 'text'):
207
+ value = None
208
+ return value
209
+
210
+ def get_field_list_type(self, field_type):
211
+ if field_type == 'text':
212
+ return str
213
+ elif field_type == 'numeric':
214
+ return (int, float)
215
+
216
+ def report_factory(app, report_name, **kwargs):
217
+ """Report instance factory populating boilerplate raw data
218
+
219
+ Args:
220
+ app (App): Swimlane App instance
221
+ report_name (str): Generated Report name
222
+
223
+ Keyword Args
224
+ **kwargs: Kwargs to pass to the Report class
225
+ """
226
+ # pylint: disable=protected-access
227
+ created = pendulum.now().to_rfc3339_string()
228
+ user_model = app._swimlane.user.as_usergroup_selection()
229
+
230
+ return Report(
231
+ app,
232
+ {
233
+ "$type": Report._type,
234
+ "groupBys": [],
235
+ "aggregates": [],
236
+ "applicationIds": [app.id],
237
+ "columns": [],
238
+ "sorts": {
239
+ "$type": "System.Collections.Generic.Dictionary`2"
240
+ "[[System.String, mscorlib],"
241
+ "[Core.Models.Search.SortTypes, Core]], mscorlib",
242
+ },
243
+ "filters": [],
244
+ "defaultSearchReport": False,
245
+ "allowed": [],
246
+ "permissions": {
247
+ "$type": "Core.Models.Security.PermissionMatrix, Core"
248
+ },
249
+ "createdDate": created,
250
+ "modifiedDate": created,
251
+ "createdByUser": user_model,
252
+ "modifiedByUser": user_model,
253
+ "id": None,
254
+ "name": report_name,
255
+ "disabled": False,
256
+ "keywords": ""
257
+ },
258
+ **kwargs
259
+ )
@@ -0,0 +1,69 @@
1
+ import pendulum
2
+
3
+ from swimlane.core.resources.base import APIResource
4
+ from swimlane.core.resources.usergroup import UserGroup
5
+
6
+
7
+ class RevisionBase(APIResource):
8
+ """
9
+ The base class representing a single revision returned from a History lookup.
10
+
11
+ Attributes:
12
+ Attributes:
13
+ modified_date: The date this revision was created.
14
+ revision_number: The revision number of this revision.
15
+ status: Indicates whether this revision is the current revision or a historical revision.
16
+ user: The user that saved this revision.
17
+ """
18
+
19
+ def __init__(self, swimlane, raw):
20
+ super(RevisionBase, self).__init__(swimlane, raw)
21
+
22
+ self._modified_date = pendulum.parse(self._raw['modifiedDate'])
23
+ self._revision_number = self._raw['revisionNumber']
24
+ self.status = self._raw['status']
25
+
26
+ # UserGroupSelection, can't set as User without additional lookup
27
+ self._user = UserGroup(self._swimlane, self._raw['userId'])
28
+
29
+ self._raw_version = self._raw['version']
30
+ self._version = None
31
+
32
+ def __str__(self):
33
+ return '{} ({})'.format(self.version, self.revision_number)
34
+
35
+ @property
36
+ def version(self):
37
+ raise NotImplementedError
38
+
39
+ def for_json(self):
40
+ """Return revision metadata"""
41
+ return {
42
+ 'modifiedDate': self._raw['modifiedDate'],
43
+ 'revisionNumber': self.revision_number,
44
+ 'user': self.user.for_json()
45
+ }
46
+
47
+ @property
48
+ def revision_number(self):
49
+ return self._revision_number
50
+
51
+ @revision_number.setter
52
+ def revision_number(self, value):
53
+ raise AttributeError("can't set attribute")
54
+
55
+ @property
56
+ def modified_date(self):
57
+ return self._modified_date
58
+
59
+ @modified_date.setter
60
+ def modified_date(self, value):
61
+ raise AttributeError("can't set attribute")
62
+
63
+ @property
64
+ def user(self):
65
+ return self._user
66
+
67
+ @user.setter
68
+ def user(self, value):
69
+ raise AttributeError("can't set attribute")
@@ -0,0 +1,16 @@
1
+ from swimlane.core.resources.base import APIResource
2
+
3
+
4
+ class Task(APIResource):
5
+ _type = 'Core.Models.Integrations.Task, Core'
6
+
7
+ def __init__(self, swimlane, raw):
8
+ super(Task, self).__init__(swimlane, raw)
9
+ self.app_id = raw.get('applicationId')
10
+ self.id = raw.get('id')
11
+ self.name = raw.get('name')
12
+ self.script = raw.get('action').get('script')
13
+
14
+ def __str__(self):
15
+ return '{self.name}'.format(self=self)
16
+
@@ -0,0 +1,166 @@
1
+ from functools import total_ordering
2
+
3
+ from swimlane.core.cursor import Cursor
4
+ from swimlane.core.resolver import SwimlaneResolver
5
+ from swimlane.core.resources.base import APIResource
6
+
7
+
8
+ # pylint: disable=abstract-method
9
+ @total_ordering
10
+ class UserGroup(APIResource):
11
+ """Base class for Users and Groups
12
+
13
+ Notes:
14
+ Returned in some places where determining whether object is a User or Group is not possible without additional
15
+ requests. Use appropriate adapter on `swimlane` client to retrieve more specific instance using `id` as needed
16
+
17
+ Can be compared to User or Group instances directly without ensuring the classes are the same
18
+
19
+ Attributes:
20
+ id (str): Full user/group ID
21
+ name (str): User/group name
22
+ """
23
+
24
+ def __init__(self, swimlane, raw):
25
+ super(UserGroup, self).__init__(swimlane, raw)
26
+
27
+ self.id = self._raw['id']
28
+ self.name = self._raw['name']
29
+
30
+ def __str__(self):
31
+ return self.name
32
+
33
+ def __hash__(self):
34
+ return hash((self.id, self.name))
35
+
36
+ def __eq__(self, other):
37
+ """Override to allow equality comparisons across UserGroup, User, and Group instances"""
38
+ return isinstance(other, UserGroup) and hash(self) == hash(other)
39
+
40
+ def __lt__(self, other):
41
+ if not isinstance(other, UserGroup):
42
+ raise TypeError('Comparisons not supported between instances of "{}" and "{}"'.format(
43
+ other.__class__.__name__,
44
+ self.__class__.__name__
45
+ ))
46
+
47
+ return self.name < other.name
48
+
49
+ def resolve(self):
50
+ """Retrieve and return correct User or Group instance from UserGroup
51
+
52
+ .. versionadded:: 2.16.1
53
+
54
+ Returns:
55
+ User | Group: Resolved User or Group instance
56
+ """
57
+ # Skip resolving if not a generic instance
58
+ if self.__class__ is not UserGroup:
59
+ return self
60
+
61
+ else:
62
+ try:
63
+ return self._swimlane.users.get(id=self.id)
64
+ except ValueError:
65
+ return self._swimlane.groups.get(id=self.id)
66
+
67
+ def as_usergroup_selection(self):
68
+ """Converts UserGroup to raw UserGroupSelection for populating record
69
+
70
+ Returns:
71
+ dict: Formatted UserGroup data as used by selection fields
72
+ """
73
+ return {
74
+ '$type': 'Core.Models.Utilities.UserGroupSelection, Core',
75
+ 'id': self.id,
76
+ 'name': self.name
77
+ }
78
+
79
+ def for_json(self):
80
+ """Get JSON-compatible representation"""
81
+ return {
82
+ 'id': self.id,
83
+ 'name': self.name
84
+ }
85
+
86
+
87
+ class Group(UserGroup):
88
+ """Swimlane group record
89
+
90
+ Attributes:
91
+ description (str): Group description
92
+ users (GroupUsersCursor): List of users belonging to group.
93
+ """
94
+
95
+ _type = 'Core.Models.Groups.Group, Core'
96
+
97
+ def __init__(self, swimlane, raw):
98
+ super(Group, self).__init__(swimlane, raw)
99
+ self.__user_ids = [item['id'] for item in self._raw.get('users')]
100
+ self.description = self._raw.get('description')
101
+ self.__users = None
102
+
103
+ @property
104
+ def users(self):
105
+ """Returns a GroupUsersCursor with list of User instances for this Group
106
+
107
+ .. versionadded:: 2.16.2
108
+ """
109
+ if self.__users is None:
110
+ self.__users = GroupUsersCursor(swimlane=self._swimlane, user_ids=self.__user_ids)
111
+ return self.__users
112
+
113
+ def get_cache_index_keys(self):
114
+ return {
115
+ 'id': self.id,
116
+ 'name': self.name
117
+ }
118
+
119
+
120
+ class User(UserGroup):
121
+ """Swimlane user record
122
+
123
+ Attributes:
124
+ username (str): Unique username
125
+ display_name (str): User display name
126
+ email (str): User email
127
+ """
128
+
129
+ _type = 'Core.Models.Identity.ApplicationUser, Core'
130
+
131
+ def __init__(self, swimlane, raw):
132
+ super(User, self).__init__(swimlane, raw)
133
+
134
+ self.username = self._raw.get('userName')
135
+ self.display_name = self._raw.get('displayName')
136
+ self.email = self._raw.get('email')
137
+
138
+ def get_cache_index_keys(self):
139
+ return {
140
+ 'id': self.id,
141
+ 'username': self.username,
142
+ 'display_name': self.display_name
143
+ }
144
+
145
+
146
+ class GroupUsersCursor(SwimlaneResolver, Cursor):
147
+ """Handles retrieval for user endpoint"""
148
+
149
+ def __init__(self, swimlane, user_ids):
150
+ SwimlaneResolver.__init__(self, swimlane)
151
+ Cursor.__init__(self)
152
+ self.__user_ids = user_ids
153
+
154
+ def _evaluate(self):
155
+ """Lazily retrieve and build User instances from returned data"""
156
+ if self._elements:
157
+ for element in self._elements:
158
+ yield element
159
+ else:
160
+ for user_id in self.__user_ids:
161
+ try:
162
+ element = self._swimlane.users.get(id=user_id)
163
+ self._elements.append(element)
164
+ yield element
165
+ except StopIteration:
166
+ return
@@ -0,0 +1,31 @@
1
+ """Provides handful of constants used in record reports/searches"""
2
+
3
+ # Filter
4
+ EQ = "equals"
5
+ NOT_EQ = "doesNotEqual"
6
+ CONTAINS = "contains"
7
+ EXCLUDES = "excludes"
8
+ GT = "greaterThan"
9
+ LT = "lessThan"
10
+ LTE = "lessThanOrEqual"
11
+ GTE = "greaterThanOrEqual"
12
+
13
+ # Aggregate
14
+ AVG = "average"
15
+ COUNT = "count"
16
+ SUM = "sum"
17
+ MIN = "min"
18
+ MAX = "max"
19
+
20
+ # GroupBy
21
+ GB = "groupBy"
22
+ HOUR = "groupByHour"
23
+ DAY = "groupByDay"
24
+ WEEK = "groupByWeek"
25
+ MONTH = "groupByMonth"
26
+ QUARTER = "groupByQuarter"
27
+ YEAR = "groupByYear"
28
+
29
+ # Sorts
30
+ ASC = "ascending"
31
+ DESC = "descending"
@@ -0,0 +1,12 @@
1
+ import requests
2
+
3
+ class WrappedSession(requests.Session):
4
+ """A wrapper for requests.Session to override 'verify' property, ignoring REQUESTS_CA_BUNDLE environment variable.
5
+
6
+ This is a workaround for https://github.com/kennethreitz/requests/issues/3829 (will be fixed in requests 3.0.0)
7
+ """
8
+ def merge_environment_settings(self, url, proxies, stream, verify, *args, **kwargs):
9
+ if self.verify is False:
10
+ verify = False
11
+
12
+ return super(WrappedSession, self).merge_environment_settings(url, proxies, stream, verify, *args, **kwargs)