AtlassianJiraServiceManagement

Use this integration to manage Jira objects and attach files to Jira objects from Cortex XSOAR.

IT Services · Atlassian Jira Service Management

Details

IDAtlassianJiraServiceManagement
ProviderAtlassian
CategoryIT Services
From Version6.10.0
Docker Imagedemisto/python3:3.12.13.10116658
Supported ModulesAgentix XSIAM

README

Use this integration to manage Jira objects and attach files to Jira objects from Cortex XSOAR.
This integration was integrated and tested with version 5.4.15 of AtlassianJiraServiceManagement.

Configure Atlassian Jira Service Management in Cortex

Parameter Required
Server URL True
API Token True
Trust any certificate (not secure) False
Use system proxy settings False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

jira-asset-object-schema-list


List all object schemas.

Base Command

jira-asset-object-schema-list

Input

Argument Name Description Required
limit The maximum amount of schemas to retrieve. Cannot be used alongside the page and page_size arguments. Optional
all_results Whether to retrieve all object schemas with no limit. Possible values are: true, false. Optional

Context Output

Path Type Description
JiraAsset.Schema.ID Number The ID of the object schema.
JiraAsset.Schema.Name String The name of the object schema.
JiraAsset.Schema.Key String The key of the object schema.
JiraAsset.Schema.Status String The status of the object schema.
JiraAsset.Schema.Created Date The date in which this object schema was created.
JiraAsset.Schema.Updated Date The date in which this object schema was updated.
JiraAsset.Schema.ObjectCount Number The number of objects in the schema.
JiraAsset.Schema.ObjectTypeCount Number The number of different object types in the schema.

jira-asset-object-type-list


List all object types.

Base Command

jira-asset-object-type-list

Input

Argument Name Description Required
schema_id The schema from which the command will retrieve object types. Values can be received by running the jira-asset-object-schema-list command. Required
exclude Object types with that name will be excluded from the results. Optional
limit The maximum amount of object types to retrieve. Optional
all_results Whether to retrieve all object types with no limit. Possible values are: true, false. Optional

Context Output

Path Type Description
JiraAsset.ObjectType.ID Number The ID of the object type.
JiraAsset.ObjectType.Name String The name of the object type.
JiraAsset.ObjectType.Type Number The type of the object type.
JiraAsset.ObjectType.Position Number The position of the object type among other object types with the same level in the schema hierarchy.
JiraAsset.ObjectType.Created Date The date the object type was created.
JiraAsset.ObjectType.Updated Date The date the object type was last updated.
JiraAsset.ObjectType.ObjectCount Number The amount of objects listed under this object type.
JiraAsset.ObjectType.ObjectSchemaId Number The ID of the schema this object type is listed under.
JiraAsset.ObjectType.Inherited Boolean Whether this object type inherits from a parent object type.
JiraAsset.ObjectType.AbstractObjectType Boolean Is this object type abstract (can only be inherited).
JiraAsset.ObjectType.ParentObjectTypeInherited Boolean Whether this object type inherits from a parent object type.

jira-asset-object-type-attribute-list


List all attributes for an object type.

Base Command

jira-asset-object-type-attribute-list

Input

Argument Name Description Required
object_type_id The object type for which the command will retrieve attributes. Values can be received by running the jira-asset-object-type-list command. Required
is_editable Whether to only return attributes that can be edited. Possible values are: true, false. Optional
order_by_name Whether to sort the results by the attribute name. Possible values are: true, false. Optional
query Query to filter on available object type attributes. Filter attributes that start with the query. Optional
include_value_exist Should the response only include attributes where attribute values exists. Possible values are: true, false. Optional
exclude_parent_attributes Should the response exclude parent attributes. Possible values are: true, false. Optional
include_children Should the response include child attributes. Possible values are: true, false. Optional
order_by_required Should the response be ordered by the number of required attributes. Possible values are: true, false. Optional
limit The maximum amount of object types to retrieve. Optional
all_results Whether to retrieve all object types with no limit. Possible values are: true, false. Optional

Context Output

Path Type Description
JiraAsset.Attribute.ID Number The ID of the attribute.
JiraAsset.Attribute.Name String The name of the attribute.
JiraAsset.Attribute.Label Boolean Whether this attribute is used as the label for objects of that type.
JiraAsset.Attribute.Type String The type of the attribute (Default, Reference, User, Project etc.).
JiraAsset.Attribute.DefaultType.id Number ID for the sub-type of the default type.
JiraAsset.Attribute.DefaultType.name String Name for the sub-type of the default type.
JiraAsset.Attribute.Editable Boolean Whether this attribute is editable.
JiraAsset.Attribute.System Boolean Whether this attribute is a system attribute or a custom one.
JiraAsset.Attribute.Sortable Boolean Can the objects be sorted by that attribute.
JiraAsset.Attribute.Summable Boolean Can this attribute be summarized.
JiraAsset.Attribute.Indexed Boolean Is this attribute indexed.
JiraAsset.Attribute.MinimumCardinality Number The minimum amount of elements this attribute should populate.
JiraAsset.Attribute.MaximumCardinality Number The maximum amount of elements this attribute should populate.
JiraAsset.Attribute.Removable Boolean Whether this attribute can be removed from the object.
JiraAsset.Attribute.Hidden Boolean Whether this attribute is a hidden attribute.
JiraAsset.Attribute.IncludeChildObjectTypes Boolean Whether this attribute includes child object types.
JiraAsset.Attribute.UniqueAttribute Boolean Whether this attribute is unique to its object.
JiraAsset.Attribute.Options String Options for the attributes.
JiraAsset.Attribute.Position Number The position of the attribute in relation to other object attributes.

