From 7bf936e824e6296ccf8825ac5edd9a3dbacc48eb Mon Sep 17 00:00:00 2001 From: Moshe Eichler <78307768+MosheEichler@users.noreply.github.com> Date: Thu, 9 Nov 2023 10:15:53 +0200 Subject: [PATCH] SNOW add custom resolution code parameter (#30599) * 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 <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * Update Packs/ServiceNow/Integrations/ServiceNowv2/README.md Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> * overwrites * custom --------- Co-authored-by: ShirleyDenkberg <62508050+ShirleyDenkberg@users.noreply.github.com> --- .../Integrations/ServiceNowv2/README.md | 5 +- .../Integrations/ServiceNowv2/ServiceNowv2.py | 52 ++++++++++++------- .../ServiceNowv2/ServiceNowv2.yml | 9 +++- .../ServiceNowv2/ServiceNowv2_test.py | 36 ++++++++----- Packs/ServiceNow/ReleaseNotes/2_5_45.md | 6 +++ Packs/ServiceNow/pack_metadata.json | 2 +- 6 files changed, 73 insertions(+), 37 deletions(-) create mode 100644 Packs/ServiceNow/ReleaseNotes/2_5_45.md diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/README.md b/Packs/ServiceNow/Integrations/ServiceNowv2/README.md index 7e0eda9b6ccd..216582f6b1b2 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/README.md +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/README.md @@ -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). @@ -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 | diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py index b6f5934fa653..a661801d00f6 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.py @@ -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 }) @@ -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}') diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml index 7512d4fddef3..7f2f1d6726a9 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2.yml @@ -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 @@ -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 @@ -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 diff --git a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py index ef47120ce5ca..9f7d280f23e4 100644 --- a/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py +++ b/Packs/ServiceNow/Integrations/ServiceNowv2/ServiceNowv2_test.py @@ -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, \ @@ -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): diff --git a/Packs/ServiceNow/ReleaseNotes/2_5_45.md b/Packs/ServiceNow/ReleaseNotes/2_5_45.md new file mode 100644 index 000000000000..e7954587ec74 --- /dev/null +++ b/Packs/ServiceNow/ReleaseNotes/2_5_45.md @@ -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. diff --git a/Packs/ServiceNow/pack_metadata.json b/Packs/ServiceNow/pack_metadata.json index fa6d04b75828..4810d665a1ec 100644 --- a/Packs/ServiceNow/pack_metadata.json +++ b/Packs/ServiceNow/pack_metadata.json @@ -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": "",