From a7ba2868c17808a973c0e326a7d8fcd6fc6bf283 Mon Sep 17 00:00:00 2001 From: Menachem Weinfeld <90556466+mmhw@users.noreply.github.com> Date: Mon, 2 Dec 2024 15:55:19 +0200 Subject: [PATCH] [GetFailedTasks] New argument - get_scripts_name (#37504) * [GetFailedTasks] New argument - get_scripts_name * Add unit tests --- .../ReleaseNotes/1_3_23.md | 6 ++ .../Scripts/GetFailedTasks/GetFailedTasks.py | 58 ++++++++++++++-- .../Scripts/GetFailedTasks/GetFailedTasks.yml | 6 ++ .../GetFailedTasks/GetFailedTasks_test.py | 69 ++++++++++++++++++- .../pack_metadata.json | 2 +- 5 files changed, 134 insertions(+), 7 deletions(-) create mode 100644 Packs/IntegrationsAndIncidentsHealthCheck/ReleaseNotes/1_3_23.md diff --git a/Packs/IntegrationsAndIncidentsHealthCheck/ReleaseNotes/1_3_23.md b/Packs/IntegrationsAndIncidentsHealthCheck/ReleaseNotes/1_3_23.md new file mode 100644 index 000000000000..2d785875af99 --- /dev/null +++ b/Packs/IntegrationsAndIncidentsHealthCheck/ReleaseNotes/1_3_23.md @@ -0,0 +1,6 @@ + +#### Scripts + +##### GetFailedTasks + +Added the *get_scripts_name* argument that enables the script to retrieve and display custom script names instead of script IDs. diff --git a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.py b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.py index da397957350b..fffdd5779ebd 100644 --- a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.py +++ b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.py @@ -40,7 +40,7 @@ def get_tenant_name(): return tenant_name -def get_failed_tasks_output(tasks: list, incident: dict): +def get_failed_tasks_output(tasks: list, incident: dict, custom_scripts_map_id_and_name: dict[str, str] = {}): """ Converts the failing task objects of an incident to context outputs. @@ -59,6 +59,7 @@ def get_failed_tasks_output(tasks: list, incident: dict): for task in tasks: error_entries = task.get("entries", []) + command_id = task.get("task", {}).get("scriptId", '').replace('|||', '') entry = { "Incident ID": incident.get("id"), "Playbook Name": task.get("ancestors", [''])[0], @@ -67,7 +68,7 @@ def get_failed_tasks_output(tasks: list, incident: dict): "Number of Errors": len(error_entries), "Task ID": task.get("id"), "Incident Created Date": incident.get("created", ''), - "Command Name": task.get("task", {}).get("scriptId", '').replace('|||', ''), + "Command Name": custom_scripts_map_id_and_name.get(command_id, command_id), "Incident Owner": incident["owner"] } if task.get("task", {}).get("description"): @@ -141,7 +142,49 @@ def get_incident_tasks_using_internal_request(incident: dict): return tasks -def get_incident_data(incident: dict, rest_api_instance: str = None): +def get_custom_scripts_map_id_and_name(rest_api_instance: str | None = None) -> dict[str, str]: + uri = "automation/search" + body = {"query": "system:F"} + + scripts = [] + if rest_api_instance: + demisto.debug(f"Retrieving custom scripts map using REST API instance: {rest_api_instance}") + response = demisto.executeCommand( + "core-api-post", + { + "uri": uri, + "body": body, + "using": rest_api_instance, + } + ) + + if is_error(response): + demisto.error(f"Failed retrieving custom scripts map.\n{get_error(response)}") + else: + scripts = response[0]["Contents"]["response"].get("scripts", []) + + else: + demisto.debug("Retrieving custom scripts map using internal HTTP request") + response = demisto.internalHttpRequest( + method="POST", + uri=uri, + body=body + ) + + if response and response.get('statusCode') == 200: + scripts = json.loads(response.get('body', '{}')).get("scripts", []) + else: + demisto.error(f'Failed running POST query to {uri}.\n{str(response)}') + + custom_scripts_map_id_and_name = { + script["id"]: script["name"] + for script in scripts + } + demisto.debug(f"Retrieve the following map: {custom_scripts_map_id_and_name}") + return custom_scripts_map_id_and_name + + +def get_incident_data(incident: dict, rest_api_instance: str = None, get_scripts_name: bool = False): """ Returns the failing task objects of an incident. The request is done using a Core REST API instance if given, @@ -168,7 +211,11 @@ def get_incident_data(incident: dict, rest_api_instance: str = None): 'Please specify the rest_api_instance argument.') tasks = get_incident_tasks_using_rest_api_instance(incident, rest_api_instance) - task_outputs, tasks_error_entries_number = get_failed_tasks_output(tasks, incident) + custom_scripts_map_id_and_name = {} + if get_scripts_name: + custom_scripts_map_id_and_name = get_custom_scripts_map_id_and_name(rest_api_instance) + + task_outputs, tasks_error_entries_number = get_failed_tasks_output(tasks, incident, custom_scripts_map_id_and_name) if task_outputs: return task_outputs, tasks_error_entries_number else: @@ -181,6 +228,7 @@ def main(): max_incidents = arg_to_number(args.get("max_incidents")) or 300 max_incidents = min(max_incidents, 1000) rest_api_instance = args.get("rest_api_instance") + get_scripts_name = argToBoolean(args.get("get_scripts_name", False)) number_of_failed_incidents = 0 number_of_error_entries = 0 @@ -199,7 +247,7 @@ def main(): f'Elapsed time: {time.time() - start_time}') for incident in total_incidents: - task_outputs, incident_error_entries_num = get_incident_data(incident, rest_api_instance) + task_outputs, incident_error_entries_num = get_incident_data(incident, rest_api_instance, get_scripts_name) if task_outputs: incidents_output.extend(task_outputs) diff --git a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.yml b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.yml index 1123384e279f..fe3551c237ae 100644 --- a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.yml +++ b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks.yml @@ -7,6 +7,12 @@ args: name: max_incidents - description: Rest API instance to use. name: rest_api_instance +- auto: PREDEFINED + description: Whether to replace the scripts ids to their name in case of custom scripts. + name: get_scripts_name + predefined: + - 'true' + - 'false' comment: |- Gets failed tasks details for incidents based on a query. Limited to 1000 incidents. diff --git a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks_test.py b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks_test.py index 600fbff501af..02014e161718 100644 --- a/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks_test.py +++ b/Packs/IntegrationsAndIncidentsHealthCheck/Scripts/GetFailedTasks/GetFailedTasks_test.py @@ -1,7 +1,9 @@ +from pytest_mock import MockerFixture import demistomock as demisto import pytest import json -from GetFailedTasks import main, get_failed_tasks_output, get_incident_tasks_using_internal_request, get_incident_data +from GetFailedTasks import main, get_failed_tasks_output, get_incident_tasks_using_internal_request, get_incident_data, \ + get_custom_scripts_map_id_and_name from test_data.constants import INCIDENTS_RESULT, RESTAPI_TAS_RESULT, INTERNAL_TASKS_RESULT @@ -154,3 +156,68 @@ def test_get_incident_data_internal_http_request_fail(mocker): assert internal_request_mock_res.call_count == 1 assert api_instanc_mock_res.call_count == 1 assert result[0] == [] + + +def test_get_custom_scripts_map_id_and_name_with_rest_api(mocker: MockerFixture): + """ + Given: + A REST API instance is provided. + When: + The get_custom_scripts_map_id_and_name function is called. + Then: + It should use the core-api-post command and return the correct script map. + """ + mock_execute_command = mocker.patch.object(demisto, 'executeCommand') + mock_execute_command.return_value = [{ + 'Contents': { + 'response': { + 'scripts': [ + {'id': 'script1', 'name': 'Script One'}, + {'id': 'script2', 'name': 'Script Two'} + ] + } + }, + "Type": 1 + }] + + result = get_custom_scripts_map_id_and_name('rest_api_instance') + + assert result == {'script1': 'Script One', 'script2': 'Script Two'} + mock_execute_command.assert_called_once_with( + 'core-api-post', + { + 'uri': 'automation/search', + 'body': {'query': 'system:F'}, + 'using': 'rest_api_instance' + } + ) + + +def test_get_custom_scripts_map_id_and_name_without_rest_api(mocker): + """ + Given: + No REST API instance is provided. + When: + The get_custom_scripts_map_id_and_name function is called. + Then: + It should use the internalHttpRequest and return the correct script map. + """ + mock_internal_request = mocker.patch('GetFailedTasks.demisto.internalHttpRequest') + mock_internal_request.return_value = { + 'statusCode': 200, + 'body': json.dumps({ + 'scripts': [ + {'id': 'script3', 'name': 'Script Three'}, + {'id': 'script4', 'name': 'Script Four'} + ] + }) + } + + result = get_custom_scripts_map_id_and_name() + + assert result == {'script3': 'Script Three', 'script4': 'Script Four'} + mock_internal_request.assert_called_once_with( + method='POST', + uri='automation/search', + body={'query': 'system:F'} + ) diff --git a/Packs/IntegrationsAndIncidentsHealthCheck/pack_metadata.json b/Packs/IntegrationsAndIncidentsHealthCheck/pack_metadata.json index bfcc44caf507..33d95deff408 100644 --- a/Packs/IntegrationsAndIncidentsHealthCheck/pack_metadata.json +++ b/Packs/IntegrationsAndIncidentsHealthCheck/pack_metadata.json @@ -2,7 +2,7 @@ "name": "Integrations & Incidents Health Check", "description": "Do you know which of your integrations or open incidents failed? With this content, you can view your failed integrations and open incidents", "support": "xsoar", - "currentVersion": "1.3.22", + "currentVersion": "1.3.23", "author": "Cortex XSOAR", "url": "https://www.paloaltonetworks.com/cortex", "email": "",