jira-asset-object-create


Create a new object of the specified object type ID. Either attributes or attributes_json must be provided.

Base Command

jira-asset-object-create

Input

Argument Name Description Required
object_type_id The object type for which a new object will be created. Required
attributes Provide this field if attributes_json is not provided. A key-value map of object attributes. The structure of the field is: { “attributeId1”: [ “value1”, “value2”] , “attributeId2”:[“value1”, “value2”] }. You can run the jira-asset-object-type-attribute-list command to retrieve the list of available attributes. Optional
attributes_json Provide this field if attributes is not provided. A JSON string of object attributes to be added to the new object. Take a look at https://docs.atlassian.com/assets/REST/10.7.0/#object-createObject for the structure of this field. You can use the jira-asset-object-type-attribute-list command to retrieve the list of available attributes. Optional

Context Output

Path Type Description
JiraAsset.Object.ID Number ID of the newly created object.
JiraAsset.Object.Label String The value of the attribute that is marked as Label for this object type.
JiraAsset.Object.ObjectKey String The auto-generated object key.
JiraAsset.Object.Avatar.objectId Number ID of the newly created object.
JiraAsset.Object.Created Date The date in which this object was created.
JiraAsset.Object.Updated Date The date in which this object was last updated.
JiraAsset.Object.HasAvatar Boolean Does this object have an Avatar.
JiraAsset.Object.Timestamp Date Epoch version of the object’s creation date.
JiraAsset.Object.Links.self String The link to view this object in the Jira web app.
JiraAsset.Object.Name String The name of the newly created object.

jira-asset-object-update


Updates an existing object of specified object type ID. Either attributes or attributes_json must be provided.

Base Command

jira-asset-object-update

Input

Argument Name Description Required
object_id ID of the object that will be updated. Required
attributes Provide this field if attributes_json is not provided. A key-value map of object attributes. The structure of the field is: { “attributeId1”: [ “value1”, “value2”] , “attributeId2”:[“value1”, “value2”] }. You can use the jira-asset-object-type-attribute-list command to retrieve the list of available attributes. Optional
attributes_json Provide this field if attributes is not provided. A JSON string of object attributes to be added to the new object. Take a look at https://docs.atlassian.com/assets/REST/10.7.0/#object-createObject for the structure of this field. You can use the jira-asset-object-type-attribute-list command to retrieve the list of available attributes. Optional

Context Output

There is no context output for this command.

jira-asset-object-delete


Deletes an existing object of the specified object ID.

Base Command

jira-asset-object-delete

Input

Argument Name Description Required
object_id ID of the object that will be deleted. Required

Context Output

There is no context output for this command.

jira-asset-object-get


Retrieves the object with the specified object ID.

Base Command

jira-asset-object-get

Input

Argument Name Description Required
object_id ID of the object that will be retrieved. Required

Context Output

Path Type Description
JiraAsset.Object.ID Number ID of the retrieved object.
JiraAsset.Object.Label String The value of the attribute that is marked as Label for this object type.
JiraAsset.Object.ObjectKey String The auto-generated object key.
JiraAsset.Object.Created Date The date in which this object was created.
JiraAsset.Object.Updated Date The date in which this object was last updated.
JiraAsset.Object.HasAvatar Boolean Does this object have an Avatar.
JiraAsset.Object.Timestamp Date Epoch version of the object’s creation date.
JiraAsset.Object.ExtendedInfo.openIssuesExists Boolean Does this object appear in any open issues.
JiraAsset.Object.ExtendedInfo.attachmentsExists Boolean Does this object have any attachments.
JiraAsset.Object.Links.self String The link to view this object in the Jira web app.
JiraAsset.Object.Name String The name of the object.

jira-asset-object-search


Searches for objects of the specified object type.

Base Command

jira-asset-object-search

Input

Argument Name Description Required
ql_query An aql query to search objects by. View https://support.atlassian.com/jira-service-management-cloud/docs/use-assets-query-language-aql/ for further details. Required
include_attributes Whether to include the attributes structure in the response. Possible values are: true, false. Optional
page Page number. Optional
page_size Use this argument or limit, but not both. Size of the page. Defaults to 50. Optional
limit Use this argument or a combination of page and page_size, but not both. Limit the number of entries returned by the command. Optional

Context Output

Path Type Description
JiraAsset.Object.ID Number ID of the retrieved object.
JiraAsset.Object.Label String The value of the attribute that is marked as Label for this object type.
JiraAsset.Object.ObjectKey String The auto-generated object key.
JiraAsset.Object.Created Date The date in which this object was created.
JiraAsset.Object.Updated Date The date in which this object was last updated.
JiraAsset.Object.HasAvatar Boolean Does this object have an Avatar.
JiraAsset.Object.Timestamp Date Epoch version of the object’s creation date.
JiraAsset.Object.Attributes.id Number The ID of the attribute.
JiraAsset.Object.Attributes.objectTypeAttributeId Number The ID of the attribute, not relative to the object type.
JiraAsset.Object.Attributes.objectAttributeValues.value String The value of the specific attribute for this object.
JiraAsset.Object.Attributes.objectAttributeValues.referencedType Boolean Whether this attribute is referenced by other types.
JiraAsset.Object.Attributes.objectAttributeValues.displayValue String The display value for this attribute.
JiraAsset.Object.Attributes.objectAttributeValues.searchValue String The search value for this attribute.
JiraAsset.Object.Attributes.objectId Number The ID of the object this attribute is assigned to.
JiraAsset.Object.Links.self String The link to view this object in the Jira web app.
JiraAsset.Object.Name String The name of the object.

