Source
import re import demistomock as demisto # noqa: F401 from CommonServerPython import * # noqa: F401 def escape_backslashes_in_field_filters(search: str) -> str: """Re-escapes backslashes inside the value of `field="value"` filters in an SPL search. The drilldown enrichment data is persisted on the incident as a JSON string. When this script reads it back with ``json.loads``, backslashes inside a double-quoted field filter value are collapsed to a single backslash, which is not valid SPL. We re-escape (double) them so the query shown to the user can be copied straight into the Splunk UI. Only genuine ``field="value"`` filters are touched - the quoted value must be directly preceded by a field-name token and ``=``. Regex/string literals that follow ``(`` or ``,`` inside SPL functions (e.g. ``eval x=replace(field,"(\\)","\\\\")``) and free-text / ``rex`` quoted strings are left untouched. The operation is idempotent. Args: search (str): The SPL drilldown search. Returns: str: The SPL search with backslashes escaped inside field filter values. """ if not isinstance(search, str): return search def _escape(match: re.Match) -> str: prefix = match.group(1) # the field name, '=' and any surrounding whitespace value = match.group(2) # the value between the double quotes normalized = value.replace("\\\\", "\\") # normalize already-doubled backslashes escaped = normalized.replace("\\", "\\\\") # then double every backslash (idempotent) return f'{prefix}"{escaped}"' return re.sub(r'([\w.]+\s*=\s*)"([^"]*)"', _escape, search) QUERY_KEYS = {"search", "query_search"} def escape_drilldown_data(data): """Recursively re-escape backslashes in SPL query fields of parsed drilldown data. Applies escape_backslashes_in_field_filters to the values of the 'search' and 'query_search' keys so any displayed query is valid for the Splunk UI, regardless of nesting (list/dict). """ if isinstance(data, list): return [escape_drilldown_data(item) for item in data] if isinstance(data, dict): return { key: escape_backslashes_in_field_filters(value) if key in QUERY_KEYS and isinstance(value, str) else escape_drilldown_data(value) for key, value in data.items() } return data def format_drilldown_config(parsed_json) -> str: """Render parsed drilldown configuration as Markdown, showing each SPL query in a fenced code block. A fenced code block is used (instead of a Markdown table cell) because tableToMarkdown escapes backticks and Markdown collapses backslashes in plain cells, which would corrupt the displayed query. Inside a fenced code block the query - including its escaped backslashes - is shown verbatim, so the user can copy a Splunk-valid query. Assumes values were re-escaped by escape_drilldown_data. """ items = parsed_json if isinstance(parsed_json, list) else [parsed_json] sections = [] for item in items: if not isinstance(item, dict): sections.append(f"```\n{item}\n```") continue lines = [] name = item.get("name") or item.get("query_name") if name: lines.append(f"**Name:** {name}\n") for key in QUERY_KEYS: if isinstance(item.get(key), str): lines.append(f"**Search:**\n```\n{item[key]}\n```\n") # Show any remaining metadata fields (e.g. earliest_offset, disabled) for context. for key, value in item.items(): if key in QUERY_KEYS or key in ("name", "query_name"): continue lines.append(f"**{key}:** {value}\n") sections.append("".join(lines)) return "\n".join(sections) def format_raw_data(data: str) -> str: """Format raw JSON-like string for better readability.""" if isinstance(data, str): return data.replace("},{", "},\n{").replace("[{", "[\n{").replace("}]", "}\n]") return str(data) def display_error_with_raw_data(title: str, error_msg: str, raw_data: str) -> dict: """Display error message with formatted raw data.""" markdown = f"#### {title}\n\n" markdown += f"**Error:** {error_msg}\n\n" if raw_data: markdown += "**Raw Drilldown Searches Data:**\n```\n" markdown += format_raw_data(raw_data) + "\n```" else: markdown += "*No drilldown searches data found.*" return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} def display_json_parsing_error(title: str, error_msg: str, raw_data: str) -> dict: """Display detailed error message for JSON parsing issues from Splunk.""" markdown = f"#### {title}\n\n" markdown += "⚠️ **Note:** The drilldown_searches data received from Splunk contains invalid JSON formatting.\n\n" markdown += "The data from Splunk has JSON syntax issues (such as unescaped quotes or malformed structure).\n\n" markdown += f"**Error Details:** {error_msg}\n\n" if raw_data: markdown += "**Raw Data from Splunk:**\n```\n" markdown += format_raw_data(raw_data) + "\n```\n\n" markdown += "**Recommendation:** Check the drilldown configuration in Splunk to ensure it generates valid JSON." else: markdown += "*No drilldown searches data found.*" return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} def main(): drilldown_results = [] incident = demisto.incident() if not incident: raise ValueError("Error - demisto.incident() expected to return current incident from context but returned None") labels = incident.get("labels", []) custom_fields = incident.get("CustomFields", {}) drilldown_searches = custom_fields.get("splunkdrilldown", "") # If drilldown_searches not found in custom fields, search in labels if not drilldown_searches: for label in labels: if label.get("type") == "drilldown_searches": drilldown_searches = label.get("value", "") break # Check if enrichment is configured has_enrichment_status = False for label in labels: if label.get("type") == "successful_drilldown_enrichment": has_enrichment_status = True is_successful = label.get("value") if is_successful == "false": return display_error_with_raw_data( "Drilldown Enrichment Not Successful", ( "The drilldown enrichment did not complete successfully. " "This could be due to query parsing issues, no results found, or other errors." ), drilldown_searches, ) if label.get("type") == "Drilldown": try: # json.loads collapses doubled backslashes in field filter values; re-escape them so the # displayed query is valid for the Splunk UI (XSUP-70829). drilldown_results = escape_drilldown_data(json.loads(label.get("value", []))) except Exception as e: return display_json_parsing_error( "Drilldown Searches (Invalid JSON)", f"JSON Parsing Error: {str(e)}", label.get("value", "") ) if not drilldown_results: # No enrichment results found in labels # Case 1: No enrichment status label AND no configuration data -> enrichment is not configured. if not has_enrichment_status and not drilldown_searches: markdown = "#### Drilldown Configuration Status\n\n" markdown += ( "⚠️ **Drilldown enrichment is not configured for this integration instance.**\n\n" "Enrichment is not enabled, so drilldown results are not available.\n\n" "**To enable drilldown enrichment:**\n" "1. Go to the integration instance settings\n" "2. In the 'Enrichment Types' parameter, select 'Drilldown'\n" "3. Save the configuration and fetch new incidents\n\n" ) return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} # Case 1b: A drilldown configuration exists but produced no results - it is configured, but the # search likely failed or returned nothing on the Splunk side (e.g. an invalid query). if not has_enrichment_status: markdown = "#### Drilldown Configuration Status\n\n" markdown += ( "⚠️ **The drilldown is configured, but no results were returned.**\n\n" "The drilldown search was not run successfully on the Splunk side, or it returned no " "results. Review the search query below for errors (for example, unbalanced quotes or " "invalid syntax) and validate it directly in the Splunk UI.\n\n" ) # Show the configuration so the user can review/fix the query. try: parsed_json = escape_drilldown_data(json.loads(drilldown_searches)) markdown += "**Raw Drilldown Searches Configuration (from Splunk):**\n\n" markdown += format_drilldown_config(parsed_json) except Exception as e: # Failed to parse raw configuration markdown += "**Raw Drilldown Searches Data (Failed to Parse):**\n\n" markdown += f"⚠️ Failed to parse the raw drilldown configuration. Error: {str(e)}\n\n" markdown += "**Raw Data:**\n```\n" markdown += format_raw_data(drilldown_searches) + "\n```" return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} # Case 2: Enrichment IS configured but no results found else: markdown = "#### Drilldown Enrichment Results\n\n" markdown += ( "⚠️ **Drilldown enrichment results not found.**\n\n" "Enrichment is configured, but the results could not be retrieved from the incident context.\n\n" ) # Try to show raw configuration if available if drilldown_searches: try: parsed_json = escape_drilldown_data(json.loads(drilldown_searches)) markdown += "**Drilldown Searches Configuration:**\n\n" markdown += format_drilldown_config(parsed_json) except Exception as e: # Failed to parse raw configuration markdown += "**Raw Drilldown Searches Data (Failed to Parse):**\n\n" markdown += f"⚠️ Failed to parse the drilldown configuration. Error: {str(e)}\n\n" markdown += "**Raw Data:**\n```\n" markdown += format_raw_data(drilldown_searches) + "\n```" else: markdown += "*No drilldown searches configuration data found.*" return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} if isinstance(drilldown_results, list): if "query_name" in drilldown_results[0]: # Get drilldown results of multiple drilldown searches markdown = "#### Drilldown Searches Results\n" for drilldown in drilldown_results: # Render the query inside a fenced code block so markdown does not collapse the # escaped backslashes - the user can copy a Splunk-valid query directly (XSUP-70829). markdown += ( f"**Query Name:** {drilldown.get('query_name','')}\n\n" f"**Query Search:**\n```\n{drilldown.get('query_search','')}\n```\n\n" f"**Results:**\n" ) if drilldown.get("enrichment_status") == "Enrichment failed": markdown += "\nDrilldown enrichment failed." elif results := drilldown.get("query_results", []): markdown += tableToMarkdown("", results, headers=results[0].keys()) else: markdown += "\nNo results found for drilldown search." markdown += "\n\n" else: # Drilldown results of a single drilldown search markdown = tableToMarkdown("", drilldown_results, headers=drilldown_results[0].keys()) else: markdown = tableToMarkdown("", drilldown_results) return {"ContentsFormat": formats["markdown"], "Type": entryTypes["note"], "Contents": markdown} if __name__ in ("__main__", "__builtin__", "builtins"): try: return_results(main()) except Exception as e: return_error(f"Got an error while parsing Splunk events: {e}")
README
Automation to display drilldown search results from Splunk.
Script Data
| Name | Description |
|---|---|
| Script Type | python3 |
| Tags | dynamic-section |
| Cortex XSOAR Version | 6.0.0 |
Inputs
There are no inputs for this script.
Outputs
There are no outputs for this script.