Jira V3

Use the Jira integration to manage issues, create Cortex XSOAR incidents from Jira projects, and mirror issues to existing issue incidents in Cortex XSOAR. The integration now supports both OnPrem, and Cloud instances.

Case Management · Atlassian Jira

Details

IDJira V3
ProviderAtlassian
CategoryCase Management
From Version6.8.0
Docker Imagedemisto/btfl-soup:1.0.1.10120494
Supported ModulesAgentix Cortex Cloud Cloud Runtime Security Cloud Posture Security XSIAM EDR

README

Use the Jira integration to manage issues and create Cortex XSOAR incidents from Jira projects. From Cortex XSOAR version 6.0 and above, the integration also mirrors issues to existing issue incidents in Cortex XSOAR. The integration now supports both OnPrem, and Cloud instances.
This integration was integrated and tested with: Jira Cloud Platform V3 (for Cloud), Jira Software Cloud (for Cloud), Jira Server Platform 9.6.0 (for OnPrem), Jira Software Server 9.6.0 (for OnPrem).

Some changes have been made that might affect your existing content.
If you are upgrading from a previous version of this integration, see Breaking Changes.

Configure Atlassian Jira V3 on Cortex XSOAR

  1. Navigate to Settings > Integrations > Servers & Services.
  2. Search for Atlassian Jira V3.
  3. Authentication: Basic Authentication, Personal Access Token, and OAuth 2.0 are used for Jira Cloud and On-prem. Read the Authentication process in order to configure your instance
  4. Click Add instance to create and configure a new integration instance.

    Parameter Description Required
    Server URL The base URL. For a Cloud instance, use the default URL as configured. For an OnPrem instance, use your respective domain. True
    Cloud ID This field is used to configure the Jira Cloud.  
    User name used only for basic authentication method False
    API key used only for basic authentication method False
    Callback URL used only for OAuth 2.0 method False
    Client ID used only for OAuth 2.0 method False
    Client Secret used only for OAuth 2.0 method False
    Personal Access Token used only for the Personal Access Token method False
    Query (in JQL) for fetching incidents The field that was selected in the “Issue Field to fetch by” can’t be used. in the query. False
    Issue Field to fetch by This is how the field (e.g, created date) is applied to the query: created >= {created date in last run} ORDER BY created ASC False
    Issue index to start fetching incidents from This parameter is dismissed if “id” is not chosen in “Issue Field to Fetch by”. This will only fetch Jira issues that are part of the same project as the issue that is configured in this parameter. If this value is 0, then the fetch mechanism will automatically start the fetch from the smallest ID with respect to the fetch query. False
    Trust any certificate (not secure)   False
    Use system proxy settings   False
    Fetch incidents   False
    Incident type   False
    Close Mirrored XSOAR Incident When selected, marking the Jira issue as resolved will be mirrored in Cortex XSOAR, and will close the corresponding incident. Please make sure the `Resolved` field in your Jira instance is configured in order for this to work properly, if not, it will rely on if the status of the issue is changed to `Done`. False
    Mirroring Direction   False
    Attachment Entry Tag to Jira Add this tag to an entry to mirror it as an attachment to Jira. False
    Attachment Entry Tag from Jira Add this tag to an entry to mirror it as an attachment from Jira. False
    Comment Entry Tag to Jira Add this tag to an entry to mirror it as a comment in Jira. False
    Comment Entry Tag from Jira Add this tag to an entry to mirror it as a comment from Jira. False
    Fetch comments Fetch comments for a Jira ticket. False
    Fetch attachments Fetch attachments for a Jira ticket. False
    Max incidents per fetch The maximum allowed value is dictated by the Jira property ‘jira.search.views.default.max’. If you specify a value that is higher than this number, your search results will be truncated. Default value is 50. False
    Time range for initial data fetch The time range to consider for the initial data fetch in format: <number> <unit>. This parameter is only relevant when selecting to fetch by created time, or updated time. Default is 3 days. For example: 2 minutes, 2 hours, 2 days, 2 months, 2 years False
  5. Check Authorization Flow In Cortex XSOAR in order to authenticate and test the connection.

Configure only one of the following fields:

  • Cloud ID - Used for Jira Cloud instance.
  • OnPrem - Leave the Cloud ID empty, and fill in the rest of the fields.
Cloud ID
  1. Go to your Admin page.
  2. Click the Products tab on the top banner and choose the appropriate site under Sites and Products on the left side bar. Your Cloud ID will appear in the URL:
    https://admin.atlassian.com/s/{cloud_id}/users

Authentication

Basic Authentication

Leave the Client ID, Client Secret and Personal Access Token fields empty and fill in the following fields:

  • User name - Enter your user email.
  • API key - Enter the API token. To generate API token, see here
Basic Authentication permissions

Ensure that you possess the necessary permissions by navigating to Project Settings > Permissions in the project.
Locate permissions for the tasks listed below:

  • Browse projects
  • Create issues
  • Edit issues
  • Delete issues
  • Transition issues
  • Create attachments
  • Add comments
  • Link issues

Personal Access Token Authentication

Leave the User name, API key, Client ID and Client Secret empty and fill in the following fields:

  • Personal Access Token - Enter the Personal Access Token you created for a user, see here

OAuth 2.0

For both instances (Cloud ID & OnPrem), it is advised to use the https://oproxy.demisto.ninja/authcode Callback URL. The OProxy URL is a client side only web page which provides an easy interface to copy the obtained auth code from the authorization response to the integration configuration in the authorization flow steps. Optionally, if you don’t want to use the OProxy URL, you can use a localhost URL on a port that is not used locally on your machine. For example: http://localhost:9004. You will then need to copy the code from the URL address bar in the response (see OAuth 2.0 authorization Flow In Cortex XSOAR).

Cloud authentication

Go to your Developer console page, and choose the App you want to integrate with your instance. It must be of type OAuth 2.0. For creating a new app with type OAuth 2.0, click Create and choose OAuth 2.0 integration and follow the steps.

Callback URL
  1. Go to the Authorization tab, and click Add on the authorization with type of OAuth 2.0 (3LO).
  2. Insert a Callback URL.
Client ID, Client Secret
  1. Go to the Settings tab.
  2. Copy the Client ID and Secret to the Client ID and Client Secret fields, respectively.
Cloud Scopes

The integration uses the offline_access scope, in order to retrieve refresh tokens.

Classic Scopes
  • read:jira-work
  • read:jira-user
  • write:jira-work
Granular Scopes
  • read:jql:jira
  • read:issue-details:jira
  • write:board-scope:jira-software
  • read:board-scope:jira-software
  • read:sprint:jira-software
  • read:epic:jira-software
  • write:sprint:jira-software

OnPrem authentication

  1. Log in to Jira as a user with Jira Administrator permissions.
  2. Click the Jira Administration tab (the gear icon found in the top right corner) and click Applications.
  3. To create a new Application link, which will be used to integrate Cortex XSOAR with Jira:
    a. Click Application links under Integrations, found on the left side menu.
    b. Click Create link and choose External application with the Incoming direction.
  4. Fill in the required details as explained in the page, by inserting a Callback URL, and choosing the permission Write.
  5. Once the link is created, you will be able to see Client ID, and the Client secret, which are required in the configuration screen. Copy these values and paste them into the respective fields in the configuration screen.
OnPrem Scopes

Write

OAuth 2.0 authorization Flow In Cortex XSOAR

  1. Create the authentication application as explained in the OAuth 2.0 section.
  2. Run the command !jira-oauth-start, where you will be presented with a URL to authenticate yourself.
  3. After authenticating, you will be redirected to the configured Callback URL, where you will retrieve the authorization code provided as a query parameter called code.
  4. Insert the retrieved authorization code as an argument to the !jira-oauth-complete command.
  5. Run the !jira-oauth-test to test the connection of the instance.
Authenticating using custom callback URL

Custom callback URL

Authenticating using the oproxy callback URL

Oproxy callback URL

Fetch Incidents

When you enable incidents fetching, you have the option to configure a query which will be used to fetch Jira issues as incidents in Cortex XSOAR. The query uses one of the following fields in order to progressively fetch incidents:

  • id of Jira issues
  • created time of Jira issues
  • updated time of Jira issues (using this field may cause duplicates, so be wary)

If created time, or updated time is selected when fetching incidents for the first time, then the Time range for initial data fetch argument is used in the fetch query.
By default, 50 issues are fetched for each call.
If Fetch comments is enabled, The fetched incidents will include the comments in the Jira issue, and preview them in the form of a table in the incident info tab.
If Fetch attachments is enabled, The fetched incidents will include the attachments in the Jira issue.

Note: Changing certain incidents fetching configuration parameters, such as Issue Field to fetch by on the fly may result in unintended side effects and behaviors, including duplicate or missing incidents. It is recommended to reset the “last run” timestamp before applying such changes or, preferably, configure a new integration instance with the new fetch configuration.

Commands

You can execute these commands from the Cortex XSOAR 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.

Notes:

  1. For OnPrem, all the commands use the WRITE scope, for Cloud, the required scopes for each command will be mentioned under the name of the command.
  2. For the commands that are shared between Jira V2 and V3, we still support the arguments from V2, but are configured to be hidden, and are prioritized when supplied to the commands, to not break backwards compatibility.

jira-issue-get-attachment


Download attachments for a specific issue (Will download a file to War Room).

Scope: read:jira-work

Base Command

jira-issue-get-attachment

Input

Argument Name Description Required
attachment_id A CSV list of the attachments’ ID, which can be retrieved using the jira-get-issue command. Required

Context Output

Path Type Description
InfoFile.Name String The file name.
InfoFile.EntryID String The EntryID of the file in the War Room.
InfoFile.Size Number The size of the file (in bytes).
InfoFile.Type String The file type, as determined by libmagic (same as displayed in file entries).
InfoFile.Extension String The file extension.
InfoFile.Info String Basic information of the file.

Command example

!jira-issue-get-attachment attachment_id=16477

Context Example

{
    "InfoFile": {
        "EntryID": "10906@33e912c0-76a9-4a08-8cc6-1b20b1980b20",
        "Extension": "pdf",
        "Info": "application/pdf",
        "Name": "dummy.pdf",
        "Size": 13264,
        "Type": "PDF document, version 1.4, 1 pages (zip deflate encoded)"
    }
}

Human Readable Output

jira-delete-issue


Deletes an issue in Jira. Issue’s sub-tasks will also be deleted if there are any.

Scope: write:jira-work

Base Command

jira-delete-issue

Input

Argument Name Description Required
issueIdOrKey Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional

Context Output

There is no context output for this command.

Command example

!jira-delete-issue issue_key=PROJECTKEY-114

Human Readable Output

Issue deleted successfully.

jira-get-specific-field


Gets specific fields from a Jira issue and adds it to context dynamically.

Scope: read:jira-work

Base Command

jira-get-specific-field

Input

Argument Name Description Required
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
issueId Deprecated. Please use issue_id or issue_key. Optional
field Deprecated. Please use fields. Optional
fields The fields to retrieve from the issue. For example field=”customfield_164,labels”. Optional

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Assignee String The user assigned to the ticket.
Ticket.Creator String The user who created the ticket.
Ticket.Summary String The ticket summary.
Ticket.Status String The ticket status.

Command example

!jira-get-specific-field issue_key=PROJECTKEY-35 fields="watches,rank"

Context Example

{
    "Ticket": {
        "Assignee": "Example User(example@example.com)",
        "Attachments": [
            {
                "created": "2023-05-04T21:11:51.286+0300",
                "filename": "dummy_attachment_content.txt",
                "id": "16475",
                "size": 13264
            },
            {
                "created": "2023-05-04T21:32:57.042+0300",
                "filename": "dummy.pdf",
                "id": "16478",
                "size": 13264
            }
        ],
        "Components": [
            "dummy-comp",
            "Integration"
        ],
        "Created": "2023-03-01T11:34:49.730+0200",
        "Creator": "Example User(example@example.com)",
        "Description": "Edited subbscription",
        "DueDate": "2023-01-01",
        "Id": "21487",
        "Key": "PROJECTKEY-35",
        "Labels": [
            "label1",
            "label2"
        ],
        "LastSeen": "2023-05-08T19:02:34.151+0300",
        "LastUpdate": "2023-05-08T19:07:44.900+0300",
        "Priority": "Highest",
        "ProjectName": "Company Snoozing App",
        "Status": "Backlog",
        "Summary": "Edited Summary",
        "customfield_10019": {
            "issueFieldDisplayName": "Rank",
            "rawData": "0|i00kjb:"
        },
        "watches": {
            "issueFieldDisplayName": "Watchers",
            "rawData": {
                "isWatching": true,
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/PROJECTKEY-35/watchers",
                "watchCount": 1
            }
        }
    }
}

Human Readable Output

Issue PROJECTKEY-35

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
Example User(example@example.com) 2023-03-01T11:34:49.730+0200 Example User(example@example.com) Edited subbscription 2023-01-01 21487 Story PROJECTKEY-35 label1,
label2
Highest Company Snoozing App Example User(example@example.com) Backlog Edited Summary https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21487

jira-issue-add-link


Creates (or updates) an issue link.

Scope: write:jira-work

Base Command

jira-issue-add-link

Input

Argument Name Description Required
globalId Deprecated. Please use global_id. Optional
global_id If the global ID is provided and a remote issue link exists with that global ID, the remote issue link is updated. Optional
relationship The object relationship to the issue, for example: causes. Optional
url The URL link. Required
title The link title. Required
summary The link summary. Optional
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
application_type The application type of the linked remote application. For example “com.atlassian.confluence”. Optional
applicationType Deprecated. Please use application_type. Optional
application_name The application name of the linked remote application. For example “My Confluence Instance”. Optional
applicationName Deprecated. Please use application_name. Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-add-link title="Demo" url="https://demisto.com" issue_key=PROJECTKEY-35

Human Readable Output

Remote Issue Link

id ticket_link
16506 https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/PROJECTKEY-35/remotelink/16506

jira-get-id-by-attribute


Gets the account ID for a given user attribute.

Scope: read:jira-user

Base Command

jira-get-id-by-attribute

Input

Argument Name Description Required
attribute The user attribute value. For Jira Cloud, this value can be the display name, or email address, and for Jira OnPrem, this value can be the display name, email address, or username. Required
max_results The maximum number of users to fetch when searching for a matching user (default is 50). The maximum allowed value is dictated by the Jira property ‘jira.search.views.default.max’. If you specify a value greater than this number, your search results are truncated. Default is 50. Optional

Context Output

Path Type Description
Jira.User.Attribute String The user’s attribute.
Jira.User.AccountId String The user’s account ID.

Command example

!jira-get-id-by-attribute attribute=Tomer

Context Example

{
    "Jira": {
        "User": {
            "AccountId": "12345",
            "Attribute": "Tomer"
        }
    }
}

Human Readable Output

The account ID that holds the attribute Tomer: 12345

jira-issue-query


Queries Jira issues.

Scope: read:jira-work

Base Command

jira-issue-query

Input

Argument Name Description Required
query The JQL query string. Required
start_at The index (integer) of the first issue to return (0-based). Optional
startAt Deprecated. Please use start_at. Optional
max_results The maximum number of users to fetch when searching for a matching user (default is 50). The maximum allowed value is dictated by the Jira property ‘jira.search.views.default.max’. If you specify a value greater than this number, your search results are truncated. Optional
maxResults Deprecated. Please use max_results. Optional
headers Displays the headers in human readable format. Optional
fields A CSV list of fields to return (Using the name or ID of the fields) to context data. Supplying all will return all the issue fields (which will include nested values), except for the comments, which can be retrieved using the !jira-get-comments command. Optional
extraFields Deprecated. Please use fields. Optional

Context Output

Path Type Description
Jira.Query.nextPageToken String A token for fetching the next page of the query if such exists.
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Assignee String The user assigned to the ticket.
Ticket.Creator String The user who created the ticket.
Ticket.Summary String The ticket summary.
Ticket.Description String The ticket’s description.
Ticket.RawDescription String The ticket’s raw description.
Ticket.Labels Array The ticket’s labels.
Ticket.Components Array The ticket’s components.
Ticket.Status String The ticket status.
Ticket.Priority String The ticket priority.
Ticket.ProjectName String The ticket project name.
Ticket.DueDate Date The due date.
Ticket.Created Date The time the ticket was created.
Ticket.LastSeen Date The last time the ticket was viewed.
Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-issue-query query="status!=done" max_results=2 fields="watches,rank"

Context Example

{
    "Ticket": [
        {
            "Assignee": "",
            "Attachments": [
                {
                    "created": "2023-03-26T18:06:24.325+0300",
                    "filename": "DemistoContent-InstallContentEnvironment-160822-0023-68.pdf",
                    "id": "16426",
                    "size": 17815
                }
            ],
            "Components": [],
            "Created": "2023-03-01T11:59:18.202+0200",
            "Creator": "Example User(example@example.com)",
            "Description": "Dummy description",
            "DueDate": "",
            "Id": "21490",
            "Key": "XSOAR-19",
            "Labels": [],
            "LastSeen": "",
            "LastUpdate": "2023-05-04T21:41:44.516+0300",
            "Priority": "Highest",
            "ProjectName": "XSOARJiraV3",
            "Status": "To Do",
            "Summary": "something",
            "customfield_10019": {
                "issueFieldDisplayName": "Rank",
                "rawData": "0|i00kjr:"
            },
            "watches": {
                "issueFieldDisplayName": "Watchers",
                "rawData": {
                    "isWatching": true,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/XSOAR-19/watchers",
                    "watchCount": 1
                }
            }
        },
        {
            "Assignee": "",
            "Attachments": [],
            "Components": [],
            "Created": "2023-03-01T14:10:55.495+0200",
            "Creator": "Example User(example@example.com)",
            "Description": "Dummy",
            "DueDate": "",
            "Id": "21494",
            "Key": "XSOAR-18",
            "Labels": [],
            "LastSeen": "",
            "LastUpdate": "2023-03-01T14:10:55.495+0200",
            "Priority": "Medium",
            "ProjectName": "XSOARJiraV3",
            "Status": "To Do",
            "Summary": "something something",
            "customfield_10019": {
                "issueFieldDisplayName": "Rank",
                "rawData": "0|i00kkn:"
            },
            "watches": {
                "issueFieldDisplayName": "Watchers",
                "rawData": {
                    "isWatching": true,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/XSOAR-18/watchers",
                    "watchCount": 1
                }
            }
        }
    ]
}

Human Readable Output

Issue XSOAR-19

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
  2023-03-01T11:59:18.202+0200 Example User(example@example.com) Dummy description   21490 Task XSOAR-19   Highest XSOARJiraV3 Example User(example@example.com) To Do something https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21490

Issue XSOAR-18

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
  2023-03-01T14:10:55.495+0200 Example User(example@example.com) Dummy   21494 Task XSOAR-18   Medium XSOARJiraV3 Example User(example@example.com) To Do something something https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21494

jira-get-id-offset


Returns the ID offset, which is the ID of the first issue that was created in the current Jira instance, or the ID of the first issue with respect to the given query argument if given. You can use its result as the initial ID when fetching incidents by the issue id field.

Scope: read:jira-work

Base Command

jira-get-id-offset

Input

Argument Name Description Required
query The query that will be used to retrieve the first issue ID in it. Optional

Context Output

Path Type Description
Ticket.idOffSet String The ID offset.

Command example

!jira-get-id-offset query="status!=done"

Context Example

{
    "Ticket": {
        "idOffSet": "10161"
    }
}

Human Readable Output

ID Offset: 10161

jira-issue-add-comment


Adds a new comment to an existing Jira issue.

Scope: write:jira-work

Base Command

jira-issue-add-comment

Input

Argument Name Description Required
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
comment The comment body. Required
visibility The roles that can view the comment, for example: Administrators. Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-add-comment issue_key=PROJECTKEY-31 comment="New comment"

Human Readable Output

Comment added successfully

Comment Id Ticket Link
New comment 18456 https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21432/comment/18456

jira-issue-upload-file


Uploads a file attachment to an issue.

Scope: write:jira-work

Base Command

jira-issue-upload-file

Input

Argument Name Description Required
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
upload The entry ID to upload. Required
attachment_name The attachment name to be displayed in Jira (overrides the original file name). Optional
attachmentName Deprecated. Please use attachment_name. Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-upload-file upload=10833@33e912c0-76a9-4a08-8cc6-1b20b1980b20 issue_key=PROJECTKEY-31

Human Readable Output

Attachment added successfully

Attachment Link Attachment Name Id Issue Key
https://api.atlassian.com/ex/jira/1234/rest/api/3/attachment/16505 dummy.pdf 16505 PROJECTKEY-31

jira-issue-delete-file


Delete an attachment from an issue.

Scope: write:jira-work

Base Command

jira-issue-delete-file

Input

Argument Name Description Required
attachment_id The attachment ID. Required

Context Output

There is no context output for this command.

Command example

!jira-issue-delete-file attachment_id=ATTACHMENT_ID

Human Readable Output

Attachment id ATTACHMENT_ID was deleted successfully.

jira-list-transitions


Lists all possible transitions for a given ticket in its current status.

Scope: read:jira-work

Base Command

jira-list-transitions

Input

Argument Name Description Required
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Transitions Object An object that holds data about all possible transitions.
Ticket.Transitions.transitions Array An array of all possible transitions.
Ticket.Transitions.ticketId String The ticket ID.

Command example

!jira-list-transitions issue_key=PROJECTKEY-35

Context Example

{
    "Ticket": {
        "Key": "PROJECTKEY-35",
        "Transitions": {
            "ticketId": "PROJECTKEY-35",
            "transitions": [
                "Backlog",
                "In Development"
            ]
        }
    }
}

Human Readable Output

List Transitions

Transition Names
Backlog
In Development

jira-edit-issue


Modifies an issue in Jira. If the status or transition fields are given, then the other supplied fields should appear on the transition screen.

Scope: write:jira-work

Base Command

jira-edit-issue

Input