jira-asset-attribute-json-create


Utility command used to create a JSON file with all attributes of the specified object type. All that is left is to fill in the values of each attribute.

Base Command

jira-asset-attribute-json-create

Input

Argument Name Description Required
object_type_id ID of the object type that attributes JSON will be created for. You could use the jira-asset-object-type-list command to find object types. Required
is_editable Whether to fetch only editable attributes. Possible values are: true, false. Optional
is_required Whether to fetch only required attributes. Possible values are: true, false. Optional

Context Output

Path Type Description
InfoFile.Name string File name.
InfoFile.EntryID string The entry ID of the report.
InfoFile.Size number File size.
InfoFile.Type string File type, e.g., “PE”.
InfoFile.Info string Basic information of the file.

jira-asset-comment-create


Creates a comment for the specified object.

Base Command

jira-asset-comment-create

Input

Argument Name Description Required
object_id ID of the object that the comment will be added to. You could use the jira-asset-object-search command to find objects. Required
comment Body of the comment that will be created. Required

Context Output

Path Type Description
JiraAsset.Comment.Created Date The date in which this comment was created.
JiraAsset.Comment.Updated Date The date in which this object was last updated.
JiraAsset.Comment.Id Number The ID of the newly created comment.
JiraAsset.Comment.Actor.AvatarUrl String URL to the avatar of the author of the comment.
JiraAsset.Comment.Actor.DisplayName String Display name of the author of the comment.
JiraAsset.Comment.Actor.Name String Name of the author of the comment.
JiraAsset.Comment.Actor.Key String Key of the author of the comment.
JiraAsset.Comment.Actor.RenderedLink String A link to the author’s Jira profile.
JiraAsset.Comment.Actor.IsDeleted Boolean Is the author of the comment deleted.
JiraAsset.Comment.Role Number The role of the author of the comment.
JiraAsset.Comment.Comment String The body of the comment that was newly added.
JiraAsset.Comment.CommentOutput String The body of the comment that was newly added.
JiraAsset.Comment.ObjectId Number The ID of the object the new comment was added to.
JiraAsset.Comment.CanEdit Boolean Whether the comment is editable.
JiraAsset.Comment.CanDelete Boolean Whether the comment can be deleted.

jira-asset-connected-ticket-list


Returns a list of all connected tickets for the specified object.

Base Command

jira-asset-connected-ticket-list

Input

Argument Name Description Required
object_id ID of the object for which connected tickets will be fetched. Required

Context Output

Path Type Description
JiraAsset.ConnectedTicket.Tickets.Key String Key for the connected ticket.
JiraAsset.ConnectedTicket.Tickets.ID Number The ID of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Reporter String The user who reported the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Created Date The date in which this connected ticket was created.
JiraAsset.ConnectedTicket.Tickets.Updated Date The date in which this connected ticket was last updated.
JiraAsset.ConnectedTicket.Tickets.Title String Title of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Status.Name String The name of the status of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Status.Colorname String The color of the connected tikcet.
JiraAsset.ConnectedTicket.Tickets.Type.Name String The type of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Type.Description String The description of the type of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Type.Iconurl String The URL to the icon representing the type of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Priority.Name String Priority of the connected ticket.
JiraAsset.ConnectedTicket.Tickets.Priority.Iconurl String The URL to the icon representing the priority of the connected ticket.
JiraAsset.ConnectedTicket.Allticketsquery String The query used to fetch the connected tickets.

jira-asset-comment-list


Returns a list of comments for the specified object.

Base Command

jira-asset-comment-list

Input

Argument Name Description Required
object_id ID of the object that comments will be retrieved for. You could use the jira-asset-object-search command to find objects. Required

Context Output

Path Type Description
JiraAsset.Comment.Created Date The date in which this comment was created.
JiraAsset.Comment.Updated Date The date in which this object was last updated.
JiraAsset.Comment.ID Number The ID of the newly created comment.
JiraAsset.Comment.Role Number The role of the author of the comment.
JiraAsset.Comment.Comment String The body of the comment that was newly added.
JiraAsset.Comment.CommentOutput String The body of the comment that was newly added.
JiraAsset.Comment.ObjectId Number The ID of the object the new comment was added to.
JiraAsset.Comment.CanEdit Boolean Whether the comment is editable.
JiraAsset.Comment.CanDelete unknown Whether the comment can be deleted.

jira-asset-attachment-add


Uploads a file attachment to the specified object.

Base Command

jira-asset-attachment-add

Input

Argument Name Description Required
object_id ID of the object that the file will be attached to. Required
entry_id ID of the file to upload. Required

Context Output

Path Type Description
JiraAsset.Attachment.ID Number ID of the attachment.
JiraAsset.Attachment.Author String Author of the attachment.
JiraAsset.Attachment.MimeType String The file’s MIME type.
JiraAsset.Attachment.Filename String The file’s name.
JiraAsset.Attachment.Filesize String The size of the file.
JiraAsset.Attachment.Created Date The date in which this attachment was created.
JiraAsset.Attachment.Comment String The comment attached to the attachment, if there is one.
JiraAsset.Attachment.CommentOutput String The comment output of the attachment, if there is one.
JiraAsset.Attachment.Url String URL to the attachment.

jira-asset-attachment-list


Returns a list of attachments for the specified object. You can also download the files in a zipped format.

Base Command

jira-asset-attachment-list

Input

Argument Name Description Required
object_id ID of the object that attachments will be fetched for. Required
download_file Whether to download the attachments for this object in a zipped format. Possible values are: true, false. Optional

Context Output

