Skip to content

Commit

Permalink
SNOW add custom resolution code parameter (#30599)
Browse files Browse the repository at this point in the history
* sysparm_query

* default get

* fix

* update the integration to support custom close reason

* revert changes

* revert changes

* RN

* docker

* UT

* ignore flake8 error

* docs

* Update Packs/ServiceNow/ReleaseNotes/2_5_45.md

Co-authored-by: ShirleyDenkberg <[email protected]>

* Update Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml

Co-authored-by: ShirleyDenkberg <[email protected]>

* Update Packs/ServiceNow/Integrations/ServiceNowv2/README.md

Co-authored-by: ShirleyDenkberg <[email protected]>

* overwrites

* custom

---------

Co-authored-by: ShirleyDenkberg <[email protected]>
  • Loading branch information
MosheEichler and ShirleyDenkberg authored Nov 9, 2023
1 parent de27333 commit 7bf936e
Show file tree
Hide file tree
Showing 6 changed files with 73 additions and 37 deletions.
5 changes: 3 additions & 2 deletions Packs/ServiceNow/Integrations/ServiceNowv2/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ These scripts are wrapped around the incident table, so to wrap them around anot
3. Under **Mapper (incoming)**, select ServiceNow - Incoming Mapper.
4. Under **Mapper (outgoing)**, select ServiceNow - Outgoing Mapper.
5. To enable mirroring to close a ticket in Cortex XSOAR, under the **Mirrored XSOAR Ticket closure method** dropdown, select the ticket closing method,
or set the *Mirrored XSOAR Ticket custom close state code* parameter, in order to override the default closure method with a custom state.
In order to use *Mirrored XSOAR Ticket custom close state code* parameter, it must follow this format: "custom_state_code1=custom_label1,custom_state_code2=custom_label2,...",
or set the *Mirrored XSOAR Ticket custom close resolution code* or *Mirrored XSOAR Ticket custom close state code* parameter, in order to override the default closure method with a custom close code or custom state.
In order to use *Mirrored XSOAR Ticket custom close resolution code* or *Mirrored XSOAR Ticket custom close state code* parameter, it must follow this format: "custom_state_code1=custom_label1,custom_state_code2=custom_label2,...",
for example: “10=Design,11=Development,12=Testing”.
Also, a matching user-defined list of customized incident close reasons must be configured as a "Server configuration" in Cortex XSOAR. (Meaning each Service Now custom state label will have a matching Cortex XSOAR custom close reason with the same name). ***Not following this format will result in a server error!***
For more information about Customize Incident Close Reasons, see [this link](https://docs-cortex.paloaltonetworks.com/r/Cortex-XSOAR/6.10/Cortex-XSOAR-Administrator-Guide/Customize-Incident-Close-Reasons).
Expand Down Expand Up @@ -107,6 +107,7 @@ If MFA is enabled for your user, follow the next steps:
| Custom Fields to Mirror | Custom \(user defined\) fields in the format: u_fieldname1,u_fieldname2 custom fields start with a 'u_'. These fields will be included in the mirroring capabilities, if added here. | False |
| Mirrored XSOAR Ticket closure method | Define how to close the mirrored tickets in Cortex XSOAR. Choose 'resolved' to enable reopening from the UI. Otherwise, choose 'closed'. Choose 'None' to disable closing the mirrored tickets in Cortex XSOAR. | False |
| Mirrored XSOAR Ticket custom close state code | Define how to close the mirrored tickets in Cortex XSOAR with a custom state. Enter here a comma-separated list of custom closure state codes and their labels (acceptable format example: “10=Design,11=Development,12=Testing”) to override the default closure method. Note that a matching user-defined list of custom close reasons must be configured as a "Server configuration" in Cortex XSOAR. Not following this format will result in closing the incident with a default close reason. | False |
| Mirrored XSOAR Ticket custom close resolution code | Define how to close the mirrored tickets in Cortex XSOAR with a custom resolution code. Enter a comma-separated list of custom resolution codes and their labels (acceptable format example: “10=Design,11=Development,12=Testing”) to override the default closure method. Note that a matching user-defined list of custom close reasons must be configured as a "Server configuration" in Cortex XSOAR. Not following this format will result in closing the incident with a default close reason. | False |
| Mirrored ServiceNow Ticket closure method | Define how to close the mirrored tickets in ServiceNow, choose 'resolved' to enable reopening from the UI. Otherwise, choose 'closed'. | False |
| Mirrored ServiceNow Ticket custom close state code | Define how to close the mirrored tickets in ServiceNow with custom state. Enter here the custom closure state code \(should be an integer\) to override the default closure method. If the closure code does not exist, the default one will be used instead. | False |
| Use system proxy settings | | False |
Expand Down
52 changes: 34 additions & 18 deletions Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py
Original file line number Diff line number Diff line change
Expand Up @@ -2495,20 +2495,25 @@ def get_remote_data_command(client: Client, args: dict[str, Any], params: dict)
close_incident = params.get('close_incident')
if close_incident != 'None':
server_close_custom_state = params.get('server_close_custom_state', '')
server_custom_close_code = params.get('server_custom_close_code', '')
ticket_state = ticket.get('state', '')
ticket_close_code = ticket.get('close_code', '')
# The first condition is for closing the incident if the ticket's state is in the
# `Mirrored XSOAR Ticket custom close state code` parameter, which is configured by the user in the
# integration configuration.
if (ticket_state and ticket_state in server_close_custom_state) \
or (ticket.get('closed_at') and close_incident == 'closed') \
or (ticket.get('resolved_at') and close_incident == 'resolved'):
or (ticket_close_code and ticket_close_code in server_custom_close_code) \
or (ticket.get('closed_at') and close_incident == 'closed') \
or (ticket.get('resolved_at') and close_incident == 'resolved'): # noqa: E127
demisto.debug(f'SNOW ticket changed state - should be closed in XSOAR: {ticket}')
entries.append({
'Type': EntryType.NOTE,
'Contents': {
'dbotIncidentClose': True,
'closeNotes': ticket.get("close_notes"),
'closeReason': converts_state_close_reason(ticket_state, server_close_custom_state)
'closeReason': converts_close_code_or_state_to_close_reason(ticket_state, ticket_close_code,
server_close_custom_state,
server_custom_close_code)
},
'ContentsFormat': EntryFormat.JSON
})
Expand All @@ -2517,36 +2522,47 @@ def get_remote_data_command(client: Client, args: dict[str, Any], params: dict)
return [ticket] + entries


def converts_state_close_reason(ticket_state: Optional[str], server_close_custom_state: Optional[str]):
def converts_close_code_or_state_to_close_reason(ticket_state: str, ticket_close_code: str, server_close_custom_state: str,
server_custom_close_code: str):
"""
determine the XSOAR incident close reason based on the Service Now ticket state.
if 'Mirrored XSOAR Ticket custom close state code' parameter is set, the function will try to use it to
determine the close reason (should be corresponding to a user-defined list of close reasons in the server configuration).
determine the XSOAR incident close reason based on the ServiceNow ticket close_code or state.
if 'Mirrored XSOAR Ticket custom close resolution code' parameter is set, the function will try to use it to
determine the close reason.
else if 'Mirrored XSOAR Ticket custom close state code' parameter is set, the function will try to use it to
determine the close reason.
the close reason should be corresponding to a user-defined list of close reasons in the server configuration.
then it will try using 'closed' or 'resolved' state, if set using 'Mirrored XSOAR Ticket closure method' parameter.
otherwise, it will use the default 'out of the box' server incident close reason.
Args:
ticket_state: Service now ticket state
ticket_close_code: Service now ticket close code
server_close_custom_state: server close custom state parameter
server_custom_close_code: server custom close code parameter
Returns:
The XSOAR state
"""

custom_label = ''
# if custom close code parameter is set and ticket close code is returned from the SNOW incident
if server_custom_close_code and ticket_close_code:
demisto.debug(f'trying to close XSOAR incident using custom resolution code: {server_custom_close_code}, with \
received close code: {ticket_close_code}')
# parse custom close code parameter into a dictionary of custom close codes and their names (label)
server_close_custom_code_dict = dict(item.strip().split("=") for item in server_custom_close_code.split(","))
# check if close code is in the parsed dictionary
if close_code_label := server_close_custom_code_dict.get(ticket_close_code):
demisto.debug(f'incident closed using custom close code. Close Code: {ticket_close_code}, Label: {close_code_label}')
return close_code_label
# if custom state parameter is set and ticket state is returned from incident is not empty
if server_close_custom_state and ticket_state:
demisto.debug(f'trying to close XSOAR incident using custom states: {server_close_custom_state}, with \
received state code: {ticket_state}')
# parse custom state parameter into a dictionary of custom state codes and their names (label)
server_close_custom_state_dict = dict(item.split("=") for item in server_close_custom_state.split(","))
if ticket_state in server_close_custom_state_dict and (
custom_state_label := server_close_custom_state_dict.get(ticket_state)):
# check if state code is in the parsed dictionary
custom_label = custom_state_label

if custom_label:
demisto.debug(f'incident should be closed using custom state. State Code: {ticket_state}, Label: {custom_label}')
return custom_label
elif ticket_state in ['6', '7']: # default states for closed (6) and resolved (7)
server_close_custom_state_dict = dict(item.strip().split("=") for item in server_close_custom_state.split(","))
# check if state code is in the parsed dictionary
if state_label := server_close_custom_state_dict.get(ticket_state):
demisto.debug(f'incident closed using custom state. State Code: {ticket_state}, Label: {state_label}')
return state_label
if ticket_state in ['6', '7']: # default states for closed (6) and resolved (7)
demisto.debug(f'incident should be closed using default state. State Code: {ticket_state}')
return 'Resolved'
demisto.debug(f'incident is closed using default close reason "Other". State Code: {ticket_state}')
Expand Down
9 changes: 7 additions & 2 deletions Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ configuration:
name: server_close_custom_state
type: 0
required: false
- additionalinfo: 'Define how to close the mirrored tickets in Cortex XSOAR with a custom resolution code. Enter a comma-separated list of custom resolution codes and their labels (acceptable format example: “10=Design,11=Development,12=Testing”) to override the default closure method. Note that a matching user-defined list of custom close reasons must be configured as a "Server configuration" in Cortex XSOAR. Not following this format will result in closing the incident with a default close reason.'
display: Mirrored XSOAR Ticket custom close resolution code (overwrites the custom close state)
name: server_custom_close_code
type: 0
required: false
- additionalinfo: Define how to close the mirrored tickets in ServiceNow. Choose 'resolved' to enable reopening from the UI. Otherwise, choose 'closed'.
defaultvalue: 'None'
display: Mirrored ServiceNow Ticket closure method
Expand Down Expand Up @@ -1562,7 +1567,7 @@ script:
type: Unknown
- arguments:
- auto: PREDEFINED
defaultValue: '0'
defaultValue: 'GET'
description: action to be performed on path.
isArray: true
name: method
Expand Down Expand Up @@ -1601,7 +1606,7 @@ script:
- contextPath: ServiceNow.Generic.Response
description: Generic response to servicenow api.
type: string
dockerimage: demisto/python3:3.10.13.78960
dockerimage: demisto/python3:3.10.13.80014
isfetch: true
ismappable: true
isremotesyncin: true
Expand Down
36 changes: 22 additions & 14 deletions Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
get_item_details_command, create_order_item_command, document_route_to_table, fetch_incidents, main, \
get_mapping_fields_command, get_remote_data_command, update_remote_system_command, \
ServiceNowClient, oauth_test_module, login_command, get_modified_remote_data_command, \
get_ticket_fields, check_assigned_to_field, generic_api_call_command, get_closure_case, converts_state_close_reason, \
get_timezone_offset, split_notes, DATE_FORMAT, convert_to_notes_result, DATE_FORMAT_OPTIONS
get_ticket_fields, check_assigned_to_field, generic_api_call_command, get_closure_case, get_timezone_offset, \
converts_close_code_or_state_to_close_reason, split_notes, DATE_FORMAT, convert_to_notes_result, DATE_FORMAT_OPTIONS
from ServiceNowv2 import test_module as module
from test_data.response_constants import RESPONSE_TICKET, RESPONSE_MULTIPLE_TICKET, RESPONSE_UPDATE_TICKET, \
RESPONSE_UPDATE_TICKET_SC_REQ, RESPONSE_CREATE_TICKET, RESPONSE_CREATE_TICKET_WITH_OUT_JSON, RESPONSE_QUERY_TICKETS, \
Expand Down Expand Up @@ -1883,28 +1883,36 @@ def test_get_closure_case(params, expected):
assert get_closure_case(params) == expected


@pytest.mark.parametrize('ticket_state, server_close_custom_state, expected_res',
[('1', '', 'Other'),
('7', '', 'Resolved'),
('6', '', 'Resolved'),
('10', '10=Test', 'Test'),
('10', '10=Test,11=Test2', 'Test'),
('6', '6=Test', 'Test'), # If builtin state was override by custom state.
('corrupt_state', '', 'Other'),
('corrupt_state', 'custom_state=Test', 'Other'),
('6', 'custom_state=Test', 'Resolved'),
@pytest.mark.parametrize('ticket_state, ticket_close_code, server_close_custom_state, server_close_custom_code, expected_res',
[('1', 'default close code', '', '', 'Other'),
('7', 'default close code', '', '', 'Resolved'),
('6', 'default close code', '', '', 'Resolved'),
('10', 'default close code', '10=Test', '', 'Test'),
('10', 'default close code', '10=Test,11=Test2', '', 'Test'),
# If builtin state was override by custom.
('6', 'default close code', '6=Test', '', 'Test'),
('corrupt_state', 'default close code', '', '', 'Other'),
('corrupt_state', 'default close code', 'custom_state=Test', '', 'Other'),
('6', 'default close code', 'custom_state=Test', '', 'Resolved'),
# custom close_code overwrites custom sate.
('10', 'custom close code', '10=Test,11=Test2', 'custom close code=Custom,90=90 Custom', 'Custom'),
('10', '90', '10=Test,11=Test2', '80=Custom, 90=90 Custom', '90 Custom'),
])
def test_converts_state_close_reason(ticket_state, server_close_custom_state, expected_res):
def test_converts_close_code_or_state_to_close_reason(ticket_state, ticket_close_code, server_close_custom_state,
server_close_custom_code, expected_res):
"""
Given:
- ticket_state: The state for the closed service now ticket
- ticket_close_code: The Service now ticket close code
- server_close_custom_state: The custom state for the closed service now ticket
- server_close_custom_code: The custom close code for the closed service now ticket
When:
- closing a ticket on service now
Then:
- return the matching XSOAR incident state.
"""
assert converts_state_close_reason(ticket_state, server_close_custom_state) == expected_res
assert converts_close_code_or_state_to_close_reason(ticket_state, ticket_close_code, server_close_custom_state,
server_close_custom_code) == expected_res


def ticket_fields_mocker(*args, **kwargs):
Expand Down
6 changes: 6 additions & 0 deletions Packs/ServiceNow/ReleaseNotes/2_5_45.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

#### Integrations

##### ServiceNow v2
- Updated the Docker image to: *demisto/python3:3.10.13.80014*.
- Added the *Mirrored XSOAR Ticket custom close resolution code* parameter, which allows you to define the custom ServiceNow resolution codes that correspond to the Cortex XSOAR incident custom close reasons defined in the server configuration.
2 changes: 1 addition & 1 deletion Packs/ServiceNow/pack_metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "ServiceNow",
"description": "Use The ServiceNow IT Service Management (ITSM) solution to modernize the way you manage and deliver services to your users.",
"support": "xsoar",
"currentVersion": "2.5.44",
"currentVersion": "2.5.45",
"author": "Cortex XSOAR",
"url": "https://www.paloaltonetworks.com/cortex",
"email": "",
Expand Down

0 comments on commit 7bf936e

Please sign in to comment.