GetTasksWithSections

Groups all tasks for a specific incident according to the task headers (titles).

python · Cortex REST API

Source

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) unknown
Tasks.completedBy Name of the user who completed the task string