Path Type Description
JiraAsset.Attachment.ID Number ID of the attachment.
JiraAsset.Attachment.Author String Author of the attachment.
JiraAsset.Attachment.MimeType String The file’s MIME type.
JiraAsset.Attachment.Filename String The file’s name.
JiraAsset.Attachment.Filesize String The size of the file.
JiraAsset.Attachment.Created Date The date in which this attachment was created.
JiraAsset.Attachment.Comment String The comment attached to the attachment, if there is one.
JiraAsset.Attachment.CommentOutput String The comment output of the attachment, if there is one.
JiraAsset.Attachment.Url String URL to the attachment.

jira-asset-attachment-remove


Removes an attachment from a specific object.

Base Command

jira-asset-attachment-remove

Input

Argument Name Description Required
id ID of the attachment that will be deleted. Required

Context Output

Path Type Description
JiraAsset.Attachment.ID Number ID of the attachment that was deleted.
JiraAsset.Attachment.Author String Author of the attachment.
JiraAsset.Attachment.MimeType String The file’s MIME type.
JiraAsset.Attachment.Filename String The file’s name.
JiraAsset.Attachment.Filesize String The size of the file.
JiraAsset.Attachment.Created Date The date in which this attachment was created.
JiraAsset.Attachment.Comment String The comment attached to the attachment, if there is one.
JiraAsset.Attachment.CommentOutput String The comment output of the attachment, if there is one.
JiraAsset.Attachment.Url String URL to the attachment.

Configuration parameters

  • url — Server URL (required)
  • credentials — (required)
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings

Commands (15)

  • jira-asset-attachment-add

    Uploads a file attachment to the specified object.

  • jira-asset-attachment-list

    Returns a list of attachments for the specified object. You can also download the files in a zipped format.

  • jira-asset-attachment-remove

    Removes an attachment from a specific object.

  • jira-asset-attribute-json-create

    Utility command used to create a JSON file with all attributes of the specified object type. All that is left is to fill in the values of each attribute.

  • jira-asset-comment-create

    Creates a comment for the specified object.

  • jira-asset-comment-list

    Returns a list of comments for the specified object.

  • jira-asset-connected-ticket-list

    Returns a list of all connected tickets for the specified object.

  • jira-asset-object-create

    Create a new object of the specified object type ID. Either attributes or attributes_json must be provided.

  • jira-asset-object-delete

    Deletes an existing object of the specified object ID.

  • jira-asset-object-get

    Retrieves the object with the specified object ID.

  • jira-asset-object-schema-list

    List all object schemas.

  • jira-asset-object-search

    Searches for objects of the specified object type.

  • jira-asset-object-type-attribute-list

    List all attributes for an object type.

  • jira-asset-object-type-list

    List all object types.

  • jira-asset-object-update

    Updates an existing object of specified object type ID. Either attributes or attributes_json must be provided.

import io
import json
import os
import zipfile
from typing import Any

import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401

from CommonServerUserPython import *  # noqa

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"  # ISO8601 format with UTC, default in XSOAR
ON_PREM_URL_STRUCTURE = "{}/rest/assets/1.0/"
INTEGRATION_OUTPUTS_BASE_PATH = "JiraAsset"
""" CLIENT CLASS """


class Client(BaseClient):
    def __init__(self, base_url: str, api_key: str, verify: bool, proxy: bool):
        headers = {"Authorization": f"Bearer {api_key}", "Accept": "application/json"}
        super().__init__(base_url=base_url, verify=verify, proxy=proxy, headers=headers, ok_codes=(200, 201, 204, 404))

    def get_schema_list(self):
        return self._http_request(method="GET", url_suffix="/objectschema/list")

    def get_object_type_list(self, schema_id: str, exclude):
        url_suffix = f"objectschema/{schema_id}/objecttypes/flat"

        # build request params
        params = {"exclude": exclude} if exclude else {}

        return self._http_request(method="GET", url_suffix=url_suffix, params=params)

    def get_object_type_attributes(
        self,
        object_type_id: str,
        is_editable: bool = False,
        order_by_name: bool = False,
        query: str = None,
        include_value_exist: bool = False,
        exclude_parent_attributes: bool = False,
        include_children: bool = False,
        order_by_required: bool = False,
    ):
        url_suffix = f"objecttype/{object_type_id}/attributes"

        # build request params
        params = {
            "onlyValueEditable": is_editable,
            "orderByName": order_by_name,
            "query": query,
            "includeValueExist": include_value_exist,
            "excludeParentAttributes": exclude_parent_attributes,
            "includeChildren": include_children,
            "orderByRequired": order_by_required,
        }

        return self._http_request(method="GET", url_suffix=url_suffix, params=params)

    def get_object(self, object_id):
        """
        hr_headers = ['ID', 'Label', 'Type', 'Created']
        readable_output = get_object_readable_outputs([res])
        outputs = {k: v for k, v in res.items() if k != 'objectType' and k != 'avatar'}
        """
        res = self._http_request(method="GET", url_suffix=f"/object/{object_id}", resp_type="response")
        if res.status_code == 404:
            return None
        return res.json()

    def search_objects(self, ql_query: str, include_attributes: bool, page: int, page_size: int, limit: int = None):
        params = {
            "qlQuery": ql_query,
            "includeAttributes": include_attributes,
            "page": page,
            "resultsPerPage": limit if limit else page_size,
        }

        return self._http_request(method="GET", url_suffix="/aql/objects", params=params)

    def get_comment_list(self, object_id: str):
        return self._http_request(method="GET", url_suffix=f"/comment/object/{object_id}")

    def get_object_connected_tickets(self, object_id: str):
        return self._http_request(method="GET", url_suffix=f"/objectconnectedtickets/{object_id}/tickets")

    def get_object_attachment_list(self, object_id):
        res = self._http_request(method="GET", url_suffix=f"/attachments/object/{object_id}", resp_type="response")
        if res.status_code == 404:
            return None
        return res.json()

    def get_file(self, file_url):
        return self._http_request(method="GET", full_url=file_url, resp_type="response")

    def create_object(self, json_data):
        return self._http_request(method="POST", url_suffix="/object/create", json_data=json_data)

    def create_comment(self, object_id, comment):
        url_suffix = "/comment/create"
        json_data = {"objectId": object_id, "comment": comment}
        return self._http_request(method="POST", url_suffix=url_suffix, json_data=json_data)

    def send_file(self, object_id, file_path):
        return self._http_request(
            method="POST", url_suffix=f"/attachments/object/{object_id}", files={"file": (file_path, open(file_path, "rb"))}
        )

    def update_object(self, object_id: str, json_data: Dict[str, Any]):
        return self._http_request(method="PUT", url_suffix=f"/object/{object_id}", json_data=json_data)

    def delete_object(self, object_id: str):
        res = self._http_request(method="DELETE", url_suffix=f"/object/{object_id}", resp_type="response")
        if res.status_code == 404:
            return None
        return res.json()

    def remove_file(self, attachment_id):
        return self._http_request(method="DELETE", url_suffix=f"/attachments/{attachment_id}")


