import copy
from itertools import chain
from CommonServerPython import *
def find_start_task(tasks: Dict):
for task in tasks.values():
if task.get("type") == "start":
return task
return DemistoException("No start task was configured")
def traverse_tasks(
tasks: Dict[str, Dict], current_task: Dict, results: Dict, prev_task: Dict = None, path: List[str] = None, visited: Set = None
) -> None:
"""
A function to traverse playbook tasks and gather all the information about the tasks in `results`
Args:
tasks:
Tasks in the structure of the DEMISTO API
current_task:
The current task we traverse on
results:
The results Dictionary. The tasks will be stored on the path of the task
prev_task:
The task we we're previously on
path:
This is a list of the section headers in the way to the task
visited:
Set if the visited tasks
Returns:
"""
if visited is None:
visited = set()
if path is None:
path = []
if prev_task is None:
prev_task = {}
task_id = current_task.get("id")
task_type = current_task.get("type")
task_state = current_task.get("state")
if task_id not in visited:
visited.add(task_id)
if task_type == "start" or task_type == "title":
title = demisto.get(current_task, "task.name")
if not title:
title = "Start"
new_path = path + [title] if prev_task and prev_task.get("type") == "title" else [title]
elif task_type == "condition" or task_state == "WillNotBeExecuted":
new_path = path
else:
task = assign_task_output(current_task, path)
# This is for accessing the correct `path` in results to store the specific task
dct = results
for p in path:
dct.setdefault(p, {})
dct = dct[p]
dct.setdefault("tasks", []).append(task)
new_path = path
if current_task.get("nextTasks"):
next_tasks_ids = chain(*demisto.get(current_task, "nextTasks").values())
next_tasks: List[Dict] = [tasks.get(task_id) for task_id in next_tasks_ids] # type: ignore
for next_task in next_tasks:
traverse_tasks(tasks, next_task, results, current_task, new_path, visited)
def assign_task_output(task: Dict, path: List[str]):
task_started_date = task.get("startDate") if task.get("startDate") != "0001-01-01T00:00:00Z" else "Not Started"
task_completion_time = task.get("completedDate") if task.get("completedDate") != "0001-01-01T00:00:00Z" else "Not Started"
due_date = task.get("dueDate") if task.get("dueDate") != "0001-01-01T00:00:00Z" else "Not Started"
task_state = task.get("state") if task.get("state") else "Not Started"
return assign_params(
id=task.get("id"),
name=demisto.get(task, "task.name"),
section="/".join(path),
type=task.get("type"),
owner=task.get("assignee"),
state=task_state,
scriptId=demisto.get(task, "task.scriptId"),
startDate=task_started_date,
dueDate=due_date,
completedDate=task_completion_time,
parentPlaybookID=task.get("parentPlaybookID"),
completedBy=task.get("completedBy"),
)
def add_url_to_tasks(tasks: List[Dict[str, str]], workplan_url: str):
tasks = copy.deepcopy(tasks)
for task in tasks:
task_id = task["id"]
task_url = os.path.join(workplan_url, task_id)
task["id"] = f"[{task_id}]({task_url})"
return tasks
def get_tasks(incident_id: str):
urls = demisto.demistoUrls() # works in multi tenant env as well
res = demisto.internalHttpRequest("GET", f"/investigation/{incident_id}/workplan")
demisto.debug(f"sent GET /investigation/{incident_id}/workplan, got response={res}")
if not (tasks := json.loads(res.get("body", "{}")).get("invPlaybook", {}).get("tasks", {})):
raise DemistoException(f"Workplan for incident {incident_id}, has no tasks.")
demisto.debug(f"got {len(tasks)} tasks")
start_task = find_start_task(tasks)
tasks_nested_results: Dict = {}
traverse_tasks(tasks, start_task, tasks_nested_results)
task_results, md = get_tasks_and_readable(tasks_nested_results, urls.get("workPlan"))
return CommandResults(
outputs_prefix="Tasks",
outputs_key_field="id",
entry_type=EntryType.NOTE,
raw_response=tasks_nested_results,
outputs=task_results,
readable_output=md,
)
def get_tasks_and_readable(tasks_nested_results: Dict[str, Dict], workplan_url: Optional[str] = None):
task_results: List[Dict] = []
md_lst = []
headers = ["id", "name", "state", "completedDate"]
# Go over all nested task, and fill the md table accordingly, and fill the tasks list
# I assume the nested task is nested only in two levels
for k1, v1 in tasks_nested_results.items():
if "tasks" in v1:
tasks = v1.get("tasks")
task_results.extend(tasks) # type: ignore
tasks = add_url_to_tasks(tasks, workplan_url) if workplan_url else tasks # type: ignore
md_lst.append(tableToMarkdown(k1, tasks, headers=headers)[1:]) # this is for making the title bigger
else:
md_lst.append(f"## {k1}")
for k2, v2 in v1.items():
tasks = v2.get("tasks")
task_results.extend(tasks) # type: ignore
tasks = add_url_to_tasks(tasks, workplan_url) if workplan_url else tasks # type: ignore
md_lst.append(tableToMarkdown(k2, tasks, headers=headers))
md = "\n".join(md_lst)
return task_results, md
def main():
try:
incident_id = demisto.args().get("investigation_id")
if not incident_id:
incident_id = demisto.incident().get("id")
return_results(get_tasks(incident_id))
except Exception as e:
demisto.error(traceback.format_exc()) # print the traceback
return_error(f"Failed to execute GetTasksWithSections.\nError:\n{type(e)}, {e!s}")
if __name__ in ("__main__", "builtin", "builtins"):
main()
README
Groups all tasks for a specific incident according to the task headers (titles).
Script Data
Name
Description
Script Type
python3
Tags
Utility
Cortex XSOAR Version
6.0.0
Inputs
Argument Name
Description
investigation_id
Incident ID to create the task table.
Outputs
Path
Description
Type
Tasks
The entire task object
Unknown
Tasks.id
Task ID
string
Tasks.name
Task name
string
Tasks.section
Task section path
string
Tasks.type
Task type (regular, condition, title, playbook, start)
string
Tasks.owner
Task owner
string
Tasks.state
Task state (inprogress, Completed, WillNotBeExecuted, Error, Waiting, Blocked, and empty string for not started)
string
Tasks.scriptId
Task related script (empty if manual)
string
Tasks.startDate
Task start date
unknown
Tasks.completedDate
Task completed date
unknown
Tasks.dueDate
Task due date (SLA)
unknown
Tasks.parentPlaybookID
Task parent playbook ID (if the task is part of a sub-playbook)