@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,70 @@
1
+ import six
2
+
3
+ from swimlane.exceptions import ValidationError
4
+ from .base import MultiSelectField
5
+
6
+
7
+ class ValuesListField(MultiSelectField):
8
+
9
+ field_type = (
10
+ 'Core.Models.Fields.ValuesListField, Core',
11
+ 'Core.Models.Fields.ValuesList.ValuesListField, Core'
12
+ )
13
+ supported_types = six.string_types
14
+
15
+ def __init__(self, *args, **kwargs):
16
+ """Map names to IDs for use in field rehydration"""
17
+ super(ValuesListField, self).__init__(*args, **kwargs)
18
+ self.selection_to_id_map = {f['name']: f['id'] for f in self.field_definition['values']}
19
+
20
+ def validate_value(self, value):
21
+ """Validate provided value is one of the valid options"""
22
+ super(ValuesListField, self).validate_value(value)
23
+
24
+ if value is not None:
25
+ if value not in self.selection_to_id_map:
26
+ raise ValidationError(
27
+ self.record,
28
+ 'Field "{}" invalid value "{}". Valid options: {}'.format(
29
+ self.name,
30
+ value,
31
+ ', '.join(self.selection_to_id_map.keys())
32
+ )
33
+ )
34
+
35
+ def get_batch_representation(self):
36
+ """Return best batch process representation of field value"""
37
+ return self.get_swimlane()
38
+
39
+ def cast_to_python(self, value):
40
+ """Store actual value as internal representation"""
41
+ if value is not None:
42
+ if value['value'] in self.selection_to_id_map:
43
+ return value['value']
44
+
45
+ return None
46
+
47
+ def cast_to_swimlane(self, value):
48
+ """Rehydrate value back as full JSON representation"""
49
+ if value is None:
50
+ return value
51
+ if value not in self.selection_to_id_map:
52
+ return None
53
+
54
+ return {
55
+ '$type': 'Core.Models.Record.ValueSelection, Core',
56
+ 'id': self.selection_to_id_map[value],
57
+ 'value': value
58
+ }
59
+
60
+ def cast_to_report(self, value):
61
+ """Report format uses only the value's id"""
62
+ value = super(ValuesListField, self).cast_to_report(value)
63
+
64
+ if value:
65
+ return value['id']
66
+
67
+ def cast_to_bulk_modify(self, value):
68
+ """Bulk modify uses the normal Swimlane representation"""
69
+ self.validate_value(value)
70
+ return self.cast_to_swimlane(value)
@@ -0,0 +1,46 @@
1
+ import weakref
2
+
3
+
4
+ class SwimlaneResolver(object):
5
+ """Provides automatic weakref resolution for Swimlane client to avoid circular references and
6
+ memory leaks """
7
+
8
+ def __init__(self, swimlane):
9
+ self.__ref_swimlane = weakref.ref(swimlane) if swimlane else swimlane
10
+
11
+ @property
12
+ def _swimlane(self):
13
+ """Transparently resolve the swimlane weakref"""
14
+ if not self.__ref_swimlane:
15
+ referent = self.__ref_swimlane
16
+ else:
17
+ referent = self.__ref_swimlane()
18
+ if referent is None:
19
+ raise ReferenceError("The swimlane object has been garbage collected. The Swimlane "
20
+ "object uses weak references to avoid memory leaks. The object "
21
+ "gets garbage collected when there are no more references to it. "
22
+ "See the weakref documentation for more information.")
23
+ return referent
24
+
25
+
26
+ class AppResolver(SwimlaneResolver):
27
+ """Provides automatic weakref resolution for Swimlane client and App instance"""
28
+
29
+ def __init__(self, app):
30
+ super(AppResolver, self).__init__(app._swimlane)
31
+
32
+ self.__ref_app = weakref.ref(app) if app else app
33
+
34
+ @property
35
+ def _app(self):
36
+ if not self.__ref_app:
37
+ referent = self.__ref_app
38
+ else:
39
+ referent = self.__ref_app()
40
+ if referent is None:
41
+ raise ReferenceError(
42
+ "The App object of the Swimlane Object has been garbage collected. Both the App "
43
+ "object and the Swimlane object use weak references to avoid memory leaks. The "
44
+ "instance gets garbage collected when there are no more strong references to it. "
45
+ "See the weakref documentation for more information.")
46
+ return referent
@@ -0,0 +1,136 @@
1
+ from functools import total_ordering
2
+
3
+ import six
4
+ import pendulum
5
+
6
+ from swimlane.exceptions import UnknownField
7
+ from .base import APIResource
8
+
9
+
10
+ @total_ordering
11
+ class App(APIResource):
12
+ """A single App record instance
13
+
14
+ Used lookup field definitions and retrieve/create child Record instances
15
+
16
+ Attributes:
17
+ name (str): App name
18
+ acronym (str): App acronym
19
+ description (str): App description
20
+ id (str): Full App ID
21
+ tracking_id (str): App tracking ID
22
+ records (RecordAdapter): :class:`~swimlane.core.adapters.record.RecordAdapter` configured for current App
23
+ reports (ReportAdapter): :class:`~swimlane.core.adapters.report.ReportAdapter` configured for current App
24
+ """
25
+
26
+ _type = 'Core.Models.Application.Application, Core'
27
+
28
+ def __init__(self, swimlane, raw):
29
+ super(App, self).__init__(swimlane, raw)
30
+
31
+ self.acronym = self._raw['acronym']
32
+ self.name = self._raw['name']
33
+ self.description = self._raw.get('description', '')
34
+ self.id = self._raw['id']
35
+ self.tracking_id = self._raw.get('trackingFieldId')
36
+
37
+ self._fields_by_id = dict()
38
+ self._fields_by_name = dict()
39
+ self._defaults = dict()
40
+
41
+ for field in self._raw['fields']:
42
+ self._fields_by_id[field['id']] = field
43
+ self._fields_by_name[field['name']] = field
44
+ if 'fieldType' in field and field['fieldType'] == "valuesList":
45
+ selection_type = field['selectionType']
46
+ for value in field['values']:
47
+ if 'selected' in value and value['selected']:
48
+ if selection_type == 'single':
49
+ self._defaults[field['name']] = value['name']
50
+ break
51
+ else:
52
+ default = self._defaults.get(field['name'], list())
53
+ default.extend([value['name']])
54
+ self._defaults[field['name']] = default
55
+ if 'fieldType' in field and field['fieldType'] == "date":
56
+ default_value_type = field['defaultValueType']
57
+ if default_value_type == 'specific':
58
+ self._defaults[field['name']] = pendulum.parse(field['defaultValue'])
59
+
60
+ self._keys_to_field_names = {}
61
+ for name, field_def in six.iteritems(self._fields_by_name):
62
+ # Include original name to simplify name resolution
63
+ self._keys_to_field_names[name] = name
64
+ key = field_def.get('key')
65
+ if key:
66
+ self._keys_to_field_names[key] = name
67
+
68
+ # Avoid circular import
69
+ from swimlane.core.adapters import RecordAdapter, ReportAdapter, AppRevisionAdapter
70
+ self.records = RecordAdapter(self)
71
+ self.reports = ReportAdapter(self)
72
+ self.revisions = AppRevisionAdapter(self)
73
+
74
+ def __str__(self):
75
+ return '{self.name} ({self.acronym})'.format(self=self)
76
+
77
+ def __hash__(self):
78
+ return hash((self.id, self.name))
79
+
80
+ def __lt__(self, other):
81
+ if not isinstance(other, self.__class__):
82
+ raise TypeError('Comparisons not supported between instances of "{}" and "{}"'.format(
83
+ other.__class__.__name__,
84
+ self.__class__.__name__
85
+ ))
86
+
87
+ return self.name < other.name
88
+
89
+ def get_cache_index_keys(self):
90
+ """Return all fields available when retrieving apps"""
91
+ return {
92
+ 'id': self.id,
93
+ 'name': self.name,
94
+ 'acroynm': self.acronym
95
+ }
96
+
97
+ def resolve_field_name(self, field_key):
98
+ """Return the field name matching the given key or None. Searches field keys first, falls back to field names"""
99
+ return self._keys_to_field_names.get(field_key)
100
+
101
+ def get_field_definition_by_name(self, field_name):
102
+ """Get JSON field definition for field matching provided name or key
103
+
104
+ .. versionchanged:: 4.1.0
105
+ Added support for field keys
106
+
107
+ Args:
108
+ field_name (str): Target field name or key to get definition for
109
+
110
+ Raises:
111
+ swimlane.exceptions.UnknownField: Raised when given a field name not found in App
112
+
113
+ Returns:
114
+ dict: Field metadata definition
115
+ """
116
+ try:
117
+ return self._fields_by_name[self.resolve_field_name(field_name)]
118
+ except KeyError:
119
+ raise UnknownField(self, field_name, self._fields_by_name.keys())
120
+
121
+ def get_field_definition_by_id(self, field_id):
122
+ """Get JSON field definition for field matching provided id
123
+
124
+ Args:
125
+ field_id (str): Target field ID to get definition for
126
+
127
+ Raises:
128
+ swimlane.exceptions.UnknownField: Raised when given a field ID not found in App
129
+
130
+ Returns:
131
+ dict: Field metadata definition
132
+ """
133
+ try:
134
+ return self._fields_by_id[field_id]
135
+ except KeyError:
136
+ raise UnknownField(self, field_id, self._fields_by_id.keys())
@@ -0,0 +1,43 @@
1
+ from swimlane.core.resources.app import App
2
+ from swimlane.core.resources.revision_base import RevisionBase
3
+
4
+
5
+ class AppRevision(RevisionBase):
6
+ """
7
+ Encapsulates a single revision returned from a History lookup.
8
+
9
+ Attributes:
10
+ Attributes:
11
+ modified_date: The date this app revision was created.
12
+ revision_number: The revision number of this app revision.
13
+ status: Indicates whether this revision is the current revision or a historical revision.
14
+ user: The user that saved this revision of the record.
15
+ version: The App corresponding to the data contained in this app revision.
16
+ """
17
+
18
+ # Separator for unique ids. Unlikely to be found in application ids. Although technically we do not currently
19
+ # validate app ids in the backend for specific characters so this sequence could be found.
20
+ SEPARATOR = ' --- '
21
+
22
+ @staticmethod
23
+ def get_unique_id(app_id, revision_number):
24
+ """Returns the unique identifier for the given AppRevision."""
25
+ return '{0}{1}{2}'.format(app_id, AppRevision.SEPARATOR, revision_number)
26
+
27
+ @staticmethod
28
+ def parse_unique_id(unique_id):
29
+ """Returns an array containing two items: the app_id and revision number parsed from the given unique_id."""
30
+ return unique_id.split(AppRevision.SEPARATOR)
31
+
32
+ @property
33
+ def version(self):
34
+ """Returns an App from the _raw_version info in this app revision. Lazy loaded. Overridden from base class."""
35
+ if not self._version:
36
+ self._version = App(self._swimlane, self._raw_version)
37
+ return self._version
38
+
39
+ def get_cache_index_keys(self):
40
+ """Returns cache index keys for this AppRevision."""
41
+ return {
42
+ 'app_id_revision': self.get_unique_id(self.version.id, self.revision_number)
43
+ }
@@ -0,0 +1,64 @@
1
+ from io import BytesIO
2
+
3
+ import pendulum
4
+
5
+ from swimlane.core.resources.base import APIResource
6
+
7
+
8
+ class Attachment(APIResource):
9
+ """Abstraction of an attachment from an AttachmentsField
10
+
11
+ Attributes:
12
+ file_id (str): Full file ID used in download request URL
13
+ filename (str): Attachment filename
14
+ upload_date (pendulum.DateTime): Pendulum datetime when attachment was uploaded
15
+ record_id (str): Associated record ID used in download request URL
16
+ field_id (str): Assocated field ID used in download request URL
17
+ """
18
+
19
+ _type = 'Core.Models.Record.Attachment, Core'
20
+
21
+ def __init__(self, swimlane, raw, record_id, field_id):
22
+ super(Attachment, self).__init__(swimlane, raw)
23
+
24
+ self.file_id = self._raw['fileId']
25
+ self.filename = self._raw['filename']
26
+ self.upload_date = pendulum.parse(self._raw['uploadDate'])
27
+ self.record_id = record_id
28
+ self.field_id = field_id
29
+
30
+ def __str__(self):
31
+ return str(self.filename)
32
+
33
+ def __hash__(self):
34
+ return hash(self.file_id)
35
+
36
+ def download(self, chunk_size=1024):
37
+ """Download attachment
38
+
39
+ Args:
40
+ chunk_size (int): Byte-size of chunked download request stream
41
+
42
+ Returns:
43
+ BytesIO: Stream ready for reading containing the attachment file contents
44
+ """
45
+ stream = BytesIO()
46
+
47
+ response = self._swimlane.request(
48
+ 'get',
49
+ 'attachment/{}/{}/{}'.format(self.record_id, self.field_id, self.file_id),
50
+ stream=True
51
+ )
52
+
53
+ for chunk in response.iter_content(chunk_size):
54
+ stream.write(chunk)
55
+
56
+ stream.seek(0)
57
+
58
+ return stream
59
+
60
+ def for_json(self):
61
+ """Return metadata for JSON-compatible representation"""
62
+ return_value = self._raw.copy()
63
+ return_value.pop('$type')
64
+ return return_value
@@ -0,0 +1,55 @@
1
+ import six
2
+
3
+ from swimlane.core.resolver import SwimlaneResolver
4
+
5
+
6
+ class APIResourceMetaclass(type):
7
+ """Metaclass for all APIResource classes"""
8
+
9
+ def __call__(cls, *args, **kwargs):
10
+ """Hook __init__ call to push resource instance into Swimlane client ResourceCache after instantiation"""
11
+ resource_instance = type.__call__(cls, *args, **kwargs)
12
+
13
+ resource_instance._swimlane.resources_cache.cache(resource_instance)
14
+
15
+ return resource_instance
16
+
17
+
18
+ class APIResource(six.with_metaclass(APIResourceMetaclass, SwimlaneResolver)):
19
+ """Base class for all API resources with an associated $type and/or raw data"""
20
+
21
+ _type = None
22
+
23
+ def __init__(self, swimlane, raw):
24
+ super(APIResource, self).__init__(swimlane)
25
+ self._raw = raw
26
+
27
+ raw_type = self._raw.get('$type')
28
+ if self._type and raw_type != self._type:
29
+ raise TypeError('Expected $type = "{}", received "{}"'.format(self._type, raw_type))
30
+
31
+ def __repr__(self):
32
+ return '<{self.__class__.__name__}: {self!s}>'.format(self=self)
33
+
34
+ def __str__(self):
35
+ return ''
36
+
37
+ def __hash__(self):
38
+ """Added for py2+3 compat"""
39
+ return int(id(self) / 16)
40
+
41
+ def __eq__(self, other):
42
+ """Determine if an APIResource is of the same type and has the same hash value"""
43
+ return isinstance(other, self.__class__) and hash(self) == hash(other)
44
+
45
+ def __ne__(self, other):
46
+ # Default __ne__ for python 2 compat
47
+ return not self == other
48
+
49
+ def get_cache_internal_key(self):
50
+ """Return real internal cache key for resource instance"""
51
+ return hash(self)
52
+
53
+ def get_cache_index_keys(self):
54
+ """Return dict of key/value pairs used by ResourceCache to map resource values to internal cache instance"""
55
+ raise NotImplementedError
@@ -0,0 +1,33 @@
1
+ import pendulum
2
+
3
+ from swimlane.core.resources.base import APIResource
4
+ from swimlane.core.resources.usergroup import UserGroup
5
+
6
+
7
+ class Comment(APIResource):
8
+ """Abstraction of a single comment from a comment field
9
+
10
+ Attributes:
11
+ user (UserGroup): UserGroup instance of user who created the comment
12
+ created_date (pendulum.DateTime): Pendulum datetime of when comment was created
13
+ message (str): Comment message body
14
+ """
15
+
16
+ def __init__(self, swimlane, raw):
17
+ super(Comment, self).__init__(swimlane, raw)
18
+
19
+ self.user = UserGroup(swimlane, self._raw['createdByUser'])
20
+ self.created_date = pendulum.parse(self._raw['createdDate'])
21
+ self.message = self._raw['message']
22
+ self.is_rich_text = self._raw.get('isRichText', False)
23
+
24
+ def __str__(self):
25
+ return self.message
26
+
27
+ def for_json(self):
28
+ """Called by CommentField.for_json(), returns relevant Comment attributes in JSON-compatible format"""
29
+ return {
30
+ 'message': self.message,
31
+ 'createdDate': self._raw['createdDate'],
32
+ 'createdByUser': self.user.for_json()
33
+ }