ServiceNowTroubleshoot

Troubleshot ServiceNow v2 integration by displaying health information of enabled instances and old and active incidents of disabled instances.

python · ServiceNow

Source

import json
from collections import defaultdict
from typing import Any

import demistomock as demisto
from CommonServerPython import *

INTEGRATION = "ServiceNow v2"
NOTE_INCIDENTS = (
    "### Note: The active incidents, created 30 days ago and listed in the tables for both enabled and"
    " disabled instances, are still being mirrored.\n ### If the issue is no longer relevant and does not"
    " require further attention, it is recommended to close related incidents promptly to prevent"
    " potential system overload."
)


def http_request_wrapper(method: str, url: str, body: dict | None = None):
    """
    Sends an internal HTTP request using Demisto's `internalHttpRequest` and returns the parsed JSON response.

    Args:
        method (str): HTTP method (e.g., 'GET', 'POST').
        url (str): Request URL.
        body (dict | None, optional): Request payload.

    Returns:
        dict: Parsed JSON response or an empty dictionary if parsing fails.
    """
    http_result = demisto.internalHttpRequest(method, url, body=json.dumps(body))
    http_result_body_raw_response = cast(dict, http_result).get("body", "{}")
    try:
        http_result_body_response = json.loads(http_result_body_raw_response)
    except json.JSONDecodeError as e:
        raise DemistoException(f"Unable to load response {http_result_body_raw_response}: {e!s}")
    return http_result_body_response


def get_integrations_details() -> dict[str, Any]:
    """
    Retrieve details of the integrations, including their health status.

    :return: A Dictionary containing the details of the integrations and their health status.
    :rtype: Dict[str, Any]
    """
    integrations_search_response = http_request_wrapper(method="POST", url="settings/integration/search")
    instances, health = integrations_search_response.get("instances", {}), integrations_search_response.get("health", {})
    instances_health = {}
    for instance in instances:
        instance_name = instance.get("name")
        if instance.get("brand") == INTEGRATION:
            instances_health[instance_name] = instance
            if instance_name in health:
                instances_health[instance_name].update({"health": health[instance_name]})
    return instances_health


def filter_instances_data(instances_data: dict[str, Any]) -> tuple[dict, list]:
    """
    Filter the instances data to separate enabled instances and disabled instances with active incidents.

    :param instances_data: A Dictionary containing data for each instance.
    :type instances_data: Dict

    :return: A Tuple containing the filtered data for enabled instances and a list of disabled instances with active incidents.
    :rtype: Tuple[Dict, list]
    """
    filtered_data = {}
    disabled_instances = []
    for instance_name, data in instances_data.items():
        if data.get("enabled") == "true":
            filtered_data[instance_name] = data
            continue
        if (
            int(data.get("health", {}).get("incidentsPulled", 0)) > 0
            and data.get("configvalues", {}).get("mirror_direction", "") != "None"
        ):
            disabled_instances.append(instance_name)
        else:
            filtered_data[instance_name] = data
    return filtered_data, disabled_instances


def categorize_active_incidents(disabled_instances: list[str]) -> tuple[dict, dict]:
    """
    Retrieve incidents from ServiceNow instances and filter them based on whether the created instance is enabled or disabled.

    Filter the instances data to separate enabled instances and disabled instances with active incidents.

    :param disabled_instances: A list containing names of disabled instances.
    :type disabled_instances: list

    :return: A Tuple containing the active incidents for enabled instances and for disabled instances.
    :rtype: Tuple[Dict, list]
    """
    query = {"filter": {"query": f'sourceBrand: "{INTEGRATION}" and status: Active and created: >="30 days ago"'}}
    incidents_response = http_request_wrapper(method="POST", url="incidents/search", body=query)
    categorized_incidents: dict[str, Any] = {"enabled": defaultdict(list), "disabled": defaultdict(list)}

    for incident in incidents_response.get("data", {}):
        source_instance = incident.get("sourceInstance")
        incident_name = incident.get("name")

        if source_instance and incident_name:
            category = "disabled" if source_instance in disabled_instances else "enabled"
            categorized_incidents[category][source_instance].append(incident_name)

    return categorized_incidents.get("enabled", {}), categorized_incidents.get("disabled", {})


