@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
@@ -5,7 +5,37 @@ class SwimlaneContext:
5
5
  self.asset = asset
6
6
  self.asset_schema = asset_schema
7
7
  self.http_proxy = http_proxy
8
+ url = inputs.pop("SwimlaneUrl", "")
9
+ self.config = {
10
+ "ApplicationId": inputs.pop("ApplicationId", ""),
11
+ "RecordId": inputs.pop("RecordId", ""),
12
+ "InternalSwimlaneUrl": url,
13
+ "SwimlaneUrl": url
14
+ }
15
+ self.user = {
16
+ "first_name": "Swimlane",
17
+ "last_name": "Admin",
18
+ "middle_initial": "",
19
+ "display_name": "Swimlane Admin",
20
+ "last_login": "2026-02-09T18:40:10.386Z",
21
+ "domain": "",
22
+ "active": True,
23
+ "name": "swimlanesystem",
24
+ "last_password_change_date": "2026-01-22T00:31:49.783Z",
25
+ "password_reset_required": False,
26
+ "default_workspace_id": "",
27
+ "default_dashboard_id": "",
28
+ "groups": [],
29
+ "roles": [],
30
+ "primary_group": "",
31
+ "email": "",
32
+ "user_name": "swimlaneadmin",
33
+ "phone_number": "",
34
+ "disabled":False,
35
+ "id": ""
36
+ }
8
37
  self.inputs = inputs
38
+ self.state = {}
9
39
  # We purposefully do not include the sw_context.config object here as it is not supported
10
40
 
11
41
  class Authenticate:
@@ -9,23 +9,95 @@ class RunnerOverride(RO):
9
9
  super().__init__(asset, asset_schema, http_proxy)
10
10
 
11
11
  def run(self, inputs, action_schema):
12
+ # Rebuild inputs from YAML-defined keys (empty per type), then merge asset, then overlay incoming inputs
13
+ # INPUTS_MERGE_HERE
14
+
12
15
  sw_context = SwimlaneContext(asset=self.asset, asset_schema=self.asset_schema,
13
16
  http_proxy=self.http_proxy, inputs=inputs)
14
- sw_outputs = None
17
+ sw_outputs = []
18
+ from swimlane import Swimlane
19
+ Swimlane.turbineAccountId = sw_context.inputs.pop('TurbineAccountId', '')
20
+ Swimlane.turbineTenantId = sw_context.inputs.pop('TurbineTenantId', '')
21
+ Swimlane._api_root = f'/api/account/{Swimlane.turbineAccountId}/tenant/{Swimlane.turbineTenantId}/'
22
+ Swimlane._execute_task_webhook_url = sw_context.inputs.pop('ExecuteTaskWebhookUrl', '')
15
23
 
16
24
  # IMPORTED CODE BELOW
17
25
  # HERE
18
26
 
19
27
 
20
28
  # END OF IMPORTED CODE
29
+ import os
30
+ import base64
31
+ import requests
32
+ IPC_API_URI = os.getenv('IPC_API_URI', '')
33
+ IPC_API_TOKEN = os.getenv('IPC_API_TOKEN', '')
34
+
35
+ OUTPUT_DATE_CONVERSIONS = __OUTPUT_DATE_CONVERSIONS__
36
+
37
+ def convert_timestamp(value, timetype):
38
+ import pendulum
39
+ if timetype == "milliseconds":
40
+ value = value / 1000
41
+ if timetype in ('milliseconds', 'seconds'):
42
+ return pendulum.from_timestamp(value).to_iso8601_string()
43
+ return pendulum.from_format(value, timetype).to_iso8601_string()
44
+
45
+ def sanitize_filename(filename):
46
+ """Removes or replaces illegal characters from file names."""
47
+ illegal_chars = r'[\/:*?"<>|[\]{}()\%]' # Define characters to be removed
48
+ sanitized = re.sub(illegal_chars, '', filename) # Remove illegal characters
49
+ return sanitized.strip() # Remove leading/trailing spaces
50
+
51
+ def handle_output_files(output):
52
+ post_url = f"{IPC_API_URI}/files"
53
+ params = {"token": IPC_API_TOKEN}
54
+ # There are potentially many dicts in output, but usually only one
55
+ for o in output:
56
+ for key in o:
57
+ file_output = o.get(key, None)
58
+ temp_files = []
59
+ if file_output and isinstance(file_output, list):
60
+ # value is a list, but we only want to proceed if it's a list of files
61
+ if all(isinstance(i, dict) and 'base64' in i and 'filename' in i for i in file_output):
62
+ # we need to convert the objects to turbine format, then upload
63
+ temp_files = [{'file_data': base64.b64decode(i['base64']), 'file_name': sanitize_filename(i['filename'])} for i in file_output]
64
+ attachments = []
65
+ for file in temp_files:
66
+ attachments.append(
67
+ (
68
+ "files",
69
+ (
70
+ file["file_name"],
71
+ file["file_data"],
72
+ "multipart/form-data",
73
+ )
74
+ )
75
+ )
76
+ res = requests.post(post_url, files=attachments, params=params)
77
+ res.raise_for_status()
78
+ o[key] = res.json()
79
+ for key, timetype in OUTPUT_DATE_CONVERSIONS.items():
80
+ if key in o and o[key] is not None:
81
+ try:
82
+ o[key] = convert_timestamp(o[key], timetype)
83
+ except Exception:
84
+ pass
85
+
86
+ return output
21
87
  if isinstance(sw_outputs, dict):
