@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,499 @@
|
|
|
1
|
+
import copy
|
|
2
|
+
from functools import total_ordering
|
|
3
|
+
import time
|
|
4
|
+
import pendulum
|
|
5
|
+
import six
|
|
6
|
+
from swimlane.core.resources.base import APIResource
|
|
7
|
+
from swimlane.core.resources.usergroup import UserGroup, User
|
|
8
|
+
from swimlane.exceptions import SwimlaneException, UnknownField, ValidationError
|
|
9
|
+
import swimlane.core.adapters.task # avoid circular reference
|
|
10
|
+
import swimlane.core.adapters.helper # avoid circular reference
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@total_ordering
|
|
15
|
+
class Record(APIResource):
|
|
16
|
+
"""A single Swimlane Record instance
|
|
17
|
+
|
|
18
|
+
Attributes:
|
|
19
|
+
id (str): Full Record ID
|
|
20
|
+
tracking_id (str): Record tracking ID
|
|
21
|
+
created (pendulum.DateTime): Pendulum datetime for Record created date
|
|
22
|
+
modified (pendulum.DateTime): Pendulum datetime for Record last modified date
|
|
23
|
+
is_new (bool): True if Record does not yet exist on server. Other values may be temporarily None if True
|
|
24
|
+
app (App): App instance that Record belongs to
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
_type = 'Core.Models.Record.Record, Core'
|
|
28
|
+
|
|
29
|
+
def __init__(self, app, raw, is_new=None):
|
|
30
|
+
super(Record, self).__init__(app._swimlane, raw)
|
|
31
|
+
|
|
32
|
+
self.__app = app
|
|
33
|
+
|
|
34
|
+
self.is_new = self._raw.get('isNew', False)
|
|
35
|
+
if is_new is not None:
|
|
36
|
+
self.is_new = is_new
|
|
37
|
+
# Protect against creation from generic raw data not yet containing server-generated values
|
|
38
|
+
if self.is_new:
|
|
39
|
+
self.id = self.tracking_id = self.created = self.modified = None
|
|
40
|
+
else:
|
|
41
|
+
record_app_id = raw['applicationId']
|
|
42
|
+
if record_app_id != app.id:
|
|
43
|
+
raise ValueError('Record applicationId "{}" does not match source app id "{}"'.format(
|
|
44
|
+
record_app_id,
|
|
45
|
+
app.id
|
|
46
|
+
))
|
|
47
|
+
|
|
48
|
+
self.id = self._raw['id']
|
|
49
|
+
|
|
50
|
+
# Combine app acronym + trackingId instead of using trackingFull raw
|
|
51
|
+
# for guaranteed value (not available through report results)
|
|
52
|
+
self.tracking_id = '-'.join([
|
|
53
|
+
self.app.acronym,
|
|
54
|
+
str(int(self._raw['trackingId']))
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
self.created = pendulum.parse(self._raw['createdDate'])
|
|
58
|
+
self.modified = pendulum.parse(self._raw['modifiedDate'])
|
|
59
|
+
|
|
60
|
+
self.__allowed = []
|
|
61
|
+
|
|
62
|
+
self._fields = {}
|
|
63
|
+
self.__premap_fields()
|
|
64
|
+
|
|
65
|
+
# Get trackingFull if available
|
|
66
|
+
if app.tracking_id in self._raw['values']:
|
|
67
|
+
self._raw['trackingFull'] = self._raw['values'].get(app.tracking_id)
|
|
68
|
+
|
|
69
|
+
self.__existing_values = {k: self.get_field(k).get_batch_representation() for (k, v) in self}
|
|
70
|
+
self._comments_modified = False
|
|
71
|
+
|
|
72
|
+
self.locked = False
|
|
73
|
+
self.locking_user = None
|
|
74
|
+
self.locked_date = None
|
|
75
|
+
|
|
76
|
+
# avoid circular reference
|
|
77
|
+
from swimlane.core.adapters import RecordRevisionAdapter
|
|
78
|
+
self.revisions = RecordRevisionAdapter(app, self)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def app(self):
|
|
82
|
+
return self.__app
|
|
83
|
+
|
|
84
|
+
def __str__(self):
|
|
85
|
+
if self.is_new:
|
|
86
|
+
return '{} - New'.format(self.app.acronym)
|
|
87
|
+
|
|
88
|
+
return str(self.tracking_id)
|
|
89
|
+
|
|
90
|
+
def __setitem__(self, field_name, value):
|
|
91
|
+
keys = dir(value)
|
|
92
|
+
if '_elements' in keys:
|
|
93
|
+
value = value._elements
|
|
94
|
+
self.get_field(field_name).set_python(value)
|
|
95
|
+
|
|
96
|
+
def __getitem__(self, field_name):
|
|
97
|
+
return self.get_field(field_name).get_item()
|
|
98
|
+
|
|
99
|
+
def __delitem__(self, field_name):
|
|
100
|
+
self[field_name] = None
|
|
101
|
+
|
|
102
|
+
def __iter__(self):
|
|
103
|
+
for field_name, field in six.iteritems(self._fields):
|
|
104
|
+
yield field_name, field.get_python()
|
|
105
|
+
|
|
106
|
+
def __hash__(self):
|
|
107
|
+
return hash((self.id, self.app))
|
|
108
|
+
|
|
109
|
+
def __lt__(self, other):
|
|
110
|
+
if not isinstance(other, self.__class__):
|
|
111
|
+
raise TypeError('Comparisons not supported between instances of "{}" and "{}"'.format(
|
|
112
|
+
other.__class__.__name__,
|
|
113
|
+
self.__class__.__name__
|
|
114
|
+
))
|
|
115
|
+
|
|
116
|
+
tracking_number_self = int(self.tracking_id.split('-')[1])
|
|
117
|
+
tracking_number_other = int(other.tracking_id.split('-')[1])
|
|
118
|
+
|
|
119
|
+
return (self.app.name, tracking_number_self) < (other.app.name, tracking_number_other)
|
|
120
|
+
|
|
121
|
+
def __premap_fields(self):
|
|
122
|
+
"""Build field instances using field definitions in app manifest
|
|
123
|
+
|
|
124
|
+
Map raw record field data into appropriate field instances with their correct respective types
|
|
125
|
+
"""
|
|
126
|
+
# Circular imports
|
|
127
|
+
from swimlane.core.fields import resolve_field_class
|
|
128
|
+
|
|
129
|
+
for field_definition in self.app._raw['fields']:
|
|
130
|
+
field_class = resolve_field_class(field_definition)
|
|
131
|
+
|
|
132
|
+
field_instance = field_class(field_definition['name'], self)
|
|
133
|
+
value = self._raw['values'].get(field_instance.id)
|
|
134
|
+
field_instance.set_swimlane(value)
|
|
135
|
+
|
|
136
|
+
self._fields[field_instance.name] = field_instance
|
|
137
|
+
|
|
138
|
+
def get_cache_index_keys(self):
|
|
139
|
+
"""Return values available for retrieving records, but only for already existing records"""
|
|
140
|
+
if not (self.id and self.tracking_id):
|
|
141
|
+
raise NotImplementedError
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
'id': self.id,
|
|
145
|
+
'tracking_id': self.tracking_id
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
def get_field(self, field_name):
|
|
149
|
+
"""Get field instance used to get, set, and serialize internal field value
|
|
150
|
+
|
|
151
|
+
Args:
|
|
152
|
+
field_name (str): Field name or key to retrieve
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Field: Requested field instance
|
|
156
|
+
|
|
157
|
+
Raises:
|
|
158
|
+
UnknownField: Raised if `field_name` not found in parent App
|
|
159
|
+
"""
|
|
160
|
+
try:
|
|
161
|
+
return self._fields[self.app.resolve_field_name(field_name)]
|
|
162
|
+
except KeyError:
|
|
163
|
+
raise UnknownField(self.app, field_name, self._fields.keys())
|
|
164
|
+
|
|
165
|
+
def validate(self):
|
|
166
|
+
"""Explicitly validate field data
|
|
167
|
+
|
|
168
|
+
Notes:
|
|
169
|
+
Called automatically during save call before sending data to server
|
|
170
|
+
|
|
171
|
+
Raises:
|
|
172
|
+
ValidationError: If any fields fail validation
|
|
173
|
+
"""
|
|
174
|
+
for field in (_field for _field in six.itervalues(self._fields) if _field.required):
|
|
175
|
+
if field.get_swimlane() is None:
|
|
176
|
+
raise ValidationError(
|
|
177
|
+
self, 'Required field "{}" is not set'.format(field.name))
|
|
178
|
+
|
|
179
|
+
def __request_and_reinitialize(self, method, endpoint, data):
|
|
180
|
+
response = self._swimlane.request(
|
|
181
|
+
method,
|
|
182
|
+
endpoint,
|
|
183
|
+
json=data
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
# Reinitialize record with new raw content returned from server to update any calculated fields
|
|
187
|
+
self.__init__(self.app, response.json(), is_new=False)
|
|
188
|
+
|
|
189
|
+
# Manually cache self after save to keep cache updated with latest data
|
|
190
|
+
self._swimlane.resources_cache.cache(self)
|
|
191
|
+
|
|
192
|
+
def save(self):
|
|
193
|
+
"""Persist record changes on Swimlane server
|
|
194
|
+
|
|
195
|
+
Updates internal raw data with response content from server to guarantee calculated field values match values on
|
|
196
|
+
server
|
|
197
|
+
|
|
198
|
+
Raises:
|
|
199
|
+
ValidationError: If any fields fail validation
|
|
200
|
+
"""
|
|
201
|
+
|
|
202
|
+
if self.is_new:
|
|
203
|
+
method = 'post'
|
|
204
|
+
else:
|
|
205
|
+
method = 'put'
|
|
206
|
+
|
|
207
|
+
# Pop off fields with None value to allow for saving empty fields
|
|
208
|
+
copy_raw = copy.copy(self._raw)
|
|
209
|
+
values_dict = {}
|
|
210
|
+
for key, value in six.iteritems(copy_raw['values']):
|
|
211
|
+
if value is not None:
|
|
212
|
+
values_dict[key] = value
|
|
213
|
+
copy_raw['values'] = values_dict
|
|
214
|
+
|
|
215
|
+
self.validate()
|
|
216
|
+
|
|
217
|
+
self.__request_and_reinitialize(
|
|
218
|
+
method,
|
|
219
|
+
'app/{}/record'.format(self.app.id),
|
|
220
|
+
copy_raw
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
def patch(self):
|
|
224
|
+
"""Patch record on Swimlane server
|
|
225
|
+
|
|
226
|
+
Raises
|
|
227
|
+
ValueError: If record.is_new, or if comments or attachments are attempted to be patched
|
|
228
|
+
"""
|
|
229
|
+
if self.is_new:
|
|
230
|
+
raise ValueError('Cannot patch a new Record')
|
|
231
|
+
elif self._comments_modified:
|
|
232
|
+
raise ValueError('Can not patch with added comments')
|
|
233
|
+
|
|
234
|
+
copy_raw = copy.copy(self._raw)
|
|
235
|
+
|
|
236
|
+
pending_values = {k: self.get_field(k).get_batch_representation() for (k, v) in self}
|
|
237
|
+
patch_values = {
|
|
238
|
+
self.get_field(k).id: pending_values[k] for k in set(pending_values) & set(self.__existing_values)
|
|
239
|
+
if pending_values[k] != self.__existing_values[k]
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
for field_id, value in six.iteritems(patch_values):
|
|
243
|
+
#
|
|
244
|
+
if self.app.get_field_definition_by_id(field_id)['fieldType'] == 'attachment':
|
|
245
|
+
raise ValueError('Can not patch new attachments')
|
|
246
|
+
# Use None for empty arrays to ensure field is removed from Record on PATCH
|
|
247
|
+
if not value and value != 0:
|
|
248
|
+
patch_values[field_id] = None
|
|
249
|
+
|
|
250
|
+
# $type needed here for dotnet to deserialize correctly
|
|
251
|
+
patch_values['$type'] = self._raw['values']['$type']
|
|
252
|
+
copy_raw['values'] = patch_values
|
|
253
|
+
|
|
254
|
+
self.validate()
|
|
255
|
+
|
|
256
|
+
self.__request_and_reinitialize(
|
|
257
|
+
'patch',
|
|
258
|
+
'app/{}/record/{}'.format(self.app.id, self.id),
|
|
259
|
+
copy_raw
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
def delete(self):
|
|
263
|
+
"""Delete record from Swimlane server
|
|
264
|
+
|
|
265
|
+
.. versionadded:: 2.16.1
|
|
266
|
+
|
|
267
|
+
Resets to new state, but leaves field data as-is. Saving a deleted record will create a new Swimlane record
|
|
268
|
+
|
|
269
|
+
Raises
|
|
270
|
+
ValueError: If record.is_new
|
|
271
|
+
"""
|
|
272
|
+
if self.is_new:
|
|
273
|
+
raise ValueError('Cannot delete a new Record')
|
|
274
|
+
|
|
275
|
+
self._swimlane.request(
|
|
276
|
+
'delete',
|
|
277
|
+
'app/{}/record/{}'.format(self.app.id, self.id)
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
del self._swimlane.resources_cache[self]
|
|
281
|
+
|
|
282
|
+
# Modify current raw values indicating an unsaved record but persisting field data
|
|
283
|
+
raw = copy.deepcopy(self._raw)
|
|
284
|
+
raw['id'] = None
|
|
285
|
+
raw['isNew'] = True
|
|
286
|
+
|
|
287
|
+
self.__init__(self.app, raw)
|
|
288
|
+
|
|
289
|
+
def for_json(self, *field_names):
|
|
290
|
+
"""Returns json.dump()-compatible dict representation of the record
|
|
291
|
+
|
|
292
|
+
.. versionadded:: 4.1
|
|
293
|
+
|
|
294
|
+
Useful for resolving any Cursor, datetime/Pendulum, etc. field values to useful formats outside of Python
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
*field_names (str): Optional subset of field(s) to include in returned dict. Defaults to all fields
|
|
298
|
+
|
|
299
|
+
Raises:
|
|
300
|
+
UnknownField: Raised if any of `field_names` not found in parent App
|
|
301
|
+
|
|
302
|
+
Returns:
|
|
303
|
+
dict: field names -> JSON compatible field values
|
|
304
|
+
"""
|
|
305
|
+
field_names = field_names or self._fields.keys()
|
|
306
|
+
|
|
307
|
+
return {field_name: self.get_field(field_name).for_json() for field_name in field_names}
|
|
308
|
+
|
|
309
|
+
@property
|
|
310
|
+
def restrictions(self):
|
|
311
|
+
"""Returns cached set of retrieved UserGroups in the record's list of allowed accounts"""
|
|
312
|
+
return [UserGroup(self._swimlane, raw) for raw in self._raw['allowed']]
|
|
313
|
+
|
|
314
|
+
def add_restriction(self, *usergroups):
|
|
315
|
+
"""Add UserGroup(s) to list of accounts with access to record
|
|
316
|
+
|
|
317
|
+
.. versionadded:: 2.16.1
|
|
318
|
+
|
|
319
|
+
UserGroups already in the restricted list can be added multiple times and duplicates will be ignored
|
|
320
|
+
|
|
321
|
+
Notes:
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
*usergroups (UserGroup): 1 or more Swimlane UserGroup(s) to add to restriction list
|
|
325
|
+
|
|
326
|
+
Raises:
|
|
327
|
+
TypeError: If 0 UserGroups provided or provided a non-UserGroup instance
|
|
328
|
+
"""
|
|
329
|
+
if not usergroups:
|
|
330
|
+
raise TypeError(
|
|
331
|
+
'Must provide at least one UserGroup for restriction')
|
|
332
|
+
|
|
333
|
+
allowed = copy.copy(self._raw.get('allowed', []))
|
|
334
|
+
|
|
335
|
+
for usergroup in usergroups:
|
|
336
|
+
if not isinstance(usergroup, UserGroup):
|
|
337
|
+
raise TypeError(
|
|
338
|
+
'Expected UserGroup, received "{}" instead'.format(usergroup))
|
|
339
|
+
|
|
340
|
+
selection = usergroup.as_usergroup_selection()
|
|
341
|
+
if selection not in allowed:
|
|
342
|
+
allowed.append(selection)
|
|
343
|
+
|
|
344
|
+
self.validate()
|
|
345
|
+
self._swimlane.request(
|
|
346
|
+
'put',
|
|
347
|
+
'app/{}/record/{}/restrict'.format(self.app.id, self.id),
|
|
348
|
+
json=allowed
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
self._raw['allowed'] = allowed
|
|
352
|
+
|
|
353
|
+
def remove_restriction(self, *usergroups):
|
|
354
|
+
"""Remove UserGroup(s) from list of accounts with access to record
|
|
355
|
+
|
|
356
|
+
.. versionadded:: 2.16.1
|
|
357
|
+
|
|
358
|
+
Notes:
|
|
359
|
+
|
|
360
|
+
Warnings:
|
|
361
|
+
Providing no UserGroups will clear the restriction list, opening access to ALL accounts
|
|
362
|
+
|
|
363
|
+
Args:
|
|
364
|
+
*usergroups (UserGroup): 0 or more Swimlane UserGroup(s) to remove from restriction list
|
|
365
|
+
|
|
366
|
+
Raises:
|
|
367
|
+
TypeError: If provided a non-UserGroup instance
|
|
368
|
+
ValueError: If provided UserGroup not in current restriction list
|
|
369
|
+
"""
|
|
370
|
+
if usergroups:
|
|
371
|
+
allowed = copy.copy(self._raw.get('allowed', []))
|
|
372
|
+
|
|
373
|
+
for usergroup in usergroups:
|
|
374
|
+
if not isinstance(usergroup, UserGroup):
|
|
375
|
+
raise TypeError(
|
|
376
|
+
'Expected UserGroup, received "{}" instead'.format(usergroup))
|
|
377
|
+
try:
|
|
378
|
+
allowed.remove(usergroup.as_usergroup_selection())
|
|
379
|
+
except ValueError:
|
|
380
|
+
raise ValueError(
|
|
381
|
+
'UserGroup "{}" not in record "{}" restriction list'.format(usergroup, self))
|
|
382
|
+
else:
|
|
383
|
+
allowed = []
|
|
384
|
+
|
|
385
|
+
self.validate()
|
|
386
|
+
self._swimlane.request(
|
|
387
|
+
'put',
|
|
388
|
+
'app/{}/record/{}/restrict'.format(self.app.id, self.id),
|
|
389
|
+
json=allowed
|
|
390
|
+
)
|
|
391
|
+
|
|
392
|
+
self._raw['allowed'] = allowed
|
|
393
|
+
|
|
394
|
+
def lock(self):
|
|
395
|
+
"""
|
|
396
|
+
Lock the record to the Current User.
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
Notes:
|
|
400
|
+
|
|
401
|
+
Warnings:
|
|
402
|
+
|
|
403
|
+
Args:
|
|
404
|
+
|
|
405
|
+
"""
|
|
406
|
+
self.validate()
|
|
407
|
+
response = self._swimlane.request(
|
|
408
|
+
'post',
|
|
409
|
+
'app/{}/record/{}/lock'.format(
|
|
410
|
+
self.app.id, self.id)
|
|
411
|
+
).json()
|
|
412
|
+
self.locked = True
|
|
413
|
+
self.locking_user = User(self._swimlane, response['lockingUser'])
|
|
414
|
+
self.locked_date = response['lockedDate']
|
|
415
|
+
|
|
416
|
+
def unlock(self):
|
|
417
|
+
"""
|
|
418
|
+
Unlock the record.
|
|
419
|
+
|
|
420
|
+
|
|
421
|
+
Notes:
|
|
422
|
+
|
|
423
|
+
Warnings:
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
|
|
427
|
+
"""
|
|
428
|
+
self.validate()
|
|
429
|
+
self._swimlane.request(
|
|
430
|
+
'post',
|
|
431
|
+
'app/{}/record/{}/unlock'.format(
|
|
432
|
+
self.app.id, self.id)
|
|
433
|
+
).json()
|
|
434
|
+
self.locked = False
|
|
435
|
+
self.locking_user = None
|
|
436
|
+
self.locked_date = None
|
|
437
|
+
|
|
438
|
+
def execute_task(self, task_name, timeout=int(20)):
|
|
439
|
+
job_info = swimlane.core.adapters.task.TaskAdapter(self.app._swimlane).execute(task_name, self._raw)
|
|
440
|
+
'''timeout_start = pendulum.now()
|
|
441
|
+
while pendulum.now() < timeout_start.add(seconds=timeout):
|
|
442
|
+
status = self.app._swimlane.helpers.check_bulk_job_status(job_info.text)
|
|
443
|
+
if len(status):
|
|
444
|
+
for item in status:
|
|
445
|
+
if item.get('status') == 'completed':
|
|
446
|
+
self.__request_and_reinitialize(
|
|
447
|
+
'get', '/app/{appId}/record/{id}'.format(appId=self.app.id, id=self.id), None)
|
|
448
|
+
timeout = 0
|
|
449
|
+
if item.get('status') == 'failed':
|
|
450
|
+
raise SwimlaneException('Task failed: {}'.format(item.get('message')))
|
|
451
|
+
time.sleep(1)
|
|
452
|
+
'''
|
|
453
|
+
'''if self._swimlane._execute_task_webhook_url:
|
|
454
|
+
self._swimlane._session.post(self._swimlane._execute_task_webhook_url, json={'task_name': task_name, 'record': self.for_json()})
|
|
455
|
+
else:
|
|
456
|
+
raise SwimlaneException('Task webhook URL is not set')'''
|
|
457
|
+
|
|
458
|
+
def record_factory(app, fields=None):
|
|
459
|
+
"""Return a temporary Record instance to be used for field validation and value parsing
|
|
460
|
+
|
|
461
|
+
Args:
|
|
462
|
+
app (App): Target App to create a transient Record instance for
|
|
463
|
+
fields (dict): Optional dict of fields and values to set on new Record instance before returning
|
|
464
|
+
|
|
465
|
+
Returns:
|
|
466
|
+
Record: Unsaved Record instance to be used for validation, creation, etc.
|
|
467
|
+
"""
|
|
468
|
+
# pylint: disable=line-too-long
|
|
469
|
+
record = Record(app, {
|
|
470
|
+
'$type': Record._type,
|
|
471
|
+
'isNew': True,
|
|
472
|
+
'applicationId': app.id,
|
|
473
|
+
'comments': {
|
|
474
|
+
'$type': 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.List`1[[Core.Models.Record.Comments, Core]], mscorlib]], mscorlib'
|
|
475
|
+
},
|
|
476
|
+
'values': {
|
|
477
|
+
'$type': 'System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]], mscorlib'
|
|
478
|
+
}
|
|
479
|
+
})
|
|
480
|
+
|
|
481
|
+
fields = fields or {}
|
|
482
|
+
|
|
483
|
+
# Apply Default Values
|
|
484
|
+
for name, value in six.iteritems(app._defaults):
|
|
485
|
+
record[name] = value
|
|
486
|
+
|
|
487
|
+
# Apply Provided Field Values
|
|
488
|
+
for name, value in six.iteritems(fields):
|
|
489
|
+
record[name] = value
|
|
490
|
+
|
|
491
|
+
# Pop off fields with None value to allow for saving empty fields
|
|
492
|
+
copy_raw = copy.copy(record._raw)
|
|
493
|
+
values_dict = {}
|
|
494
|
+
for key, value in six.iteritems(copy_raw['values']):
|
|
495
|
+
if value is not None:
|
|
496
|
+
values_dict[key] = value
|
|
497
|
+
record._raw['values'] = values_dict
|
|
498
|
+
|
|
499
|
+
return record
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from swimlane.core.resources.record import Record
|
|
2
|
+
from swimlane.core.resources.revision_base import RevisionBase
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class RecordRevision(RevisionBase):
|
|
6
|
+
"""
|
|
7
|
+
Encapsulates a single revision returned from a History lookup.
|
|
8
|
+
|
|
9
|
+
Attributes:
|
|
10
|
+
app_revision_number: The app revision number this record revision was created using.
|
|
11
|
+
|
|
12
|
+
Properties:
|
|
13
|
+
app_version: Returns an App corresponding to the app_revision_number of this record revision.
|
|
14
|
+
version: Returns a Record corresponding to the app_version and data contained in this record revision.
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, app, raw):
|
|
17
|
+
super(RecordRevision, self).__init__(app._swimlane, raw)
|
|
18
|
+
|
|
19
|
+
self.__app_version = None
|
|
20
|
+
self._app = app
|
|
21
|
+
|
|
22
|
+
self._app_revision_number = self._raw_version['applicationRevision']
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
def app_version(self):
|
|
26
|
+
"""The app revision corresponding to this record revision. Lazy loaded"""
|
|
27
|
+
if not self.__app_version:
|
|
28
|
+
self.__app_version = self._app.revisions.get(self.app_revision_number).version
|
|
29
|
+
return self.__app_version
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def version(self):
|
|
33
|
+
"""The record contained in this record revision. Lazy loaded. Overridden from base class."""
|
|
34
|
+
if not self._version:
|
|
35
|
+
self._version = Record(self.app_version, self._raw_version)
|
|
36
|
+
return self._version
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def app_revision_number(self):
|
|
40
|
+
return self._app_revision_number
|
|
41
|
+
|
|
42
|
+
@app_revision_number.setter
|
|
43
|
+
def app_revision_number(self, value):
|
|
44
|
+
raise AttributeError("can't set attribute")
|