Argument Name Description Required
parent_issue_key The parent issue key (if you’re editing a sub-task). This argument is only relevant for Jira Cloud. Optional
parent_issue_id The parent issue ID (if you’re editing a sub-task). This argument is only relevant for Jira Cloud. Optional
action Whether to append or rewrite the values. Only issue fields of type string or array support appending. If the field is of type string, then the new appended value will be: ‘old value, new value’, where they will be separated by a comma. Possible values are: append, rewrite. Default is rewrite. Optional
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
issue_json The issue object (in JSON format). This argument is for advanced users, as when utilizing this argument, one cannot use other arguments (it will raise an error) except the issue_id issue_eky action transition and status and must input all required arguments into the issue_json. In addition, when issue_json is used, it is not possible to use the project name but only the project key. For example {“fields”:{“customfield_10037”:”field_value”, “summary”: “some summary”, “project”: {“key”: “"}}}. For more information about this argument, see https://developer.atlassian.com/server/jira/platform/jira-rest-api-example-create-issue-7897248/. Optional
issueJson Deprecated. Please use issue_json. Optional
summary The issue summary. Optional
description The issue description. Optional
labels A CSV list of labels. Optional
priority The issue priority, for example: High, Medium. Optional
due_date The due date for the issue (in the format yyyy-mm-dd). Optional
dueDate Deprecated. Please use due_date. Optional
assignee The name of the assignee. Relevant for Jira Server only. If you are using Jira Cloud, provide the assignee_id argument instead. Use the jira-get-id-by-attribute command to get the user’s name. Optional
assignee_id The account ID of the assignee. Relevant for Jira Cloud only. Use the jira-get-id-by-attribute command to get the user’s account ID. Optional
status The issue status (Either choose the issue status or transition, but not both). Optional
transition The issue transition (Either choose the issue status or transition, but not both). Optional
environment A text field for describing the environment in which the issue occurred. For example environment=”IE9 on Windows 7”. Optional
security The security level name of the issue. For example security=”Anyone”. Optional
components A CSV list of components. For example components=”component1,component2”. When using a Jira OnPrem instance, or using the action append, the entered components must already exist. Optional

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Assignee String The user assigned to the ticket.
Ticket.Creator String The user who created the ticket.
Ticket.Summary String The ticket summary.
Ticket.Description String The ticket’s project description.
Ticket.RawDescription String The ticket’s project raw description.
Ticket.Labels Array The ticket’s project labels.
Ticket.Status String The ticket status.
Ticket.Priority String The ticket priority.
Ticket.ProjectName String The ticket project name.
Ticket.DueDate Date The due date.
Ticket.Created Date The time the ticket was created.
Ticket.LastSeen Date The last time the ticket was viewed.
Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-edit-issue action=rewrite issue_key=PROJECTKEY-35 assignee_id=12345 description="Edited subbscription" due_date="2023-01-01" environment="Windows XP" labels="label1,label2" priority="Highest" security=Anyone status="Backlog" summary="Edited Summary"

Context Example

{
    "Ticket": {
        "Assignee": "Example User(example@example.com)",
        "Attachments": [
            {
                "created": "2023-05-07T20:00:59.121+0300",
                "filename": "dummy_attachment_content.txt",
                "id": "16504",
                "size": 13264
            },
            {
                "created": "2023-05-04T21:29:49.246+0300",
                "filename": "dummy.pdf",
                "id": "16477",
                "size": 13264
            }
        ],
        "Components": [
            "dummy-comp",
            "Integration"
        ],
        "Created": "2023-03-01T11:34:49.730+0200",
        "Creator": "Example User(example@example.com)",
        "Description": "Edited subbscription",
        "DueDate": "2023-01-01",
        "Id": "21487",
        "Key": "PROJECTKEY-35",
        "Labels": [
            "label1",
            "label2"
        ],
        "LastSeen": "2023-05-08T19:02:34.151+0300",
        "LastUpdate": "2023-05-08T19:07:44.900+0300",
        "Priority": "Highest",
        "ProjectName": "Company Snoozing App",
        "Status": "Backlog",
        "Summary": "Edited Summary"
    }
}

Human Readable Output

Issue PROJECTKEY-35

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
Example User(example@example.com) 2023-03-01T11:34:49.730+0200 Example User(example@example.com) Edited subbscription 2023-01-01 21487 Story PROJECTKEY-35 label1,
label2
Highest Company Snoozing App Example User(example@example.com) Backlog Edited Summary https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21487

jira-issue-assign


Assigns an assignee to an existing issue.

Scope: write:jira-work

Base Command

jira-issue-assign

Input

Argument Name Description Required
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
assignee The name of the assignee. Relevant for Jira Server only. If you are using Jira Cloud, provide the assignee_id argument instead. Use the jira-get-id-by-attribute command to get the user’s name. Optional
assignee_id The account ID of the assignee. Relevant for Jira Cloud only. Use the jira-get-id-by-attribute command to get the user’s account ID. Optional

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Assignee String The user assigned to the ticket.
Ticket.Creator String The user who created the ticket.
Ticket.Summary String The ticket summary.
Ticket.Description String The ticket’s project description.
Ticket.RawDescription String The ticket’s project raw description.
Ticket.Labels Array The ticket’s project labels.
Ticket.Status String The ticket status.
Ticket.Priority String The ticket priority.
Ticket.ProjectName String The ticket project name.
Ticket.DueDate Date The due date.
Ticket.Created Date The time the ticket was created.
Ticket.LastSeen Date The last time the ticket was viewed.
Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-issue-assign issue_key=PROJECTKEY-35 assignee_id=12345

Context Example

{
    "Ticket": {
        "Assignee": "Example User(example@example.com)",
        "Attachments": [
            {
                "created": "2023-05-07T20:00:59.121+0300",
                "filename": "dummy_attachment_content.txt",
                "id": "16504",
                "size": 13264
            },
            {
                "created": "2023-05-04T21:29:49.246+0300",
                "filename": "dummy.pdf",
                "id": "16477",
                "size": 13264
            }
        ],
        "Components": [
            "dummy-comp",
            "Integration"
        ],
        "Created": "2023-03-01T11:34:49.730+0200",
        "Creator": "Example User(example@example.com)",
        "Description": "Edited subbscription",
        "DueDate": "2023-01-01",
        "Id": "21487",
        "Key": "PROJECTKEY-35",
        "Labels": [
            "label1",
            "label2"
        ],
        "LastSeen": "2023-05-08T19:02:34.151+0300",
        "LastUpdate": "2023-05-08T19:07:44.900+0300",
        "Priority": "Highest",
        "ProjectName": "Company Snoozing App",
        "Status": "Backlog",
        "Summary": "Edited Summary"
    }
}

Human Readable Output

Issue PROJECTKEY-35

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
Example User(example@example.com) 2023-03-01T11:34:49.730+0200 Example User(example@example.com) Edited subbscription 2023-01-01 21487 Story PROJECTKEY-35 label1,
label2
Highest Company Snoozing App Example User(example@example.com) Backlog Edited Summary https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21487

jira-create-issue


Creates a new issue in Jira.

Scope: write:jira-work

Base Command

jira-create-issue

Input

Argument Name Description Required
issue_json ‘The issue object (in JSON format). This argument is for advanced users, as when utilizing this argument, one cannot use other arguments (it will raise an error) and must input all required arguments into the issue_json. In addition, when issue_json is used, it is not possible to use the project name but only the project key. For example {“fields”:{“customfield_10037”:”field_value”, “summary”: “some summary”, “project”: {“key”: “"}}}. For more information about this argument, see https://developer.atlassian.com/server/jira/platform/jira-rest-api-example-create-issue-7897248/'. Optional
issueJson Deprecated. Please use issue_json. Optional
summary The summary of the issue (Summary value is required, either from the “summary” argument, or from the “issue_json” argument). Optional
project_key The project key with which to associate the issue (Project Key or name is required). Optional
projectKey Deprecated. Please use project_key. Optional
issue_type_name Selects an issue type by name, for example: “Problem”. (Issue type name or id is required). Optional
issueTypeName Deprecated. Please use issue_type_name. Optional
issue_type_id Selects an issue type by its numeric ID (Issue type name or id is required). Optional
issueTypeId Deprecated. Please use issue_type_id. Optional
project_name The project name with which to associate the issue (Project Key or name is required). Optional
projectName Deprecated. Please use project_name. Optional
description A description of the issue. Optional
labels A CSV list of labels. Optional
priority The priority of the issue, for example: High, Medium. Optional
due_date The due date for the issue (in the format yyyy-mm-dd). Optional
dueDate Deprecated. Please use due_date. Optional
assignee The name of the assignee. Relevant for Jira Server only. If you are using Jira Cloud, provide the assignee_id argument instead. Use the jira-get-id-by-attribute command to get the user’s name. Optional
assignee_id The account ID of the assignee. Relevant for Jira Cloud only. Use the jira-get-id-by-attribute command to get the user’s account ID. Optional
reporter_id The account ID of the reporter. Relevant for Jira Cloud only. Use the jira-get-id-by-attribute command to get the user’s account ID. Optional
reporter The name of the of the reporter. Relevant for Jira Server only. Please use reporter_id for Jira Cloud. Use the jira-get-id-by-attribute command to get the user’s name. Optional
parent_issue_key The parent issue key (if you’re creating a sub-task). Optional
parentIssueKey Deprecated. Please use parent_issue_key. Optional
parent_issue_id The parent issue ID (if you’re creating a sub-task). Optional
parentIssueId Deprecated. Please use parent_issue_id. Optional
environment A text field for describing the environment in which the issue occurred. For example environment=”IE9 on Windows 7”. Optional
security The security level of the issue. For example security=”Anyone”. Optional
components The component names of the issue. For example components=”component1,component2”. When using a Jira OnPrem instance, the entered components must already exist. Optional

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.

Command example

!jira-create-issue summary="Dummy Summary" assignee_id=12345 components="dummy-comp,New-Component" description="Dummy description" due_date="2023-01-01" environment="Windows XP" issue_type_name="Sub-task" labels="label1,label2" parent_issue_key=PROJECTKEY-35 priority=Highest project_name="Company Snoozing App" reporter_id=12345 security=Assignee

Context Example

{
    "Ticket": {
        "Id": "21619",
        "Key": "PROJECTKEY-145"
    }
}

Human Readable Output

Issue PROJECTKEY-145

Id Key Project Key Ticket Link
21619 PROJECTKEY-145 PROJECTKEY https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21619

jira-get-issue


Fetches an issue from Jira.

Scope: read:jira-work

Base Command

jira-get-issue

Input

Argument Name Description Required
fields A CSV list of fields to return (Using the name or ID of the fields) to context data. Supplying all will return all the issue fields (which will include nested values), except for the comments, which can be retrieved using the !jira-get-comments command. Optional
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
headers Displays the headers in human readable format. Optional
get_attachments If “true”, retrieves the issue attachments and downloads the file to the War Room. Possible values are: true, false. Default is false. Optional
getAttachments Deprecated. Please use get_attachments. Possible values are: true, false. Optional
expand_links If “true”, expands the issue links (the linked issues and subtasks). Possible values are: true, false. Default is false. Optional
expandLinks Deprecated. Please use expand_links. Possible values are: true, false. Optional

Note: When supplying the all value to the fields argument, then all the issue fields will be returned, and every issue field that was not parsed by the code, will be parsed in the following format: {customfield_1: SOME_NESTED_VALUE} -> {customfield_1: {issueFieldDisplayName: THE_DISPLAY_NAME_OF_THE_FIELD, rawData: SOME_NESTED_VALUE}}

Context Output

Path Type Description
Ticket.Id String The ticket ID.
Ticket.Key String The ticket key.
Ticket.Assignee String The user assigned to the ticket, in the form `Username(User email address)`.
Ticket.Creator String The user who created the ticket.
Ticket.Summary String The ticket summary.
Ticket.Description String The ticket’s project description.
Ticket.RawDescription String The ticket’s project raw description.
Ticket.Status String The ticket status.
Ticket.Labels Array The ticket’s labels.
Ticket.Components Array The ticket’s components.
Ticket.Priority String The ticket priority.
Ticket.ProjectName String The ticket project name.
Ticket.DueDate Date The due date.
Ticket.Created Date The time the ticket was created.
Ticket.LastSeen Date The last time the ticket was viewed.
Ticket.LastUpdate Date The last time the ticket was updated.
Ticket.Attachments.created Date The created time of the attachment.
Ticket.Attachments.filename String The file name of the attachment.
Ticket.Attachments.id String The id of the attachment.
Ticket.Attachments.size Number The size (in bytes) of the attachment.
InfoFile.Name String The file name.
InfoFile.EntryID String The ID for locating the file in the War Room.
InfoFile.Size Number The size of the file (in bytes).
InfoFile.Type String The file type, as determined by libmagic (same as displayed in file entries).
InfoFile.Extension String The file extension.
InfoFile.Info String Basic information about the file.

Command example

!jira-get-issue issue_key=PROJECTKEY-35 expand_links=true fields=watches,rank get_attachments=true

Context Example

{
    "InfoFile": [
        {
            "EntryID": "10845@33e912c0-76a9-4a08-8cc6-1b20b1980b20",
            "Extension": "png",
            "Info": "image/png",
            "Name": "1682340338180_home_screen_background_mirrored_from_xsoar.png",
            "Size": 212597,
            "Type": "PNG image data, 430 x 932, 8-bit/color RGBA, non-interlaced"
        },
        {
            "EntryID": "10846@33e912c0-76a9-4a08-8cc6-1b20b1980b20",
            "Extension": "txt",
            "Info": "text/plain; charset=utf-8",
            "Name": "dummy_attachment_content.txt",
            "Size": 13264,
            "Type": "PDF document, version 1.4, 1 pages (zip deflate encoded)"
        }
    ],
    "Ticket": [
        {
            "Assignee": "Example User(example@example.com)",
            "Attachments": [
                {
                    "created": "2023-04-24T15:46:18.919+0300",
                    "filename": "1682340338180_home_screen_background_mirrored_from_xsoar.png",
                    "id": "16466",
                    "size": 212597
                },
                {
                    "created": "2023-05-07T20:00:59.121+0300",
                    "filename": "dummy_attachment_content.txt",
                    "id": "16504",
                    "size": 13264
                }
            ],
            "Components": [
                "dummy-comp",
                "Integration"
            ],
            "Created": "2023-03-01T11:34:49.730+0200",
            "Creator": "Example User(example@example.com)",
            "Description": "Edited subbscription",
            "DueDate": "2023-01-01",
            "Id": "21487",
            "Key": "PROJECTKEY-35",
            "Labels": [
                "label1",
                "label2"
            ],
            "LastSeen": "2023-05-08T19:02:34.151+0300",
            "LastUpdate": "2023-05-08T19:05:03.569+0300",
            "Priority": "Highest",
            "ProjectName": "Company Snoozing App",
            "Status": "Selected for Development",
            "Summary": "Edited Summary",
            "customfield_10019": {
                "issueFieldDisplayName": "Rank",
                "rawData": "0|i00kjb:"
            },
            "watches": {
                "issueFieldDisplayName": "Watchers",
                "rawData": {
                    "isWatching": true,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/PROJECTKEY-35/watchers",
                    "watchCount": 1
                }
            }
        },
        {
            "Assignee": "",
            "Attachments": [
                {
                    "created": "2023-03-26T18:06:24.325+0300",
                    "filename": "DemistoContent-InstallContentEnvironment-160822-0023-68.pdf",
                    "id": "16426",
                    "size": 17815
                }
            ],
            "Components": [],
            "Created": "2023-03-01T11:59:18.202+0200",
            "Creator": "Example User(example@example.com)",
            "Description": "Dummy description",
            "DueDate": "",
            "Id": "21490",
            "Key": "XSOAR-19",
            "Labels": [],
            "LastSeen": "",
            "LastUpdate": "2023-05-04T21:41:44.516+0300",
            "Priority": "Highest",
            "ProjectName": "XSOARJiraV3",
            "Status": "To Do",
            "Summary": "something",
            "customfield_10019": {
                "issueFieldDisplayName": "Rank",
                "rawData": "0|i00kjr:"
            },
            "watches": {
                "issueFieldDisplayName": "Watchers",
                "rawData": {
                    "isWatching": true,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/XSOAR-19/watchers",
                    "watchCount": 1
                }
            }
        }
    ]
}

Human Readable Output

Issue COMPANYSA-35

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
Example User(example@example.com) 2023-03-01T11:34:49.730+0200 Example User(example@example.com) hi 2023-05-24 21487 Story COMPANYSA-35 label1,
label2
Highest Company Snoozing App Example User(example@example.com) In Progress XSOAR Meeting, Ammended summary https://api.atlassian.com/ex/jira/15d0e445-bd38-483c-b3fd-d15f562f369b/rest/api/3/issue/21487

Issue XSOAR-19

Assignee Created Creator Description Due Date Id Issue Type Key Labels Priority Project Name Reporter Status Summary Ticket Link
  2023-03-01T11:59:18.202+0200 Example User(example@example.com) Dummy description   21490 Task XSOAR-19   Highest XSOARJiraV3 Example User(example@example.com) To Do something https://api.atlassian.com/ex/jira/1234/rest/api/3/issue/21490

jira-get-comments


Returns the comments added to a ticket.

Scope: read:jira-work

Base Command

jira-get-comments

Input

Argument Name Description Required
limit The maximum number of results to return. Default is 50 and maximum is 5000. Default is 50. Optional
issueId Deprecated. Please use issue_id or issue_key. Optional
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional

Context Output

Path Type Description
Ticket.Id String The ID of the ticket.
Ticket.Key String The ticket key.
Ticket.Comment.Id String The ID of the comment.
Ticket.Comment.Comment String The text of the comment.
Ticket.Comment.Created Date The comment creation date.
Ticket.Comment.Updated Date The comment updated date.
Ticket.Comment.User String The user that created the comment.
Ticket.Comment.UpdateUser String The user that updated the comment.

Command example

!jira-get-comments issue_key=PROJECTKEY-35 limit=2

Context Example

{
    "Ticket": {
        "Comment": [
            {
                "Comment": "New comment",
                "Created": "2023-05-04T21:29:51.036+0300",
                "Id": "18423",
                "UpdateUser": "Example User",
                "Updated": "2023-05-04T21:29:51.036+0300",
                "User": "Example User"
            },
            {
                "Comment": "Dummy Link",
                "Created": "2023-05-04T21:30:29.599+0300",
                "Id": "18424",
                "UpdateUser": "Example User",
                "Updated": "2023-05-04T21:30:29.599+0300",
                "User": "Example User"
            }
        ],
        "Id": "21487",
        "Key": "PROJECTKEY-35"
    }
}

Human Readable Output

Comments

Comment Created Id UpdateUser Updated User
New comment 2023-05-04T21:29:51.036+0300 18423 Example User 2023-05-04T21:29:51.036+0300 Example User
Dummy Link 2023-05-04T21:30:29.599+0300 18424 Example User 2023-05-04T21:30:29.599+0300 Example User

jira-oauth-complete


Use this command to complete the authorization process. After copying the authorization code found in the query parameter code of the callback URL, paste the value in the command as an argument to finish the process.

Base Command

jira-oauth-complete

Input

Argument Name Description Required
code The authorization code retrieved from the callback URL according to the documentation. Required

Context Output

There is no context output for this command.

Human Readable Output

Logged in successfully

A refresh token was saved to the integration context. This token will be
used to generate a new access token once the current one expires.

jira-oauth-start


Use this command to start the authorization process. In order to authorize the instance, first run the command, and complete the process in the URL that is returned. You will then be redirected to the callback URL where you will copy the authorization code found in the query parameter code, and paste that value in the command !jira-ouath-complete as an argument to finish the process.

Base Command

jira-oauth-start

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Human Readable Output

In order to retrieve the authorization code, use the following link:
https://example.com
After authorizing, you will be redirected to the configured callback URL, where you will retrieve the authorization code provided as a query parameter called code, and insert it as an argument to the !jira-oauth-complete command

jira-oauth-test


Use this command to test the connectivity of the Jira instance.

Base Command

jira-oauth-test

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Human Readable Output

Successful connection.

jira-issue-delete-comment


Delete a comment from an issue.

Scope: write:jira-work

Base Command

jira-issue-delete-comment

Input

Argument Name Description Required
comment_id The comment ID. Required
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-delete-comment issue_key=PROJECTKEY-35 comment_id=18423

Human Readable Output

Comment deleted successfully.

jira-issue-edit-comment


Edit a comment that is part of an issue.

Scope: write:jira-work

Base Command

jira-issue-edit-comment

Input

Argument Name Description Required
issue_id The issue ID (Issue ID or key is required). Optional
issue_key The issue key (Issue ID or key is required). Optional
comment_id The comment ID. Required
comment The content of the comment to modify. Required
visibility The roles that can view the comment, for example: Administrators. Optional

Context Output

Path Type Description
Ticket.Id String The ID of the ticket.
Ticket.Comment.Id String The ID of the comment.
Ticket.Comment.Comment String The text of the comment.
Ticket.Comment.Created String The issue creation date.
Ticket.Comment.User String The user that created the comment.

Command example

!jira-issue-edit-comment issue_key=PROJECTKEY-35 comment_id=18425 comment="New Hello There"

Context Example

{
    "Ticket": {
        "Comment": [
            {
                "Comment": "Dummy Link",
                "Created": "2023-05-04T21:30:29.599+0300",
                "Id": "18424",
                "UpdateUser": "Example User",
                "Updated": "2023-05-04T21:30:29.599+0300",
                "User": "Example User"
            },
            {
                "Comment": "New Hello There",
                "Created": "2023-05-04T21:32:58.870+0300",
                "Id": "18425",
                "UpdateUser": "Example User",
                "Updated": "2023-05-08T19:08:04.346+0300",
                "User": "Example User"
            },
            {
                "Comment": "Dummy Link",
                "Created": "2023-05-04T21:33:29.986+0300",
                "Id": "18426",
                "UpdateUser": "Example User",
                "Updated": "2023-05-04T21:33:29.986+0300",
                "User": "Example User"
            }
        ],
        "Id": "21487",
        "Key": "PROJECTKEY-35"
    }
}

Human Readable Output

The comment has been edited successfully

jira-issue-list-fields


This command returns the issue fields, both system and custom fields.

Scope: read:jira-work

Base Command

jira-issue-list-fields

Input

Argument Name Description Required
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.IssueField.id String The ID of the field.
Jira.IssueField.key String The key of the field.
Jira.IssueField.name String The name of the field.
Jira.IssueField.custom String Whether the field is a custom field.
Jira.IssueField.orderable String Whether the content of the field can be used to order lists.
Jira.IssueField.navigable String Whether the field can be used as a column on the issue navigator.
Jira.IssueField.searchable String Whether the content of the field can be searched.
Jira.IssueField.clauseNames Array The names that can be used to reference the field in an advanced search.
Jira.IssueField.scope Object The scope of the field.
Jira.IssueField.scope.type String The type of scope. Valid values: PROJECT, TEMPLATE
Jira.IssueField.scope.project Object The project the item has scope in.
Jira.IssueField.scope.project.self String The URL of the project details.
Jira.IssueField.scope.project.id String The ID of the project.
Jira.IssueField.scope.project.key String The key of the project.
Jira.IssueField.scope.project.name String The name of the project.
Jira.IssueField.scope.project.projectTypeKey String The project type of the project. Valid values: software, service_desk, business
Jira.IssueField.scope.project.simplified Boolean Whether or not the project is simplified.
Jira.IssueField.scope.project.avatarUrls Object The URLs of the project’s avatars.
Jira.IssueField.scope.project.projectCategory Object The category the project belongs to.
Jira.IssueField.scope.project.projectCategory.self String The URL of the project category.
Jira.IssueField.scope.project.projectCategory.id String The ID of the project category.
Jira.IssueField.scope.project.projectCategory.description String The name of the project category.
Jira.IssueField.scope.project.projectCategory.name String The description of the project category.
Jira.IssueField.scope.type String The type of scope. Valid values: PROJECT, TEMPLATE
Jira.IssueField.schema Object The data schema for the field.
Jira.IssueField.schema.type String The data type of the field.
Jira.IssueField.schema.items String When the data type is an array, the name of the field items within the array.
Jira.IssueField.schema.system String If the field is a system field, the name of the field.
Jira.IssueField.schema.custom String If the field is a custom field, the URI of the field.
Jira.IssueField.schema.customId Number If the field is a custom field, the custom ID of the field.
Jira.IssueField.schema.configuration Object If the field is a custom field, the configuration of the field.

Command example

!jira-issue-list-fields page=1 page_size=2

Context Example

{
    "Jira": {
        "IssueField": [
            {
                "clauseNames": [],
                "custom": true,
                "id": "customfield_10070",
                "key": "customfield_10070",
                "name": "Responders",
                "navigable": true,
                "orderable": true,
                "schema": {
                    "custom": "com.atlassian.jira.modules.servicemanagement.responders-entity:responders-entity-field-cftype",
                    "customId": 10070,
                    "items": "string",
                    "type": "array"
                },
                "searchable": true,
                "untranslatedName": "Responders"
            },
            {
                "clauseNames": [
                    "cf[10071]",
                    "Time to resolution"
                ],
                "custom": true,
                "id": "customfield_10071",
                "key": "customfield_10071",
                "name": "Time to resolution",
                "navigable": true,
                "orderable": true,
                "schema": {
                    "custom": "com.atlassian.servicedesk:sd-sla-field",
                    "customId": 10071,
                    "type": "sd-servicelevelagreement"
                },
                "searchable": true,
                "untranslatedName": "Time to resolution"
            }
        ]
    }
}

Human Readable Output

Issue Fields

Custom Id Name Schema Type Searchable
true customfield_10070 Responders array true
true customfield_10071 Time to resolution sd-servicelevelagreement true

jira-issue-to-backlog


Moves issues to backlog. If the issues supplied are NOT part of a board that supports sprints, then the board_id argument is required. At most 50 issues may be moved at once.

Scope: write:board-scope:jira-software

Base Command

jira-issue-to-backlog

Input

Argument Name Description Required
issues The issues to move to backlog. Required
board_id The board ID. Can only be used with a Jira Cloud instance. Run the command jira-board-list to retrieve the boards’ IDs. Optional
rank_before_issue To rank the issues before the stated issue (supports issue key and ID). This argument can be used when supplying the board ID. Optional
rank_after_issue To rank the issues after the stated issue (supports issue key and ID). This argument can be used when supplying the board ID. Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-to-backlog issues="TSTPRD-3,TSTPRD-6" rank_before_issue="TSTPRD-4" board_id=12

Human Readable Output

Issues were moved to Backlog successfully

jira-issue-to-board


Moves issues from backlog to board, the command does not work with boards with sprints. At most 50 issues may be moved at once. This command is only supported for Jira Cloud instances.

Scope: write:board-scope:jira-software

Base Command

jira-issue-to-board

Input

Argument Name Description Required
issues The issues to move to board. Required
board_id The board ID. Required
rank_before_issue To rank the issues before the stated issue (supports issue key and ID). Optional
rank_after_issue To rank the issues after the stated issue (supports issue key and ID). Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-to-board issues="PROJECTKEY-35,PROJECTKEY-39" board_id=14

Human Readable Output

Issues were moved to Board successfully

jira-board-list


Get board data. If board_id is given, then only the board that corresponds to the board_id is returned, otherwise, the the command will return boards that match the project_key_id, type, or board_name arguments.

Scopes: read:board-scope:jira-software, read:issue-details:jira

Base Command

jira-board-list

Input

Argument Name Description Required
board_name The name of the board. Optional
board_id The board ID. Optional
project_key_id The project key or ID. Optional
type The type of the board. Allowed values are: scrum, kanban, simple. Possible values are: scrum, kanban, simple. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.Board.id Number The ID of the board.
Jira.Board.self String The URL of the board.
Jira.Board.name String The name of the board.
Jira.Board.type String The type the board.
Jira.Board.admins Object The administrators of the board.
Jira.Board.admins.users Object The users who own the board.
Jira.Board.admins.users.self String The URL of the user.
Jira.Board.admins.users.displayName String The display name of the user. Depending on the user’s privacy setting, this may return an alternative value.
Jira.Board.admins.users.active String Whether the user is active.
Jira.Board.admins.users.accountId String The account ID of the user, which uniquely identifies the user across all Atlassian products.
Jira.Board.admins.users.avatarUrls Object The avatars of the user.
Jira.Board.admins.groups Object The groups who own the board.
Jira.Board.admins.groups.name String The name of the group.
Jira.Board.admins.groups.self String The URL of the group.
Jira.Board.location Object The container that the board is located in.
Jira.Board.location.projectId Number The project ID.
Jira.Board.location.userId Number The user ID.
Jira.Board.location.userAccountId String The user account ID.
Jira.Board.location.displayName String The display name of the project.
Jira.Board.location.projectName String The project name.
Jira.Board.location.projectKey String The project key.
Jira.Board.location.projectTypeKey String The type of the project.
Jira.Board.location.avatarURI String The avatar of the project.
Jira.Board.location.name String The name of the project.
Jira.Board.canEdit String Whether the board can be edited.
Jira.Board.isPrivate String Whether the board is private.
Jira.Board.favourite String Whether the board is selected as a favorite.

Command example

!jira-board-list project_key_id=PROJECTKEY type=kanban

Context Example

{
    "Jira": {
        "Board": {
            "id": 14,
            "location": {
                "avatarURI": "https://api.atlassian.com/ex/jira/1234/rest/api/2/universal_avatar/view/type/project/avatar/10402?size=small",
                "displayName": "Company Snoozing App (PROJECTKEY)",
                "name": "Company Snoozing App (PROJECTKEY)",
                "projectId": 10022,
                "projectKey": "PROJECTKEY",
                "projectName": "Company Snoozing App",
                "projectTypeKey": "software"
            },
            "name": "PROJECTKEY board",
            "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/board/14",
            "type": "kanban"
        }
    }
}

Human Readable Output

Boards

ID Name Project ID Project Name Type
14 PROJECTKEY board 10022 Company Snoozing App kanban

jira-board-backlog-list


Get issues from the backlog of a specific board. For Jira OnPrem, the board must be of type scrum.

Scopes: read:board-scope:jira-software, read:issue-details:jira

Base Command

jira-board-backlog-list

Input

Argument Name Description Required
board_id The board ID. Required
jql_query A JQL query to filter the issues. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.BoardBacklog.boardId Number The ID of the board.
Jira.BoardBacklog.Ticket.Id Number The ticket ID.
Jira.BoardBacklog.Ticket.Key String The ticket key.
Jira.BoardBacklog.Ticket.Assignee String The user assigned to the ticket.
Jira.BoardBacklog.Ticket.Creator String The user who created the ticket.
Jira.BoardBacklog.Ticket.Summary String The ticket summary.
Jira.BoardBacklog.Ticket.Status String The ticket status.
Jira.BoardBacklog.Ticket.Priority String The ticket priority.
Jira.BoardBacklog.Ticket.ProjectName String The ticket project name.
Jira.BoardBacklog.Ticket.DueDate Date The due date.
Jira.BoardBacklog.Ticket.Created Date The time the ticket was created.
Jira.BoardBacklog.Ticket.LastSeen Date The last time the ticket was viewed.
Jira.BoardBacklog.Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-board-backlog-list board_id=14 jql_query="status!=done" limit=2

Context Example

{
    "Jira": {
        "BoardBacklog": {
            "Ticket": [
                {
                    "Assignee": "",
                    "Attachments": [],
                    "Components": [],
                    "Created": "2023-01-01T22:57:28.689+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "",
                    "DueDate": "",
                    "Id": "21360",
                    "Key": "PROJECTKEY-1",
                    "Labels": [],
                    "LastSeen": "",
                    "LastUpdate": "2023-03-07T17:31:48.332+0200",
                    "Priority": "Medium",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Backlog",
                    "Summary": "Start creating the company snoozing app"
                },
                {
                    "Assignee": "Example User()",
                    "Attachments": [],
                    "Components": [
                        "Integration"
                    ],
                    "Created": "2023-01-30T16:42:47.476+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "Complete this issue",
                    "DueDate": "2023-09-09",
                    "Id": "21424",
                    "Key": "PROJECTKEY-30",
                    "Labels": [
                        "collab",
                        "good_job"
                    ],
                    "LastSeen": "",
                    "LastUpdate": "2023-03-13T21:09:47.898+0200",
                    "Priority": "Highest",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Backlog",
                    "Summary": "Issue for demo"
                }
            ],
            "boardId": "14"
        }
    }
}

Human Readable Output

Backlog Issues

Assignee Created Creator Description DueDate Id IssueType Key Labels Priority ProjectName Reporter Status Summary TicketLink
  2023-01-01T22:57:28.689+0200 Example User(example@example.com)     21360 Epic PROJECTKEY-1   Medium Company Snoozing App Example User(example@example.com) Backlog Start creating the company snoozing app https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21360
Example User() 2023-01-30T16:42:47.476+0200 Example User(example@example.com) Complete this issue 2023-09-09 21424 Epic PROJECTKEY-30 collab,
good_job
Highest Company Snoozing App Example User() Backlog Issue for demo https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21424

jira-board-issue-list


Get all issues from a specific board.

Scopes: read:board-scope:jira-software, read:issue-details:jira

Base Command

jira-board-issue-list

Input

Argument Name Description Required
board_id The board ID. Required
jql_query A JQL query to filter the issues. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.BoardIssue.boardId Number The ID of the board.
Jira.BoardIssue.Ticket.Id Number The ticket ID.
Jira.BoardIssue.Ticket.Key String The ticket key.
Jira.BoardIssue.Ticket.Assignee String The user assigned to the ticket.
Jira.BoardIssue.Ticket.Creator String The user who created the ticket.
Jira.BoardIssue.Ticket.Summary String The ticket summary.
Jira.BoardIssue.Ticket.Status String The ticket status.
Jira.BoardIssue.Ticket.Priority String The ticket priority.
Jira.BoardIssue.Ticket.ProjectName String The ticket project name.
Jira.BoardIssue.Ticket.DueDate Date The due date.
Jira.BoardIssue.Ticket.Created Date The time the ticket was created.
Jira.BoardIssue.Ticket.LastSeen Date The last time the ticket was viewed.
Jira.BoardIssue.Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-board-issue-list board_id=14 jql_query="status!=done" limit=2

Context Example

{
    "Jira": {
        "BoardIssue": {
            "Ticket": [
                {
                    "Assignee": "Example User()",
                    "Attachments": [],
                    "Components": [
                        "comp2",
                        "Integration",
                        "test_test"
                    ],
                    "Created": "2023-01-30T16:35:50.482+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "",
                    "DueDate": "2023-09-09",
                    "Id": "21423",
                    "Key": "PROJECTKEY-29",
                    "Labels": [
                        "Demisto",
                        "XSOAR",
                        "testnow"
                    ],
                    "LastSeen": "2023-05-07T17:40:56.751+0300",
                    "LastUpdate": "2023-03-26T18:26:06.762+0300",
                    "Priority": "Low",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Selected for Development",
                    "Summary": "Issue for demo"
                },
                {
                    "Assignee": "",
                    "Attachments": [
                        {
                            "created": "2023-01-22T16:43:29.878+0200",
                            "filename": "Screen Shot 2022-10-19 at 20.09.46.png",
                            "id": "16387",
                            "size": 450738
                        },
                        {
                            "created": "2023-02-05T21:11:43.429+0200",
                            "filename": "stam.png",
                            "id": "16399",
                            "size": 450738
                        }
                    ],
                    "Components": [],
                    "Created": "2023-01-03T10:25:17.257+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "This issue is for creating the home page.",
                    "DueDate": "",
                    "Id": "21361",
                    "Key": "PROJECTKEY-2",
                    "Labels": [
                        "Demisto",
                        "testnow"
                    ],
                    "LastSeen": "",
                    "LastUpdate": "2023-02-26T18:24:04.172+0200",
                    "Priority": "Medium",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Selected for Development",
                    "Summary": "Create Home Page (including the log in option)"
                }
            ],
            "boardId": "14"
        }
    }
}

Human Readable Output

Board Issues

Assignee Created Creator Description DueDate Id IssueType Key Labels Priority ProjectName Reporter Status Summary TicketLink
Example User() 2023-01-30T16:35:50.482+0200 Example User(example@example.com)   2023-09-09 21423 Sub-task PROJECTKEY-29 Demisto,
XSOAR,
testnow
Low Company Snoozing App Example User() Selected for Development Issue for demo https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21423
  2023-01-03T10:25:17.257+0200 Example User(example@example.com) This issue is for creating the home page.   21361 Story PROJECTKEY-2 Demisto,
testnow
Medium Company Snoozing App Example User(example@example.com) Selected for Development Create Home Page (including the log in option) https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21361

jira-board-sprint-list


Get all sprints of a specific board.

Scope: read:sprint:jira-software

Base Command

jira-board-sprint-list

Input

Argument Name Description Required
board_id The board ID. Required
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.BoardSprint.boardId String The ID of the board.
Jira.BoardSprint.Sprints Object The sprints in the board.
Jira.BoardSprint.Sprints.id Number The ID of the sprint.
Jira.BoardSprint.Sprints.self String The URL of the sprint.
Jira.BoardSprint.Sprints.state String The state of the sprint.
Jira.BoardSprint.Sprints.name String The name of the sprint.
Jira.BoardSprint.Sprints.startDate String The starting date of the sprint.
Jira.BoardSprint.Sprints.endDate String The ending date of the sprint.
Jira.BoardSprint.Sprints.completeDate String The date the sprint was completed.
Jira.BoardSprint.Sprints.originBoardId Number The ID of the origin board.
Jira.BoardSprint.Sprints.goal String The goal of the sprint.

Command example

!jira-board-sprint-list board_id=12

Context Example

{
    "Jira": {
        "BoardSprint": {
            "Sprints": [
                {
                    "endDate": "2023-02-27T15:12:00.000Z",
                    "goal": "",
                    "id": 4,
                    "name": "TSTPRD Sprint 1",
                    "originBoardId": 12,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/sprint/4",
                    "startDate": "2023-02-13T15:12:39.565Z",
                    "state": "active"
                },
                {
                    "endDate": "2023-02-23T14:57:00.000Z",
                    "goal": "Goal",
                    "id": 5,
                    "name": "TSTPRD Sprint 2",
                    "originBoardId": 12,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/sprint/5",
                    "startDate": "2023-02-07T14:57:00.000Z",
                    "state": "future"
                },
                {
                    "id": 6,
                    "name": "TSTPRD Sprint 3",
                    "originBoardId": 12,
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/sprint/6",
                    "state": "future"
                }
            ],
            "boardId": "12"
        }
    }
}

Human Readable Output

Sprints

End Date ID Name Start Date State
2023-02-27T15:12:00.000Z 4 TSTPRD Sprint 1 2023-02-13T15:12:39.565Z active
2023-02-23T14:57:00.000Z 5 TSTPRD Sprint 2 2023-02-07T14:57:00.000Z future
  6 TSTPRD Sprint 3   future

jira-board-epic-list


Get all epics from a specific board

Scope: read:epic:jira-software

Base Command

jira-board-epic-list

Input

Argument Name Description Required
board_id The board ID. Required
done Filters results to epics that are either done or not done. This uses the Epic Status field, and not the Status field of the issue. Possible values are: false, true. Default is false. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.BoardEpic.boardId Number The ID of the board.
Jira.BoardEpic.Epics Object Holds the data of the epic issues.
Jira.BoardEpic.Epics.id Number The ID of the epic.
Jira.BoardEpic.Epics.key String The key of the epic.
Jira.BoardEpic.Epics.self String The URL of the epic.
Jira.BoardEpic.Epics.name String The name of the epic.
Jira.BoardEpic.Epics.summary String The summary of the epic.
Jira.BoardEpic.Epics.done Boolean Whether the epic has been completed or not.
Jira.BoardEpic.Epics.color Object Data about the color of the epic.
Jira.BoardEpic.Epics.color.key String The key of the color associated with the epic.

Command example

!jira-board-epic-list board_id=14

Context Example

{
    "Jira": {
        "BoardEpic": {
            "Epics": [
                {
                    "color": {
                        "key": "color_4"
                    },
                    "done": false,
                    "id": 21360,
                    "key": "PROJECTKEY-1",
                    "name": "Create Company Snoozing App",
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/epic/21360",
                    "summary": "Start creating the company snoozing app"
                },
                {
                    "color": {
                        "key": "color_3"
                    },
                    "done": false,
                    "id": 21424,
                    "key": "PROJECTKEY-30",
                    "name": "New Epic",
                    "self": "https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/epic/21424",
                    "summary": "Issue for demo"
                }
            ],
            "boardId": "14"
        }
    }
}

Human Readable Output

Epics

Done ID Key Name Summary
false 21360 PROJECTKEY-1 Create Company Snoozing App Start creating the company snoozing app
false 21424 PROJECTKEY-30 New Epic Issue for demo

jira-sprint-issue-list


Get all issues in a sprint.

Scopes: read:sprint:jira-software, read:issue-details:jira, read:jql:jira

Base Command

jira-sprint-issue-list

Input

Argument Name Description Required
board_id The board ID. Optional
sprint_id The sprint ID. Required
jql_query A JQL query to filter the issues. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.SprintIssues.boardId String The ID of the board that the sprint belongs to.
Jira.SprintIssues.sprintId String The ID of the sprint.
Jira.SprintIssues.Ticket.Id Number The ticket ID.
Jira.SprintIssues.Ticket.Key String The ticket key.
Jira.SprintIssues.Ticket.Assignee String The user assigned to the ticket.
Jira.SprintIssues.Ticket.Creator String The user who created the ticket.
Jira.SprintIssues.Ticket.Summary String The ticket summary.
Jira.SprintIssues.Ticket.Status String The ticket status.
Jira.SprintIssues.Ticket.Priority String The ticket priority.
Jira.SprintIssues.Ticket.ProjectName String The ticket project name.
Jira.SprintIssues.Ticket.DueDate Date The due date.
Jira.SprintIssues.Ticket.Created Date The time the ticket was created.
Jira.SprintIssues.Ticket.LastSeen Date The last time the ticket was viewed.
Jira.SprintIssues.Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-sprint-issue-list sprint_id=4

Context Example

{
    "Jira": {
        "SprintIssues": {
            "Ticket": [
                {
                    "Assignee": "",
                    "Attachments": [],
                    "Components": [],
                    "Created": "2023-02-02T17:59:21.527+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "Please finish this ASAP.",
                    "DueDate": "",
                    "Id": "21430",
                    "Key": "TSTPRD-5",
                    "Labels": [],
                    "LastSeen": "2023-05-07T19:11:24.324+0300",
                    "LastUpdate": "2023-05-07T19:11:25.682+0300",
                    "Priority": "Medium",
                    "ProjectName": "TestProd",
                    "Status": "To Do",
                    "Summary": "Hire managers DO NOT DELETE OR MOVE"
                },
                {
                    "Assignee": "",
                    "Attachments": [],
                    "Components": [],
                    "Created": "2023-02-02T16:44:38.839+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "",
                    "DueDate": "",
                    "Id": "21427",
                    "Key": "TSTPRD-2",
                    "Labels": [],
                    "LastSeen": "2023-05-07T19:37:42.323+0300",
                    "LastUpdate": "2023-05-07T19:11:33.460+0300",
                    "Priority": "Medium",
                    "ProjectName": "TestProd",
                    "Status": "To Do",
                    "Summary": "Build UI DO NOT DELETE OR MOVE"
                },
                {
                    "Assignee": "",
                    "Attachments": [],
                    "Components": [],
                    "Created": "2023-02-02T16:45:37.346+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "",
                    "DueDate": "",
                    "Id": "21429",
                    "Key": "TSTPRD-4",
                    "Labels": [],
                    "LastSeen": "2023-05-04T20:45:40.938+0300",
                    "LastUpdate": "2023-05-04T21:03:30.876+0300",
                    "Priority": "Medium",
                    "ProjectName": "TestProd",
                    "Status": "To Do",
                    "Summary": "Build testing env"
                }
            ],
            "boardId": "12",
            "sprintId": "4"
        }
    }
}

Human Readable Output

Sprint Issues in board 12

Assignee Created Creator Description DueDate Id IssueType Key Labels Priority ProjectName Reporter Status Summary TicketLink
  2023-02-02T17:59:21.527+0200 Example User(example@example.com) Please finish this ASAP.   21430 Story TSTPRD-5   Medium TestProd Example User(example@example.com) To Do Hire managers DO NOT DELETE OR MOVE https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21430
  2023-02-02T16:44:38.839+0200 Example User(example@example.com)     21427 Task TSTPRD-2   Medium TestProd Example User(example@example.com) To Do Build UI DO NOT DELETE OR MOVE https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21427
  2023-02-02T16:45:37.346+0200 Example User(example@example.com)     21429 Task TSTPRD-4   Medium TestProd Example User(example@example.com) To Do Build testing env https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21429

jira-sprint-issue-move


Moves issues to a sprint, for a given sprint ID. Issues can only be moved to open or active sprints. At most 50 issues may be moved at once.

Scope: write:sprint:jira-software

Base Command

jira-sprint-issue-move

Input

Argument Name Description Required
issues The issues to move to the sprint. Required
sprint_id The sprint ID. Required
rank_before_issue To rank the issues before the stated issue (supports issue key and ID). Optional
rank_after_issue To rank the issues after the stated issue (supports issue key and ID). Optional

Context Output

There is no context output for this command.

Command example

!jira-sprint-issue-move issues=TSTPRD-4 sprint_id=4

Human Readable Output

Issues were moved to the Sprint successfully

jira-user-group-search


Gets users and groups that match a query string.

Scope: read:jira-work

Base Command

jira-user-group-search

Input

Argument Name Description Required
query The search string to match against users and groups. For users, performs a case-insensitive substring match on display name and email address. For groups, performs a case-sensitive substring match on group name. Note: if a user has hidden their email address, an exact email match is required. Required
limit The maximum number of results to return. Optional

Context Output

Path Type Description
Jira.UsersAndGroups.Users Object The Users that match the query string.
Jira.UsersAndGroups.Groups Object The Groups that match the query string.

Command example

!jira-user-group-search query="query"

Context Example

{
  "groups": {
    "groups": [
      {
        "groupId": "276f955c-63d7-42c8-9520-92d01dca0625",
        "html": "<b>j</b>dog-developers",
        "name": "jdog-developers"
      },
      {
        "groupId": "6e87dc72-4f1f-421f-9382-2fee8b652487",
        "html": "<b>j</b>uvenal-bot",
        "name": "juvenal-bot"
      }
    ],
    "header": "Showing 20 of 25 matching groups",
    "total": 25
  },
  "users": {
    "header": "Showing 20 of 25 matching users",
    "total": 25,
    "users": [
      {
        "accountId": "5b10a2844c20165700ede21g",
        "accountType": "atlassian",
        "avatarUrl": "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16",
        "displayName": "Mia Krystof",
        "html": "<strong>Mi</strong>a Krystof - <strong>mi</strong>a@example.com (<strong>mi</strong>a)",
        "key": "mia",
        "name": "mia"
      }
    ]
  }
}

Human Readable Output

Users

Account ID Display Name Account Type
5b10a2844c20165700ede21g Mia Krystof atlassian

Groups

Group ID Name
276f955c-63d7-42c8-9520-92d01dca0625 jdog-developers
6e87dc72-4f1f-421f-9382-2fee8b652487 juvenal-bot

jira-epic-issue-list


Get all issues that belong to an epic (Child Issues).

Scopes: read:epic:jira-software, read:issue-details:jira, read:jql:jira

Base Command

jira-epic-issue-list

Input

Argument Name Description Required
epic_key The key of the epic. (The epic ID or key is required). Optional
epic_id The ID of the epic. (The epic ID or key is required). Optional
jql_query A JQL query to filter the issues. Optional
limit The maximum number of results to return. This argument will be ignored if page or page_size arguments were given. Default value is 50. Optional
page The page number. Default value is 0. Optional
page_size The number of requested results per page. Default value is 50. Optional

Context Output

Path Type Description
Jira.EpicIssues.boardId String The ID of the board that holds the issues.
Jira.EpicIssues.epicId String The ID of the epic that holds the issues.
Jira.EpicIssues.key String The key of the epic.
Jira.EpicIssues.Ticket.Id Number The ticket ID.
Jira.EpicIssues.Ticket.Key String The ticket key.
Jira.EpicIssues.Ticket.Assignee String The user assigned to the ticket.
Jira.EpicIssues.Ticket.Creator String The user who created the ticket.
Jira.EpicIssues.Ticket.Summary String The ticket summary.
Jira.EpicIssues.Ticket.Status String The ticket status.
Jira.EpicIssues.Ticket.Priority String The ticket priority.
Jira.EpicIssues.Ticket.ProjectName String The ticket project name.
Jira.EpicIssues.Ticket.DueDate Date The due date.
Jira.EpicIssues.Ticket.Created Date The time the ticket was created.
Jira.EpicIssues.Ticket.LastSeen Date The last time the ticket was viewed.
Jira.EpicIssues.Ticket.LastUpdate Date The last time the ticket was updated.

Command example

!jira-epic-issue-list epic_key=PROJECTKEY-1 jql_query="status!=done"

Context Example

{
    "Jira": {
        "EpicIssues": {
            "Ticket": [
                {
                    "Assignee": "",
                    "Attachments": [
                        {
                            "created": "2023-01-22T16:43:29.878+0200",
                            "filename": "Screen Shot 2022-10-19 at 20.09.46.png",
                            "id": "16387",
                            "size": 450738
                        },
                        {
                            "created": "2023-02-05T21:11:43.429+0200",
                            "filename": "stam.png",
                            "id": "16399",
                            "size": 450738
                        }
                    ],
                    "Components": [],
                    "Created": "2023-01-03T10:25:17.257+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "This issue is for creating the home page.",
                    "DueDate": "",
                    "Id": "21361",
                    "Key": "PROJECTKEY-2",
                    "Labels": [
                        "Demisto",
                        "testnow"
                    ],
                    "LastSeen": "",
                    "LastUpdate": "2023-02-26T18:24:04.172+0200",
                    "Priority": "Medium",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Selected for Development",
                    "Summary": "Create Home Page (including the log in option)"
                },
                {
                    "Assignee": "",
                    "Attachments": [
                        {
                            "created": "2023-03-08T21:04:02.570+0200",
                            "filename": "DemistoContent-InstallContentEnvironment-160822-0023-68.pdf",
                            "id": "16401",
                            "size": 17815
                        },
                        {
                            "created": "2023-03-08T21:03:37.418+0200",
                            "filename": "Screen_Shot_2022-10-19_at_20.09.46 (2).png",
                            "id": "16400",
                            "size": 450738
                        }
                    ],
                    "Components": [
                        "comp2",
                        "Integration"
                    ],
                    "Created": "2023-03-07T17:31:48.225+0200",
                    "Creator": "Example User(example@example.com)",
                    "Description": "Dummy child description",
                    "DueDate": "",
                    "Id": "21496",
                    "Key": "PROJECTKEY-39",
                    "Labels": [
                        "Demisto",
                        "testnow"
                    ],
                    "LastSeen": "",
                    "LastUpdate": "2023-05-04T21:46:22.076+0300",
                    "Priority": "Medium",
                    "ProjectName": "Company Snoozing App",
                    "Status": "Selected for Development",
                    "Summary": "Dummy child issue - delete later"
                }
            ],
            "epicId": "21360"
        }
    }
}

Human Readable Output

Child Issues in epic PROJECTKEY-1

Assignee Created Creator Description DueDate Id IssueType Key Labels Priority ProjectName Reporter Status Summary TicketLink
  2023-01-03T10:25:17.257+0200 Example User(example@example.com) This issue is for creating the home page.   21361 Story PROJECTKEY-2 Demisto,
testnow
Medium Company Snoozing App Example User(example@example.com) Selected for Development Create Home Page (including the log in option) https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21361
  2023-03-07T17:31:48.225+0200 Example User(example@example.com) Dummy child description   21496 Task PROJECTKEY-39 Demisto,
testnow
Medium Company Snoozing App Example User(example@example.com) Selected for Development Dummy child issue - delete later https://api.atlassian.com/ex/jira/1234/rest/agile/1.0/issue/21496

jira-issue-link-type-get


Returns a list of all issue link types.

Scope: read:jira-work

Base Command

jira-issue-link-type-get

Input

There are no input arguments for this command.

Context Output

Path Type Description
Jira.IssueLinkType Object The issue link type bean.
Jira.IssueLinkType.id String The ID of the issue link type.
Jira.IssueLinkType.inward String The description of the issue link type inward link.
Jira.IssueLinkType.name String The name of the issue link type.
Jira.IssueLinkType.outward String The description of the issue link type outward link.
Jira.IssueLinkType.self String The URL of the issue link type.

Command example


#### Context Example

```json
{
    "Jira": {
        "IssueLinkType": [
            {
                "id": "10000",
                "inward": "is blocked by",
                "name": "Blocks",
                "outward": "blocks",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10000"
            },
            {
                "id": "10001",
                "inward": "is cloned by",
                "name": "Cloners",
                "outward": "clones",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10001"
            },
            {
                "id": "10002",
                "inward": "is duplicated by",
                "name": "Duplicate",
                "outward": "duplicates",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10002"
            },
            {
                "id": "10202",
                "inward": "is reviewed by",
                "name": "Post-Incident Reviews",
                "outward": "reviews",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10202"
            },
            {
                "id": "10201",
                "inward": "is caused by",
                "name": "Problem/Incident",
                "outward": "causes",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10201"
            },
            {
                "id": "10003",
                "inward": "relates to",
                "name": "Relates",
                "outward": "relates to",
                "self": "https://api.atlassian.com/ex/jira/1234/rest/api/3/issueLinkType/10003"
            }
        ]
    }
}

Human Readable Output

Issue Link Types

ID Inward Name Outward
10000 is blocked by Blocks blocks
10001 is cloned by Cloners clones
10002 is duplicated by Duplicate duplicates
10202 is reviewed by Post-Incident Reviews reviews
10201 is caused by Problem/Incident causes
10003 relates to Relates relates to

jira-issue-to-issue-link


Use this command to create a link between two issues.

Scope: write:jira-work

Base Command

jira-issue-to-issue-link

Input

Argument Name Description Required
outward_issue The key of the outward issue. Required
inward_issue The key of the inward issue. Required
link_type The name of the link to apply. Use jira-issue-link-type-get to see available links. Required
comment A comment to add to the inward issue. Optional

Context Output

There is no context output for this command.

Command example

!jira-issue-to-issue-link inward_issue=PROJECTKEY-31 outward_issue=XSOAR-19 link_type="Post-Incident Reviews" comment="Dummy Link"

Human Readable Output

Issue link created successfully

fetch-incidents


Dummy fetch

Base Command

fetch-incidents

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

get-remote-data


Get remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes only.

Base Command

get-remote-data

Input

Argument Name Description Required
id The remote incident ID. Required
lastUpdate The UTC timestamp in seconds of the last update. The incident is only updated if it was modified after the last update time. Default is 0. Optional

Context Output

There is no context output for this command.

get-mapping-fields


Returns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes.

Base Command

get-mapping-fields

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

get-modified-remote-data


Get the list of incidents that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1.

Base Command

get-modified-remote-data

Input

Argument Name Description Required
lastUpdate Date string representing the local time. The incident is only returned if it was modified after the last update time. Optional

Context Output

There is no context output for this command.

update-remote-system


Updates the remote incident with local incident changes. This method is only used for debugging purposes and will not update the current incident.

Base Command

update-remote-system

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

jira-get-user-info


Retrieves information about a specified Jira user. For on-prem instances, you should use the user’s key and username to retrieve their details. For Cloud instances, user’s account_id is required. If no identifier is supplied, information for the user the API credentials belong to is returned.

Base Command

jira-get-user-info

Input

Argument Name Description Required
key The user’s key (On-prem only). Optional
username The user’s username (On-prem only). Optional
account_id The user’s account ID (Cloud only). Optional

Context Output

Path Type Description
Jira.Users.Key Unknown The user’s key or ID.
Jira.Users.Name Unknown Name of the user.
Jira.Users.Email Unknown User’s email address.
Jira.Users.Display Name Unknown Display name for the user.
Jira.Users.Active Unknown If the user is active or not.
Jira.Users.Deleted Unknown If the user is deleted or not.
Jira.Users.Timezone Unknown Timezone setting for the user.
Jira.Users.Locale Unknown Locale setting for the user.
Jira.Users.AccountID Unknown The account ID of the user, which uniquely identifies the user across all Atlassian products. For example, 5b10ac8d82e05b22cc7d4ef5. Required in requests.
Jira.Users.AccountType Unknown The user account type. Can take the following values: atlassian, app, customer.

Command example

!jira-get-user-info key=JIRAUSER10000

Context Example

{
    "Active": true,
    "Deleted": false,
    "Display Name": "John Doe",
    "Email": "johndoe@example.com",
    "Key": "JIRAUSER10000",
    "Locale": "en_US",
    "Name": "johndoe",
    "Timezone": "America/Detroit"
}

jira-issue-get-forms


Fetches questions and answers for forms tied to a Jira issue. This command requires Jira Service Desk, the ProForma plugin and an API user with Service Desk Team or higher permissions on the Jira project the forms are being pulled from.

Base Command

jira-issue-get-forms

Input

Argument Name Description Required
issue_id The issue ID. Required

Context Output

Path Type Description
JiraForms.ID Unknown The ID of the form.
JiraForms.Issue Unknown Issue the form belongs to.
JiraForms.Questions Unknown Questions for the form.

Command example

!jira-issue-get-forms issue_id=TES-2

Context Example

{
    "ID": 1,
    "Issue": "TES-2",
    "Name": "8/9/2024 New Form",
    "Questions": [
        {
            "Answer": {
                "choices": [
                    {
                        "id": "26",
                        "label": "I have read and agree to the terms and conditions."
                    }
                ]
            },
            "Description": "",
            "ID": "8",
            "Key": "q_terms",
            "Label": "",
            "Type": "cm"
        },
        {
            "Answer": {},
            "Description": "",
            "ID": "4",
            "Key": "q_email",
            "Label": "Email",
            "Type": "te"
        },
        {
            "Answer": {},
            "Description": "",
            "ID": "5",
            "Key": "q_request_for",
            "Label": "Name of person needing building access",
            "Type": "tl"
        },
        {
            "Answer": {},
            "Description": "",
            "ID": "6",
            "Key": "q_description",
            "Label": "Request description",
            "Type": "pg"
        },
        {
            "Answer": {
                "text": "John"
            },
            "Description": "",
            "ID": "1",
            "Key": "q_name",
            "Label": "Name",
            "Type": "ts"
        },
        {
            "Answer": {
                "text": "555-555-1212"
            },
            "Description": "",
            "ID": "2",
            "Key": "q_phone",
            "Label": "Phone",
            "Type": "ts"
        },
        {
            "Answer": {},
            "Description": "",
            "ID": "7",
            "Key": "q_reason",
            "Label": "Reason for access",
            "Type": "pg"
        },
        {
            "Answer": {},
            "Description": "",
            "ID": "3",
            "Key": "q_job_title",
            "Label": "Job title",
            "Type": "ts"
        }
    ]
}

Incident Mirroring

You can enable incident mirroring between Cortex XSOAR incidents and Atlassian Jira V3 corresponding events (available from Cortex XSOAR version 6.0.0).
To set up the mirroring:

  1. Enable Fetching incidents in your instance configuration.
  2. In the Mirroring Direction integration parameter, select in which direction the incidents should be mirrored:

    Option Description
    None Turns off incident mirroring.
    Incoming Any changes in Atlassian Jira V3 events (mirroring incoming fields) will be reflected in Cortex XSOAR incidents.
    Outgoing Any changes in Cortex XSOAR incidents will be reflected in Atlassian Jira V3 events (outgoing mirrored fields).
    Incoming And Outgoing Changes in Cortex XSOAR incidents and Atlassian Jira V3 events will be reflected in both directions.
  3. Optional: Check the Close Mirrored XSOAR Incident integration parameter to close the Cortex XSOAR incident when the corresponding event is closed in Atlassian Jira V3.

When mirroring incidents, you can make changes in Jira, which will be reflected in Cortex XSOAR, or vice versa.
You can also attach files from either of the systems, which will then be available in the other system.

Newly fetched incidents will be mirrored in the chosen direction. However, this selection does not affect existing incidents.
In order for the mirroring to work properly, please keep the timezone of the Jira profile public.
Important Note: To ensure the mirroring works as expected, mappers are required, both for incoming and outgoing, to map the expected fields in Cortex XSOAR and Atlassian Jira V3.

9 Incident Fields have been added/updated to enhance the mirroring experience

  • dbotMirrorTags - Determines the tags that you need to add in Cortex XSOAR for entries to be pushed to Jira, and entries coming from Jira (Attachments or comments entries)
    • You can set the tags for incoming entries in the instance configuration, using Attachment Entry Tag from Jira, and Comment Entry Tag from Jira.
    • You can set the tags for outgoing entries in the instance configuration, using Attachment Entry Tag to Jira, and Comment Entry Tag to Jira.
  • Jira Issue Type - Shows the type of the Jira issue.
  • JiraV3 Status - Shows the status of the Jira issue. You can press on it in the JiraV3 Incident Layout to see the available statuses that you can transition into, and press on one of them to change the status of the issue. Show Jira statuses
  • Jira Estimate - Shows the estimate of the Jira issue. The field can also be edited and mirrored out. Use the format 2w 4d 6h 45m (w - weeks, d - days, h - hours, m - minutes). Please add the Time Tracking field to the screen in your Jira projects in order for this field to work properly.
  • Jira Component - Shows the components of the Jira issue. The field can also be edited and mirrored out.
  • Source Created By - Shows the display name, and email of the user who created the Jira issue.
  • Jira Subtask - Shows the subtasks of the Jira issue in the form of a table.
  • Jira Project - Shows the project name where the Jira issue resides in.
  • Last Update Time - Shows the last updated time of the Jira issue.
  • Jira Comment - Shows the comments of the Jira issue in the form of a table.

Modify the incoming mapper

  1. Navigate to Settings -> Objects Setup -> Classification and Mapping and click classifier-mapper-incoming-JiraV3.
  2. Under the Incident Type dropdown, select JiraV3 Incident.
  3. Change the mapping according to your needs.
  4. Save your changes.

Modify the outgoing mapper

  1. Under Classification and Mapping, click classifier-mapper-outgoing-Jira.
  2. Under Select Instance dropdown, select the instance name you want to work with.
    The left side of the screen shows the Jira fields to map and the right side of the
    screen shows the Cortex XSOAR fields by which you are mapping.
    *Note: If Select Instance dropdown is empty, go to the integration’s settings and under Incident Type select JiraV3 Incident and try again.
  3. Under Schema Type, select JiraV3 Incident. The Schema Type represents the Jira entity that
    you are mapping to. In our example it is an incident, but it can also be any other kind of ticket that
    Jira supports.
    Outgoing editor
  4. Under the Incident Type dropdown, select JiraV3 Incident.
  5. On the right side of the screen, under Incident, select the incident based on which you want to
    match.
  6. Change the mapping according to your needs.
  7. Save your changes.

Configure the following integration parameters in order to customize the mirroring feature

  1. Mirror Direction:
    • Mirror outgoing incidents: If enabled, any incident data changed in existing fetched incidents will be reflected in the remote Jira server.
    • Mirror incoming incidents: If enabled, any incident data changed in the remote Jira server will be reflected in existing fetched incidents.
  2. Fetch incidents: Should be enabled in order to mirror in, and out new incidents.
  3. Incident type: In order to mirror out changes, and mirror in incoming changes, provide an incident type that is associated with a layout containing the fields you want to mirror. You can use ‘JiraV3 Incident’, which already has a built-in layout, and functionality fit for the JiraV3 Incident type.
  4. Attachment Entry Tag from Jira: Choose a tag to add to an attachment entry when mirroring in an attachment from Jira.
  5. Attachment Entry Tag to Jira: Choose a tag to add to an entry in order to mirror it as an attachment in Jira.
  6. Comment Entry Tag from Jira: Choose a tag to add to a note entry when mirroring in comments from Jira.
  7. Comment Entry Tag to Jira: Choose a tag to add to an entry in order to mirror it as a comment in Jira.
  8. Fetch Comments: Fetch comments for Jira ticket.
  9. Fetch Attachments: Fetch attachments for Jira ticket.
  10. Close Mirrored XSOAR Incident: Marking the Jira issue as resolved will be mirrored in Cortex XSOAR, and will close the corresponding incident. Please make sure the Resolved field in your Jira instance is configured in order for this to work properly, if not, it will rely on if the status of the issue is changed to Done.

Simple Mirroring flow

  1. Create an incident in Jira. For the purpose of this use case, it can be a very simple incident. In Cortex XSOAR, the new ticket will be ingested in approximately one minute
  2. Add a note to the incident, which can be done using the Add Comment section in the JiraV3 Incident Layout. In the example below, we have written a comment from Cortex XSOAR to Jira.
    Add comment button
  3. Add the Comment Entry Tag to Jira tag that you’ve configured in the instance before. The default is: “comment tag”.
  4. Add a file to the incident’s War Room.
  5. Click Actions -> Tags and add the Attachment Entry Tag to Jira tag that you’ve configured in the instance before. The default is: “attachment tag”.
    Add attachment with tag
  6. Go back to Incident Info and locate a field you’ve configured, or was already configured in the outgoing mapper that is displayed in the incident’s layout. Change its value and click ‘V’ in the ‘V/X’ menu to save the changes.
    Changed value in layout
  7. Navigate back to the issue in Jira, and within approximately one minute, the changes will be reflected there as well.
  8. Change a Jira field you’ve configured in the incoming mapper.
  9. Go back to Cortex XSOAR and within approximately one minute, the changes will be reflected there as well.

Simple Comment and Attachment Mirroring flow

Mirror out Comment
  1. Press on the Press to add comment to Jira issue button in the Add Comment section.
  2. Add a comment and the tag that was configured in the Comment Entry Tag to Jira parameter.
  3. Once the comment is mirrored out to Jira, it will include the sentence Mirrored from Cortex XSOAR, to differentiate between comments mirrored from XSOAR, and normal comments.
    Mirrored out comment
Mirror in Comment
  1. Add a comment inside the Jira issue.
  2. The newly added comment will be mirrored in to XSOAR as a note, with the addition of the sentence Jira Author: {Author name}, to know the author of the comment, and tagged with the tag configured in Comment Entry Tag from Jira parameter.
    Mirrored in comment
Mirror out Attachment
  1. Add an attachment to the incident, and add the tag that was configured in the Attachment Entry Tag to Jira parameter.
  2. Once the attachment is reflected in Jira, the name of the file will have _mirrored_from_xsoar in it, to differentiate between attachments mirrored from XSOAR, and normal attachments.

Mirror out attachment

Mirror in Attachment
  1. Once an attachment is mirrored in from Jira into XSOAR, the attachment will be tagged with the tag configured in Attahcment Entry Tag from Jira parameter.
    Mirror in attachment

Notes

  • The final source of truth of the incident for Cortex XSOAR are the values in Cortex XSOAR.
    Meaning, if you change the severity in Cortex XSOAR and then change it back in Jira, the final value that will be presented is the one in Cortex XSOAR.
    You can see a list of these fields for each incident under “Context Data” -> “dbotDirtyFields”.
  • If you wish to mirror in and out Jira’s custom fields, please see the Mirror In And Out Custom Fields section below.
  • If you wish to change the status of a JiraV3 Incident using the available transitions, please see the Change Ticket’s Status Using Transitions section below.
  • If you wish to change the status of a JiraV3 Incident using the available statuses, please see the Change Ticket’s Status section below.

Mirror In And Out Custom Fields

Add a new custom field and add it to the incident type's layout

Add a new custom field and add it to the incident type's layout:

1.Create a new incident field:

  1. Go to Settings -> Object Setup -> Incident Fields -> New Field
  2. Choose a field type. For example, for a label, you might want to use “Tag” as a type.
  3. Add a short description in the Tooltip box (not mandatory).
  4. Click on Attributes:
    - Uncheck Add to incident types checkbox.
    - For Add to associated type, select the incident type you want to work with, preferably choose JiraV3 Incident.
    New incident field

2.Add the new incident field you've just created to the layout associated with the integration's incident type. In this example, the layout is “JiraV3 Incident Layout”:
Layout

Add the new field to the incoming mapper

  1. Configure the Atlassian Jira V3 integration, and test the connection of the instance as explained in the Authentication.
  2. Make sure that:
    • Under “Incident type” you've selected the incident type you want to work with and it is the same one you've selected once you created the new field. In our example: JiraV3 Incident.
    • The option Fetch Incidents is checked.
    • You have filled the respective parameters for the fetch to work, as explained in Fetch Incidents
    • The Mirror Direction is set to Incoming And Outgoing.
      Finally, click Done, and wait for new JiraV3 Incidents to be pulled:
  3. Once you've finished the setup stage, go to the Mapper (incoming) field and select the mapper you want to work with by clicking on Select:

  4. Click on the button next to the Mapper (incoming) field in order to edit the selected mapper. It will open the Incident Incoming Mapping Editor.
    1.For “Incident Type”, please provide the incident type you've selected in the integration settings.
    2.In “Select Instance” choose the name of your instance. You can find it in the integration settings under Name.
    Once done, You'll see on the right side of the screen a JiraV3 Incident.
    • Note: If you want to work with a specific incident, in Get data select the option: Upload JSON and provide the wanted incident as JSON.
      In order to get the incident as JSON, you can see the section: How to get an incident as JSON below.
      On the left side, You'll see all JiraV3 Incident fields.
      3.Use “Search Cortex XSOAR field…” in order to find your new incident field by searching its name.
      4.Once found, click on “Choose data path” and map it to the custom jira field:
      a.Find the jira field you want to map to this incident field on the right side, and click on its value.
      b.Then you will see the path you've selected under your new added field:
      New incident field mapping in
      *Note: In this example: customfield_10097 is the Jira field ID we want to map. You can find IDs of custom fields for your field in Jira using this guide:Click Here
      *Note: You can also type the path manually.
  5. Click “Save Version”.

Add the new field to the outgoing mapper

  1. Got to the configured Atlassian Jira V3 instance’s settings.
  2. Go to the Mapper (outgoing) field and select the mapper you want to work with by clicking on “Select” (the same process we did with the incoming mapper).
    3.
  3. Click on the button next to the Mapper (outgoing) field in order to edit the selected mapper. It will open the “Incident Outgoing Mapping Editor”.
  4. In “Select Instance” choose the name of your instance. You can find it in the integration settings under Name.
  5. Select one incident to work with by specifying it in the Incident list.
    1.On the right side you are now able to see your selected incident.
    2.On the left side you are able to see all the Jira fields you can map in the system.
  6. Search for the Jira field you want to map on the left side. In this example, we want to map customfield_10097
    New incident field mapping out
  7. Map the Jira field to the incident field you've created by clicking on Select incident field and type its name.
    *The name of the incident will not include spaces, underscores, capital letters, etc. for example: Jira Transitions will be transformed to “jiratransitions” once saved in the incident's context (you can see the name you need to select by fetching incidents after configuring the mapper in. In the “Incident” field select one of the new incidents that the edited mapper in has worked on. On the right side you should be able to see the new added field and the name you need to provide).
  8. Click the “Save Version” button.

How to get an incident as JSON

  1. Use the !jira-get-issue command with the raw-response=true parameter. For example: !jira-get-issue issue_id=VIK-183 raw-response=true
  2. Click on view full JSON in a new tab.
  3. Click on Download as a file.
  4. Open the downloaded file and put its content inside a list, resulting in a list which contains only one item, the downloaded JSON.
  5. When selecting Upload JSON for Get data as mentioned above, you can upload the file using the Upload JSON option.

Change Ticket’s Status Using Transitions or Statuses

  1. Make sure you’re using the ‘JiraV3 Incident Layout’ for displaying the incident which status you want to change.
    The reason this is required is that this layout is structured in a way that the script-JiraChangeTransition, script-JiraListTransition, JiraListStatus, and JiraChangeStatus scripts can work, which is crucial when you want to change the status of the issue.
  2. In order to change an incident status, open the incident you would like to update.
  3. Under Incident Info search for the Jira Transitions, or Jira Status field.
  4. Click on “Select” and choose the name of the transition or status to apply.
    Jira transition
    Jira status
  5. Click on the ‘V’ option in the ‘V/X’ menu.
  6. You can see the new status under ‘Jira Status’.
  7. Go to Jira, and you will be able to see the new status there as well.

Breaking changes from the previous version of this integration - Atlassian Jira V3

The following sections list the changes in this version.

Commands

The following commands were removed in this version

  • jira-get-specific-field - This command was removed, and its functionality is implemented using the fields argument in the jira-get-issue command.

  • jira-append-to-field - This command was removed, and its functionality is implemented using the action argument when supplying it the value append in the jira-edit-issue command.

Arguments

The following arguments were removed in this version

In the jira-get-id-by-attribute command:

  • is_jirav2api - The command does not require this argument anymore.

The behavior of the following arguments were changed

In the jira-create-issue command:

  • issueJson - This argument will override the other arguments supplied to the command.

In the jira-edit-issue command:

  • issueJson - This argument will override the other arguments supplied to the command.

In the jira-get-issue command:

  • get_attachments - Supplying this argument with the value true will return the attachments found in the specified issue as Entry Info File, and not as File.

jira-create-metadata-issue-types-list


Returns a page of issue type metadata for a specified project.

Base Command

jira-create-metadata-issue-types-list

Input

Argument Name Description Required
project_id_or_key The ID or key of the project. Required
start_at The index of the first item to return in a page of results (page offset). Default is 0. Optional
max_results The maximum number of items to return per page. Between 0 and 200. Default is 50. Optional

Context Output

Path Type Description
Jira.IssueType.AvatarID Number The ID of the issue type’s avatar.
Jira.IssueType.Description String The description of the issue type.
Jira.IssueType.EntityID String Unique ID for next-gen projects.
Jira.IssueType.Expand String Expand options that include additional issue type metadata details in the response.
Jira.IssueType.IconURL String The URL of the issue type’s avatar.
Jira.IssueType.ID String The ID of the issue type.
Jira.IssueType.Name String The name of the issue type.
Jira.IssueType.Self String The URL of these issue type details.
Jira.IssueType.Subtask Boolean Whether this issue type is used to create subtasks.
Jira.IssueType.Scope Object Details of the next-gen projects the issue type is available in.
Jira.IssueType.Scope.type String The type of scope. Valid values: PROJECT, TEMPLATE.
Jira.IssueType.Scope.project Object The project the item has scope in.
Jira.IssueType.Scope.project.self String The URL of the project details.
Jira.IssueType.Scope.project.id String The ID of the project.
Jira.IssueType.Scope.project.key String The key of the project.
Jira.IssueType.Scope.project.name String The name of the project.
Jira.IssueType.Scope.project.projectTypeKey String The project type of the project. Valid values: software, service_desk, business.
Jira.IssueType.Scope.project.simplified Boolean Whether or not the project is simplified.
Jira.IssueType.Scope.project.avatarUrls Object The URLs of the project’s avatars.
Jira.IssueType.Scope.project.projectCategory Object The category the project belongs to.
Jira.IssueType.Scope.project.projectCategory.self String The URL of the project category.
Jira.IssueType.Scope.project.projectCategory.id String The ID of the project category.
Jira.IssueType.Scope.project.projectCategory.description String The name of the project category.
Jira.IssueType.Scope.project.projectCategory.name String The description of the project category.

jira-create-metadata-field-list


Returns a page of field metadata for a specified project and issue type.

Base Command

jira-create-metadata-field-list

Input

Argument Name Description Required
project_id_or_key The ID or key of the project. Required
issue_type_id The issue type ID. Required
start_at The index of the first item to return in a page of results (page offset). Default is 0. Optional
max_results The maximum number of items to return per page. Between 0 and 200. Default is 50. Optional

Context Output

Path Type Description
Jira.IssueField.AllowedValues Array The list of values allowed in the field.
Jira.IssueField.AutoCompleteUrl String The URL that can be used to automatically complete the field.
Jira.IssueField.Configuration Object The configuration properties.
Jira.IssueField.DefaultValue String The default value of the field.
Jira.IssueField.FieldID String The field ID.
Jira.IssueField.HasDefaultValue Boolean Whether the field has a default value.
Jira.IssueField.Key String The key of the field.
Jira.IssueField.Name String The name of the field.
Jira.IssueField.Operations Array The list of operations that can be performed on the field.
Jira.IssueField.Required Boolean Whether the field is required.
Jira.IssueField.Schema Object The data type for the field.
Jira.IssueField.Schema.type String The data type of the field.
Jira.IssueField.Schema.items String When the data type is an array, the name of the field items within the array.
Jira.IssueField.Schema.system String If the field is a system field, the name of the field.
Jira.IssueField.Schema.custom String If the field is a custom field, the URI of the field.
Jira.IssueField.Schema.customId Number If the field is a custom field, the custom ID of the field.
Jira.IssueField.Schema.configuration Object If the field is a custom field, the configuration of the field.

jira-reset-timezone-cache


Clears the cached Jira user timezone used for fetching incidents and mirroring from the internal cache. Use if the timezone settings tied to the chosen authentication method were changed.

Base Command

jira-reset-timezone-cache

Input

There are no input arguments for this command.

Context Output

There is no context output for this command.

Command example

!jira-reset-timezone-cache

Human Readable Output

The Jira user timezone was successfully cleared from the cache

Configuration parameters

  • server_url — Server URL (required)
  • basic_credentials — User name
  • cloud_id — Cloud ID
  • callback_url — Callback URL
  • credentials — Client ID
  • pat_credential — Personal Access Token
  • fetch_query — Query (in JQL) for fetching incidents
  • issue_field_to_fetch_from — Issue Field to fetch by
  • id_offset — Issue index to start fetching incidents from
  • first_fetch — Time range for initial data fetch
  • insecure — Trust any certificate (not secure)
  • proxy — Use system proxy settings
  • isFetch — Fetch incidents
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval
  • close_incident — Close Mirrored XSOAR Incident
  • mirror_direction — Mirroring Direction
  • attachment_tag_to_jira — Attachment Entry Tag to Jira
  • attachment_tag_from_jira — Attachment Entry Tag from Jira
  • comment_tag_to_jira — Comment Entry Tag to Jira
  • comment_tag_from_jira — Comment Entry Tag from Jira
  • fetch_comments — Fetch comments
  • fetch_attachments — Fetch attachments
  • max_fetch — Max incidents per fetch

Commands (46)

  • get-mapping-fields

    Returns the list of fields to map in outgoing mirroring. This command is only used for debugging purposes.

  • get-modified-remote-data

    Get the list of incidents that were modified since the last update time. This method is used for debugging purposes. The get-modified-remote-data command is used as part of the Mirroring feature that was introduced in Cortex XSOAR version 6.1.

  • get-remote-data

    Get remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes only.

  • get-remote-data-preview

    Get remote data from a remote incident. This method does not update the current incident, and should be used for debugging purposes only.

  • jira-board-backlog-list

    Get issues from the backlog of a specific board. For Jira OnPrem, the board must be of type scrum.

  • jira-board-epic-list

    Get all epics from a specific board.

  • jira-board-issue-list

    Get all issues from a specific board.

  • jira-board-list

    Get board data. If board_id is given, then only the board that corresponds to the board_id is returned, otherwise, the the command will return boards that match the project_key_id, type, or board_name arguments.

  • jira-board-sprint-list

    Get all sprints of a specific board.

  • jira-create-issue

    Creates a new issue in Jira.

  • jira-create-issue-quick-action

    Creates a new issue in Jira.

  • jira-create-metadata-field-list

    Returns a page of field metadata for a specified project and issue type.

  • jira-create-metadata-issue-types-list

    Returns a page of issue type metadata for a specified project.

  • jira-delete-issue

    Deletes an issue in Jira. Issue's sub-tasks will also be deleted if there are any.

  • jira-edit-issue

    Modifies an issue in Jira. If the status or transition fields are given, then the other supplied fields should appear on the transition screen.

  • jira-epic-issue-list

    Get all issues that belong to an epic (Child Issues).

  • jira-get-comments

    Returns the comments added to a ticket.

  • jira-get-id-by-attribute

    Gets the account ID for a given user attribute.

  • jira-get-id-offset

    Returns the ID offset, which is the ID of the first issue that was created in the current Jira instance, or the ID of the first issue with respect to the given query argument if given. You can use its result as the initial ID when fetching incidents by the issue id field.

  • jira-get-issue

    Fetches an issue from Jira.

  • jira-get-specific-field

    Gets specific fields from a Jira issue and adds it to context dynamically.

  • jira-get-user-info

    Retrieves information about a specified Jira user. For on-prem instances, you should use the user's key and username to retrieve their details. For Cloud instances, user's `account_id` is required. If no identifier is supplied, information for the user the API credentials belong to is returned.

  • jira-issue-add-comment

    Adds a new comment to an existing Jira issue.

  • jira-issue-add-link

    Creates (or updates) an issue link.

  • jira-issue-assign

    Assigns an assignee to an existing issue.

  • jira-issue-delete-comment

    Delete a comment from an issue.

  • jira-issue-delete-file

    Delete an attachment from an issue.

  • jira-issue-edit-comment

    Edit a comment that is part of an issue.

  • jira-issue-get-attachment

    Download attachments for a specific issue (Will download a file to War Room).

  • jira-issue-get-forms

    Fetches questions and answers for forms tied to a Jira issue. This command requires Jira Service Desk, the ProForma plugin and an API user with `Service Desk Team` or higher permissions on the Jira project the forms are being pulled from.

  • jira-issue-link-type-get

    Returns a list of all issue link types.

  • jira-issue-list-fields

    This command returns the issue fields, both system and custom fields.

  • jira-issue-query

    Queries Jira issues.

  • jira-issue-to-backlog

    Moves issues to backlog. If the issues supplied are NOT part of a board that supports sprints, then the board_id argument is required. At most 50 issues may be moved at once.

  • jira-issue-to-board

    Moves issues from backlog to board, the command does not work with boards with sprints. At most 50 issues may be moved at once. This command is only supported for Jira Cloud instances.

  • jira-issue-to-issue-link

    Use this command to create a link between two issues.

  • jira-issue-upload-file

    Uploads a file attachment to an issue.

  • jira-list-transitions

    Lists all possible transitions for a given ticket in its current status.

  • jira-oauth-complete

    Use this command to complete the authorization process. After copying the authorization code found in the query parameter `code` of the callback URL, paste the value in the command as an argument to finish the process.

  • jira-oauth-start

    Use this command to start the authorization process. In order to authorize the instance, first run the command, and complete the process in the URL that is returned. You will then be redirected to the callback URL where you will copy the authorization code found in the query parameter `code`, and paste that value in the command `!jira-ouath-complete` as an argument to finish the process.

  • jira-oauth-test

    Use this command to test the connectivity of the Jira instance.

  • jira-reset-timezone-cache

    Clears the cached Jira user timezone used for fetching incidents and mirroring from the internal cache. Use if the timezone settings tied to the chosen authentication method were changed.

  • jira-sprint-issue-list

    Get all issues in a sprint.

  • jira-sprint-issue-move

    Moves issues to a sprint, for a given sprint ID. Issues can only be moved to open or active sprints. At most 50 issues may be moved at once.

  • jira-user-group-search

    Searches for users and groups in Jira that match the query string.

  • update-remote-system

    Updates the remote incident with local incident changes. This method is only used for debugging purposes and will not update the current incident.

import json
from unittest.mock import patch, Mock

import demistomock as demisto
import pytest
from CommonServerPython import *
from JiraV3 import JiraBaseClient, JiraCloudClient, JiraOnPremClient, get_remote_data_preview_command
from pytest_mock import MockerFixture


def util_load_json(path: str):
    with open(path, encoding="utf-8") as f:
        return json.loads(f.read())


def util_load_bytes_file(path: str):
    with open(path, mode="rb") as f:
        return f.read()
        # return json.loads(f.read())


@patch.object(JiraBaseClient, "__abstractmethods__", set())
def jira_base_client_mock(username: str = "", api_key: str = "", pat: str = "") -> JiraBaseClient:
    """The way to mock an abstract class is using the trick @patch.object(Abstract_Class, __abstractmethods__, set()),
    since Python, behind the scenes, checks the __abstractmethods__ property, which contains a set of the names of all
    the abstract methods defined on the abstract class, if it is not empty, we won't be able to instantiate the abstract class,
    however, if this set is empty, the Python interpreter will happily instantiate our class without any problems.
    """
    return JiraBaseClient(
        base_url="https://dummy_url",
        proxy=False,
        verify=False,
        callback_url="dummy_callback",
        api_version="999",
        username=username,
        api_key=api_key,
        pat=pat,
    )


def jira_cloud_client_mock() -> JiraCloudClient:
    return JiraCloudClient(
        proxy=False,
        verify=False,
        client_id="dummy_client_id",
        client_secret="dummy_secret",
        callback_url="dummy_url",
        cloud_id="dummy_cloud_id",
        server_url="https://dummy_server_url",
        username="",
        api_key="",
        pat="",
    )


def jira_onprem_client_mock() -> JiraOnPremClient:
    return JiraOnPremClient(
        proxy=False,
        verify=False,
        client_id="dummy_client_id",
        client_secret="dummy_secret",
        callback_url="dummy_url",
        server_url="https://dummy_server_url",
        username="",
        api_key="",
        pat="",
    )


def test_v2_args_to_v3():
    from JiraV3 import map_v2_args_to_v3

    v2_args = {
        "startAt": "dummy_start_at",
        "maxResults": "dummy_max_results",
        "extraFields": "dummy_fields",
        "getAttachments": "dummy_get_attachments",
        "expandLinks": "dummy_expand_links",
        "expand_links": "not prioritized",
        "issueJson": "dummy_issue_json",
        "projectKey": "dummy_project_key",
        "issueTypeName": "dummy_issue_type_name",
        "issueTypeId": "dummy_issue_type_id",
        "projectName": "dummy_project_name",
        "dueDate": "dummy_due_date",
        "due_date": "not prioritized",
        "parentIssueKey": "dummy_parent_issue_key",
        "parentIssueId": "dummy_parent_issue_id",
        "attachmentName": "dummy_attachment_name",
        "globalId": "dummy_global_id",
        "applicationType": "dummy_application_type",
        "applicationName": "dummy_application_name",
        "issueId": "1234",
        "issueIdOrKey": "dummy_issue_key",
    }
    expected_v3_args = {
        "start_at": "dummy_start_at",
        "max_results": "dummy_max_results",
        "fields": "dummy_fields",
        "get_attachments": "dummy_get_attachments",
        "expand_links": "dummy_expand_links",
        "issue_json": "dummy_issue_json",
        "project_key": "dummy_project_key",
        "issue_type_name": "dummy_issue_type_name",
        "issue_type_id": "dummy_issue_type_id",
        "project_name": "dummy_project_name",
        "due_date": "dummy_due_date",
        "parent_issue_key": "dummy_parent_issue_key",
        "parent_issue_id": "dummy_parent_issue_id",
        "attachment_name": "dummy_attachment_name",
        "global_id": "dummy_global_id",
        "application_type": "dummy_application_type",
        "application_name": "dummy_application_name",
        "issue_id": "1234",
        "issue_key": "dummy_issue_key",
    }
    v3_args = map_v2_args_to_v3(args=v2_args)
    assert v3_args == expected_v3_args


# Helper functions unit tests
ADF_TEXT_CASES = [
    (
        "Hello there",
        {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"text": "Hello there", "type": "text"}]}]},
    ),
    (
        '{"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"text": "Hello there", "type": "text"}]}]}',
        {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"text": "Hello there", "type": "text"}]}]},
    ),
    (
        {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"text": "Hello there", "type": "text"}]}]},
        {"type": "doc", "version": 1, "content": [{"type": "paragraph", "content": [{"text": "Hello there", "type": "text"}]}]},
    ),
    (
        '{"invalid": "json"}',
        {
            "type": "doc",
            "version": 1,
            "content": [{"type": "paragraph", "content": [{"text": '{"invalid": "json"}', "type": "text"}]}],
        },
    ),
    (
        '{"type": "doc", "version": 1}',
        {
            "type": "doc",
            "version": 1,
            "content": [{"type": "paragraph", "content": [{"text": '{"type": "doc", "version": 1}', "type": "text"}]}],
        },
    ),
    (
        {"type": "doc", "version": 1, "content": ["invalid_node"]},
        {
            "type": "doc",
            "version": 1,
            "content": [
                {
                    "type": "paragraph",
                    "content": [{"text": {"type": "doc", "version": 1, "content": ["invalid_node"]}, "type": "text"}],
                }
            ],
        },
    ),
]


@pytest.mark.parametrize("text, expected_adf_text", ADF_TEXT_CASES)
def test_text_to_adf(text, expected_adf_text):
    from JiraV3 import text_to_adf

    adf_text = text_to_adf(text=text)
    assert expected_adf_text == adf_text


COMMENT_URL_CASES = [
    ("https://dummy-domain.atlassian.net/rest/api/3/issue/10010/comment/10000", "10010"),
    ("http://dummy-domain.com/some_path/latest/issue/123/comment/", "123"),
]


@pytest.mark.parametrize("url, expected_issue_id", COMMENT_URL_CASES)
def test_extract_issue_id_from_comment_url(url, expected_issue_id):
    from JiraV3 import extract_issue_id_from_comment_url

    issue_id = extract_issue_id_from_comment_url(comment_url=url)
    assert expected_issue_id == issue_id


ISSUE_FIELDS_MAPPING_CASES = [
    (
        [
            {
                "id": "statuscategorychangedate",
                "key": "statuscategorychangedate",
                "name": "Status Category Changed",
                "custom": False,
                "orderable": False,
                "navigable": True,
                "searchable": True,
                "clauseNames": ["statusCategoryChangedDate"],
                "schema": {"type": "datetime", "system": "statuscategorychangedate"},
            },
            {
                "id": "parent",
                "key": "parent",
                "name": "Parent",
                "custom": False,
                "orderable": False,
                "navigable": True,
                "searchable": False,
                "clauseNames": ["parent"],
            },
        ],
        {"statuscategorychangedate": "Status Category Changed", "parent": "Parent"},
    )
]


@pytest.mark.parametrize("issue_fields, issue_fields_mapping", ISSUE_FIELDS_MAPPING_CASES)
def test_get_issue_fields_mapping(mocker, issue_fields, issue_fields_mapping):
    from JiraV3 import JiraBaseClient, get_issue_fields_id_to_name_mapping

    mocker.patch.object(JiraBaseClient, "get_issue_fields", return_value=issue_fields)
    client = jira_base_client_mock()
    mapping_result = get_issue_fields_id_to_name_mapping(client=client)
    assert mapping_result == issue_fields_mapping


CREATE_ISSUE_QUERY_CASES = [
    (
        "some_jql_string",
        None,
        None,
        {"jql": "some_jql_string", "maxResults": 50},
        "",
    ),
    (
        "some_jql_string",
        12,
        None,
        {"jql": "some_jql_string", "startAt": 12, "maxResults": 50},
        "",
    ),
    (
        "some_jql_string",
        1,
        80,
        {"jql": "some_jql_string", "startAt": 1, "maxResults": 80},
        "",
    ),
    (
        "some_jql_string",
        5,
        80,
        {"jql": "some_jql_string", "nextPageToken": "dummy_page_token", "maxResults": 80},
        "dummy_page_token",
    ),
]


@pytest.mark.parametrize("jql, start_at, max_results, expected_query_params, next_page_token", CREATE_ISSUE_QUERY_CASES)
def test_create_query_params(jql, start_at, max_results, expected_query_params, next_page_token):
    from JiraV3 import create_query_params

    query_params = create_query_params(jql_query=jql, start_at=start_at, max_results=max_results, next_page_token=next_page_token)
    assert query_params == expected_query_params


PAGINATION_ARGS_CASES = [
    ({"page": 1, "page_size": 3, "limit": 5}, {"start_at": 3, "max_results": 3}),
    ({"limit": 5}, {"start_at": 0, "max_results": 5}),
    ({"page": 4, "limit": 5}, {"start_at": 200, "max_results": 50}),
    ({"page_size": 23, "limit": 5}, {"start_at": 0, "max_results": 23}),
]


@pytest.mark.parametrize("pagination_args, expected_parsed_pagination_args", PAGINATION_ARGS_CASES)
def test_prepare_pagination_args(pagination_args, expected_parsed_pagination_args):
    from JiraV3 import prepare_pagination_args

    parsed_pagination_args = prepare_pagination_args(**pagination_args)
    assert expected_parsed_pagination_args == parsed_pagination_args


@pytest.mark.parametrize("issue_id, issue_key", [("1234", "key1"), ("", "")])
def test_get_issue_id_or_key_error(issue_id, issue_key):
    from JiraV3 import get_issue_id_or_key

    with pytest.raises(DemistoException):
        get_issue_id_or_key(issue_id, issue_key)


@pytest.mark.parametrize("issue_id, issue_key, expected_issue_id_or_key", [("1234", "", "1234"), ("", "key-1", "key-1")])
def test_get_issue_id_or_key(issue_id, issue_key, expected_issue_id_or_key):
    from JiraV3 import get_issue_id_or_key

    issue_id_or_key = get_issue_id_or_key(issue_id, issue_key)
    assert issue_id_or_key == expected_issue_id_or_key


@pytest.mark.parametrize(
    "username, api_key, pat",
    [
        ("dummy_username", "dummy_api_key", ""),
        ("", "", ""),
        ("", "", "dummy_pat"),
    ],
)
def test_http_request(mocker, username: str, api_key: str, pat: str):
    """
    Given:
        - username, api_key and pat
    When:
        - run http_request method
    Then:
        - Ensure when the username and api_key are provided then only the 'get_headers_with_basic_auth' method is called
        - Ensure when the username and api_key are not provided then only the 'get_headers_with_access_token' method is called
    """
    client = jira_base_client_mock(username=username, api_key=api_key, pat=pat)

    basic_auth_mock = mocker.patch.object(client, "get_headers_with_basic_auth", return_value={})
    oauth2_mock = mocker.patch.object(client, "get_headers_with_access_token", return_value={})
    mocker.patch.object(client, "_http_request")
    client.http_request("GET")

    assert basic_auth_mock.call_count == int(bool(client.username))
    assert oauth2_mock.call_count == int(not bool(client.username))


def test_test_module_basic_auth(mocker):
    """
    Given:
        - mock client with username and api_key (basic auth)
    When:
        - run `jira_test_module` function
    Then:
        - Ensure no error is raised, and return `ok`
    """
    from JiraV3 import jira_test_module

    client = jira_base_client_mock("dummy_username", "dummy_api_key")
    mocker.patch.object(client, "jira_test_instance_connection")
    assert jira_test_module(client, params={}) == "ok"


def test_test_module_pat(mocker):
    """
    Given:
        - mock client with personal access token (pat)
    When:
        - run `jira_test_module` function
    Then:
        - Ensure no error is raised, and return `ok`
    """
    from JiraV3 import jira_test_module

    client = jira_base_client_mock(pat="dummy_pat")
    mocker.patch.object(client, "jira_test_instance_connection")
    assert jira_test_module(client, params={}) == "ok"


def test_module_oauth2(mocker):
    """
    Given:
        - mock client without username and api_key (oauth2)
    When:
        - run `jira_test_module` function
    Then:
        - Ensure that error msg is raised, with a guide how to connect through oauth2
    """
    from JiraV3 import jira_test_module

    client = jira_base_client_mock()
    mocker.patch.object(client, "jira_test_instance_connection")
    with pytest.raises(DemistoException, match="In order to authorize the instance, first run the command `!jira-oauth-start`."):
        jira_test_module(client, params={})


@pytest.mark.parametrize(
    "params, expected_exception",
    [
        pytest.param(
            {
                "username": "",
                "api_key": "",
                "client_id": "",
                "client_secret": "",
                "pat": "",
            },
            "The required parameters were not provided. See the help window for more information.",
            id="no auth params provided",
        ),
        pytest.param(
            {
                "username": "dummy_username",
                "api_key": "dummy_api_key",
                "client_id": "dummy_client_id",
                "client_secret": "dummy_client_secret",
                "pat": "dummy_pat",
            },
            "The `User name` or `API key` parameters cannot be provided together with the `Client ID` or `Client Secret`"
            " parameters or with the `Personal Access Token` parameters. See the help window for more information.",
            id="multiple types of auth params are provided",
        ),
        pytest.param(
            {
                "username": "dummy_username",
                "api_key": "",
                "client_id": "",
                "client_secret": "",
                "pat": "",
            },
            "To use basic authentication, the 'User name' and 'API key' parameters are mandatory",
            id="only `username` parameter was provided",
        ),
        pytest.param(
            {
                "username": "",
                "api_key": "",
                "client_id": "dummy_client_id",
                "client_secret": "",
                "pat": "",
            },
            "To use OAuth 2.0, the 'Client ID' and 'Client Secret' parameters are mandatory",
            id="only `client_id` parameter was provided",
        ),
    ],
)
def test_validate_params_failure(params: dict[str, str], expected_exception: str):
    """
    Given:
        - auth params
    When:
        - run `validate_auth_params` function
    Then:
        - Ensure that as long as no valid auth params are sent an error is raised with a special message
    """
    from JiraV3 import validate_auth_params

    with pytest.raises(DemistoException, match=expected_exception):
        validate_auth_params(**params)


@pytest.mark.parametrize(
    "params",
    [
        pytest.param(
            {"username": "dummy_username", "api_key": "dummy_api_key", "client_id": "", "client_secret": "", "pat": ""},
            id="Only basic auth params were provided",
        ),
        pytest.param(
            {"username": "", "api_key": "", "client_id": "dummy_client_id", "client_secret": "dummy_client_secret", "pat": ""},
            id="Only oauth2 params were provided oauth2",
        ),
        pytest.param(
            {"username": "", "api_key": "", "client_id": "", "client_secret": "", "pat": "dummy_pat_secret"},
            id="Only pat param was provided pat",
        ),
    ],
)
def test_validate_auth_params(params: dict[str, str]):
    """
    Given:
        - auth params
    When:
        - run `validate_auth_params` function
    Then:
        - Ensure that when provided valid auth params
          the function does not raise
    """
    from JiraV3 import validate_auth_params

    validate_auth_params(**params)


@pytest.mark.parametrize("username, api_key", [("dummy_username", "dummy_api_key"), ("", "")])
def test_client_is_basic_auth_or_oauth(username: str, api_key: str):
    """
    Given:
        - username and api_key
    When:
        - run `__init__` method for `JiraBaseClient` class
    Then:
        - Ensure that when the client class receives both username and api_key,
          the `is_basic_auth` flag is True otherwise False
    """
    client = jira_base_client_mock(username, api_key)
    assert client.is_basic_auth == bool(username)


class TestJiraGetIssueCommand:
    def test_create_file_info_from_attachment(self, mocker):
        """
        Given:
            - An attachment id
        When
            - Calling the get create_file_info_from_attachment function to create a file of type EntryType.ENTRY_INFO_FILE
        Then
            - Validate that the file has been created, is of the correct type, has the correct file name, and was created
            with the correct content.
        """
        from pathlib import Path

        from JiraV3 import create_file_info_from_attachment

        client = jira_base_client_mock()
        raw_response_attachment_metadata = util_load_json("test_data/get_issue_test/raw_response_attachment_metadata.json")
        dummy_attachment_content = util_load_bytes_file("test_data/get_issue_test/dummy_attachment_content.txt")
        mocker.patch.object(client, "get_attachment_metadata", return_value=raw_response_attachment_metadata)
        mocker.patch.object(client, "get_attachment_content", return_value=dummy_attachment_content)
        file_name = "dummy_file_name.pdf"
        file_result_mocker = mocker.patch("JiraV3.fileResult", side_effect=fileResult)
        file_info_res = create_file_info_from_attachment(client=client, attachment_id="dummy_attachment_id", file_name=file_name)
        assert file_result_mocker.call_args[1].get("data") == dummy_attachment_content
        assert file_info_res.get("Type") == EntryType.ENTRY_INFO_FILE
        assert file_info_res.get("File", "") == file_name
        assert Path.exists(Path(f"{demisto.investigation()['id']}_{file_info_res.get('FileID', '')}"))
        Path.unlink(Path(f"{demisto.investigation()['id']}_{file_info_res.get('FileID', '')}"))

    @pytest.mark.parametrize("get_attachments", [(True), (False)])
    def test_download_issue_attachments_to_war_room(self, mocker, get_attachments):
        """
        Given:
            - A boolean on whether to download the attachments from Jira to the war room or not.
        When
            - Calling the function that is in charge of downloading the attachments to the war room.
        Then
            - Validate that a fileResult object was created
        """
        from JiraV3 import get_issue_command

        client = jira_base_client_mock()
        raw_issue_response = util_load_json("test_data/get_issue_test/raw_response.json")
        mocker.patch.object(client, "get_issue", return_value=raw_issue_response)
        mocker.patch(
            "JiraV3.create_file_info_from_attachment",
            return_value={
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": "dummy_type",
                "File": "dummy_filename",
                "FileID": "dummy_id",
            },
        )
        demisto_results_mocker = mocker.patch.object(demisto, "results")
        get_issue_command(client=client, args={"issue_id": "1234", "get_attachments": get_attachments})
        if get_attachments:
            demisto_results_mocker.assert_called_once()
        else:
            demisto_results_mocker.assert_not_called()

    def test_jira_get_issue(self, mocker):
        """
        Given:
            - An issue key or id, and the arguments: expand_links=true, fields=watches,rank
        When
            - Calling the get issue command
        Then
            - Validate that the context data and human readable are correct.
        """
        from JiraV3 import get_issue_command

        client = jira_base_client_mock()
        args = {"issue_key": "dummy_key", "get_attachments": "true", "expand_links": "true", "fields": "watches,rank"}
        raw_response = util_load_json("test_data/get_issue_test/raw_response.json")
        raw_response_extended_issues = util_load_json("test_data/get_issue_test/raw_response_extended_issues.json")
        expected_command_results_context = util_load_json("test_data/get_issue_test/parsed_result.json")
        mocker.patch.object(client, "get_issue", return_value=raw_response)
        mocker.patch("JiraV3.get_expanded_issues", return_value=raw_response_extended_issues)
        mocker.patch("JiraV3.download_issue_attachments_to_war_room", return_value=None)
        command_results = get_issue_command(client, args)
        for expected_command_result_context, command_result in zip(expected_command_results_context, command_results):
            assert expected_command_result_context["EntryContext"] == command_result.to_context()["EntryContext"]
            assert expected_command_result_context["HumanReadable"] == command_result.to_context()["HumanReadable"]


class TestJiraGetCommentsCommand:
    def test_jira_get_comments(self, mocker):
        """
        Given:
            - An issue key or id.
        When
            - Calling the get comments command.
        Then
            - Validate that the context data and human readable are correct.
        """
        from JiraV3 import get_comments_command

        client = jira_base_client_mock()
        raw_response = util_load_json("test_data/get_comments_test/raw_response.json")
        expected_command_results_context = util_load_json("test_data/get_comments_test/parsed_result.json")
        mocker.patch.object(client, "get_comments", return_value=raw_response)
        command_result = get_comments_command(client=client, args={"issue_key": "dummy_issue_key"})
        assert expected_command_results_context["EntryContext"] == command_result.to_context()["EntryContext"]
        assert expected_command_results_context["HumanReadable"] == command_result.to_context()["HumanReadable"]

    def test_extract_comment_entry_from_raw_response(self):
        """
        Given:
            - A comment that has been returned from the Jira API.
        When
            - Extracting the comment entry from the raw response.
        Then
            - Validate that the comment entry includes the correct values.
        """
        from JiraV3 import extract_comment_entry_from_raw_response

        comment_raw_response = {
            "id": "18322",
            "author": {
                "displayName": "Example User",
            },
            "body": {
                "version": 1,
                "type": "doc",
                "content": [{"type": "paragraph", "content": [{"type": "text", "text": "Hello there"}]}],
            },
            "renderedBody": "<p>Hello there</p>",
            "updateAuthor": {
                "displayName": "Example User",
            },
            "created": "2023-03-23T07:45:29.056+0200",
            "updated": "2023-03-23T07:45:29.056+0200",
        }
        expected_comment_entry = {
            "Id": "18322",
            "Comment": "Hello there",
            "User": "Example User",
            "Created": "2023-03-23T07:45:29.056+0200",
            "Updated": "2023-03-23T07:45:29.056+0200",
            "UpdateUser": "Example User",
        }
        comment_entry = extract_comment_entry_from_raw_response(comment_response=comment_raw_response)
        assert comment_entry == expected_comment_entry


class TestJiraEditIssueCommand:
    def test_edit_issue_with_transition_and_status_error(self):
        """
        Given:
            - A Jira client, and arguments that hold the status and transition supplied by the user.
        When
            - Calling the edit issue command.
        Then
            - Validate that an error is returned since the user cannot supply both a status and transition.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        with pytest.raises(DemistoException) as e:
            edit_issue_command(client=client, args={"issue_id": "1", "status": "dummy_status", "transition": "dummy_transition"})
        assert "Please provide only status or transition, but not both" in str(e)

    def test_edit_issue_command(self, mocker):
        """
        Given:
            - A Jira client, and arguments to edit a Jira issue (without status and transition arguments).
        When
            - Calling the edit issue command.
        Then
            - Validate that the edit_issue method was called, then get_issue was called to retrieve the newly
            updated issue, and that the updated issue's data was returned to context data.
        """
        from JiraV3 import create_issue_md_and_outputs_dict, edit_issue_command

        client = jira_base_client_mock()
        args = {"issue_key": "dummy_key", "due_date": "2024-05-07"}
        mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        dummy_issue_data = {"id": "1234", "key": "dummy_key", "fields": {"duedate": "2024-05-07"}}
        mocker.patch.object(client, "get_issue", return_value=dummy_issue_data)
        _, outputs = create_issue_md_and_outputs_dict(issue_data=dummy_issue_data)
        command_result = edit_issue_command(client=client, args=args)
        assert command_result.to_context().get("EntryContext") == {"Ticket(val.Id && val.Id == obj.Id)": outputs}

    @pytest.mark.parametrize(
        "args",
        [
            ({"issue_key": "dummy_key", "status": "Selected for development"}),
            ({"issue_key": "dummy_key", "transition": "In Development"}),
        ],
    )
    def test_apply_issue_status_and_transition(self, mocker, args):
        """
        Given:
            - A Jira client, and the status, or transition argument to change the status of the issue.
        When
            - Calling the edit issue command.
        Then
            - Validate that get_transitions, and transition_issue method were called, which is in charge of changing the status
            of the issue.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        transitions_raw_response = util_load_json("test_data/get_transitions_test/raw_response.json")
        get_transitions_mocker = mocker.patch.object(client, "get_transitions", return_value=transitions_raw_response)
        apply_transition_mocker = mocker.patch.object(client, "transition_issue", return_value=requests.Response())
        mocker.patch.object(client, "get_issue", return_value={})
        mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        edit_issue_command(client=client, args=args)
        get_transitions_mocker.assert_called_once()
        apply_transition_mocker.assert_called_once()

    @pytest.mark.parametrize(
        "args",
        [
            ({"issue_key": "dummy_key", "status": "Selected for development"}),
            ({"issue_key": "dummy_key", "transition": "In Development"}),
        ],
    )
    def test_apply_issue_status_and_transition_with_arguments(self, mocker, args):
        """
        Given:
            - A Jira client, and the status, or transition argument to change the status of the issue.
        When
            - Calling the edit issue command, with additional arguments to edit the issue.
        Then
            - Validate that correct issue fields were sent as part of the request.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        transitions_raw_response = util_load_json("test_data/get_transitions_test/raw_response.json")
        mocker.patch.object(client, "get_transitions", return_value=transitions_raw_response)
        mocker.patch.object(client, "transition_issue", return_value=requests.Response())
        command_args = args | {
            "issue_key": "dummy_key",
            "description": "dummy description",
            "project_key": "dummy_project_key",
            "project_id": "dummy_project_id",
            "labels": "label1,label2",
            "components": "comp1,comp2",
            "customfield_1": "dummy custom field",
        }
        # The transition ID is 21 since the mocked transition 'In Development' has an ID of 21 and the status
        # 'Selected for development' correlates to the transition 'In Development', which as stated, has an ID of 21
        expected_issue_fields = {
            "transition": {"id": "21"},
            "fields": {
                "description": "dummy description",
                "project": {"key": "dummy_project_key", "id": "dummy_project_id"},
                "labels": ["label1", "label2"],
                "components": [{"name": "comp1"}, {"name": "comp2"}],
                "customfield_1": "dummy custom field",
            },
        }
        mocker.patch.object(client, "get_issue", return_value={})
        transition_issue_mocker = mocker.patch.object(client, "transition_issue", return_value=requests.Response())
        edit_issue_command(client=client, args=command_args)
        assert expected_issue_fields == transition_issue_mocker.call_args[1].get("json_data")

    def test_create_issue_fields_with_action_rewrite(self, mocker):
        """
        Given:
            - A Jira client, and issue fields to edit the Jira issue, with the rewrite action.
        When
            - Calling the edit issue command.
        Then
            - Validate that the edit_issue method (which is in charge of calling the endpoint with the relevant data
            to edit the issue) was called with the correct json data.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        args = {
            "issue_key": "dummy_key",
            "description": "dummy description",
            "project_key": "dummy_project_key",
            "project_id": "dummy_project_id",
            "labels": "label1,label2",
            "components": "comp1,comp2",
            "customfield_1": "dummy custom field",
        }
        expected_issue_fields = {
            "fields": {
                "description": "dummy description",
                "project": {"key": "dummy_project_key", "id": "dummy_project_id"},
                "labels": ["label1", "label2"],
                "components": [{"name": "comp1"}, {"name": "comp2"}],
                "customfield_1": "dummy custom field",
            }
        }
        mocker.patch.object(client, "get_issue", return_value={})
        edit_issue_mocker = mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        edit_issue_command(client=client, args=args)
        assert expected_issue_fields == edit_issue_mocker.call_args[1].get("json_data")

    def test_create_issue_fields_for_update_with_action_append(self, mocker):
        """
        Given:
            - A Jira client, and issue fields to edit the Jira issue, with the append action.
        When
            - Calling the edit issue command.
        Then
            - Validate that the edit_issue method (which is in charge of calling the endpoint with the relevant data
            to edit the issue) was called with the correct json data.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        args = {
            "issue_key": "dummy_key",
            "components": "comp1,comp2",
            "labels": "label1,label2",
            "summary": "appended summary",
            "action": "append",
        }
        expected_issue_fields = {
            "fields": {
                "components": [{"name": "current-comp1"}, {"name": "current-comp2"}, {"name": "comp1"}, {"name": "comp2"}],
                "labels": ["current-label1", "current-label2", "label1", "label2"],
                "summary": "current summary, appended summary",
            }
        }
        mocker.patch.object(
            client,
            "get_issue",
            side_effect=[
                {
                    "fields": {
                        "components": [{"name": "current-comp1"}, {"name": "current-comp2"}],
                        "labels": ["current-label1", "current-label2"],
                        "summary": "current summary",
                    }
                },
                {},
            ],
        )
        edit_issue_mocker = mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        edit_issue_command(client=client, args=args)
        assert expected_issue_fields == edit_issue_mocker.call_args[1].get("json_data")

    def test_create_custom_issue_fields_for_update_with_action_append(self, mocker):
        """
        Given:
            - A Jira client, and custom issue fields (supplied using the issue_json argument) to edit the Jira issue,
            with the append action.
        When
            - Calling the edit issue command.
        Then
            - Validate that the edit_issue method (which is in charge of calling the endpoint with the relevant data
            to edit the issue) was called with the correct json data.
        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        args = {
            "issue_key": "dummy_key",
            "issue_json": '{"fields": {"customfield_1": "new data", "customfield_2": ["new data"]}}',
            "action": "append",
        }
        expected_issue_fields = {"fields": {"customfield_1": "old data, new data", "customfield_2": ["old data", "new data"]}}
        mocker.patch.object(
            client, "get_issue", side_effect=[{"fields": {"customfield_1": "old data", "customfield_2": ["old data"]}}, {}]
        )
        edit_issue_mocker = mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        edit_issue_command(client=client, args=args)
        assert expected_issue_fields == edit_issue_mocker.call_args[1].get("json_data")

    def test_edit_issue_command_with_issue_json_and_another_arg_error(self):
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        with pytest.raises(
            DemistoException,
            match=(
                "When using the `issue_json` argument, additional arguments cannot be used "
                "except `issue_id`, `issue_key`, `status`, `transition`, and `action` arguments.ֿֿֿ"
                "\n see the argument description"
            ),
        ):
            edit_issue_command(
                client=client, args={"summary": "test", "issue_json": '{"fields": {"customfield_10037":"field_value"}}'}
            )

    @pytest.mark.parametrize(
        "extra_args",
        [
            {"action": "test"},
            {"status": "test"},
            {"transition": "test"},
            {"issue_key": "test"},
            {"issue_id": "test"},
        ],
    )
    def test_edit_issue_command_with_issue_json_and_another_arg_no_error(self, mocker: MockerFixture, extra_args: dict):
        """
        Given:
            - The `issue_json` arg and one more arg allowed for use with `issue_json`
        When:
            - run edit_issue_command function
        Then:
            - Ensure that the validation process,
              which ensures that no additional arguments are present alongside the 'issue_json' argument,
              does not result in an error in cases where the additional arguments are one of:
              `action`, `status`, `transition`.

        """
        from JiraV3 import edit_issue_command

        client = jira_base_client_mock()
        mocker.patch("JiraV3.apply_issue_status")
        mocker.patch("JiraV3.apply_issue_transition")
        mocker.patch.object(client, "edit_issue")
        mocker.patch.object(client, "get_issue", return_value={})
        mocker.patch("JiraV3.create_issue_md_and_outputs_dict", return_value=({}, {}))
        mocker.patch("JiraV3.create_issue_fields", return_value={})
        mocker.patch("JiraV3.create_issue_fields_for_appending", return_value={})
        mocker.patch("JiraV3.get_issue_id_or_key", return_value="test")
        args = {"issue_json": '{"fields": {"customfield_10037":"field_value"}}'} | extra_args
        assert edit_issue_command(client=client, args=args)


class TestJiraCreateIssueCommand:
    def test_create_issue_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the create issue command.
        Then
            - Validate that the issue id and key of the newly created issue is returned.
        """
        from JiraV3 import create_issue_command

        client = jira_base_client_mock()
        raw_response = {"id": "1234", "key": "dummy_key", "self": "dummy_link"}
        expected_outputs = {"Id": "1234", "Key": "dummy_key"}
        expected_mo_outputs = {
            "object_id": "1234",
            "object_name": "dummy_key",
            "object_url": "https://example.com/browse/dummy_key",
        }

        mocker.patch.object(client, "create_issue", return_value=raw_response)
        mocker.patch.object(demisto, "results")
        command_results = create_issue_command(
            client=client, args={"summary": "test"}, is_quick_action=True, server_url="https://example.com"
        )
        assert command_results[0].to_context().get("EntryContext") == {"Ticket(val.Id && val.Id == obj.Id)": expected_outputs}
        assert demisto.results.call_args[0][0]["ExtendedPayload"].get("MirrorObject") == expected_mo_outputs

    def test_create_issue_command_with_issue_json(self, mocker):
        """
        Given:
            - A Jira client
            - Jira summary from issue_json
        When
            - Calling the create issue command.
        Then
            - Validate that the issue id and key of the newly created issue is returned.
        """
        from JiraV3 import create_issue_command

        client = jira_base_client_mock()
        raw_response = {"id": "1234", "key": "dummy_key", "self": "dummy_link"}
        expected_outputs = {"Id": "1234", "Key": "dummy_key"}
        expected_mo_outputs = {
            "object_id": "1234",
            "object_name": "dummy_key",
            "object_url": "http://example.com/browse/dummy_key",
        }

        mocker.patch.object(client, "create_issue", return_value=raw_response)
        mocker.patch.object(demisto, "results")

        command_results = create_issue_command(
            client=client,
            args={"issue_json": '{"fields": {"summary": "test"}}'},
            is_quick_action=True,
            server_url="http://example.com",
        )
        assert command_results[0].to_context().get("EntryContext") == {"Ticket(val.Id && val.Id == obj.Id)": expected_outputs}
        assert demisto.results.call_args[0][0]["ExtendedPayload"].get("MirrorObject") == expected_mo_outputs

    def test_create_issue_command_with_issue_json_and_another_arg(self):
        """
        Given:
            - A Jira client
            - issue_json and summary args
        When
            - Calling the create issue command.
        Then
            - Ensure an error is raised with an expected error message.
        """
        from JiraV3 import create_issue_command

        client = jira_base_client_mock()
        with pytest.raises(
            DemistoException,
            match="When using the argument `issue_json`, additional arguments should be placed within the `fields` in the json.",
        ):
            create_issue_command(
                client=client, args={"summary": "test", "issue_json": '{"fields": {"customfield_10037":"field_value"}}'}
            )

    def test_create_issue_command_no_summary(self):
        """
        Given:
            - A Jira client
            - no Jira summary from issue_json / args
        When
            - Calling the create issue command.
        Then
            - Validate that DemistoException is raised
        """
        from JiraV3 import create_issue_command

        client = jira_base_client_mock()
        with pytest.raises(DemistoException) as e:
            create_issue_command(client=client, args={})
        assert "The summary argument must be provided" in str(e)


class TestJiraDeleteIssueCommand:
    def test_delete_issue_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the delete issue command.
        Then
            - Validate that the correct readable output is outputted to the user.
        """
        from JiraV3 import delete_issue_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "delete_issue", return_value=requests.Response())
        command_result = delete_issue_command(client=client, args={"issue_key": "dummy_key"})
        assert "Issue deleted successfully" in command_result.to_context().get("HumanReadable")


class TestJiraDeleteAttachmentFileCommand:
    def test_delete_attachment_file_command(self, mocker: MockerFixture):
        """
        Given:
            - A Jira client.
        When
            - Calling the delete attachment file command.
        Then
            - Validate that the correct readable output is outputted to the user.
        """
        from JiraV3 import delete_attachment_file_command

        attachment_id = "dummy_id"
        client = jira_base_client_mock()
        mocker.patch.object(client, "delete_attachment_file", return_value=requests.Response())
        command_result = delete_attachment_file_command(client=client, args={"attachment_id": attachment_id})
        assert f"Attachment id {attachment_id} was deleted successfully" in command_result.to_context().get("HumanReadable")


class TestJiraGetTransitionsCommand:
    def test_get_transitions_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the get_transitions_command
        Then
            - Validate that the correct CommandResult object is returned to the user.
        """
        from JiraV3 import get_transitions_command

        client = jira_base_client_mock()
        raw_response = util_load_json("test_data/get_transitions_test/raw_response.json")
        expected_command_results_context = util_load_json("test_data/get_transitions_test/parsed_result.json")
        mocker.patch.object(client, "get_transitions", return_value=raw_response)
        command_result = get_transitions_command(client=client, args={"issue_key": "dummy_key"})
        assert expected_command_results_context["EntryContext"] == command_result.to_context()["EntryContext"]
        assert expected_command_results_context["HumanReadable"] == command_result.to_context()["HumanReadable"]


class TestJiraAddCommentCommand:
    def test_add_comment_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the add_comment_command
        Then
            - Validate that the correct CommandResult object is returned to the user.
        """
        from JiraV3 import add_comment_command

        client = jira_base_client_mock()
        args = {"visibility": "Administrators", "issue_key": "dummy_key", "comment": "dummy_comment"}
        raw_response = util_load_json("test_data/create_comment_test/raw_response.json")
        expected_command_results_context = util_load_json("test_data/create_comment_test/parsed_result.json")
        mocker.patch.object(client, "add_comment", return_value=raw_response)
        command_result = add_comment_command(client=client, args=args)
        assert expected_command_results_context["EntryContext"] == command_result.to_context()["EntryContext"]
        assert expected_command_results_context["HumanReadable"] == command_result.to_context()["HumanReadable"]


class TestJiraGetIDOffsetCommand:
    def test_get_id_offset_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the get_id_offset_command, in order to retrieve the first issue id in the Jira instance.
        Then
            - Validate that the correct CommandResult object is returned to the user, and that the correct JQL
            was sent to in order to retrieve the ID.
        """
        from JiraV3 import get_id_offset_command

        client = jira_base_client_mock()
        raw_response = util_load_json("test_data/issue_query_test/raw_response.json")
        run_query_mocker = mocker.patch.object(client, "run_query", return_value=raw_response)
        command_result = get_id_offset_command(client=client, args={})
        assert run_query_mocker.call_args[1].get("query_params", {}).get("jql") == "ORDER BY created ASC"
        assert command_result.to_context()["EntryContext"] == {"Ticket": {"idOffSet": "10161"}}

    def test_get_id_offset_command_with_custom_query_argument(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the get_id_offset_command, with the argument `query` in order to retrieve the first issue id with respect
            to the given query.
        Then
            - Validate that the correct query is being sent with the API call.
        """
        from JiraV3 import get_id_offset_command

        client = jira_base_client_mock()
        raw_response = util_load_json("test_data/issue_query_test/raw_response.json")
        run_query_mocker = mocker.patch.object(client, "run_query", return_value=raw_response)
        get_id_offset_command(client=client, args={"query": 'project = "Dummy Project"'})
        assert (
            run_query_mocker.call_args[1].get("query_params", {}).get("jql") == 'project = "Dummy Project" ORDER BY created ASC'
        )

    def test_get_id_offset_empty_results(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the get_id_offset_command, and getting no issues from the API.
        Then
            - Validate that the correct message is returned to the user.
        """
        from JiraV3 import get_id_offset_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "run_query", return_value={})
        command_result = get_id_offset_command(client=client, args={})
        assert command_result.to_context().get("HumanReadable") == "No issues found to retrieve the ID offset"

    def test_edit_comment_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the edit_comment_command.
        Then
            - Validate that the get_comments method is called to retrieve the comments of the issue, that includes the
            newly edited comment, and that the correct outputs is returned to the context data.
        """
        from JiraV3 import edit_comment_command

        client = jira_base_client_mock()
        comments_raw_response = util_load_json("test_data/get_comments_test/raw_response.json")
        expected_command_results_context = util_load_json("test_data/get_comments_test/parsed_result.json")
        mocker.patch.object(client, "edit_comment", return_value=requests.Response())
        get_comments_mocker = mocker.patch.object(client, "get_comments", return_value=comments_raw_response)
        command_result = edit_comment_command(client=client, args={"issue_key": "dummy_issue_key"})
        get_comments_mocker.assert_called_once()
        assert expected_command_results_context["EntryContext"] == command_result.to_context()["EntryContext"]


class TestJiraListIssueFieldsCommand:
    @pytest.mark.parametrize("pagination_args", [({"start_at": 0, "max_results": 2}), ({"start_at": 1, "max_results": 3})])
    def test_list_fields_command(self, mocker, pagination_args):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-list-fields command.
        Then
            - Validate that correct pagination has been applied, since the API endpoint does not support pagination,
            and we do it manually.
        """
        from JiraV3 import list_fields_command

        client = jira_base_client_mock()
        issue_fields_raw_response = util_load_json("test_data/get_issue_fields_test/raw_response.json")
        expected_context = util_load_json("test_data/get_issue_fields_test/parsed_result.json")
        start_at = pagination_args.get("start_at", 0)
        max_results = pagination_args.get("max_results", 50)
        mocker.patch.object(client, "get_issue_fields", return_value=issue_fields_raw_response)
        mocker.patch("JiraV3.prepare_pagination_args", return_value=pagination_args)
        command_result = list_fields_command(client=client, args={"issue_key": "dummy_issue_key"})
        # [start_at: start_at + max_results] is the way do the pagination manually, therefore we check it.
        expected_outputs = expected_context["EntryContext"]["Jira.IssueField(val.id && val.id == obj.id)"][
            start_at : start_at + max_results
        ]
        assert expected_outputs == command_result.to_context()["EntryContext"]["Jira.IssueField(val.id && val.id == obj.id)"]


class TestJiraIssueToBacklogCommand:
    @pytest.mark.parametrize(
        "args",
        [({"rank_before_issue": "key1", "issues": "issue1,issue2"}), ({"rank_after_issue": "key1", "issues": "issue1,issue2"})],
    )
    def test_using_rank_without_board_id_error(self, args):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-backlog command, with the rank_before_issue, or rank_after_issue arguments, without
            the board_id argument.
        Then
            - Validate that an error is thrown.
        """
        from JiraV3 import issues_to_backlog_command

        client = jira_base_client_mock()
        with pytest.raises(DemistoException) as e:
            issues_to_backlog_command(client=client, args=args)
        assert "Please supply the board_id argument" in str(e)

    def test_issues_to_backlog_is_called_when_using_board_id(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-backlog command, with the board_id argument.
        Then
            - Validate that the correct API call is being called (issues_to_backlog, which is available only for Jira Cloud).
        """
        from JiraV3 import issues_to_backlog_command

        client = jira_cloud_client_mock()
        issues_to_backlog_mocker = mocker.patch.object(client, "issues_to_backlog", return_value=requests.Response())
        issues_to_backlog_command(client=client, args={"board_id": "dummy_board_id"})
        issues_to_backlog_mocker.assert_called_once()

    def test_using_board_id_with_onprem_error(self):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-backlog command, with the board_id argument, on an OnPrem instance.
        Then
            - Validate that an error is thrown, since only Jira Cloud supports the board_id argument.
        """
        from JiraV3 import issues_to_backlog_command

        client = jira_onprem_client_mock()
        with pytest.raises(DemistoException) as e:
            issues_to_backlog_command(client=client, args={"board_id": "dummy_board_id"})
        assert "The argument board_id is not supported for a Jira OnPrem instance" in str(e)

    def test_issues_to_backlog_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-backlog command.
        Then
            - Validate that the correct API call is being called.
        """
        from JiraV3 import issues_to_backlog_command

        client = jira_base_client_mock()
        issues_from_sprint_to_backlog_mocker = mocker.patch.object(
            client, "issues_from_sprint_to_backlog", return_value=requests.Response()
        )
        command_results = issues_to_backlog_command(client=client, args={})
        issues_from_sprint_to_backlog_mocker.assert_called_once()
        assert command_results.to_context()["HumanReadable"] == "Issues were moved to Backlog successfully"


class TestJiraIssuesToBoardCommand:
    def test_issues_to_board_with_onprem_error(self):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-board command, with an OnPrem instance.
        Then
            - Validate that an error is thrown, since this command is only supported by Jira Cloud.
        """
        from JiraV3 import issues_to_board_command

        client = jira_onprem_client_mock()
        with pytest.raises(DemistoException) as e:
            issues_to_board_command(client=client, args={})
        assert "This command is not supported by a Jira OnPrem instance" in str(e)

    def test_issues_to_board_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-board command, with a Cloud instance.
        Then
            - Validate that the correct CommandResults is returned to the user.
        """
        from JiraV3 import issues_to_board_command

        client = jira_cloud_client_mock()
        mocker.patch.object(client, "issues_to_board", return_value=requests.Response())
        command_results = issues_to_board_command(client=client, args={})
        assert command_results.to_context()["HumanReadable"] == "Issues were moved to Board successfully"


class TestJiraBoardListCommand:
    def test_get_board_using_board_id_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-list command, with the board_id argument.
        Then
            - Validate that the correct API call is called to retrieve the specific data corresponding to the board id.
        """
        from JiraV3 import board_list_command

        client = jira_base_client_mock()
        board_raw_response = util_load_json("test_data/get_board_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_board_test/parsed_result.json")
        get_board_mocker = mocker.patch.object(client, "get_board", return_value=board_raw_response)
        get_boards_mocker = mocker.patch.object(client, "get_boards", return_value={})
        command_results = board_list_command(client=client, args={"board_id": "dummy_board_id"})
        get_board_mocker.assert_called_once()
        get_boards_mocker.assert_not_called()
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]

    def test_get_boards_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-list command, without the board_id argument.
        Then
            - Validate that the correct API call is called to retrieve the specific data.
        """
        from JiraV3 import board_list_command

        client = jira_base_client_mock()
        board_raw_response = util_load_json("test_data/get_boards_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_boards_test/parsed_result.json")
        get_board_mocker = mocker.patch.object(client, "get_board", return_value={})
        get_boards_mocker = mocker.patch.object(client, "get_boards", return_value=board_raw_response)
        command_results = board_list_command(client=client, args={})
        get_board_mocker.assert_not_called()
        get_boards_mocker.assert_called_once()
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraIssuesFromBacklogOfBoardCommand:
    def test_get_issues_from_backlog_of_board_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-backlog-list command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import board_backlog_list_command

        client = jira_base_client_mock()
        backlog_issues_raw_response = util_load_json("test_data/get_issues_from_backlog_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issues_from_backlog_test/parsed_result.json")
        mocker.patch.object(client, "get_issues_from_backlog", return_value=backlog_issues_raw_response)
        command_results = board_backlog_list_command(client=client, args={"board_id": "14"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraIssuesFromBoardCommand:
    def test_get_issues_from_board_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-issue-list command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import board_issues_list_command

        client = jira_base_client_mock()
        board_issues_raw_response = util_load_json("test_data/get_issues_from_board_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issues_from_board_test/parsed_result.json")
        mocker.patch.object(client, "get_issues_from_board", return_value=board_issues_raw_response)
        command_results = board_issues_list_command(client=client, args={"board_id": "14"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraBoarSprintsCommand:
    def test_get_issues_from_board_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-sprint-list command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import board_sprint_list_command

        client = jira_base_client_mock()
        board_sprints_raw_response = util_load_json("test_data/get_board_sprints_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_board_sprints_test/parsed_result.json")
        mocker.patch.object(client, "get_sprints_from_board", return_value=board_sprints_raw_response)
        command_results = board_sprint_list_command(client=client, args={"board_id": "12"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraIssueLinkTypesCommand:
    def test_get_issue_link_types_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-link-type-get command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import get_issue_link_types_command

        client = jira_base_client_mock()
        link_types_raw_response = util_load_json("test_data/get_issue_link_types_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issue_link_types_test/parsed_result.json")
        mocker.patch.object(client, "get_issue_link_types", return_value=link_types_raw_response)
        command_results = get_issue_link_types_command(client=client, args={})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraIssueToIssueCommand:
    def test_issue_to_issue_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-issue-to-issue-link command.
        Then
            - Validate that the correct message is returned to the user.
        """
        from JiraV3 import link_issue_to_issue_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "create_issue_link", return_value=requests.Response())
        command_results = link_issue_to_issue_command(client=client, args={})
        assert command_results.to_context()["HumanReadable"] == "Issue link created successfully"


class TestJiraSprintIssueMoveCommand:
    def test_jira_sprint_issue_move(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-sprint-issue-move command.
        Then
            - Validate that the correct message is returned to the user.
        """
        from JiraV3 import issues_to_sprint_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "issues_to_sprint", return_value=requests.Response())
        command_results = issues_to_sprint_command(client=client, args={})
        assert command_results.to_context()["HumanReadable"] == "Issues were moved to the Sprint successfully"


class TestJiraEpicIssuesCommand:
    def test_get_epic_issues_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-epic-issue-list command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import epic_issues_list_command

        client = jira_base_client_mock()
        epic_issues_raw_response = util_load_json("test_data/get_epic_issues_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_epic_issues_test/parsed_result.json")
        mocker.patch.object(client, "get_epic_issues", return_value=epic_issues_raw_response)
        command_results = epic_issues_list_command(client=client, args={"epic_key": "TSTPRD-1"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]

    def test_get_epic_issues_without_extracting_epic_id_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-epic-issue-list command, and not being able
            to extract the board_id from the response.
        Then
            - Validate that the context data is identified only using the epic id, without the board id.
        """
        from JiraV3 import epic_issues_list_command

        client = jira_base_client_mock()
        epic_issues_raw_response = util_load_json("test_data/get_epic_issues_test/raw_response.json")
        mocker.patch.object(client, "get_epic_issues", return_value=epic_issues_raw_response)
        for issue in epic_issues_raw_response.get("issues", []):
            (issue.get("fields", {}).get("sprint", {}) or {})["originBoardId"] = ""
        command_results = epic_issues_list_command(client=client, args={"epic_key": "COMPANYSA-1"})
        entry_context = command_results.to_context()["EntryContext"]
        assert "Jira.EpicIssues(val.epicId && val.epicId == obj.epicId)" in entry_context
        assert (
            "Jira.EpicIssues(val.epicId && val.epicId == obj.epicId && val.boardId && val.boardId == obj.boardId)"
        ) not in entry_context


class TestJiraBoardEpicsCommand:
    def test_get_board_epics_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-board-epic-list command.
        Then
            - Validate that the correct context data is returned to the user.
        """
        from JiraV3 import board_epic_list_command

        client = jira_base_client_mock()
        board_epics_raw_response = util_load_json("test_data/get_board_epics_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_board_epics_test/parsed_result.json")
        mocker.patch.object(client, "get_epics_from_board", return_value=board_epics_raw_response)
        command_results = board_epic_list_command(client=client, args={"board_id": "14"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]


class TestJiraSprintIssuesCommand:
    def test_get_sprint_issues_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-sprint-issue-list command, without the board_id argument.
        Then
            - Validate that the correct API call was called to retrieve the sprint issues, which is the API
            call that does not require a board_id, only a sprint_id argument, and that correct context data
            is returned to the user.
        """
        from JiraV3 import sprint_issues_list_command

        client = jira_base_client_mock()
        sprint_issues_raw_response = util_load_json("test_data/get_sprint_issues_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_sprint_issues_test/parsed_result.json")
        issues_from_sprint_mocker = mocker.patch.object(client, "get_issues_from_sprint", return_value=sprint_issues_raw_response)
        sprint_issues_from_board_mocker = mocker.patch.object(
            client, "get_sprint_issues_from_board", return_value=sprint_issues_raw_response
        )
        command_results = sprint_issues_list_command(client=client, args={"sprint_id": "4"})
        issues_from_sprint_mocker.assert_called_once()
        sprint_issues_from_board_mocker.assert_not_called()
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]

    def test_get_sprint_issues_from_board_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - Calling the jira-sprint-issue-list command, with the board_id argument.
        Then
            - Validate that the correct API call was called to retrieve the sprint issues, which is the API
            call that does require a board_id, in addition to a sprint_id argument, and that correct context data
            is returned to the user.
        """
        from JiraV3 import sprint_issues_list_command

        client = jira_base_client_mock()
        sprint_issues_raw_response = util_load_json("test_data/get_sprint_issues_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_sprint_issues_test/parsed_result.json")
        issues_from_sprint_mocker = mocker.patch.object(client, "get_issues_from_sprint", return_value=sprint_issues_raw_response)
        sprint_issues_from_board_mocker = mocker.patch.object(
            client, "get_sprint_issues_from_board", return_value=sprint_issues_raw_response
        )
        command_results = sprint_issues_list_command(client=client, args={"board_id": "12", "sprint_id": "4"})
        issues_from_sprint_mocker.assert_not_called()
        sprint_issues_from_board_mocker.assert_called_once()
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]

    def test_get_sprint_issues_without_extracting_board_id(self, mocker):
        """
        Given:
            - A Jira client.
        When
            - Calling the jira-sprint-issue-list command, without the board_id argument, and not being able
            to extract the board_id from the response.
        Then
            - Validate that the context data is identified only using the sprint id, without the board id.
        """
        from JiraV3 import sprint_issues_list_command

        client = jira_base_client_mock()
        sprint_issues_raw_response: Dict[str, Any] = util_load_json("test_data/get_sprint_issues_test/raw_response.json")
        issues_from_sprint_mocker = mocker.patch.object(client, "get_issues_from_sprint", return_value=sprint_issues_raw_response)
        sprint_issues_from_board_mocker = mocker.patch.object(
            client, "get_sprint_issues_from_board", return_value=sprint_issues_raw_response
        )
        for issue in sprint_issues_raw_response.get("issues", []):
            (issue.get("fields", {}).get("sprint", {}) or {})["originBoardId"] = ""
        command_results = sprint_issues_list_command(client=client, args={"sprint_id": "4"})
        issues_from_sprint_mocker.assert_called_once()
        sprint_issues_from_board_mocker.assert_not_called()
        entry_context = command_results.to_context()["EntryContext"]
        assert "Jira.SprintIssues(val.sprintId && val.sprintId == obj.sprintId)" in entry_context
        assert (
            "Jira.SprintIssues(val.boardId && val.boardId == obj.boardId && val.sprintId && val.sprintId == obj.sprintId)"
        ) not in entry_context


class TestJiraDeleteCommentCommand:
    def test_delete_comment_command(self, mocker):
        """
        Given:
            - A Jira client.
        When
            - Calling the jira-issue-delete-comment.
        Then
            - Validate that the correct message is returned to the user.
        """
        from JiraV3 import delete_comment_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "delete_comment", return_value=requests.Response())
        command_results = delete_comment_command(client=client, args={"issue_key": "dummy_issue_key"})
        assert "Comment deleted successfully" in command_results.to_context()["HumanReadable"]


class TestJiraGetIssueAttachmentsCommand:
    @pytest.mark.parametrize("args,number_of_calls", [({"attachment_id": "1,2,3"}, 3), ({"attachment_id": "1234"}, 1)])
    def test_get_issue_attachments_command(self, mocker, args, number_of_calls):
        """
        Given:
            - A Jira client, and attachment ids to retrieve the content of the attachments.
        When
            - Calling the jira-issue-get-attachment.
        Then
            - Validate that the number of times the function that is in charge of creating the files to display in the War Room
            is called with correspondence to the number of attachment ids supplied (The function that is in charge of creating
            the file themselves has been tested in the class TestJiraGetIssueCommand).
        """
        from JiraV3 import issue_get_attachment_command

        client = jira_base_client_mock()
        create_file_info_mocker = mocker.patch("JiraV3.create_file_info_from_attachment", return_value={})
        issue_get_attachment_command(client=client, args=args)
        assert len(create_file_info_mocker.mock_calls) == number_of_calls


class TestJiraUploadFileCommand:
    def test_get_file_name_and_content(self, mocker):
        """
        Given:
            - An entry id, that is found in a War Room
        When
            - Getting the file name and content of it, in order to upload to Jira
        Then
            - Validate that the function that is in charge of retrieving the name and content of the file returns
            the required data.
        """
        from JiraV3 import get_file_name_and_content

        file_path = "test_data/get_issue_test/dummy_attachment_content.txt"
        expected_file_name = "dummy_attachment_content.txt"
        mocker.patch.object(demisto, "getFilePath", return_value={"name": expected_file_name, "path": file_path})
        file_name, file_bytes = get_file_name_and_content(entry_id="dummy_entry_id")
        expected_file_bytes: bytes = b""
        with open(file_path, "rb") as f:
            expected_file_bytes = f.read()
        assert expected_file_bytes == file_bytes
        assert expected_file_name == file_name

    def test_upload_file_command(self, mocker):
        """
        Given:
            - A Jira client.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that correct message is outputted to the user.
        """
        from JiraV3 import upload_file_command

        client = jira_base_client_mock()
        upload_file_raw_response = util_load_json("test_data/upload_file_test/raw_response.json")
        expected_command_results_context = util_load_json("test_data/upload_file_test/parsed_result.json")
        mocker.patch("JiraV3.get_file_name_and_content", return_value=("dummy_file_name.pdf", b"dummy content"))
        mocker.patch.object(client, "upload_attachment", return_value=upload_file_raw_response)
        command_results = upload_file_command(client=client, args={"issue_key": "COMPANYSA-35"})
        assert command_results.to_context()["HumanReadable"] == expected_command_results_context["HumanReadable"]

    def test_upload_XSOAR_attachment_to_jira_mime_type_check(self, mocker):
        """
        Given:
            - A Jira client.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that correct mime_type was given to the file.
        """
        from JiraV3 import upload_XSOAR_attachment_to_jira

        client = jira_base_client_mock()
        file_name = "dummy_file_name.pdf"
        issue_key = "COMPANYSA-35"
        file_bytes = b"dummy content"
        expected_file_mime_type = "application/pdf"
        upload_file_raw_response = util_load_json("test_data/upload_file_test/raw_response.json")
        files = {"file": (file_name, file_bytes, expected_file_mime_type)}
        mocker.patch("JiraV3.get_file_name_and_content", return_value=("dummy_file_name.pdf", b"dummy content"))
        mocker.patch("JiraV3.guess_type", return_value=(expected_file_mime_type, ""))
        mock_request = mocker.patch.object(client, "upload_attachment", return_value=upload_file_raw_response)
        upload_XSOAR_attachment_to_jira(client=client, entry_id="", issue_id_or_key=issue_key)
        mock_request.assert_called_with(issue_id_or_key=issue_key, files=files)

    def test_upload_XSOAR_attachment_to_jira_mime_type_fail(self, mocker):
        """
        Given:
            - A Jira client.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that in case of unsuccessful upload to Jira due to mime type issue,
            we will try again with the default mime type.
        """
        from JiraV3 import upload_XSOAR_attachment_to_jira

        client = jira_base_client_mock()
        issue_key = "COMPANYSA-35"
        mocker.patch("JiraV3.get_file_name_and_content", return_value=("dummy_file_name.pdf", b"dummy content"))
        mocker.patch("JiraV3.guess_type", return_value=("application/pdf", ""))
        mocker.patch.object(client, "upload_attachment", side_effect=DemistoException("failed to upload", res={}))
        mock_request = mocker.patch.object(
            client, "upload_attachment", side_effect=[DemistoException("failed to upload", res={}), {}]
        )
        upload_XSOAR_attachment_to_jira(client=client, entry_id="", issue_id_or_key=issue_key)

        # Validate that we run upload_attachment twice, once with an error, and second time to use default file type
        assert mock_request.call_count == 2
        # Validate that the second call uses the default file type (application-type)
        mock_request.assert_called_with(
            files={"file": ("dummy_file_name.pdf", b"dummy content", "application-type")}, issue_id_or_key=issue_key
        )

    def test_create_files_to_upload(self, mocker):
        """
        Given:
            - An empty file mime type, a file name and a file bytes.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that correct mime_type was given to the file, and the object to upload is correct.
        """
        from JiraV3 import create_files_to_upload

        file_name = "dummy_file_name.pdf"
        file_bytes = b"dummy content"
        expected_file_mime_type = "application/pdf"
        expected_files = {"file": (file_name, file_bytes, expected_file_mime_type)}
        mocker.patch("JiraV3.guess_type", return_value=(expected_file_mime_type, ""))
        result_files, result_mime_type = create_files_to_upload("", file_name, file_bytes)
        assert result_files == expected_files
        assert result_mime_type == expected_file_mime_type

    def test_create_files_to_upload_none_type(self, mocker):
        """
        Given:
            - An empty file mime type, a file name and a file bytes.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that in case of unsuccessful type guess, the default mime type is given (application-type),
            and the object to upload is correct.
        """
        from JiraV3 import create_files_to_upload

        file_name = "dummy_file_name.pdf"
        file_bytes = b"dummy content"
        expected_file_mime_type = "application-type"
        expected_files = {"file": (file_name, file_bytes, expected_file_mime_type)}
        mocker.patch("JiraV3.guess_type", return_value=(None, ""))
        result_files, result_mime_type = create_files_to_upload("", file_name, file_bytes)
        assert result_files == expected_files
        assert result_mime_type == expected_file_mime_type

    def test_create_files_to_upload_given_type(self, mocker):
        """
        Given:
            - An application-type file mime type, a file name and a file bytes.
        When
            - When calling the jira-issue-upload-file command.
        Then
            - Validate that in case of a given mime type the function guess_type wasn't called,
            and the object to upload is correct.
        """
        from JiraV3 import create_files_to_upload

        file_name = "dummy_file_name.pdf"
        file_bytes = b"dummy content"
        expected_file_mime_type = "application-type"
        expected_files = {"file": (file_name, file_bytes, expected_file_mime_type)}
        mock_guess_type = mocker.patch("JiraV3.guess_type", return_value=(None, ""))
        result_files, result_mime_type = create_files_to_upload(expected_file_mime_type, file_name, file_bytes)
        assert result_files == expected_files
        assert result_mime_type == expected_file_mime_type
        mock_guess_type.assert_not_called()


class TestJiraGetIdByAttribute:
    @pytest.mark.parametrize(
        "raw_response_path,parsed_result_path",
        [
            (
                "test_data/get_id_by_attribute_test/raw_response_cloud.json",
                "test_data/get_id_by_attribute_test/parsed_result_cloud.json",
            ),
            (
                "test_data/get_id_by_attribute_test/raw_response_onprem.json",
                "test_data/get_id_by_attribute_test/parsed_result_onprem.json",
            ),
        ],
    )
    def test_get_id_when_response_returns_one_user(self, mocker, raw_response_path, parsed_result_path):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-get-id-by-attribute command, and only getting one user in the response.
        Then
            - Validate that the user is returned.
        """
        from JiraV3 import get_id_by_attribute_command

        client = jira_base_client_mock()
        user_search_raw_response = util_load_json(raw_response_path)
        expected_command_results_context = util_load_json(parsed_result_path)
        mocker.patch.object(client, "get_id_by_attribute", return_value=user_search_raw_response)
        command_results = get_id_by_attribute_command(client=client, args={"attribute": "fred@example.com"})
        assert expected_command_results_context == command_results.to_context()

    @pytest.mark.parametrize(
        "client, raw_response_path",
        [
            (jira_cloud_client_mock(), "test_data/get_id_by_attribute_test/raw_response_cloud.json"),
            (jira_onprem_client_mock(), "test_data/get_id_by_attribute_test/raw_response_onprem.json"),
        ],
    )
    def test_id_not_found_when_response_returns_multiple_users(self, mocker, client, raw_response_path):
        """
        Given:
            - A Jira client, once for Cloud, and once for OnPrem
        When
            - When calling the jira-get-id-by-attribute command, and getting multiple responses, and not being able
            to extract the account id (probably because the attribute was an email, and the email can sometimes not be
            returned for privacy reasons)
        Then
            - Validate that an appropriate message is returned to the user.
        """
        from JiraV3 import get_id_by_attribute_command

        user_search_raw_response = util_load_json(raw_response_path)
        attribute = "fred@example.com"
        command_results_message = (
            f"Multiple accounts found, but it was not possible to resolve which one"
            f" of them is most relevant to attribute {attribute}. Please try to provide"
            ' the "DisplayName" attribute if not done so before, or supply the full'
            " attribute."
        )
        user_search_raw_response = user_search_raw_response * 2  # To mock that the response returned multiple users
        mocker.patch.object(client, "get_id_by_attribute", return_value=user_search_raw_response)
        command_results = get_id_by_attribute_command(client=client, args={"attribute": attribute})
        assert command_results_message in command_results.to_context()["HumanReadable"]

    @pytest.mark.parametrize(
        "client, raw_response_path",
        [
            (jira_cloud_client_mock(), "test_data/get_id_by_attribute_test/raw_response_cloud.json"),
            (jira_onprem_client_mock(), "test_data/get_id_by_attribute_test/raw_response_onprem.json"),
        ],
    )
    def test_multiple_ids_found_when_response_returns_multiple_users(self, mocker, client, raw_response_path):
        """
        Given:
            - A Jira client, once for Cloud, and once for OnPrem
        When
            - When calling the jira-get-id-by-attribute command, and getting multiple responses, and extracting
            multiple account ids
        Then
            - Validate that an appropriate message is returned to the user.
        """
        from JiraV3 import get_id_by_attribute_command

        # client = jira_onprem_client_mock()
        user_search_raw_response = util_load_json(raw_response_path)
        attribute = "fred@example.com"
        command_results_message = (
            f"Multiple account IDs were found for attribute: {attribute}.\n"
            f"Please try to provide the other attributes available - Email or DisplayName"
            " (and Name in the case of Jira OnPrem)."
        )
        user_search_raw_response = user_search_raw_response * 2  # To mock that the response returned multiple users
        user_search_raw_response[0]["emailAddress"] = attribute
        mocker.patch.object(client, "get_id_by_attribute", return_value=user_search_raw_response)
        command_results = get_id_by_attribute_command(client=client, args={"attribute": attribute})
        assert command_results_message in command_results.to_context()["HumanReadable"]

    @pytest.mark.parametrize(
        "client, raw_response_path, parsed_result_path",
        [
            (
                jira_cloud_client_mock(),
                "test_data/get_id_by_attribute_test/raw_response_cloud.json",
                "test_data/get_id_by_attribute_test/parsed_result_cloud.json",
            ),
            (
                jira_onprem_client_mock(),
                "test_data/get_id_by_attribute_test/raw_response_onprem.json",
                "test_data/get_id_by_attribute_test/parsed_result_onprem.json",
            ),
        ],
    )
    def test_get_id_from_multiple_ids_when_response_returns_multiple_users(
        self, mocker, client, raw_response_path, parsed_result_path
    ):
        """
        Given:
            - A Jira client, once for Cloud, and once for OnPrem
        When
            - When calling the jira-get-id-by-attribute command, and getting multiple responses, and extracting
            the correct account id.
        Then
            - Validate that the user is returned.
        """
        from JiraV3 import get_id_by_attribute_command

        user_search_raw_response = util_load_json(raw_response_path)
        user = user_search_raw_response[0]  # The test data contains only one user in the raw response
        expected_command_results_context = util_load_json(parsed_result_path)
        attribute = "fred@example.com"
        # To mock that the response returned multiple users
        user_search_raw_response = [user, user.copy()]
        user_search_raw_response[0]["emailAddress"] = attribute
        user_search_raw_response[1]["emailAddress"] = "wrong attribute"
        mocker.patch.object(client, "get_id_by_attribute", return_value=user_search_raw_response)
        command_results = get_id_by_attribute_command(client=client, args={"attribute": attribute})
        assert expected_command_results_context == command_results.to_context()


class TestJiraGetSpecificField:
    def test_get_specific_field_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-get-specific-field
        Then
            - Validate that the specified fields are returned in the context data
        """
        from JiraV3 import get_specific_fields_command

        client = jira_base_client_mock()
        issue_raw_response = util_load_json("test_data/get_specific_field_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_specific_field_test/parsed_result.json")
        mocker.patch.object(client, "get_issue", return_value=issue_raw_response)
        command_results = get_specific_fields_command(client=client, args={"issue_key": "COMPANYSA-35", "fields": "watches,rank"})
        assert expected_command_results["EntryContext"] == command_results.to_context()["EntryContext"]
        assert expected_command_results["HumanReadable"] == command_results.to_context()["HumanReadable"]


class TestJiraIssueQueryField:
    def test_issue_query_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-issue-query, with the `fields` argument
        Then
            - Validate that the context data and human readable of the queried issues are returned.
        """
        from JiraV3 import issue_query_command

        client = jira_base_client_mock()
        issue_query_raw_response = util_load_json("test_data/get_issue_query_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issue_query_test/parsed_result.json")
        mocker.patch.object(client, "run_query", return_value=issue_query_raw_response)
        command_results = issue_query_command(client=client, args={"fields": "watches,rank"})
        command_results = command_results if isinstance(command_results, list) else [command_results]
        for expected_command_result, command_result in zip(expected_command_results, command_results):
            assert expected_command_result["EntryContext"] == command_result.to_context()["EntryContext"]
            assert expected_command_result["HumanReadable"] == command_result.to_context()["HumanReadable"]

    def test_issue_query_command_uses_new_endpoint_for_cloud(self, mocker, requests_mock):
        """
        Given:
            - A Cloud Jira client
        When
            - When calling the jira-issue-query
        Then
            - Validate that the new endpoint is used and the command returns the expected output.
        """
        from JiraV3 import issue_query_command

        client = jira_cloud_client_mock()
        mocker.patch.object(client, "get_headers_with_access_token", return_value={})
        issue_query_raw_response = util_load_json("test_data/get_issue_query_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issue_query_test/parsed_result.json")
        requests_mock.get("https://dummy_server_url/dummy_cloud_id/rest/api/3/search/jql", json=issue_query_raw_response)

        command_results = issue_query_command(client=client, args={"fields": "watches,rank"})
        command_results = command_results if isinstance(command_results, list) else [command_results]
        for expected_command_result, command_result in zip(expected_command_results, command_results):
            assert expected_command_result["EntryContext"] == command_result.to_context()["EntryContext"]
            assert expected_command_result["HumanReadable"] == command_result.to_context()["HumanReadable"]

    def test_issue_query_command_uses_old_endpoint_for_onprem(self, mocker, requests_mock):
        """
        Given:
            - An on-prem Jira client
        When
            - When calling the jira-issue-query
        Then
            - Validate that the old endpoint is used and the command returns the expected output.
        """
        from JiraV3 import issue_query_command

        client = jira_onprem_client_mock()
        mocker.patch.object(client, "get_headers_with_access_token", return_value={})
        issue_query_raw_response = util_load_json("test_data/get_issue_query_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issue_query_test/parsed_result.json")
        requests_mock.get("https://dummy_server_url/rest/api/2/search", json=issue_query_raw_response)

        command_results = issue_query_command(client=client, args={"fields": "watches,rank"})
        command_results = command_results if isinstance(command_results, list) else [command_results]
        for expected_command_result, command_result in zip(expected_command_results, command_results):
            assert expected_command_result["EntryContext"] == command_result.to_context()["EntryContext"]
            assert expected_command_result["HumanReadable"] == command_result.to_context()["HumanReadable"]

    def test_issue_query_command_with_start_at(self, mocker, requests_mock):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-issue-query, with the `start_at` argument
        Then
            - Validate that the old endpoint is used
        """
        from JiraV3 import issue_query_command

        client = jira_cloud_client_mock()
        mocker.patch.object(client, "get_headers_with_access_token", return_value={})
        issue_query_raw_response = util_load_json("test_data/get_issue_query_test/raw_response.json")
        expected_command_results = util_load_json("test_data/get_issue_query_test/parsed_result.json")
        requests_mock.get("https://dummy_server_url/dummy_cloud_id/rest/api/3/search", json=issue_query_raw_response)

        command_results = issue_query_command(client=client, args={"start_at": "10", "fields": "watches,rank"})
        command_results = command_results if isinstance(command_results, list) else [command_results]
        for expected_command_result, command_result in zip(expected_command_results, command_results):
            assert expected_command_result["EntryContext"] == command_result.to_context()["EntryContext"]
            assert expected_command_result["HumanReadable"] == command_result.to_context()["HumanReadable"]

    def test_issue_query_command_with_start_at_fail(self, requests_mock):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-issue-query, with the `start_at` argument
            - The old endpoint is no longer available.
        Then
            - Validate that the old endpoint is used
            - Validate that the custom error message is raised for 410 Gone.
        """
        from JiraV3 import issue_query_command

        client = jira_base_client_mock(username="user", api_key="key")
        requests_mock.get("https://dummy_url/rest/api/999/search", status_code=410)

        with pytest.raises(DemistoException, match="The start_at argument is no longer supported in this Jira instance."):
            issue_query_command(client=client, args={"start_at": "10"})


class TestJiraAddUrlLink:
    def test_add_url_link(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When calling the jira-issue-add-link
        Then
            - Validate that the correct human readable is returned to the user
        """
        from JiraV3 import add_link_command

        client = jira_base_client_mock()
        mocker.patch.object(
            client,
            "add_link",
            return_value={"id": 10000, "self": "https://your-domain.atlassian.net/rest/api/issue/MKY-1/remotelink/10000"},
        )
        command_result = add_link_command(client=client, args={"issue_key": "dummy_issue_key"})
        markdown_dict = {
            "id": 10000,
            "key": None,
            "comment": "",
            "ticket_link": "https://your-domain.atlassian.net/rest/api/issue/MKY-1/remotelink/10000",
        }
        expected_human_readable = tableToMarkdown(name="Remote Issue Link", t=markdown_dict, removeNull=True)
        assert command_result.to_context()["HumanReadable"] == expected_human_readable


class TestJiraGetModifiedRemoteIds:
    USER_INFO_RES = {
        "accountId": "dummy_account_id",
        "accountType": "atlassian",
        "emailAddress": "admin@example.com",
        "displayName": "Example Example",
        "timeZone": "Asia/Jerusalem",
        "locale": "en_US",
    }
    LAST_UPDATE_TIME = "2023-05-01"

    def test_get_modified_issue_ids(self, mocker):
        """
        Given:
            - A Jira client, the last updated time of an incident, and a timezone
        When
            - When calling get_modified_issue_ids in order to get the issues that have an updated time greater than
            the last updated time of the incident
        Then
            - Validate that the correct ids are returned
        """
        from JiraV3 import get_modified_issue_ids

        client = jira_base_client_mock()
        modified_issues = {"issues": [{"id": "1234"}, {"id": "2345"}]}
        mocker.patch.object(client, "run_query", return_value=modified_issues)
        modified_issues = get_modified_issue_ids(
            client=client, last_update_date=self.LAST_UPDATE_TIME, timezone_name=self.USER_INFO_RES.get("timeZone", "")
        )
        assert modified_issues == ["1234", "2345"]

    def test_get_modified_remote_data_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When calling the mirroring mechanism the command get_modified_remote_data_command is called in order
            to retrieve the Jira issues that have been updated since the last update time of the incident.
        Then
            - Validate that the correct ids are returned
        """
        from JiraV3 import get_modified_remote_data_command

        client = jira_base_client_mock()
        mocker.patch("JiraV3.get_modified_issue_ids", return_value=["1234", "2345"])
        mocker.patch.object(client, "get_user_info", return_value=self.USER_INFO_RES)
        get_modified_remote_data = get_modified_remote_data_command(client=client, args={"lastUpdate": self.LAST_UPDATE_TIME})
        assert get_modified_remote_data.modified_incident_ids == ["1234", "2345"]


class TestJiraGetMappingFields:
    ISSUE_FIELDS_RES = [
        {"id": "statuscategorychangedate", "key": "statuscategorychangedate", "name": "Status Category Changed"},
        {"id": "parent", "key": "parent", "name": "Parent"},
    ]

    def test_get_mapping_fields_command(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When calling get-mapping-fields command.
        Then
            - Validate that we are able to extract the ids from the response from the API.
        """
        from JiraV3 import get_mapping_fields_command

        client = jira_base_client_mock()
        mocker.patch.object(client, "get_issue_fields", return_value=self.ISSUE_FIELDS_RES)
        mapping_fields = get_mapping_fields_command(client=client)
        assert list(mapping_fields.scheme_types_mappings[0].fields.keys()) == [
            "statuscategorychangedate",
            "parent",
            "issue_id",
            "summary",
            "description",
            "labels",
            "components",
            "priority",
            "due_date",
            "assignee",
            "status",
            "assignee_id",
            "original_estimate",
        ]


class TestJiraUpdateRemoteSystem:
    def test_update_remote_system_using_delta(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror out mechanism is called, which calls the update-remote-system command, and
            we need to edit the issue
        Then
            - Validate that the API call to edit the issue was called with the correct data, which was
            extracted from the delta and data arguments.
        """
        from JiraV3 import update_remote_system_command

        client = jira_base_client_mock()
        args = {
            "incidentChanged": "17757",
            "remoteId": "17757",
            "data": {"summary": "data", "not_changes_key": "not_changes_val"},
            "delta": {"summary": "changes", "dbotMirrorDirection": "test"},
        }
        edit_issue_mocker = mocker.patch.object(client, "edit_issue", return_value=requests.Response())
        update_remote_system_res = update_remote_system_command(
            client=client, args=args, comment_tag_to_jira="", attachment_tag_to_jira=""
        )
        assert update_remote_system_res == "17757"
        assert edit_issue_mocker.call_args[1]["json_data"] == {"fields": {"summary": "data"}}

    def test_update_remote_system_using_file_entry_with_correct_tag(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror out mechanism is called, which calls the update-remote-system command, and
            we get an attachment with the appropriate attachment tag.
        Then
            - Validate that the attachment gets uploaded to Jira, and that the name of the file contains the
            constant ATTACHMENT_MIRRORED_FROM_XSOAR
        """
        from JiraV3 import ATTACHMENT_MIRRORED_FROM_XSOAR, update_remote_system_command

        client = jira_base_client_mock()
        args = {
            "remoteId": "17757",
            "entries": [{"entry_id": "1234", "type": EntryType.FILE, "tags": ["attachment_tag"]}],
        }
        upload_attachment_mocker = mocker.patch("JiraV3.upload_XSOAR_attachment_to_jira", return_value=[])
        update_remote_system_res = update_remote_system_command(
            client=client, args=args, comment_tag_to_jira="", attachment_tag_to_jira="attachment_tag"
        )
        assert update_remote_system_res == "17757"
        assert ATTACHMENT_MIRRORED_FROM_XSOAR in upload_attachment_mocker.call_args[1]["attachment_name"]

    @pytest.mark.parametrize("client", [(jira_cloud_client_mock()), (jira_onprem_client_mock())])
    def test_update_remote_system_using_entry_with_correct_comment_tag(self, mocker, client):
        """
        Given:
            - A Jira client
        When
            - When the mirror out mechanism is called, which calls the update-remote-system command, and
            we get a comment with the appropriate comment tag.
        Then
            - Validate that the comment gets uploaded to Jira, and the content of the comment contains the
            constant COMMENT_MIRRORED_FROM_XSOAR
        """
        from JiraV3 import COMMENT_MIRRORED_FROM_XSOAR, update_remote_system_command

        args = {
            "remoteId": "17757",
            "entries": [{"entry_id": "1234", "tags": ["comment_tag"], "contents": "some comment"}],
        }
        add_comment_mocker = mocker.patch.object(client, "add_comment", return_value={})
        update_remote_system_res = update_remote_system_command(
            client=client, args=args, comment_tag_to_jira="comment_tag", attachment_tag_to_jira=""
        )

        assert update_remote_system_res == "17757"
        assert COMMENT_MIRRORED_FROM_XSOAR in str(add_comment_mocker.call_args[1]["json_data"])


class TestJiraGetRemoteData:
    def test_entries_returned_when_configured_not_to_return(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror in mechanism is called, which calls the get-remote-data command, and
            the user configured not to return comments nor attachments
        Then
            - Validate that no entries are returned
        """
        from JiraV3 import ATTACHMENT_MIRRORED_FROM_XSOAR, get_updated_remote_data

        attachments_entries = [
            {"File": "dummy_file_name", "FileID": "id1"},
            {"File": f"dummy_file_name{ATTACHMENT_MIRRORED_FROM_XSOAR}", "FileID": "id2"},
        ]
        comments_entries = [
            {"Comment": "Comment 1", "Updated": "2023-01-01", "UpdatedUser": "User 1"},
            {"Comment": "Comment 2", "Updated": "2023-05-01", "UpdatedUser": "User 2"},
            {"Comment": "Comment 3", "Updated": "2023-05-01", "UpdatedUser": "User 3"},
        ]
        client = jira_base_client_mock()
        mocker.patch("JiraV3.get_attachments_entries_for_fetched_incident", return_value=attachments_entries)
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=comments_entries)
        updated_incident: Dict[str, Any] = {}
        parsed_entries = get_updated_remote_data(
            client=client,
            issue={},
            updated_incident=updated_incident,
            issue_id="1234",
            mirror_resolved_issue=False,
            attachment_tag_from_jira="attachment from jira",
            comment_tag_from_jira="",
            user_timezone_name="",
            incident_modified_date=None,
            fetch_comments=False,
            fetch_attachments=False,
        )
        assert parsed_entries == []

    def test_get_attachment_entries(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror in mechanism is called, which calls the get-remote-data command, and
            we want to extract the attachments of the Jira issue
        Then
            - Validate that the attachments get added to the appropriate incident field, and only the entries
            that don't contain the constant ATTACHMENT_MIRRORED_FROM_XSOAR in their names, get the tag added to
            them.
        """
        from JiraV3 import ATTACHMENT_MIRRORED_FROM_XSOAR, get_updated_remote_data

        attachments_entries = [
            {"File": "dummy_file_name_old", "FileID": "id1", "created": "2024-01-01T00:00:00.000+0300"},
            {"File": "dummy_file_name", "FileID": "id1", "created": "2024-02-01T00:00:00.000+0300"},
            {
                "File": f"dummy_file_name{ATTACHMENT_MIRRORED_FROM_XSOAR}",
                "FileID": "id2",
                "created": "2024-02-01T00:00:00.000+0300",
            },
        ]
        create_file_mock_res = [{k: v for k, v in item.items() if k != "created"} for item in attachments_entries[1:]]
        client = jira_base_client_mock()
        mocker.patch("JiraV3.create_file_info_from_attachment", side_effect=create_file_mock_res)
        mocker.patch("demistomock.get", return_value=attachments_entries)
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=[])
        updated_incident: Dict[str, Any] = {}
        user_timezone = "Asia/Jerusalem"
        parsed_entries = get_updated_remote_data(
            client=client,
            issue={},
            updated_incident=updated_incident,
            issue_id="1234",
            mirror_resolved_issue=False,
            attachment_tag_from_jira="attachment from jira",
            comment_tag_from_jira="",
            user_timezone_name=user_timezone,
            incident_modified_date=arg_to_datetime("2024-01-01T00:00:00.000+0300"),
            fetch_comments=False,
            fetch_attachments=True,
        )
        expected_extracted_attachments = [
            {"path": "id1", "name": "dummy_file_name"},
            {"path": "id2", "name": "dummy_file_name_mirrored_from_xsoar"},
        ]
        expected_parsed_entries = [{"File": "dummy_file_name", "FileID": "id1", "Tags": ["attachment from jira"]}]
        assert updated_incident.get("extractedAttachments") == expected_extracted_attachments
        assert parsed_entries == expected_parsed_entries

    def test_get_comment_entries(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror in mechanism is called, which calls the get-remote-data command, and
            we want to extract the comments of the Jira issue
        Then
            - Validate that the comments get added to the appropriate incident field, and only the entries
            that don't contain the constant ATTACHMENT_MIRRORED_FROM_XSOAR in their names, and have an updated time
            that is greater than the last incident update get the tag added to them
        """
        from JiraV3 import COMMENT_MIRRORED_FROM_XSOAR, get_updated_remote_data

        comments_entries = [
            {"Comment": f"Comment 1 {COMMENT_MIRRORED_FROM_XSOAR}", "Updated": "2023-01-01", "UpdatedUser": "User 1"},
            {"Comment": f"Comment 2 {COMMENT_MIRRORED_FROM_XSOAR}", "Updated": "2023-05-01", "UpdatedUser": "User 2"},
            {"Comment": "Comment 3", "Updated": "2023-05-01", "UpdatedUser": "User 3"},
        ]
        client = jira_base_client_mock()
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=comments_entries)
        mocker.patch("JiraV3.get_attachments_entries_for_fetched_incident", return_value=[])
        user_timezone = "Asia/Jerusalem"
        dateparser_parse_mocker = mocker.patch("JiraV3.dateparser.parse", side_effect=dateparser.parse)
        updated_incident: Dict[str, Any] = {}
        parsed_entries = get_updated_remote_data(
            client=client,
            issue={},
            updated_incident=updated_incident,
            issue_id="1234",
            mirror_resolved_issue=False,
            attachment_tag_from_jira="",
            comment_tag_from_jira="comment from jira",
            user_timezone_name=user_timezone,
            incident_modified_date=dateparser.parse("2023-04-01"),
            fetch_comments=True,
            fetch_attachments=False,
        )
        expected_extracted_attachments = [
            {"Comment": f"Comment 1 {COMMENT_MIRRORED_FROM_XSOAR}", "Updated": "2023-01-01", "UpdatedUser": "User 1"},
            {"Comment": f"Comment 2 {COMMENT_MIRRORED_FROM_XSOAR}", "Updated": "2023-05-01", "UpdatedUser": "User 2"},
            {"Comment": "Comment 3", "Updated": "2023-05-01", "UpdatedUser": "User 3"},
        ]
        expected_parsed_entries = [
            {
                "Type": 1,
                "Contents": "Comment 3\nJira Author: None",
                "ContentsFormat": "text",
                "Tags": ["comment from jira"],
                "Note": True,
            }
        ]
        assert updated_incident.get("extractedComments") == expected_extracted_attachments
        assert parsed_entries == expected_parsed_entries
        assert dateparser_parse_mocker.call_args[1]["settings"]["TIMEZONE"] == user_timezone

    @pytest.mark.parametrize(
        "issue, should_be_closed",
        [
            ({"id": "1234", "fields": {"status": {"name": "Done"}, "resolutiondate": ""}}, True),
            ({"id": "1234", "fields": {"status": {"name": "Fixed"}, "resolutiondate": "2023-01-01"}}, True),
            ({"id": "1234", "fields": {"status": {"name": "Fixed"}, "resolutiondate": ""}}, False),
        ],
    )
    def test_close_incident_entry(self, mocker, issue, should_be_closed):
        """
        Given:
            - A Jira client
        When
            - When the mirror in mechanism is called, which calls the get-remote-data command, and
            the remote Jira issue has been marked as resolved, or status has been changed to Done
        Then
            - Validate that correct entry is returned, which contains data about closing the incident in XSOAR
        """
        from JiraV3 import get_updated_remote_data

        client = jira_base_client_mock()
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=[])
        mocker.patch("JiraV3.get_attachments_entries_for_fetched_incident", return_value=[])
        parsed_entries = get_updated_remote_data(
            client=client,
            issue=issue,
            updated_incident=issue,
            issue_id="1234",
            mirror_resolved_issue=True,
            attachment_tag_from_jira="",
            comment_tag_from_jira=" from jira",
            user_timezone_name="",
            incident_modified_date=None,
            fetch_comments=False,
            fetch_attachments=False,
        )
        if should_be_closed:
            close_reason = 'Issue was marked as "Resolved", or status was changed to "Done"'
            closed_entry = [
                {"Type": 1, "Contents": {"dbotIncidentClose": True, "closeReason": close_reason}, "ContentsFormat": "json"}
            ]
            assert parsed_entries == closed_entry
        else:
            assert parsed_entries == []

    def test_get_remote_data_response_is_returned(self, mocker):
        """
        Given:
            - A Jira client
        When
            - When the mirror in mechanism is called, which calls the get-remote-data command
        Then
            - Validate that correct entries are indeed returned
        """
        from JiraV3 import get_remote_data_command

        client = jira_base_client_mock()
        issue_response = {"id": "1234", "fields": {"summary": "dummy summary", "updated": "2023-01-01"}}
        mocker.patch.object(client, "get_issue", return_value=issue_response)
        mocker.patch("JiraV3.get_cached_user_timezone", return_value="Asia/Jerusalem")
        close_reason = 'Issue was marked as "Resolved", or status was changed to "Done"'
        expected_parsed_entries = [
            {
                "Type": 1,
                "Contents": "Comment 3\nJira Author: None",
                "ContentsFormat": "text",
                "Tags": ["comment from jira"],
                "Note": True,
            },
            {"File": "dummy_file_name", "FileID": "id1", "Tags": ["attachment from jira"]},
            {"Type": 1, "Contents": {"dbotIncidentClose": True, "closeReason": close_reason}, "ContentsFormat": "json"},
        ]
        mocker.patch("JiraV3.get_updated_remote_data", return_value=expected_parsed_entries)
        remote_data_response = get_remote_data_command(
            client=client,
            args={"id": "1234", "lastUpdate": "2023-01-01"},
            attachment_tag_from_jira="",
            comment_tag_from_jira="",
            mirror_resolved_issue=True,
            fetch_comments=True,
            fetch_attachments=True,
        )
        assert remote_data_response.entries == expected_parsed_entries


class TestJiraFetchIncidents:
    FETCH_INCIDENTS_QUERY_CASES = [
        (
            "id",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [1, 2, 3, 4],
            "status!=done AND id >= 1234 AND ID NOT IN (1, 2, 3, 4) ORDER BY id ASC",
        ),
        (
            "id",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [],
            "status!=done AND id >= 1234 ORDER BY id ASC",
        ),
        (
            "created date",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [1, 2, 3, 4],
            'status!=done AND created >= "2023-05-01" AND ID NOT IN (1, 2, 3, 4) ORDER BY created ASC',
        ),
        (
            "created date",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [],
            'status!=done AND created >= "2023-05-01" ORDER BY created ASC',
        ),
        (
            "created date",
            "status!=done",
            "1234",
            "",
            "2023-05-02",
            "2023-02-01",
            [],
            'status!=done AND created >= "2023-02-01" ORDER BY created ASC',
        ),
        (
            "created date",
            "status!=done",
            "1234",
            "",
            "2023-05-02",
            "2023-02-01",
            [1, 2, 3, 4],
            'status!=done AND created >= "2023-02-01" AND ID NOT IN (1, 2, 3, 4) ORDER BY created ASC',
        ),
        (
            "updated date",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [1, 2, 3, 4],
            'status!=done AND updated >= "2023-05-02" AND ID NOT IN (1, 2, 3, 4) ORDER BY updated ASC',
        ),
        (
            "updated date",
            "status!=done",
            "1234",
            "2023-05-01",
            "2023-05-02",
            "2023-02-01",
            [],
            'status!=done AND updated >= "2023-05-02" ORDER BY updated ASC',
        ),
        (
            "updated date",
            "status!=done",
            "1234",
            "2023-05-01",
            "",
            "2023-02-01",
            [],
            'status!=done AND updated >= "2023-02-01" ORDER BY updated ASC',
        ),
        (
            "updated date",
            "status!=done",
            "1234",
            "2023-05-01",
            "",
            "2023-02-01",
            [1, 2, 3, 4],
            'status!=done AND updated >= "2023-02-01" AND ID NOT IN (1, 2, 3, 4) ORDER BY updated ASC',
        ),
        (
            "id",
            '"request type" = "Report a Potential Security Incident"',
            "0",
            "",
            "",
            "",
            [],
            '"request type" = "Report a Potential Security Incident" AND id >= 0 ORDER BY id ASC',
        ),
    ]

    @pytest.mark.parametrize(
        (
            "issue_field_to_fetch_from, fetch_query, last_fetch_id,"
            "last_fetch_created_time, last_fetch_updated_time,"
            "first_fetch_interval, issue_ids_to_exclude, expected_fetch_query"
        ),
        FETCH_INCIDENTS_QUERY_CASES,
    )
    def test_create_fetch_incidents_query(
        self,
        issue_field_to_fetch_from: str,
        fetch_query: str,
        last_fetch_id: int,
        last_fetch_created_time: str,
        last_fetch_updated_time: str,
        first_fetch_interval: str,
        issue_ids_to_exclude: List[int],
        expected_fetch_query: str,
    ):
        """
        Given:
            - Arguments to create the fetch query, which are:
                1. issue_field_to_fetch_from: the issue field to fetch by
                2. fetch_query: the query to include in every fetch
                3. last_fetch_id: the last fetched id
                4. last_fetch_created_time: the created time of the last fetch issue
                5. last_fetch_updated_time: the updated time of the last fetch issue
                6. first_fetch_interval: the first fetch interval to fetch from if the fetch timestamp is empty,
                    and we are fetching using created or updated time.
                7. issue_ids_to_exclude: the ids of the issues that we want to exclude.
        When
            - When fetching incidents
        Then
            - Validate that the correct fetch query is created given the above arguments
        """
        from JiraV3 import create_fetch_incidents_query

        fetch_query = create_fetch_incidents_query(
            issue_field_to_fetch_from,
            fetch_query,
            last_fetch_id,
            last_fetch_created_time,
            last_fetch_updated_time,
            first_fetch_interval,
            issue_ids_to_exclude,
        )
        assert fetch_query == expected_fetch_query

    def test_get_comments_entries_for_fetched_incident(self, mocker):
        """
        Given:
            - A Jira client, and an issue id or key
        When
            - When extracting the issue's comments as entries to put in the incident field
        Then
            - Validate that the correct data is extracted and returned
        """
        from JiraV3 import get_comments_entries_for_fetched_incident

        client = jira_base_client_mock()
        comments_raw_response = util_load_json("test_data/get_comments_test/raw_response.json")
        mocker.patch.object(client, "get_comments", return_value=comments_raw_response)
        comments_entries = get_comments_entries_for_fetched_incident(client=client, issue_id_or_key="1234")
        expected_comments_entries = [
            {
                "Id": "18322",
                "Comment": "Hello there",
                "User": "Example User",
                "Created": "2023-03-23T07:45:29.056+0200",
                "Updated": "2023-03-23T07:45:29.056+0200",
                "UpdateUser": "Example User",
            },
            {
                "Id": "18329",
                "Comment": "Second comment",
                "User": "Example User",
                "Created": "2023-03-27T20:54:15.878+0300",
                "Updated": "2023-03-27T20:54:15.878+0300",
                "UpdateUser": "Example User",
            },
            {
                "Id": "18394",
                "Comment": "This is a comment from Jira demo",
                "User": "Example User",
                "Created": "2023-04-24T15:41:54.472+0300",
                "Updated": "2023-04-24T15:41:54.472+0300",
                "UpdateUser": "Example User",
            },
        ]
        assert expected_comments_entries == comments_entries

    def test_get_attachments_entries_for_fetched_incident(self, mocker):
        """
        Given:
            - A Jira client, and an issue id or key
        When
            - When extracting the issue's attachments as entries to put in the incident field
        Then
            - Validate that the correct data is extracted and returned
        """
        from JiraV3 import get_attachments_entries_for_fetched_incident

        client = jira_base_client_mock()
        attachment_metadata_raw_response = util_load_json("test_data/get_issue_test/raw_response_attachment_metadata.json")
        expected_attachments_entries = [
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": "dummy_type",
                "File": "dummy_filename_1",
                "FileID": "dummy_id_1",
            },
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": "dummy_type",
                "File": "dummy_filename_2",
                "FileID": "dummy_id_2",
            },
        ]
        mocker.patch("JiraV3.create_file_info_from_attachment", side_effect=expected_attachments_entries)
        attachments_entries = get_attachments_entries_for_fetched_incident(
            client=client, attachments_metadata=[attachment_metadata_raw_response, attachment_metadata_raw_response]
        )
        assert expected_attachments_entries == attachments_entries

    def test_get_fetched_attachments(self, mocker):
        """
        Given:
            - A Jira client, and the issue response
        When
            - Extracting the attachments of the fetched issue
        Then
            - Validate that the correct data is extracted and returned
        """
        from JiraV3 import get_fetched_attachments

        attachments_entries = [
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": "dummy_type",
                "File": "dummy_filename_1",
                "FileID": "dummy_id_1",
            },
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": EntryType.ERROR,
                "File": "dummy_filename_2",
                "FileID": "dummy_id_2",
            },
        ]
        mocker.patch("JiraV3.get_attachments_entries_for_fetched_incident", return_value=attachments_entries)
        client = jira_base_client_mock()
        expected_fetched_attachments = [{"path": "dummy_id_1", "name": "dummy_filename_1"}]
        fetched_attachments = get_fetched_attachments(client=client, issue={})
        assert expected_fetched_attachments == fetched_attachments

    def test_get_fetched_comments(self, mocker):
        """
        Given:
            - A Jira client, and an issue id.
        When
            - Extracting the comments' entries of the fetched issue.
        Then
            - Validate that the correct data is extracted and returned.
        """
        from JiraV3 import get_fetched_comments

        expected_comments_entries = [
            {
                "Id": "18322",
                "Comment": "Hello there",
                "User": "Example User",
                "Created": "2023-03-23T07:45:29.056+0200",
                "Updated": "2023-03-23T07:45:29.056+0200",
                "UpdateUser": "Example User",
            },
            {
                "Id": "18329",
                "Comment": "Second comment",
                "User": "Example User",
                "Created": "2023-03-27T20:54:15.878+0300",
                "Updated": "2023-03-27T20:54:15.878+0300",
                "UpdateUser": "Example User",
            },
        ]
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=expected_comments_entries)
        attachments_entries = [
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": "dummy_type",
                "File": "dummy_filename_1",
                "FileID": "dummy_id_1",
            },
            {
                "Contents": "",
                "ContentsFormat": "dummy_format",
                "Type": EntryType.ERROR,
                "File": "dummy_filename_2",
                "FileID": "dummy_id_2",
            },
        ]
        mocker.patch("JiraV3.get_attachments_entries_for_fetched_incident", return_value=attachments_entries)
        client = jira_base_client_mock()
        comments_entries = get_fetched_comments(client=client, issue_id="1234")
        assert comments_entries == expected_comments_entries

    def test_add_extracted_data_to_incident(self):
        """
        Given:
            - An issue response
        When
            - Extracting data from the issue raw response, to insert it to the respective incident fields
        Then
            - Validate that the correct data is extracted and returned
        """
        from JiraV3 import add_extracted_data_to_incident

        issue = util_load_json("test_data/get_issue_test/raw_response.json")
        expected_issue = add_extracted_data_to_incident(issue=issue)
        expected_extracted_issue_data = {
            "extractedSubtasks": [{"id": "21525", "key": "COMPANYSA-63"}, {"id": "21538", "key": "COMPANYSA-70"}],
            "extractedCreator": "Example User(admin@test.com)",
            "extractedComponents": ["Almost-Done", "dummy-comp", "Integration", "New-Component"],
        }
        assert expected_extracted_issue_data.items() <= expected_issue.items()

    @pytest.mark.parametrize(
        "issue_field_priority, severity",
        [
            ({"name": "Highest"}, 4),
            ({"name": "High"}, 3),
            ({"name": "Medium"}, 2),
            ({"name": "Low"}, 1),
            ({"name": "Lowest"}, 1),
            ({"name": "Extreme"}, 0),
        ],
    )
    def test_get_jira_issue_severity(self, issue_field_priority, severity):
        """
        Given:
            - The priority field of an issue
        When
            - Determining the severity of the incident
        Then
            - Validate that the priority of the issue is mapped to the correct severity
        """
        from JiraV3 import get_jira_issue_severity

        assert severity == get_jira_issue_severity(issue_field_priority)

    def test_parse_custom_fields(self):
        """
        Given:
            - An issue response
        When
            - Parsing the custom fields in the response to a more human readable form
        Then
            - Validate that the data of the custom fields get parsed and show the correct data
        """
        from JiraV3 import parse_custom_fields

        issue = util_load_json("test_data/get_issue_test/raw_response.json")
        expected_parsed_custom_fields = util_load_json("test_data/parsed_issue_custom_fields.json")
        parse_custom_fields(issue=issue, issue_fields_id_to_name_mapping=issue.get("names", {}) or {})
        assert expected_parsed_custom_fields == issue

    @pytest.mark.parametrize(
        "dateparser_settings, expected_parsed_created_time, expected_parsed_updated_time",
        [
            pytest.param(None, "2025-03-14 06:54", "2025-03-14 07:59", id="No timezone conversion"),
            pytest.param({"TIMEZONE": "UTC-4"}, "2025-03-14 09:54", "2025-03-14 10:59", id="Convert to EST"),
            pytest.param({"TIMEZONE": "UTC+3"}, "2025-03-14 16:54", "2025-03-14 17:59", id="Convert to IDT"),
        ],
    )
    def test_parse_issue_times_for_next_run(
        self,
        dateparser_settings: dict | None,
        expected_parsed_created_time: str,
        expected_parsed_updated_time: str,
    ):
        """
        Given:
            - Issue created and updated timestamps in the UTC-7 timezone (US West Coast)
        When
            - Parsing the issue timestamps for fetch incidents next run
        Then
            - Validate that the timestamp fields get parsed correctly according to the date parser settings.
        """
        from JiraV3 import parse_issue_times_for_next_run

        parsed_created_time, parsed_updated_time = parse_issue_times_for_next_run(
            issue_id=1234,
            issue_created_time="2025-03-14T06:54:33.000-0700",
            issue_updated_time="2025-03-14T07:59:09.000-0700",
            dateparser_settings=dateparser_settings,
        )
        assert parsed_created_time == expected_parsed_created_time
        assert parsed_updated_time == expected_parsed_updated_time

    def test_set_last_run_when_first_time_running_fetch(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Calling the fetch incidents mechanism for the first time (last_run is empty)
        Then
            - Validate that the last run object gets saved with the correct data
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch("JiraV3.demisto.getLastRun", return_value={})  # empty last run (first fetch) -> Convert timezone
        get_user_timezone_mocker = mocker.patch("JiraV3.get_cached_user_timezone", return_value="UTC")
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        set_last_run_mocker = mocker.patch("JiraV3.demisto.setLastRun")
        query_raw_response = {
            "issues": [
                {"id": "1", "fields": {"created": "2023-12-11 21:09", "updated": "2023-12-12 21:09"}},
                {"id": "2", "fields": {"created": "2023-12-11 22:09", "updated": "2023-12-12 22:09"}},
            ]
        }
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="created date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        expected_last_run = {
            "issue_ids": [1, 2],
            "id": 2,
            "created_date": "2023-12-11 22:09",
            "updated_date": "2023-12-12 22:09",
            "convert_timezone": True,  # last run was empty, so timezone was converted
        }
        assert expected_last_run == set_last_run_mocker.call_args[0][0]
        assert get_user_timezone_mocker.call_count == 1

    def test_set_last_run_when_timezone_has_utc_offset(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Calling the fetch incidents mechanism with `convert_timezone` set to True
        Then
            - Validate that the last run object gets saved with the correct data
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch(
            "JiraV3.demisto.getLastRun",
            return_value={
                "issue_ids": [1],
                "id": 1,
                "created_date": "2023-12-11 21:04",
                "updated_date": "2023-12-12 22:08",
                "convert_timezone": True,
            },
        )
        get_user_timezone_mocker = mocker.patch("JiraV3.get_cached_user_timezone", return_value="UTC-4")
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        set_last_run_mocker = mocker.patch("JiraV3.demisto.setLastRun")
        query_raw_response = {
            "issues": [
                {"id": "2", "fields": {"created": "2023-12-11 21:09-0700", "updated": "2023-12-12 21:09-0700"}},
                {"id": "3", "fields": {"created": "2023-12-11 22:09-0700", "updated": "2023-12-12 23:18-0700"}},
            ]
        }
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="created date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        expected_last_run = {
            "issue_ids": [2, 3],
            "id": 3,
            "created_date": "2023-12-12 01:09",  # converted last created and updated from UTC-7 to UTC-4
            "updated_date": "2023-12-13 02:18",
            "convert_timezone": True,  # last run was empty, so timezone was converted
        }
        assert expected_last_run == set_last_run_mocker.call_args[0][0]
        assert get_user_timezone_mocker.call_count == 1

    def test_set_last_run_when_last_run_is_not_empty(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Calling the fetch incidents mechanism, and the last_run object is not empty
        Then
            - Validate that the last run object gets saved with the correct data
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch(  # convert_timezone is False -> No timezone conversion
            "JiraV3.demisto.getLastRun",
            return_value={
                "issue_ids": ["1", "2"],
                "id": "2",
                "created_date": "2023-12-11 22:09",
                "updated_date": "2023-12-12 22:09",
                "convert_timezone": False,
            },
        )
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        set_last_run_mocker = mocker.patch("JiraV3.demisto.setLastRun")
        query_raw_response = {
            "issues": [
                {"id": "3", "fields": {"created": "2024-01-11 21:09", "updated": "2024-01-12 21:09"}},
                {"id": "4", "fields": {"created": "2024-01-11 22:09", "updated": "2024-01-12 22:09"}},
            ]
        }
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="created date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        expected_last_run = {
            "issue_ids": [3, 4],
            "id": 4,
            "created_date": "2024-01-11 22:09",
            "updated_date": "2024-01-12 22:09",
            "convert_timezone": False,  # last run was not empty, no timezone conversion to keep backwards compatibility
        }
        assert expected_last_run == set_last_run_mocker.call_args[0][0]

    def test_set_last_run_when_we_did_not_progress_in_created_time(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Fetching incidents by the created date field, and we did no progress in terms of
            time (the created time stayed the same as the last fetch)
        Then
            - Validate that the issue ids from the last run get also added as part of the
            last_run object, since we did not progress in time
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch(  # last run not empty -> No timezone conversion
            "JiraV3.demisto.getLastRun",
            return_value={
                "issue_ids": ["1", "2"],
                "id": "2",
                "created_date": "2023-12-11 22:09",
                "updated_date": "2023-12-12 22:09",
            },
        )
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        set_last_run_mocker = mocker.patch("JiraV3.demisto.setLastRun")
        query_raw_response = {
            "issues": [
                {"id": "3", "fields": {"created": "2023-12-11 22:09", "updated": "2024-01-12 21:09"}},
                {"id": "4", "fields": {"created": "2023-12-11 22:09", "updated": "2024-01-12 22:09"}},
            ]
        }
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="created date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        expected_last_run = {
            "issue_ids": [3, 4, 1, 2],
            "id": 4,
            "created_date": "2023-12-11 22:09",
            "updated_date": "2024-01-12 22:09",
            "convert_timezone": False,  # last run was not empty, no timezone conversion to keep backwards compatibility
        }
        assert expected_last_run == set_last_run_mocker.call_args[0][0]

    def test_set_last_run_when_we_did_not_progress_in_updated_time(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Fetching incidents by the updated date field, and we did no progress in terms of
            time (the created time stayed the same as the last fetch)
        Then
            - Validate that the issue ids from the last run get also added as part of the
            last_run object, since we did not progress in time
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch(  # last run not empty -> No timezone conversion
            "JiraV3.demisto.getLastRun",
            return_value={
                "issue_ids": ["1", "2"],
                "id": "2",
                "created_date": "2023-12-11 22:09",
                "updated_date": "2023-12-12 22:09",
            },
        )
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        set_last_run_mocker = mocker.patch("JiraV3.demisto.setLastRun")
        query_raw_response = {
            "issues": [
                {"id": "3", "fields": {"created": "2022-01-12 22:09", "updated": "2023-12-12 22:09"}},
                {"id": "4", "fields": {"created": "2022-01-11 22:09", "updated": "2023-12-12 22:09"}},
            ]
        }
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="updated date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        expected_last_run = {
            "issue_ids": [3, 4, 1, 2],
            "id": 4,
            "created_date": "2022-01-11 22:09",
            "updated_date": "2023-12-12 22:09",
            "convert_timezone": False,  # last run was not empty, no timezone conversion to keep backwards compatibility
        }
        assert expected_last_run == set_last_run_mocker.call_args[0][0]

    def test_create_incident_from_issue(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - Fetching incidents (in this unit test, we fetch two incidents, but only check on the first
            incident)
        Then
            - Validate that the correct value of the rawJSON key is returned, which will be used for the
            incident fields
        """
        from JiraV3 import fetch_incidents

        query_raw_response = util_load_json("test_data/issue_query_test/raw_response.json")
        issue_incident = util_load_json("test_data/fetch_incidents_test/issue_incident.json")
        client = jira_base_client_mock()
        mocker.patch.object(client, "run_query", return_value=query_raw_response)
        mocker.patch("JiraV3.get_fetched_attachments", return_value=[{"FileID": "1"}, {"FileID": "2"}])
        comments_entries = [
            {
                "Id": "18322",
                "Comment": "Hello there",
                "User": "Example User",
                "Created": "2023-03-23T07:45:29.056+0200",
                "Updated": "2023-03-23T07:45:29.056+0200",
                "UpdateUser": "Example User",
            }
        ]
        mocker.patch("JiraV3.get_comments_entries_for_fetched_incident", return_value=comments_entries)
        incidents = fetch_incidents(
            client=client,
            issue_field_to_fetch_from="updated date",
            fetch_query="status!=done",
            id_offset=1234,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=3,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        assert json.dumps(issue_incident) == incidents[0].get("rawJSON")

    def test_retrieve_smallest_issue_id_when_fetching_by_id_and_offset_is_zero(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - We are fetching by the issue ID, and the ID offset is set to 0
        Then
            - Validate that the correct query is being called in order to retrieve the smallest issue ID
            with respect to the fetch query
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        smallest_issue_id = "10161"
        run_query_mocker = mocker.patch.object(client, "run_query", side_effect=[{"issues": [{"id": smallest_issue_id}]}, {}])
        fetch_query = "status!=done"
        fetch_incidents(
            client=client,
            issue_field_to_fetch_from="id",
            fetch_query=fetch_query,
            id_offset=0,
            fetch_attachments=True,
            fetch_comments=True,
            max_fetch_incidents=DEFAULT_FETCH_LIMIT,
            first_fetch_interval="3 days",
            mirror_direction="Incoming And Outgoing",
            comment_tag_to_jira="comment_tag_to_jira",
            comment_tag_from_jira="comment_tag_from_jira",
            attachment_tag_to_jira="attachment_tag_to_jira",
            attachment_tag_from_jira="attachment_tag_from_jira",
        )
        assert (
            run_query_mocker.call_args_list[0][1].get("query_params", {}).get("jql", "") == f"{fetch_query} ORDER BY created ASC"
        )
        assert (
            run_query_mocker.call_args_list[1][1].get("query_params", {}).get("jql", "")
            == f"{fetch_query} AND id >= {smallest_issue_id} ORDER BY id ASC"
        )

    def test_fetch_incidents_by_id_incorrect_id_offset_error(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - We are fetching by the issue ID, and the ID offset is set to an arbitrary number, other than 0
        Then
            - Validate that the error is caught, that stems from configuring an incorrect (does not exist) ID offset
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        smallest_issue_id = "10161"
        mocker.patch.object(
            client,
            "run_query",
            side_effect=[
                Exception("Issue does not exist or you do not have permission to see it"),
                {"issues": [{"id": smallest_issue_id}]},
            ],
        )
        fetch_query = "status!=done"
        with pytest.raises(DemistoException) as e:
            fetch_incidents(
                client=client,
                issue_field_to_fetch_from="id",
                fetch_query=fetch_query,
                id_offset=1,
                fetch_attachments=True,
                fetch_comments=True,
                max_fetch_incidents=DEFAULT_FETCH_LIMIT,
                first_fetch_interval="3 days",
                mirror_direction="Incoming And Outgoing",
                comment_tag_to_jira="comment_tag_to_jira",
                comment_tag_from_jira="comment_tag_from_jira",
                attachment_tag_to_jira="attachment_tag_to_jira",
                attachment_tag_from_jira="attachment_tag_from_jira",
            )
        assert f"The smallest issue ID with respect to the fetch query is {smallest_issue_id}" in str(e)

    def test_fetch_incidents_by_id_and_offset_is_zero_error(self, mocker):
        """
        Given:
            - Arguments to use when calling the fetch incidents mechanism
        When
            - We are fetching by the issue ID, the ID offset is set to 0, we try to acquire the smallest issue ID
            with respect to the fetch query, but there are no issues returned from the fetch query
        Then
            - Validate that an error is returned, stating that there are no issues with respect to the configured fetch query
        """
        from JiraV3 import DEFAULT_FETCH_LIMIT, fetch_incidents

        client = jira_base_client_mock()
        mocker.patch("JiraV3.create_incident_from_issue", return_value={})
        mocker.patch.object(client, "run_query", return_value={"issues": []})
        fetch_query = "status!=done"
        with pytest.raises(DemistoException) as e:
            fetch_incidents(
                client=client,
                issue_field_to_fetch_from="id",
                fetch_query=fetch_query,
                id_offset=0,
                fetch_attachments=True,
                fetch_comments=True,
                max_fetch_incidents=DEFAULT_FETCH_LIMIT,
                first_fetch_interval="3 days",
                mirror_direction="Incoming And Outgoing",
                comment_tag_to_jira="comment_tag_to_jira",
                comment_tag_from_jira="comment_tag_from_jira",
                attachment_tag_to_jira="attachment_tag_to_jira",
                attachment_tag_from_jira="attachment_tag_from_jira",
            )
        assert "The fetch query configured returned no Jira issues, please update it" in str(e)


class TestJiraIssueAssign:
    @pytest.mark.parametrize(
        "assignee, assignee_id, excpected_body_request",
        [("server_assignee", None, {"name": "server_assignee"}), (None, "cloud_assignee", {"accountId": "cloud_assignee"})],
    )
    def test_update_issue_assignee_command(self, mocker, assignee, assignee_id, excpected_body_request):
        """
        Given:
            - issue id, and assignees for cloud/server jira
        When
            - Running the update_issue_assignee_command
        Then
            - Ensure the body request is ok for both cloud/server jira
        """
        from JiraV3 import update_issue_assignee_command

        get_issue_response = util_load_json("test_data/get_issue_test/raw_response.json")
        args = {
            "assignee": assignee,  # For Jira OnPrem
            "assignee_id": assignee_id,  # For Jira Cloud
            "issue_id": 21487,
        }
        client: JiraBaseClient = jira_base_client_mock()
        if assignee_id:
            client = jira_cloud_client_mock()
        else:
            client = jira_onprem_client_mock()

        jira_req_mocker = mocker.patch.object(client, "update_assignee", return_value=None)
        mocker.patch.object(client, "get_issue", return_value=get_issue_response)
        assert update_issue_assignee_command(client=client, args=args)
        assert jira_req_mocker.call_args[1].get("assignee_body") == excpected_body_request

    def test_test_update_issue_assignee_command_no_assignees(self):
        """
        Given:
            - issue id, without assignee / assignee_id
        When
            - Running the update_issue_assignee_command
        Then
            - Ensure an exception is raised
        """
        from JiraV3 import update_issue_assignee_command

        args = {
            "assignee": None,  # For Jira OnPrem
            "assignee_id": None,  # For Jira Cloud
            "issue_id": 21487,
        }

        client = jira_base_client_mock()

        with pytest.raises(DemistoException):
            update_issue_assignee_command(client=client, args=args)


class TestJiraIssueGetForms:
    @pytest.mark.parametrize("issue_id", [("TES-2"), ("")])
    def test_issue_get_forms_command(self, mocker, issue_id):
        """
        Given:
            - issue_id
        When
            - Running the issue_get_forms_command
        Then
            - Ensure the body request is ok
        """
        from JiraV3 import issue_get_forms_command

        args = {"issue_id": issue_id}
        client: JiraBaseClient = jira_base_client_mock()
        client = jira_onprem_client_mock()

        raw_response_path = "test_data/get_issue_forms_test/raw_response.json"
        parsed_result_path = "test_data/get_issue_forms_test/parsed_result.json"
        issue_get_forms_response = util_load_json(raw_response_path)
        expected_command_results_context = util_load_json(parsed_result_path)
        mock_request = mocker.patch.object(client, "issue_get_forms", return_value=issue_get_forms_response)

        if issue_id:
            command_results = issue_get_forms_command(client=client, args=args)
            for command_result in command_results:
                assert expected_command_results_context == command_result.to_context()
            mock_request.assert_called_with(issue_id=issue_id)
        else:
            with pytest.raises(ValueError):
                issue_get_forms_command(client=client, args=args)


class TestJiraGetUserInfo:
    @pytest.mark.parametrize(
        "key, username, account_id, raw_response_path, parsed_result_path",
        [
            (
                "JIRAUSER10000",
                None,
                None,
                "test_data/get_user_info_test/onprem_raw_response.json",
                "test_data/get_user_info_test/onprem_parsed_result.json",
            ),
            (
                None,
                "firstlast",
                None,
                "test_data/get_user_info_test/onprem_raw_response.json",
                "test_data/get_user_info_test/onprem_parsed_result.json",
            ),
            (
                None,
                None,
                "user@example.com",
                "test_data/get_user_info_test/cloud_raw_response.json",
                "test_data/get_user_info_test/cloud_parsed_result.json",
            ),
            (None, None, None, None, None),
        ],
    )
    def test_get_user_info_command(self, mocker, key, username, account_id, raw_response_path, parsed_result_path):
        """
        Given:
            - key, username or account_id for cloud/server jira
        When
            - Running the get_user_info_command
        Then
            - Ensure the body request is ok for both cloud/server jira
        """
        from JiraV3 import get_user_info_command

        args = {
            "key": key,  # For Jira OnPrem
            "username": username,  # For Jira OnPrem
            "account_id": account_id,  # For Jira Cloud
        }
        client: JiraBaseClient = jira_base_client_mock()
        if account_id:
            client = jira_cloud_client_mock()
            identifier = f"accountId={account_id}"
        elif key or username:
            client = jira_onprem_client_mock()
            if key:
                identifier = f"key={key}"
            else:
                identifier = f"username={username}"
        else:
            identifier = ""

        if identifier:
            get_user_info_response = util_load_json(raw_response_path)
            expected_command_results_context = util_load_json(parsed_result_path)
            mock_request = mocker.patch.object(client, "get_user_info", return_value=get_user_info_response)

            command_results = get_user_info_command(client=client, args=args)
            assert expected_command_results_context == command_results.to_context()
            mock_request.assert_called_with(identifier)
        else:
            with pytest.raises(ValueError):
                get_user_info_command(client=client, args=args)


class TestJiraCreateMetadataIssueTypes:
    @pytest.mark.parametrize("project_id_or_key", [("test_project_id"), ("")])
    def test_get_create_metadata_issue_types(self, mocker, project_id_or_key):
        """
        Given:
            - project_id_or_key
        When:
            - running get_create_metadata_issue_types_command
        Then:
            - ensure the body request is ok
        """

        from JiraV3 import get_create_metadata_issue_types_command

        args = {
            "project_id_or_key": project_id_or_key,
        }

        client: JiraBaseClient = jira_base_client_mock()

        raw_response_path = "test_data/get_create_metadata_issue_types_test/raw_response.json"
        parsed_result_path = "test_data/get_create_metadata_issue_types_test/parsed_result.json"
        metadata_response = util_load_json(raw_response_path)
        expected_context = util_load_json(parsed_result_path)
        mock_request = mocker.patch.object(client, "get_create_metadata_issue_types", return_value=metadata_response)

        if project_id_or_key:
            command_results = get_create_metadata_issue_types_command(client=client, args=args)
            assert expected_context == command_results.to_context()
            mock_request.assert_called_with(project_id_or_key=project_id_or_key, start_at=0, max_results=50)
        else:
            with pytest.raises(ValueError):
                get_create_metadata_issue_types_command(client=client, args=args)


class TestJiraCreateMetadataField:
    @pytest.mark.parametrize("project_id_or_key, issue_type_id", [("test_project_id", "100"), ("", "")])
    def test_get_create_metadata_field(self, mocker, project_id_or_key, issue_type_id):
        """
        Given:
            - project_id_or_key
            - issue_type_id
        When:
            - running get_create_metadata_field_command
        Then:
            - ensure the body request is ok
        """

        from JiraV3 import get_create_metadata_field_command

        args = {
            "project_id_or_key": project_id_or_key,
            "issue_type_id": issue_type_id,
        }

        client: JiraBaseClient = jira_base_client_mock()

        raw_response_path = "test_data/get_create_metadata_field_test/raw_response.json"
        parsed_result_path = "test_data/get_create_metadata_field_test/parsed_result.json"
        metadata_response = util_load_json(raw_response_path)
        expected_context = util_load_json(parsed_result_path)
        mock_request = mocker.patch.object(client, "get_create_metadata_field", return_value=metadata_response)

        if project_id_or_key and issue_type_id:
            command_results = get_create_metadata_field_command(client=client, args=args)
            assert expected_context == command_results.to_context()
            mock_request.assert_called_with(
                project_id_or_key=project_id_or_key, issue_type_id=issue_type_id, start_at=0, max_results=50
            )
        else:
            with pytest.raises(ValueError):
                get_create_metadata_field_command(client=client, args=args)


def test_get_remote_data_preview_command():
    # Given: Prepare the mock objects and inputs
    mock_client = Mock()
    args = {"id": "JIRA-123"}

    # Mock Jira issue object returned by client
    mock_issue = util_load_json("test_data/get_remote_data_preview/raw_response.json")

    mock_client.get_issue.return_value = mock_issue

    # When: Execute the command under test
    result = get_remote_data_preview_command(mock_client, args)

    # Then: Validate the outputs
    assert result.outputs_prefix == "QuickActionPreview"
    assert result.outputs_key_field == "id"

    expected_preview = QuickActionPreview(
        id="21487",
        title="XSOAR description test",
        description="Testing summary XSOAR mirroring",
        status="Backlog",
        assignee="Example User(admin@test.com)",
        creation_date="2023-03-01T11:34:49.730+0200",
        severity="Low",
    ).to_context()

    assert result.outputs == expected_preview

    # Validate interactions
    mock_client.get_issue.assert_called_once_with(issue_id_or_key="JIRA-123")


@pytest.mark.parametrize(
    "url, expected_is_cloud",
    [
        ("https://yourcompany.atlassian.net", True),
        ("https://api.atlassian.com/ex/jira/", True),
        ("https://www.callback.com", False),
        ("https://test.atlassian.net.evil.com", False),
        ("https://dummy_url", False),
    ],
)
def test_is_jira_cloud_url(url, expected_is_cloud):
    """
    Given:
        - Various URL strings including Jira Cloud URLs, on-premises URLs, and invalid URLs
    When:
        - Calling is_jira_cloud_url function
    Then:
        - Validate that True is returned for Jira Cloud URLs and False for others
    """
    from JiraV3 import is_jira_cloud_url

    result = is_jira_cloud_url(url)
    assert result == expected_is_cloud