22
- return sw_outputs # Already a dictionary
88
+ sw_outputs["sw_task_status"] = "success"
89
+ return {"output": handle_output_files([sw_outputs])} # Already a dictionary
23
90
 
24
91
  elif isinstance(sw_outputs, list):
25
- return {"list_output": sw_outputs} # Wrap in a dictionary
92
+ # Add sw_task_status to each dictionary in the list
93
+ for item in sw_outputs:
94
+ if isinstance(item, dict):
95
+ item["sw_task_status"] = "success"
96
+ return {"output": handle_output_files(sw_outputs)} # Wrap in a dictionary
26
97
 
27
98
  elif sw_outputs is None:
28
- return {"output": None} # Handle None case explicitly
99
+ return {"output": [{"sw_task_status": "success"}]} # Handle None case explicitly
29
100
 
30
101
  else:
31
- return {"output": sw_outputs} # Wrap other types in a dictionary
102
+ # For other types, wrap in a dictionary with sw_task_status
103
+ return {"output": [{"result": sw_outputs, "sw_task_status": "success"}]} # Wrap other types in a dictionary
@@ -0,0 +1,18 @@
1
+ """Swimlane Python API driver"""
2
+
3
+ from __future__ import absolute_import
4
+
5
+ import logging
6
+
7
+ from .core.client import Swimlane
8
+ from .utils.version import get_package_version
9
+
10
+ __all__ = [
11
+ 'Swimlane',
12
+ ]
13
+
14
+ __version__ = get_package_version()
15
+
16
+ logger = logging.getLogger(__name__)
17
+ logger.addHandler(logging.NullHandler())
18
+
File without changes
@@ -0,0 +1,10 @@
1
+ """Contains various adapters encapsulating API logic for retrieving, searching, listing, or creating resource objects"""
2
+
3
+ from .app import AppAdapter
4
+ from .record import RecordAdapter
5
+ from .report import ReportAdapter
6
+ from .usergroup import UserAdapter, GroupAdapter
7
+ from .helper import HelperAdapter
8
+ from .app_revision import AppRevisionAdapter
9
+ from .record_revision import RecordRevisionAdapter
10
+ from .task import TaskAdapter
@@ -0,0 +1,59 @@
1
+ from swimlane.core.cache import check_cache
2
+ from swimlane.core.resolver import SwimlaneResolver
3
+ from swimlane.core.resources.app import App
4
+ from swimlane.utils import one_of_keyword_only
5
+
6
+
7
+ class AppAdapter(SwimlaneResolver):
8
+ """Handles retrieval of Swimlane App resources"""
9
+
10
+ @check_cache(App)
11
+ @one_of_keyword_only('id', 'name')
12
+ def get(self, key, value):
13
+ """Get single app by one of id or name
14
+
15
+ Supports resource cache
16
+
17
+ Keyword Args:
18
+ id (str): Full app id
19
+ name (str): App name
20
+
21
+ Returns:
22
+ App: Corresponding App resource instance
23
+
24
+ Raises:
25
+ TypeError: No or multiple keyword arguments provided
26
+ ValueError: No matching app found on server
27
+ ValueError: The lookup value is empty or None
28
+ """
29
+ if not value:
30
+ raise ValueError('The value provided for the key "{0}" cannot be empty or None'.format(key))
31
+
32
+ if key == 'id':
33
+ # Server returns 204 instead of 404 for a non-existent app id
34
+ response = self._swimlane.request('get', 'app/{}'.format(value))
35
+ if response.status_code == 204:
36
+ raise ValueError('No app with id "{}"'.format(value))
37
+
38
+ return App(
39
+ self._swimlane,
40
+ response.json()
41
+ )
42
+ else:
43
+ # Workaround for lack of support for get by name
44
+ # Holdover from previous driver support, to be fixed as part of 3.x
45
+ for app in self.list():
46
+ if value and value == app.name:
47
+ return app
48
+
49
+ # No matching app found
50
+ raise ValueError('No app with name "{}"'.format(value))
51
+
52
+ def list(self):
53
+ """Retrieve list of all apps
54
+
55
+ Returns:
56
+ :class:`list` of :class:`~swimlane.core.resources.app.App`: List of all retrieved apps
57
+ """
58
+ response = self._swimlane.request('get', 'app')
59
+ return [App(self._swimlane, item) for item in response.json()]
@@ -0,0 +1,49 @@
1
+ import math
2
+
3
+ from swimlane.core.cache import check_cache
4
+ from swimlane.core.resolver import AppResolver
5
+ from swimlane.core.resources.app_revision import AppRevision
6
+ from swimlane.utils import one_of_keyword_only
7
+
8
+
9
+ class AppRevisionAdapter(AppResolver):
10
+ """Handles retrieval of Swimlane App Revision resources"""
11
+
12
+ def get_all(self):
13
+ """
14
+ Gets all app revisions.
15
+
16
+ Returns:
17
+ AppRevision[]: Returns all AppRevisions for this Adapter's app.
18
+ """
19
+ raw_revisions = self._swimlane.request('get', 'app/{0}/history'.format(self._app.id)).json()
20
+ return [AppRevision(self._swimlane, raw) for raw in raw_revisions]
21
+
22
+ def get(self, revision_number):
23
+ """
24
+ Gets a specific app revision.
25
+
26
+ Supports resource cache
27
+
28
+ Keyword Args:
29
+ revision_number (float): App revision number
30
+
31
+ Returns:
32
+ AppRevision: The AppRevision for the given revision number.
33
+ Raises: When revision is not an integer, a float NOT ending in ".0", or is less than 1
34
+ """
35
+
36
+ if isinstance(revision_number, (int, float)):
37
+ if revision_number > 0 and revision_number % math.floor(revision_number) == 0:
38
+ key_value = AppRevision.get_unique_id(self._app.id, revision_number)
39
+ return self.__get(app_id_revision=key_value)
40
+
41
+ raise ValueError('The revision number must be a positive whole number greater than 0')
42
+
43
+ @check_cache(AppRevision)
44
+ @one_of_keyword_only('app_id_revision')
45
+ def __get(self, key, value):
46
+ """Underlying get method supporting resource cache."""
47
+ app_id, revision_number = AppRevision.parse_unique_id(value)
48
+ app_revision_raw = self._swimlane.request('get', 'app/{0}/history/{1}'.format(app_id, revision_number)).json()
49
+ return AppRevision(self._swimlane, app_revision_raw)
@@ -0,0 +1,84 @@
1
+ import pendulum
2
+
3
+ from swimlane.core.resolver import SwimlaneResolver
4
+ from swimlane.utils.version import requires_swimlane_version
5
+ from swimlane.utils.list_validator import validate_str_list
6
+ from swimlane.utils.str_validator import validate_str
7
+
8
+
9
+ class HelperAdapter(SwimlaneResolver):
10
+ """Adapter providing any miscellaneous API calls not better suited for another adapter"""
11
+
12
+ @requires_swimlane_version('2.15')
13
+ def add_record_references(self, app_id, record_id, field_id, target_record_ids):
14
+ """Bulk operation to directly add record references without making any additional requests
15
+
16
+ Warnings:
17
+ Does not perform any app, record, or target app/record validation
18
+
19
+ Args:
20
+ app_id (str): Full App ID string
21
+ record_id (str): Full parent Record ID string
22
+ field_id (str): Full field ID to target reference field on parent Record string
23
+ target_record_ids (List(str)): List of full target reference Record ID strings
24
+ """
25
+ validate_str_list(target_record_ids, "target_record_ids")
26
+
27
+ self._swimlane.request(
28
+ 'post',
29
+ 'app/{0}/record/{1}/add-references'.format(app_id, record_id),
30
+ json={
31
+ 'fieldId': field_id,
32
+ 'targetRecordIds': target_record_ids
33
+ }
34
+ )
35
+
36
+ def add_comment(self, app_id, record_id, field_id, message, rich_text=False):
37
+ """Directly add a comment to a record without retrieving the app or record first
38
+
39
+ Warnings:
40
+ Does not perform any app, record, or field ID validation
41
+
42
+ Args:
43
+ app_id (str): Full App ID string
44
+ record_id (str): Full parent Record ID string
45
+ field_id (str): Full field ID to target reference field on parent Record string
46
+ message (str): New comment message body
47
+ rich_text (bool): Declare the message as being rich text, default is False
48
+ """
49
+ validate_str(app_id, 'app_id')
50
+ validate_str(record_id, 'record_id')
51
+ validate_str(field_id, 'field_id')
52
+ validate_str(message, 'message')
53
+
54
+ if not isinstance(rich_text, bool):
55
+ raise ValueError('rich_text must be a boolean value.')
56
+
57
+ self._swimlane.request(
58
+ 'post',
59
+ 'app/{0}/record/{1}/{2}/comment'.format(
60
+ app_id,
61
+ record_id,
62
+ field_id
63
+ ),
64
+ json={
65
+ 'message': message,
66
+ 'isRichText': rich_text,
67
+ 'createdDate': pendulum.now().to_rfc3339_string()
68
+ }
69
+ )
70
+
71
+ def check_bulk_job_status(self, job_id):
72
+ """Check status of bulk_delete or bulk_modify jobs
73
+ .. versionadded:: 2.17.0
74
+ Args:
75
+ job_id (str): Job ID
76
+
77
+ Returns:
78
+ :class:`list` of :class:`dict`: List of dictionaries containing job history
79
+
80
+ """
81
+
82
+ validate_str(job_id, 'job_id')
83
+
84
+ return self._swimlane.request('get', "logging/job/{0}".format(job_id)).json()