""" HELPER FUNCTIONS """


def pascal_case(s: str) -> str:
    """Convert a string to PascalCase."""
    words = s.split("_")
    return "".join(word[:1].upper() + word[1:] for word in words)


def convert_keys_to_pascal(objects: List[dict[str, Any]], key_mapping: Optional[dict[str, str]] = None) -> List[dict[str, Any]]:
    """
    Convert keys of objects in a list to PascalCase, with optional key mapping.

    Args:
        objects (list): List of dictionaries (objects) whose keys need conversion.
        key_mapping (dict): Dictionary containing original keys and expected output keys.

    Returns:
        list: List of dictionaries with keys converted to PascalCase or as per key_mapping.
    """
    if not key_mapping:
        key_mapping = {}

    converted_objects = []

    for obj in objects:
        converted_obj = {}
        for key, value in obj.items():
            if key in key_mapping:
                new_key = key_mapping[key]
            else:
                new_key = pascal_case(key)
            converted_obj[new_key] = value
        converted_objects.append(converted_obj)

    return converted_objects


def get_object_attribute_string_type(attribute: Dict[str, Any]) -> str:
    match attribute["type"]:
        case 0:
            return attribute["defaultType"]["name"]
        case 1:
            return "Object Reference"
        case 2:
            return "User"
        case 4:
            return "Group"
        case 6:
            return "Project"
        case 7:
            return "Status"
        case 8:
            return "Bitbucket Repository"
    return "Default"


