@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,468 @@
|
|
|
1
|
+
import six
|
|
2
|
+
|
|
3
|
+
from swimlane.core.bulk import Replace, _BulkModificationOperation
|
|
4
|
+
from swimlane.core.cache import check_cache
|
|
5
|
+
from swimlane.core.resolver import AppResolver
|
|
6
|
+
from swimlane.core.resources.record import Record, record_factory
|
|
7
|
+
from swimlane.core.resources.report import Report
|
|
8
|
+
from swimlane.utils import random_string, one_of_keyword_only, validate_type
|
|
9
|
+
from swimlane.utils.version import requires_swimlane_version
|
|
10
|
+
|
|
11
|
+
class RecordAdapter(AppResolver):
|
|
12
|
+
"""Handles retrieval and creation of Swimlane Record resources"""
|
|
13
|
+
|
|
14
|
+
@check_cache(Record)
|
|
15
|
+
@one_of_keyword_only('id', 'tracking_id')
|
|
16
|
+
def get(self, key, value):
|
|
17
|
+
"""Get a single record by id
|
|
18
|
+
|
|
19
|
+
Supports resource cache
|
|
20
|
+
|
|
21
|
+
.. versionchanged:: 2.17.0
|
|
22
|
+
Added option to retrieve record by tracking_id
|
|
23
|
+
|
|
24
|
+
Keyword Args:
|
|
25
|
+
id (str): Full record ID
|
|
26
|
+
tracking_id (str): Record Tracking ID
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
Record: Matching Record instance returned from API
|
|
30
|
+
|
|
31
|
+
Raises:
|
|
32
|
+
TypeError: No id argument provided
|
|
33
|
+
ValueError: The lookup value is empty or None
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if not value:
|
|
37
|
+
raise ValueError('The value provided for the key "{0}" cannot be empty or None'.format(key))
|
|
38
|
+
|
|
39
|
+
if key == 'id':
|
|
40
|
+
response = self._swimlane.request('get', "app/{0}/record/{1}".format(self._app.id, value))
|
|
41
|
+
return Record(self._app, response.json())
|
|
42
|
+
if key == 'tracking_id':
|
|
43
|
+
response = self._swimlane.request('get', "app/{0}/record/tracking/{1}".format(self._app.id, value))
|
|
44
|
+
return Record(self._app, response.json())
|
|
45
|
+
|
|
46
|
+
def search(self, *filters, filter_type = 'And', **kwargs):
|
|
47
|
+
"""Shortcut to generate a new temporary search report using provided filters and return the resulting records
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
*filters (tuple): Zero or more filter tuples of (field_name, operator, field_value)
|
|
51
|
+
|
|
52
|
+
Keyword Args:
|
|
53
|
+
keywords (list(str)): List of strings of keywords to use in report search
|
|
54
|
+
limit (int): Set maximum number of returned Records, defaults to `Report.default_limit`. Set to 0 to return
|
|
55
|
+
all records
|
|
56
|
+
page_size: Set maximum number of returned Records per page, defaults to 1000.
|
|
57
|
+
Set to 0 to return all records
|
|
58
|
+
sort: Tuple of (field_name, order) by which results will be sorted
|
|
59
|
+
columns (list(str)): List of strings of field names to populate in the resulting records. Defaults to all
|
|
60
|
+
available fields
|
|
61
|
+
|
|
62
|
+
Notes:
|
|
63
|
+
Uses a temporary Report instance with a random name to facilitate search. Records are normally paginated,
|
|
64
|
+
but are returned as a single list here, potentially causing performance issues with large searches.
|
|
65
|
+
|
|
66
|
+
All provided filters are AND'ed together
|
|
67
|
+
|
|
68
|
+
Filter operators and sort orders are available as constants in `swimlane.core.search`
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
|
|
72
|
+
::
|
|
73
|
+
|
|
74
|
+
# Return records matching all filters with default limit and page size
|
|
75
|
+
|
|
76
|
+
from swimlane.core import search
|
|
77
|
+
|
|
78
|
+
records = app.records.search(
|
|
79
|
+
('field_name', 'equals', 'field_value'),
|
|
80
|
+
('other_field', search.NOT_EQ, 'value')
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
::
|
|
84
|
+
|
|
85
|
+
# Run keyword search with multiple keywords
|
|
86
|
+
records = app.records.search(keywords=['example', 'test'])
|
|
87
|
+
|
|
88
|
+
::
|
|
89
|
+
|
|
90
|
+
# Return all records from app
|
|
91
|
+
records = app.records.search(limit=0)
|
|
92
|
+
|
|
93
|
+
::
|
|
94
|
+
|
|
95
|
+
# Populate only the specified field and sort results
|
|
96
|
+
records = app.records.search(columns=['field_name'], sort=('field_name', 'ascending'))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
:class:`list` of :class:`~swimlane.core.resources.record.Record`: List of Record instances returned from the
|
|
101
|
+
search results
|
|
102
|
+
"""
|
|
103
|
+
report = self._app.reports.build(
|
|
104
|
+
'search-' + random_string(8),
|
|
105
|
+
keywords=kwargs.pop('keywords', []),
|
|
106
|
+
limit=kwargs.pop('limit', Report.default_limit),
|
|
107
|
+
page_size=kwargs.pop('page_size', 1000),
|
|
108
|
+
page_start=kwargs.pop('page_start', None),
|
|
109
|
+
page_end=kwargs.pop('page_end', None)
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
for filter_tuples in filters:
|
|
113
|
+
report.filter(*filter_tuples)
|
|
114
|
+
|
|
115
|
+
sort_tuple = kwargs.pop('sort', None)
|
|
116
|
+
if sort_tuple:
|
|
117
|
+
report.sort(*sort_tuple)
|
|
118
|
+
|
|
119
|
+
columns = kwargs.pop('columns', None)
|
|
120
|
+
if columns:
|
|
121
|
+
report.set_columns(*columns)
|
|
122
|
+
|
|
123
|
+
report.filter_type(filter_type)
|
|
124
|
+
|
|
125
|
+
return list(report)
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def create(self, **fields):
|
|
129
|
+
"""Create and return a new record in associated app and return the newly created Record instance
|
|
130
|
+
|
|
131
|
+
Args:
|
|
132
|
+
**fields: Field names and values to be validated and sent to server with create request
|
|
133
|
+
|
|
134
|
+
Notes:
|
|
135
|
+
Keyword arguments should be field names with their respective python values
|
|
136
|
+
|
|
137
|
+
Field values are validated before sending create request to server
|
|
138
|
+
|
|
139
|
+
Examples:
|
|
140
|
+
Create a new record on an app with simple field names
|
|
141
|
+
|
|
142
|
+
::
|
|
143
|
+
|
|
144
|
+
record = app.records.create(
|
|
145
|
+
field_a='Some Value',
|
|
146
|
+
someOtherField=100,
|
|
147
|
+
...
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
Create a new record on an app with complex field names
|
|
151
|
+
|
|
152
|
+
::
|
|
153
|
+
|
|
154
|
+
record = app.records.create(**{
|
|
155
|
+
'Field 1': 'Field 1 Value',
|
|
156
|
+
'Field 2': 100,
|
|
157
|
+
...
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Record: Newly created Record instance with data as returned from API response
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
swimlane.exceptions.UnknownField: If any fields are provided that are not available on target app
|
|
165
|
+
swimlane.exceptions.ValidationError: If any field fails validation before creation
|
|
166
|
+
"""
|
|
167
|
+
new_record = record_factory(self._app, fields)
|
|
168
|
+
|
|
169
|
+
new_record.save()
|
|
170
|
+
|
|
171
|
+
return new_record
|
|
172
|
+
|
|
173
|
+
@requires_swimlane_version('2.15')
|
|
174
|
+
def bulk_create(self, *records):
|
|
175
|
+
"""Create and validate multiple records in associated app
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
*records (dict): One or more dicts of new record field names and values
|
|
179
|
+
|
|
180
|
+
Notes:
|
|
181
|
+
Requires Swimlane 2.15+
|
|
182
|
+
|
|
183
|
+
Validates like ``create``, but only sends a single request to create all provided fields, and does not
|
|
184
|
+
return the newly created records
|
|
185
|
+
|
|
186
|
+
Any validation failures on any of the records will abort the batch creation, not creating any new records
|
|
187
|
+
|
|
188
|
+
Does not return the newly created records
|
|
189
|
+
|
|
190
|
+
Examples:
|
|
191
|
+
Create 3 new records with single request
|
|
192
|
+
|
|
193
|
+
::
|
|
194
|
+
|
|
195
|
+
app.records.bulk_create(
|
|
196
|
+
{'Field 1': 'value 1', ...},
|
|
197
|
+
{'Field 1': 'value 2', ...},
|
|
198
|
+
{'Field 1': 'value 3', ...}
|
|
199
|
+
)
|
|
200
|
+
|
|
201
|
+
Raises:
|
|
202
|
+
swimlane.exceptions.UnknownField: If any field in any new record cannot be found
|
|
203
|
+
swimlane.exceptions.ValidationError: If any field in any new record fails validation
|
|
204
|
+
TypeError: If no dict of fields was provided, or any provided argument is not a dict
|
|
205
|
+
"""
|
|
206
|
+
|
|
207
|
+
if not records:
|
|
208
|
+
raise TypeError('Must provide at least one record')
|
|
209
|
+
|
|
210
|
+
if any(not isinstance(r, dict) for r in records):
|
|
211
|
+
raise TypeError('New records must be provided as dicts')
|
|
212
|
+
|
|
213
|
+
# Create local records from factory for initial full validation
|
|
214
|
+
new_records = []
|
|
215
|
+
|
|
216
|
+
for record_data in records:
|
|
217
|
+
record = record_factory(self._app, record_data)
|
|
218
|
+
record.validate()
|
|
219
|
+
|
|
220
|
+
new_records.append(record)
|
|
221
|
+
|
|
222
|
+
return self._swimlane.request(
|
|
223
|
+
'post',
|
|
224
|
+
'app/{}/record/batch'.format(self._app.id),
|
|
225
|
+
json=[r._raw for r in new_records]
|
|
226
|
+
).json()
|
|
227
|
+
|
|
228
|
+
# pylint: disable=too-many-branches
|
|
229
|
+
@requires_swimlane_version('2.17')
|
|
230
|
+
def bulk_modify(self, *filters_or_records_or_ids, **kwargs):
|
|
231
|
+
"""Shortcut to bulk modify records
|
|
232
|
+
|
|
233
|
+
.. versionadded:: 2.17.0
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
*filters_or_records_or_ids (tuple), (Record), or (string): a list of Records, a list of recordIds, a list of filters, or a list of both records and recordIds.
|
|
237
|
+
|
|
238
|
+
Keyword Args:
|
|
239
|
+
values (dict): Dictionary of one or more 'field_name': 'new_value' pairs to update
|
|
240
|
+
|
|
241
|
+
Notes:
|
|
242
|
+
Requires Swimlane 2.17+
|
|
243
|
+
|
|
244
|
+
Examples:
|
|
245
|
+
|
|
246
|
+
::
|
|
247
|
+
|
|
248
|
+
# Bulk update records by filter
|
|
249
|
+
|
|
250
|
+
app.records.bulk_modify(
|
|
251
|
+
# Query filters
|
|
252
|
+
('Field_1', 'equals', value1),
|
|
253
|
+
('Field_2', 'equals', value2),
|
|
254
|
+
...
|
|
255
|
+
# New values for records
|
|
256
|
+
values={
|
|
257
|
+
"Field_3": value3,
|
|
258
|
+
"Field_4": value4,
|
|
259
|
+
...
|
|
260
|
+
}
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
# Bulk update records
|
|
264
|
+
|
|
265
|
+
record1 = app.records.get(tracking_id='APP-1')
|
|
266
|
+
record2 = app.records.get(tracking_id='APP-2')
|
|
267
|
+
record3 = app.records.get(tracking_id='APP-3')
|
|
268
|
+
|
|
269
|
+
app.records.bulk_modify(record1, record2, record3, values={"Field_Name": 'new value'})
|
|
270
|
+
|
|
271
|
+
# Using recordIds
|
|
272
|
+
app.records.bulk_modify("adtDzpdDRv9zM8C4o", "aHlAFdBBjE020Jrzb", "aAR67lIcEnLknaURw", values={"Field Name": "New Value"})
|
|
273
|
+
|
|
274
|
+
# Bulk modify by mixing record instances and record ids
|
|
275
|
+
record1 = app.records.get(tracking_id='APP-1')
|
|
276
|
+
app.records.bulk_modify(record1, "aHlAFdBBjE020Jrzb", "aAR67lIcEnLknaURw", values={"Field Name": "New Value"})
|
|
277
|
+
|
|
278
|
+
Returns:
|
|
279
|
+
:class:`string`: Bulk Modify Job ID
|
|
280
|
+
"""
|
|
281
|
+
values = kwargs.pop('values', None)
|
|
282
|
+
|
|
283
|
+
if kwargs:
|
|
284
|
+
raise ValueError('Unexpected arguments: {}'.format(kwargs))
|
|
285
|
+
|
|
286
|
+
if not values:
|
|
287
|
+
raise ValueError('Must provide "values" as keyword argument')
|
|
288
|
+
|
|
289
|
+
if not isinstance(values, dict):
|
|
290
|
+
raise ValueError('values parameter must be dict of {"field_name": "update_value"} pairs')
|
|
291
|
+
|
|
292
|
+
_type = validate_filters_or_records_or_ids(filters_or_records_or_ids)
|
|
293
|
+
|
|
294
|
+
request_payload = {}
|
|
295
|
+
record_stub = record_factory(self._app)
|
|
296
|
+
|
|
297
|
+
if _type is Record or _type is str:
|
|
298
|
+
the_record_ids = []
|
|
299
|
+
for record_or_id in filters_or_records_or_ids:
|
|
300
|
+
if isinstance(record_or_id, Record):
|
|
301
|
+
the_record_ids.append(record_or_id.id)
|
|
302
|
+
else:
|
|
303
|
+
the_record_ids.append(record_or_id)
|
|
304
|
+
|
|
305
|
+
request_payload["recordIds"] = [item for item in the_record_ids]
|
|
306
|
+
|
|
307
|
+
# build filters
|
|
308
|
+
else:
|
|
309
|
+
filters = []
|
|
310
|
+
for filter_tuples in filters_or_records_or_ids:
|
|
311
|
+
field_name = record_stub.get_field(filter_tuples[0])
|
|
312
|
+
|
|
313
|
+
value = filter_tuples[2]
|
|
314
|
+
validate_type(field_name, value)
|
|
315
|
+
|
|
316
|
+
filters.append({
|
|
317
|
+
"fieldId": field_name.id,
|
|
318
|
+
"filterType": filter_tuples[1],
|
|
319
|
+
"value": field_name.get_report(filter_tuples[2])
|
|
320
|
+
})
|
|
321
|
+
request_payload['filters'] = filters
|
|
322
|
+
|
|
323
|
+
# Ensure all values are wrapped in a bulk modification operation, defaulting to Replace if not provided for
|
|
324
|
+
# backwards compatibility
|
|
325
|
+
for field_name in list(values.keys()):
|
|
326
|
+
modification_operation = values[field_name]
|
|
327
|
+
if not isinstance(modification_operation, _BulkModificationOperation):
|
|
328
|
+
values[field_name] = Replace(modification_operation)
|
|
329
|
+
|
|
330
|
+
# build modifications
|
|
331
|
+
modifications = []
|
|
332
|
+
for field_name, modification_operation in values.items():
|
|
333
|
+
# Lookup target field
|
|
334
|
+
modification_field = record_stub.get_field(field_name)
|
|
335
|
+
if not modification_field.bulk_modify_support:
|
|
336
|
+
raise ValueError('Field "{}" of Type "{}", is not supported for bulk modify'.format(
|
|
337
|
+
field_name,
|
|
338
|
+
modification_field.__class__.__name__
|
|
339
|
+
))
|
|
340
|
+
|
|
341
|
+
modifications.append({
|
|
342
|
+
"fieldId": {
|
|
343
|
+
"value": modification_field.id,
|
|
344
|
+
"type": "id"
|
|
345
|
+
},
|
|
346
|
+
"value": modification_field.get_bulk_modify(modification_operation.value),
|
|
347
|
+
"type": modification_operation.type
|
|
348
|
+
})
|
|
349
|
+
request_payload['modifications'] = modifications
|
|
350
|
+
response = self._swimlane.request('put', "app/{0}/record/batch".format(self._app.id), json=request_payload)
|
|
351
|
+
|
|
352
|
+
# Update records if instances were used to submit bulk modify request after request was successful
|
|
353
|
+
if _type is Record or _type is str:
|
|
354
|
+
for record in filters_or_records_or_ids:
|
|
355
|
+
# if value is an ID, get the record instance
|
|
356
|
+
if type(record) is str:
|
|
357
|
+
record = self.get(id=record)
|
|
358
|
+
for field_name, modification_operation in six.iteritems(values):
|
|
359
|
+
record[field_name] = modification_operation.value
|
|
360
|
+
|
|
361
|
+
return response.text
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
@requires_swimlane_version('2.17')
|
|
369
|
+
def bulk_delete(self, *filters_or_records_or_ids):
|
|
370
|
+
"""Shortcut to bulk delete records
|
|
371
|
+
|
|
372
|
+
.. versionadded:: 2.17.0
|
|
373
|
+
|
|
374
|
+
Args:
|
|
375
|
+
*filters_or_records_or_ids (tuple) or (Record): Either a list of Records, a list of filters, a list of recordIds, or a list of both records and recordIds.
|
|
376
|
+
|
|
377
|
+
Notes:
|
|
378
|
+
Requires Swimlane 2.17+
|
|
379
|
+
|
|
380
|
+
Examples:
|
|
381
|
+
|
|
382
|
+
::
|
|
383
|
+
|
|
384
|
+
# Bulk delete records by filter
|
|
385
|
+
app.records.bulk_delete(
|
|
386
|
+
('Field_1', 'equals', value1),
|
|
387
|
+
('Field_2', 'equals', value2)
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
# Bulk delete by record instances
|
|
391
|
+
record1 = app.records.get(tracking_id='APP-1')
|
|
392
|
+
record2 = app.records.get(tracking_id='APP-2')
|
|
393
|
+
record3 = app.records.get(tracking_id='APP-3')
|
|
394
|
+
app.records.bulk_delete(record1, record2, record3)
|
|
395
|
+
|
|
396
|
+
# Bulk delete by record ids
|
|
397
|
+
app.records.bulk_delete("adtDzpdDRv9zM8C4o", "aHlAFdBBjE020Jrzb", "aAR67lIcEnLknaURw", values={"Field Name": "New Value"})
|
|
398
|
+
|
|
399
|
+
# Bulk delete by mixing record instances and record ids
|
|
400
|
+
record1 = app.records.get(tracking_id='APP-1')
|
|
401
|
+
app.records.bulk_delete(record1, "aHlAFdBBjE020Jrzb", "aAR67lIcEnLknaURw", values={"Field Name": "New Value"})
|
|
402
|
+
|
|
403
|
+
Returns:
|
|
404
|
+
:class:`string`: Bulk Modify Job ID
|
|
405
|
+
"""
|
|
406
|
+
|
|
407
|
+
_type = validate_filters_or_records_or_ids(filters_or_records_or_ids)
|
|
408
|
+
data_dict = {}
|
|
409
|
+
|
|
410
|
+
# build record_id list
|
|
411
|
+
if _type is Record or _type is str:
|
|
412
|
+
record_ids = []
|
|
413
|
+
for item in filters_or_records_or_ids:
|
|
414
|
+
if isinstance(item, Record):
|
|
415
|
+
record_ids.append(item.id)
|
|
416
|
+
else:
|
|
417
|
+
record_ids.append(item)
|
|
418
|
+
data_dict['recordIds'] = record_ids
|
|
419
|
+
|
|
420
|
+
# build filters
|
|
421
|
+
else:
|
|
422
|
+
filters = []
|
|
423
|
+
record_stub = record_factory(self._app)
|
|
424
|
+
for filter_tuples in filters_or_records_or_ids:
|
|
425
|
+
field = record_stub.get_field(filter_tuples[0])
|
|
426
|
+
|
|
427
|
+
value = filter_tuples[2]
|
|
428
|
+
validate_type(field, value)
|
|
429
|
+
|
|
430
|
+
filters.append({
|
|
431
|
+
"fieldId": field.id,
|
|
432
|
+
"filterType": filter_tuples[1],
|
|
433
|
+
"value": field.get_report(filter_tuples[2])
|
|
434
|
+
})
|
|
435
|
+
data_dict['filters'] = filters
|
|
436
|
+
|
|
437
|
+
return self._swimlane.request('DELETE', "app/{0}/record/batch".format(self._app.id), json=data_dict).text
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def validate_filters_or_records_or_ids(filters_or_records_or_ids):
|
|
441
|
+
"""Validation for filters_or_records variable from bulk_modify and bulk_delete"""
|
|
442
|
+
# If filters_or_records_or_ids is empty, fail
|
|
443
|
+
if not filters_or_records_or_ids:
|
|
444
|
+
raise ValueError('Must provide at least one filter tuples, Records, or list of Ids')
|
|
445
|
+
|
|
446
|
+
_types = [type(item) for item in filters_or_records_or_ids]
|
|
447
|
+
|
|
448
|
+
types_dict = {
|
|
449
|
+
"record": 0,
|
|
450
|
+
"str": 0,
|
|
451
|
+
"tuple": 0
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
for _type in _types:
|
|
455
|
+
if _type is tuple:
|
|
456
|
+
types_dict["tuple"] = types_dict["tuple"] + 1
|
|
457
|
+
elif _type is Record:
|
|
458
|
+
types_dict["record"] = types_dict["tuple"] + 1
|
|
459
|
+
elif _type is str:
|
|
460
|
+
types_dict["str"] = types_dict["tuple"] + 1
|
|
461
|
+
else:
|
|
462
|
+
raise ValueError('Expected filter tuple, Record, or string, received {0}'.format(_type))
|
|
463
|
+
|
|
464
|
+
if types_dict["tuple"] > 0 and (types_dict["record"] > 0 or types_dict["str"] > 0):
|
|
465
|
+
raise ValueError('Cannot mix filter tuples with records or ids')
|
|
466
|
+
|
|
467
|
+
# either all tuples or a mix of record and str, which is handled together
|
|
468
|
+
return _types[0]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import math
|
|
2
|
+
|
|
3
|
+
from swimlane.core.resolver import AppResolver
|
|
4
|
+
from swimlane.core.resources.record_revision import RecordRevision
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class RecordRevisionAdapter(AppResolver):
|
|
8
|
+
"""Handles retrieval of Swimlane Record Revision resources"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, app, record):
|
|
11
|
+
super(RecordRevisionAdapter, self).__init__(app)
|
|
12
|
+
self.record = record
|
|
13
|
+
|
|
14
|
+
def get_all(self):
|
|
15
|
+
"""Get all revisions for a single record.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
RecordRevision[]: All record revisions for the given record ID.
|
|
19
|
+
"""
|
|
20
|
+
raw_revisions = self._swimlane.request('get',
|
|
21
|
+
'app/{0}/record/{1}/history'.format(self._app.id, self.record.id)).json()
|
|
22
|
+
return [RecordRevision(self._app, raw) for raw in raw_revisions]
|
|
23
|
+
|
|
24
|
+
def get(self, revision_number):
|
|
25
|
+
"""Gets a specific record revision.
|
|
26
|
+
|
|
27
|
+
Keyword Args:
|
|
28
|
+
revision_number (float): Record revision number
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
RecordRevision: The RecordRevision for the given revision number.
|
|
32
|
+
Raises: When revision is not an integer, a float NOT ending in ".0", or is less than 1
|
|
33
|
+
"""
|
|
34
|
+
if isinstance(revision_number, (int, float)):
|
|
35
|
+
if revision_number > 0 and revision_number % math.floor(revision_number) == 0:
|
|
36
|
+
record_revision_raw = self._swimlane.request('get',
|
|
37
|
+
'app/{0}/record/{1}/history/{2}'.format(self._app.id,
|
|
38
|
+
self.record.id,
|
|
39
|
+
revision_number)).json()
|
|
40
|
+
return RecordRevision(self._app, record_revision_raw)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
raise ValueError('The revision number must be a positive whole number greater than 0')
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import weakref
|
|
2
|
+
|
|
3
|
+
from swimlane.core.resolver import SwimlaneResolver
|
|
4
|
+
from swimlane.core.resources.report import Report, report_factory
|
|
5
|
+
from swimlane.utils.str_validator import validate_str, validate_str_format
|
|
6
|
+
|
|
7
|
+
class ReportAdapter(SwimlaneResolver):
|
|
8
|
+
"""Handles retrieval and creation of Report resources"""
|
|
9
|
+
|
|
10
|
+
def __init__(self, app):
|
|
11
|
+
super(ReportAdapter, self).__init__(app._swimlane)
|
|
12
|
+
|
|
13
|
+
self.__ref_app = weakref.ref(app)
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
def _app(self):
|
|
17
|
+
"""Resolve weak app reference"""
|
|
18
|
+
return self.__ref_app()
|
|
19
|
+
|
|
20
|
+
def list(self):
|
|
21
|
+
"""Retrieve all reports for parent app
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
:class:`list` of :class:`~swimlane.core.resources.report.Report`: List of all returned reports
|
|
25
|
+
"""
|
|
26
|
+
raw_reports = self._swimlane.request('get', "reports/app/{}".format(self._app.id)).json()
|
|
27
|
+
|
|
28
|
+
return [Report(self._app, raw_report) for raw_report in raw_reports]
|
|
29
|
+
|
|
30
|
+
def get(self, report_id):
|
|
31
|
+
"""Retrieve report by ID
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
report_id (str): Full report ID
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
Report: Corresponding Report instance
|
|
38
|
+
"""
|
|
39
|
+
validate_str(report_id, 'report_id')
|
|
40
|
+
validate_str_format(report_id, 'report_id')
|
|
41
|
+
|
|
42
|
+
return Report(
|
|
43
|
+
self._app,
|
|
44
|
+
self._swimlane.request('get', "reports/{0}".format(report_id)).json()
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
def build(self, name, **kwargs):
|
|
48
|
+
"""Report instance factory for the adapter's App
|
|
49
|
+
|
|
50
|
+
Args:
|
|
51
|
+
name (str): New Report name
|
|
52
|
+
|
|
53
|
+
Keyword Args:
|
|
54
|
+
**kwargs: Extra keyword args passed to Report class
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
Report: Newly created local Report instance
|
|
58
|
+
"""
|
|
59
|
+
valid = lambda input: input is not None and isinstance(input, int) and input >= 0
|
|
60
|
+
limit = kwargs.get('limit', None)
|
|
61
|
+
|
|
62
|
+
if "limit" in kwargs and not valid(limit):
|
|
63
|
+
raise ValueError('The limit value must be a whole number of zero or above')
|
|
64
|
+
|
|
65
|
+
return report_factory(self._app, name, **kwargs)
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
from swimlane.core.resolver import SwimlaneResolver
|
|
2
|
+
from swimlane.core.cache import check_cache
|
|
3
|
+
from swimlane.core.resources.task import Task
|
|
4
|
+
from swimlane.utils import one_of_keyword_only
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TaskAdapter(SwimlaneResolver):
|
|
8
|
+
"""Handles retreival of Swimlane Task Resources and execution of tasks against records."""
|
|
9
|
+
|
|
10
|
+
@check_cache(Task)
|
|
11
|
+
@one_of_keyword_only('id', 'name')
|
|
12
|
+
def get(self, key, value):
|
|
13
|
+
"""Get a single task by id or name"""
|
|
14
|
+
if key == 'id':
|
|
15
|
+
response = self._swimlane.request('get', 'task/{id}'.format(id=value))
|
|
16
|
+
return Task(self._swimlane, response.json())
|
|
17
|
+
|
|
18
|
+
if key == 'name':
|
|
19
|
+
response = self._swimlane.request('get', 'task/light')
|
|
20
|
+
for task in response.json():
|
|
21
|
+
if value == task.get('name'):
|
|
22
|
+
return self._get_full(task.get('id'))
|
|
23
|
+
raise ValueError('No task with name "{value}"'.format(value=value))
|
|
24
|
+
|
|
25
|
+
def list(self):
|
|
26
|
+
"""Retrieve list of all tasks.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
:class:`list` of :class:`~swimlane.core.resources.task.Task`: List of all tasks.
|
|
30
|
+
"""
|
|
31
|
+
response = self._swimlane.request('get', 'task/light')
|
|
32
|
+
return [self._get_full(item.get('id')) for item in response.json()]
|
|
33
|
+
|
|
34
|
+
def execute(self, task_name, raw_record):
|
|
35
|
+
"""Execute job by name.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Response of request from the API endpoint.
|
|
39
|
+
"""
|
|
40
|
+
task_id = self.get(name=task_name).id
|
|
41
|
+
data = {
|
|
42
|
+
'taskId': task_id,
|
|
43
|
+
'record': raw_record,
|
|
44
|
+
}
|
|
45
|
+
#return self._swimlane.request('post', 'task/execute/record', json=data)
|
|
46
|
+
if self._swimlane._execute_task_webhook_url:
|
|
47
|
+
return self._swimlane._session.post(self._swimlane._execute_task_webhook_url, json=data)
|
|
48
|
+
else:
|
|
49
|
+
raise Exception('Task webhook URL is not set')
|
|
50
|
+
|
|
51
|
+
def _get_full(self, task_id):
|
|
52
|
+
"""Retreived complete task raw json of task from API.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
:class:`~swimlane.core.resources.task.Task`: Single Task object.
|
|
56
|
+
"""
|
|
57
|
+
response = self._swimlane.request('get', 'task/{task_id}'.format(task_id=task_id))
|
|
58
|
+
return Task(self._swimlane, response.json())
|