def parse_disabled_instances(disabled_incidents_instances: dict[str, Any]) -> str:
    """
    Parse the list of disabled instances to find those with active incidents
    and generate a Markdown table summarizing the results.

    :param disabled_incidents_instances: A Dictionary containing active incidents that were created 30 days ago
                                        of disabled instances.

    :return: A Markdown table summarizing the disabled instances and their active incidents,
             or an error message if the response is invalid.
    :rtype: ``str``
    """
    markdown_data = [
        {"Instance": instance, "Total": len(incidents), "Active incidents more than created 30 days ago": "\n".join(incidents)}
        for instance, incidents in disabled_incidents_instances.items()
    ]
    return tableToMarkdown(
        name="Disabled instances with active incidents created more than 30 days ago",
        t=markdown_data,
    )


def parse_enabled_instances(enabled_instances_health: dict[str, Any], enabled_incidents_instances: dict[str, Any]) -> str:
    """
    Parse the health information of enabled instances and generate a Markdown table.

    :param enabled_instances_health: A Dictionary containing health information for enabled instances.
    :type enabled_instances_health: Dict[str, Any]
    :param enabled_incidents_instances: A Dictionary containing active incidents that were created 30 days ago
                                        of enabled instances.
    :type enabled_instances_health: Dict[str, Any]

    :return: A Markdown table summarizing the health information of enabled instances.
    :rtype: str
    """
    human_readable_dict = []
    for instance_name, instance_data in enabled_instances_health.items():
        filtered_data = {
            "Instance Name": instance_name,
            "Size In Bytes": instance_data.get("sizeInBytes", ""),
            "Number of Incidents Pulled in Last Fetch": instance_data.get("health", {}).get("incidentsPulled", ""),
            "Last Pull Time": instance_data.get("health").get("lastPullTime", ""),
            "Query": instance_data.get("configvalues").get("sysparm_query", ""),
            "Last Error": instance_data.get("health").get("lastError", ""),
        }
        if instance_name in enabled_incidents_instances:
            filtered_data["Names of Active Incidents Created 30 days ago"] = enabled_incidents_instances[instance_name]
            filtered_data["Total Active Incidents Created 30 days ago"] = len(enabled_incidents_instances[instance_name])
        human_readable_dict.append(filtered_data)
    return tableToMarkdown(name="Enabled Instances Health Information", t=human_readable_dict, removeNull=True)


def main():
    try:
        instances = get_integrations_details()
        enabled_instances_health, disabled_instances = filter_instances_data(instances)
        enabled_incidents_instances, disabled_incidents_instances = categorize_active_incidents(disabled_instances)
        disabled_instances_hr = parse_disabled_instances(disabled_incidents_instances)
        enabled_instances_hr = parse_enabled_instances(enabled_instances_health, enabled_incidents_instances)
        return_results(
            CommandResults(readable_output=f"{enabled_instances_hr} \n --- \n {disabled_instances_hr}\n{NOTE_INCIDENTS}")
        )

    except Exception as e:
        return_error(f"Failed to execute ServiceNowTroubleshoot. Error: {e!s}")


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

README

Troubleshoot ServiceNow v2 integration by displaying health information.

Script Data


Name Description
Script Type python
Tags ServiceNow,ServiceNowv2
Cortex XSOAR Version 3.5.0+

Inputs


None

Outputs


Open Instances Health Information

Instance Name Last Pull Time Number of Incidents Pulled Open Incidents 30 days Total Open Incidents 30 days Query Size In Bytes
ServiceNow_v2_instance_1 2025-01-28T12:29:45.140651734Z 1 ServiceNow Incident INC001111, ServiceNow Incident INC002222 stateNOT IN6,7 3083  

Closed instances with open incidents

Instance Name Incidents Total
ServiceNow_v2_instance_disabled ServiceNow Incident INC001111 1

Example


image