def get_object_readable_outputs(objects: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    pascal_objects = convert_keys_to_pascal(objects, {"id": "ID"})
    readable_outputs = []
    for obj in pascal_objects:
        obj_type = obj["ObjectType"]["name"]
        output = {k: v for k, v in obj.items() if k not in ("ObjectType", "Avatar")}
        readable_output = {**output, "Type": obj_type}
        readable_outputs.append(readable_output)
    return readable_outputs


def clean_object_attributes(attributes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    string_typed_attributes = [{**attribute, "type": get_object_attribute_string_type(attribute)} for attribute in attributes]
    return [{k: v for k, v in attribute.items() if k != "objectType"} for attribute in string_typed_attributes]


def convert_attributes(attributes: Dict[str, List[str]]) -> List[Dict[str, Any]]:
    result = []
    for attribute_id, values in attributes.items():
        values = argToList(values)
        attribute_dict = {"objectTypeAttributeId": attribute_id, "objectAttributeValues": [{"value": value} for value in values]}
        result.append(attribute_dict)
    return result


def get_attributes_json_data(
    object_type_id: str, attributes: dict[str, Any] = None, attributes_json: str = None
) -> Dict[str, Any]:
    if not attributes and not attributes_json:
        raise ValueError("Either attributes or attributes_json must be provided.")
    elif attributes and attributes_json:
        raise ValueError("Only one of attributes or attributes_json must be provided.")

    if attributes:
        converted_attributes = convert_attributes(attributes)
    else:
        # cast is necessary for pre-commit. mypy isn't smart enough to know that by that point, attributes_json must be defined
        converted_attributes = json.loads(str(attributes_json)).get("attributes")

    return {"objectTypeId": object_type_id, "attributes": converted_attributes}


def parse_object_results(res: Dict[str, Any]) -> Dict[str, Any]:
    outputs = [{k: v for k, v in res.items() if k != "objectType"}]
    object_id = res.get("id")
    return {"outputs": outputs, "objectId": object_id}


""" COMMAND FUNCTIONS """


def test_module(client: Client) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param Client: client to use

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """
    try:
        client.get_schema_list()
        return "ok"
    except DemistoException as e:
        if "Forbidden" in str(e) or "Authorization" in str(e):  # TODO: make sure you capture authentication errors
            message = "Authorization Error: make sure API Key is correctly set"
        else:
            raise e
    return message


def jira_asset_object_schema_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Retrieves a list of Jira asset object schemas with an option to limit the number of results returned.

    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'limit': The maximum number of object schemas to return. Defaults to 50.
        - 'all_results': A boolean indicating whether to return all results or to respect the limit. Defaults to False.
    :return: A CommandResults object containing the list of object schemas as output, and a human-readable markdown table.
    """
    limit = args.get("limit", 50)
    all_results = argToBoolean(args.get("all_results", False))
    res = client.get_schema_list()
    object_schemas = res.get("objectschemas", [])
    key_mapping = {"id": "ID", "objectSchemaKey": "Key"}

    if not all_results:
        limit = int(limit)
        object_schemas = object_schemas[:limit]

    readable_outputs = convert_keys_to_pascal(object_schemas, key_mapping)
    hr_headers = ["ID", "Name", "Key", "Status", "Description", "Created"]
    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Schema",
        outputs_key_field="ID",
        outputs=object_schemas,
        readable_output=tableToMarkdown("Object Schemas", readable_outputs, headers=hr_headers, removeNull=True),
    )


def jira_asset_object_type_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Retrieves a list of Jira asset object types based on provided arguments.

    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'schema_id': The ID of the schema to retrieve object types from.
        - 'exclude': A string to specify object types that should be excluded from the results.
        - 'limit': The maximum number of object types to return. Defaults to 50.
        - 'all_results': A boolean indicating whether to return all results or to respect the limit. Defaults to False.
    :return: A CommandResults object containing the list of object types as output, and a human-readable markdown table.
    """
    schema_id = args.get("schema_id", "")
    exclude = args.get("exclude")
    limit = args.get("limit", 50)
    all_results = argToBoolean(args.get("all_results", False))

    if not schema_id:
        raise ValueError("schema_id is a required argument")

    # build outputs
    res = client.get_object_type_list(schema_id, exclude)
    outputs = [{k: v for k, v in ot.items() if k != "icon"} for ot in res]
    if not all_results:
        limit = int(limit)
        outputs = outputs[:limit]

    readable_outputs = convert_keys_to_pascal(list(outputs), {"id": "ID", "parentObjectTypeId": "ParentTypeID"})

    # build readable outputs
    hr_headers = ["ID", "Name", "ParentTypeID", "AbstractObjectType"]

    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.ObjectType",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Object Types", readable_outputs, headers=hr_headers, removeNull=True),
    )


def jira_asset_object_type_attribute_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Retrieves a list of attributes for a specific Jira asset object type with various filtering and sorting options.

    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_type_id': The ID of the object type to retrieve attributes for.
        - 'limit': The maximum number of attributes to return. Defaults to 50.
        - 'all_results': A boolean indicating whether to return all results or to respect the limit. Defaults to False.
        - Additional boolean arguments for filtering and sorting:
            * 'order_by_name'
            * 'include_value_exist'
            * 'exclude_parent_attributes'
            * 'include_children'
            * 'order_by_required'
    :return: A CommandResults object containing the list of attributes as output, and a human-readable markdown table.
    """
    object_type_id = args.get("object_type_id", "")
    limit = args.get("limit", 50)
    all_results = args.get("all_results", False)

    if not object_type_id:
        raise ValueError("object_type_id is a required argument")

    # build outputs
    res = client.get_object_type_attributes(
        object_type_id=object_type_id,
        order_by_name=argToBoolean(args.get("order_by_name", False)),
        query=args.get("query"),
        include_value_exist=argToBoolean(args.get("include_value_exist", False)),
        exclude_parent_attributes=argToBoolean(args.get("exclude_parent_attributes", False)),
        include_children=argToBoolean(args.get("include_children", False)),
        order_by_required=argToBoolean(args.get("order_by_required", False)),
    )

    outputs = clean_object_attributes(list(res))

    if not all_results:
        limit = int(limit)
        outputs = outputs[:limit]

    hr_headers = ["ID", "Name", "Type", "Description", "MinimumCardinality", "Editable"]
    readable_outputs = convert_keys_to_pascal(list(res), {"id": "ID"})
    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Attribute",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Object Types", readable_outputs, headers=hr_headers, removeNull=True),
    )


def jira_asset_object_create_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Creates a new Jira asset object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_type_id': The ID of the object type to create the object for.
        - 'attributes': A list of attribute names and values to set for the object.
        - 'attributes_json': A JSON string of attributes to set for the object.
    :return: A CommandResults object containing the created object as output, and a human-readable markdown table.
    """
    object_type_id = args.get("object_type_id", "")
    attributes = args.get("attributes")
    attributes_json = args.get("attributes_json")

    if not object_type_id:
        raise ValueError("object_type_id is a required argument")

    json_data = get_attributes_json_data(object_type_id, attributes, attributes_json)
    res = client.create_object(json_data)
    outputs, object_id = parse_object_results(res).values()

    return CommandResults(
        outputs=outputs,
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Object",
        readable_output=f"Object created successfully with ID: {object_id}",
    )


def jira_asset_object_update_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Updates an existing Jira asset object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to update.
        - 'attributes': A list of attribute names and values to set for the object.
        - 'attributes_json': A JSON string of attributes to set for the object.
    :return: A CommandResults object containing the updated object as output, and a human-readable markdown table.
    """
    attributes = args.get("attributes")
    attributes_json = args.get("attributes_json")
    object_id = args.get("object_id", "")

    if not object_id:
        raise ValueError("object_id is a required argument")

    jira_object = client.get_object(object_id)

    if not jira_object:
        return CommandResults(readable_output=f"Object with id: {object_id} does not exist")

    object_type_id = jira_object.get("objectType").get("id")
    json_data = get_attributes_json_data(object_type_id, attributes, attributes_json)
    res = client.update_object(object_id, json_data)
    _, object_id = parse_object_results(res).values()

    return CommandResults(readable_output=f"Object {object_id} updated successfully")


def jira_asset_object_delete_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Deletes an existing Jira asset object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to delete.
    :return: A CommandResults object containing a human-readable message.
    """
    object_id = args.get("object_id", "")
    if not object_id:
        raise ValueError("object_id is a required argument")

    res = client.delete_object(object_id)
    if not res:
        return CommandResults(readable_output=f"Object with id: {object_id} does not exist")

    return CommandResults(readable_output=f"Object with id: {object_id} was deleted successfully")


def jira_asset_object_get_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Retrieves an existing Jira asset object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to retrieve.
    :return: A CommandResults object containing the object as output, and a human-readable markdown table.
    """
    object_id = args.get("object_id", "")

    if not object_id:
        raise ValueError("object_id is a required argument")

    res = client.get_object(object_id)
    if not res:
        return CommandResults(readable_output=f"Object with id: {object_id} does not exist")
    hr_headers = ["ID", "Label", "Type", "Created"]
    readable_output = get_object_readable_outputs([res])
    outputs = {k: v for k, v in res.items() if k != "objectType" and k != "avatar"}

    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Object",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Object", readable_output, headers=hr_headers, removeNull=True),
    )


def jira_asset_object_search_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Searches for Jira asset objects.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'ql_query': The query string to search for.
        - 'include_attributes': Whether to include the object's attributes in the response.
        - 'page': The page number to retrieve.
        - 'page_size': The number of objects to retrieve per page.
        - 'limit': The maximum number of objects to retrieve.
    :return: A CommandResults object containing the objects as output, and a human-readable markdown table.
    """
    # build request params
    ql_query = args.get("ql_query", "")
    include_attributes = argToBoolean(args.get("include_attributes", False))
    page = int(args.get("page", 1))
    page_size = int(args.get("page_size", 50))
    limit = args.get("limit")

    if not ql_query:
        raise ValueError("ql_query is a required argument")

    # build outputs
    res = client.search_objects(ql_query, include_attributes, page, page_size, limit)
    objects = res.get("objectEntries", [])
    if not objects:
        return CommandResults(readable_output="No objects found.")
    hr_headers = ["ID", "Label", "Type", "ObjectKey"]
    readable_output = get_object_readable_outputs(objects)
    outputs = [{k: v for k, v in obj.items() if k != "objectType" and k != "avatar"} for obj in objects]
    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Object",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Object", readable_output, headers=hr_headers, removeNull=True),
    )


def jira_asset_attribute_json_create_command(client: Client, args: Dict[str, Any]) -> tuple[dict, CommandResults]:
    """
    Creates a Jira asset attribute JSON object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_type_id': The ID of the object type to create the attribute JSON for.
        - 'is_required': Whether to include only required attributes in the response.
        - 'is_editable': Whether to include only editable attributes in the response.
    :return: A CommandResults object containing the attribute JSON as a file entry, and a human-readable json string.
    """
    # build request params
    object_type_id = args.get("object_type_id", "")
    is_editable = args.get("is_editable", False)

    if not object_type_id:
        raise ValueError("object_type_id is a required argument")

    # build outputs
    res = client.get_object_type_attributes(object_type_id=object_type_id, is_editable=is_editable)
    if args.get("is_required"):
        res = [attribute for attribute in res if int(attribute.get("minimumCardinality")) > 0]

    outputs = {
        "attributes": [
            {"objectTypeAttributeId": attribute.get("id"), "objectAttributeValues": [{"value": ""}]} for attribute in res
        ]
    }

    hr_command_results = CommandResults(readable_output=json.dumps(outputs))
    file_entry = fileResult(filename="attributes.json", data=json.dumps(outputs, indent=2), file_type=EntryType.ENTRY_INFO_FILE)
    return file_entry, hr_command_results


def jira_asset_comment_create_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Creates a Jira asset comment.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to create the comment for.
        - 'comment': The comment to add to the object.
    :return: A CommandResults object containing the comment as output, and a confirmation message.
    """
    object_id = args.get("object_id")
    comment = args.get("comment")
    res = client.create_comment(object_id, comment)

    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Comment",
        outputs_key_field="ID",
        outputs=res,
        readable_output=f"Comment was added successfully to object with id: {object_id}",
    )


def jira_asset_comment_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Lists Jira asset comments.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to retrieve comments for.
    :return: A CommandResults object containing the comments as output, and a human-readable markdown table.
    """
    object_id = args.get("object_id", "")
    if not object_id:
        raise ValueError("object_id is a required argument")

    res = client.get_comment_list(object_id)
    readable_outputs = convert_keys_to_pascal(list(res), {"id": "ID"})
    outputs = [{k: v for k, v in output.items() if k != "actor"} for output in res]
    hr_headers = ["ID", "Comment"]

    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Comment",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Comments", readable_outputs, headers=hr_headers, removeNull=True),
    )


def jira_asset_connected_ticket_list_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Lists Jira asset connected tickets.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to retrieve connected tickets for.
    :return: A CommandResults object containing the connected tickets as output, and a human-readable markdown table.
    """
    object_id = args.get("object_id", "")
    if not object_id:
        raise ValueError("object_id is a required argument")
    res = client.get_object_connected_tickets(object_id)
    outputs = list(res.get("tickets"))
    hr_headers = ["ID", "Title", "Status", "Type"]
    readable_output = [
        {
            "Status": output.get("status", {}).get("name"),
            "Type": output.get("type", {}).get("name"),
            "Title": output.get("title", {}),
        }
        for output in outputs
    ]
    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.ConnectedTicket",
        outputs_key_field="ID",
        outputs=outputs,
        readable_output=tableToMarkdown("Connected Tickets", readable_output, headers=hr_headers, removeNull=True),
    )


def jira_asset_attachment_add_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Adds a Jira asset attachment to a specific object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to add the attachment to.
        - 'entry_id': The entry ID of the file to add as an attachment.
    :return: A CommandResults object containing the attachment as output, and a confirmation message.
    """
    object_id = args.get("object_id")
    entry_id = args.get("entry_id")
    file_path = demisto.getFilePath(entry_id)
    demisto.debug(f"File path: {file_path}")
    res = client.send_file(object_id=object_id, file_path=file_path.get("path"))
    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Attachment",
        outputs_key_field="ID",
        outputs=res,
        readable_output=f"Attachment was added successfully to object with id: {object_id}",
    )


def jira_asset_attachment_list_command(client: Client, args: dict[str, Any]) -> List[CommandResults | dict] | CommandResults:
    """
    Lists Jira asset attachments for a specific object.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'object_id': The ID of the object to retrieve attachments for.
        - 'download_file': Whether to download the attachments.
    :return: A CommandResults object containing the attachments as output, and a human-readable markdown table if download_file is
        False. If download_file is True, a list of CommandResults objects containing the attachments as output, and a fileResult
        object.
    """
    object_id = args.get("object_id")
    download_file = argToBoolean(args.get("download_file", False))
    res = client.get_object_attachment_list(f"/attachments/object/{object_id}")
    if not res:
        return CommandResults(readable_output="No attachments found.")
    readable_outputs = convert_keys_to_pascal(list(res), {"id": "ID"})
    hr_header = ["ID", "Filename", "Filesize", "Comment"]

    command_results = CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Attachment",
        outputs_key_field="ID",
        outputs=res,
        readable_output=tableToMarkdown("Attachments", readable_outputs, headers=hr_header, removeNull=True),
    )

    if not download_file:
        return command_results

    files = []
    for attachment in res:
        attachment_url = attachment.get("url")
        file_response = client.get_file(attachment_url)
        file_name = attachment_url.split("/")[-1]
        i = 1
        while file_name in files:
            name, ext = file_name.split(".")
            file_name = f"{name}_{i}.{ext}"
            i += 1
        with open(file_name, "wb") as file:
            file.write(file_response.content)
            files.append(file.name)
    zip_data_buffer = io.BytesIO()
    with zipfile.ZipFile(zip_data_buffer, "w") as zipf:
        for curr_file_name in files:
            zipf.write(curr_file_name)
            os.remove(curr_file_name)

    data = zip_data_buffer.getvalue()
    file_result = fileResult("ObjectAttachments.zip", data, EntryType.ENTRY_INFO_FILE)
    return [file_result, command_results]


def jira_asset_attachment_remove_command(client: Client, args: dict[str, Any]) -> CommandResults:
    """
    Removes a Jira asset attachment.
    :param client: Client object for performing API requests.
    :param args: A dictionary of command arguments:
        - 'id': The ID of the attachment to remove.
    :return: A CommandResults object containing the attachment as output, and a confirmation message.
    """
    attachment_id = args.get("id")

    try:
        res = client.remove_file(attachment_id)
    except DemistoException as e:
        if e.res.status_code == 404:
            return CommandResults(readable_output=f"Attachment with id: {attachment_id} does not exist")
        else:
            raise e

    return CommandResults(
        outputs_prefix=f"{INTEGRATION_OUTPUTS_BASE_PATH}.Attachment",
        outputs_key_field="ID",
        outputs=res,
        readable_output=f'Attachment with id {res.get("id")} was successfully deleted',
    )


""" MAIN FUNCTION """

commands = {
    "jira-asset-object-schema-list": jira_asset_object_schema_list_command,
    "jira-asset-object-type-list": jira_asset_object_type_list_command,
    "jira-asset-object-type-attribute-list": jira_asset_object_type_attribute_list_command,
    "jira-asset-object-create": jira_asset_object_create_command,
    "jira-asset-object-update": jira_asset_object_update_command,
    "jira-asset-object-delete": jira_asset_object_delete_command,
    "jira-asset-object-get": jira_asset_object_get_command,
    "jira-asset-object-search": jira_asset_object_search_command,
    "jira-asset-attribute-json-create": jira_asset_attribute_json_create_command,
    "jira-asset-comment-create": jira_asset_comment_create_command,
    "jira-asset-comment-list": jira_asset_comment_list_command,
    "jira-asset-connected-ticket-list": jira_asset_connected_ticket_list_command,
    "jira-asset-attachment-add": jira_asset_attachment_add_command,
    "jira-asset-attachment-remove": jira_asset_attachment_remove_command,
    "jira-asset-attachment-list": jira_asset_attachment_list_command,
}


def main() -> None:
    command = demisto.command()
    args = demisto.args()
    params = demisto.params()
    api_key = params.get("credentials", {}).get("password", "")
    server_url = params.get("url")
    verify_certificate = not params.get("insecure", False)
    proxy = params.get("proxy", False)

    try:
        base_url = ON_PREM_URL_STRUCTURE.format(server_url)
        client = Client(base_url=base_url, verify=verify_certificate, proxy=proxy, api_key=api_key)

        if command == "test-module":
            # This is the call made when pressing the integration Test button.
            test_result = test_module(client)
            return_results(test_result)

        command_func = commands.get(command)
        if not command_func:
            raise NotImplementedError(f"""Command '{command}' is not implemented.""")

        result = command_func(client, args)
        return_results(result)

    except Exception as e:
        demisto.debug(f"The integration context_data is {get_integration_context()}")
        return_error(f"Failed to execute {demisto.command()} command.\nError:\n{traceback.format_exc()}\nException is: {e!s}")


""" ENTRY POINT """

if __name__ in ("__main__", "__builtin__", "builtins"):
    main()