Trello

Interact with the Trello task manager.

Case Management · Trello

Details

IDTrello
ProviderAtlassian
CategoryCase Management
From Version6.0.0
Docker Imagedemisto/python3:3.12.8.3296088
Supported ModulesAgentix XSIAM

README

Interact with the Trello task manager
This integration was integrated and tested with version 1.0.0 of Trello

Configure Trello in Cortex

First, retrieve an API key from the trello API page, by following this link
For more information, see the following Trello documentation

Parameter Description Required
Server URL (e.g. https://example.net)   True
Fetch incidents   False
Use system proxy settings   False
Default and Fetch Board ID ID of Trello board used both to fetch incidents and as the default for all integration commands. False
API Key   False
List to Fetch Incidents from Optional - If specified, incidents will only be fetched when cards are created in this specific list. Use trello-list-lists command to display IDs of available lists in Board. False
Incident type   False

Commands

You can execute these commands from the CLI, as part of an automation, or in a playbook.
After you successfully execute a command, a DBot message appears in the War Room with the command details.

trello-list-boards


List the boards available to the provided API Key

Base Command

trello-list-boards

Input

There are no input arguments for this command.

Context Output

Path Type Description
Trello.Boards.Id Unknown The board ID
Trello.Boards.Name Unknown Board name
Trello.Boards.Closed Unknown Bool - True if board closed.
Trello.Boards.DateLastActivity Unknown The last time this board was updated.

Command Example


#### Context Example

```json
{
    "Trello": {
        "Boards": [
            {
                "Closed": false,
                "DateLastActivity": "2021-03-24T04:38:20.193Z",
                "Id": "5f84cf4db437823603f98ad0",
                "Name": "Current Tasks",
                "Url": "https://trello.com/b/lGTEPWR6/current-tasks"
            },
            {
                "Closed": false,
                "DateLastActivity": "2021-03-29T05:18:09.118Z",
                "Id": "602f2f91cc8a4e23e393556a",
                "Name": "Devel",
                "Url": "https://trello.com/b/RqO8rfpZ/devel"
            },
            {
                "Closed": false,
                "DateLastActivity": "2020-12-06T23:15:15.945Z",
                "Id": "5ea81000966eeb7e86362b35",
                "Name": "MOXSOAR",
                "Url": "https://trello.com/b/L6eBwR31/moxsoar"
            },
            {
                "Closed": false,
                "DateLastActivity": "2021-03-29T00:28:52.283Z",
                "Id": "5f0fc9c1e1ca6e3eef34913d",
                "Name": "XSOAR",
                "Url": "https://trello.com/b/WGwDIdS4/xsoar"
            }
        ]
    }
}

Human Readable Output

Trello Boards

id name dateLastActivity
5f84cf4db437823603f98ad0 Current Tasks 2021-03-24T04:38:20.193Z
602f2f91cc8a4e23e393556a Devel 2021-03-29T05:18:09.118Z
5ea81000966eeb7e86362b35 MOXSOAR 2020-12-06T23:15:15.945Z
5f0fc9c1e1ca6e3eef34913d XSOAR 2021-03-29T00:28:52.283Z

trello-list-lists


List all the lists associated with the trello board.

Base Command

trello-list-lists

Input

Argument Name Description Required
board_id Optional - the ID of the board to query, if not provided will use configured . Optional

Context Output

Path Type Description
Trello.Lists.Id Unknown List ID
Trello.Lists.Name Unknown List Name
Trello.Lists.Closed Unknown Bool - True if list is closed
Trello.Lists.IdBoard Unknown ID Of the board this list belongs to

Command Example


#### Context Example

```json
{
    "Trello": {
        "Lists": [
            {
                "Closed": false,
                "Id": "606120363e1eb362adcfdb47",
                "IdBoard": "602f2f91cc8a4e23e393556a",
                "Name": "New Tasks"
            },
            {
                "Closed": false,
                "Id": "605d2a706398646de27a1f8f",
                "IdBoard": "602f2f91cc8a4e23e393556a",
                "Name": "Created by XSOAR"
            },
            {
                "Closed": false,
                "Id": "603c6dcfe6f2ef24e161d31f",
                "IdBoard": "602f2f91cc8a4e23e393556a",
                "Name": "Xsoar to Investigate"
            },
            {
                "Closed": false,
                "Id": "605ac66f9f37c20f9f3205d9",
                "IdBoard": "602f2f91cc8a4e23e393556a",
                "Name": "Completed List"
            }
        ]
    }
}

Human Readable Output

Trello Lists

closed id idBoard name pos softLimit subscribed
false 606120363e1eb362adcfdb47 602f2f91cc8a4e23e393556a New Tasks 65535.5   false
false 605d2a706398646de27a1f8f 602f2f91cc8a4e23e393556a Created by XSOAR 98303.25   false
false 603c6dcfe6f2ef24e161d31f 602f2f91cc8a4e23e393556a Xsoar to Investigate 131071   false
false 605ac66f9f37c20f9f3205d9 602f2f91cc8a4e23e393556a Completed List 196607   false

trello-create-card


Create a new card.

Base Command

trello-create-card

Input

Argument Name Description Required
name Card name. Required
desc Card Description - Markdown Compatible. Optional
list_id ID of list to create card within. Required
idLabels CSV list of Labels (by ID) to add to the card. Use trello-list-labels command to display. Optional

Context Output

Path Type Description
Trello.CreatedCard.Id Unknown ID of created card
Trello.CreatedCard.Name Unknown Name of created card
Trello.CreatedCard.Url Unknown URL Of created card
Trello.CreatedCard.IdList Unknown ID of list this card belongs to

Command Example


#### Human Readable Output

### trello-update-card

***
Update an existing card

#### Base Command

`trello-update-card`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| card_id | ID of card to be updated. | Required |
| closed | If true, archives the card. Possible values are: true, false. | Optional |
| idLabels | CSV Of Trello Label IDs to add to card. | Optional |
| idList | ID Of list to move card to. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Trello.UpdatedCard.Id | Unknown | ID of Updated Card |
| Trello.UpdatedCard.Name | Unknown | Name of updated card |

#### Command Example

Human Readable Output

trello-delete-card


Delete a card

Base Command

trello-delete-card

Input

Argument Name Description Required
card_id ID of card to delete. Optional

Context Output

There is no context output for this command.

Command Example


#### Human Readable Output

### trello-list-actions

***
List all actions on a board, such as card updates, additions, and deletes.

#### Base Command

`trello-list-actions`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| board_id | Optional - the ID of  the board to query, if not provided will use configured . | Optional |
| filter | CSV of action types used to filter response. Possible values are: , acceptEnterpriseJoinRequest<br/>, addAttachmentToCard<br/>, addChecklistToCard<br/>, addLabelToCard<br/>, addMemberToBoard<br/>, addMemberToCard<br/>, addMemberToOrganization<br/>, addOrganizationToEnterprise<br/>, addToEnterprisePluginWhitelist<br/>, addToOrganizationBoard<br/>, commentCard<br/>, convertToCardFromCheckItem<br/>, copyBoard<br/>, copyCard<br/>, copyChecklist<br/>, createLabel<br/>, copyCommentCard<br/>, createBoard<br/>, createBoardInvitation<br/>, createBoardPreference<br/>, createCard<br/>, createList<br/>, createOrganization<br/>, createOrganizationInvitation<br/>, deleteAttachmentFromCard<br/>, deleteBoardInvitation<br/>, deleteCard<br/>, deleteCheckItem<br/>, deleteLabel<br/>, deleteOrganizationInvitation<br/>, disableEnterprisePluginWhitelist<br/>, disablePlugin<br/>, disablePowerUp<br/>, emailCard<br/>, enableEnterprisePluginWhitelist<br/>, enablePlugin<br/>, enablePowerUp<br/>, makeAdminOfBoard<br/>, makeAdminOfOrganization<br/>, makeNormalMemberOfBoard<br/>, makeNormalMemberOfOrganization<br/>, makeObserverOfBoard<br/>, memberJoinedTrello<br/>, moveCardFromBoard<br/>, moveCardToBoard<br/>, moveListFromBoard<br/>, moveListToBoard<br/>, removeAdminFromBoard (Deprecated in favor of makeNormalMemberOfBoard) removeAdminFromOrganization (Deprecated in favor of - makeNormalMemberOfOrganization)<br/>, removeChecklistFromCard<br/>, removeFromEnterprisePluginWhitelist<br/>, removeFromOrganizationBoard<br/>, removeLabelFromCard<br/>, removeMemberFromBoard<br/>, removeMemberFromCard<br/>, removeMemberFromOrganization<br/>, removeOrganizationFromEnterprise<br/>, unconfirmedBoardInvitation<br/>, unconfirmedOrganizationInvitation<br/>, updateBoard<br/>, updateCard<br/>, updateCheckItem<br/>, updateCheckItemStateOnCard<br/>, updateChecklist<br/>, updateLabel<br/>, updateList<br/>, updateMember<br/>, updateOrganization, voteOnCard. | Optional |
| since | First action time. | Optional |
| before | Last action time. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Trello.Actions.Id | Unknown | Action ID |
| Trello.Actions.Type | Unknown | Type of action |
| Trello.Actions.Date | Date | Date of action |
| Trello.Actions.ListId | Unknown | List ID - Null if not card or list action |
| Trello.Actions.CardId | Unknown | Card ID - Null if not card action |
| Trello.Actions.BoardId | Unknown | Board ID |

#### Command Example

```!trello-list-actions filter="createCard" since="2021-03-29T00:53:21.972Z"```

#### Context Example

```json
{
    "Trello": {
        "Actions": [
            {
                "BoardId": "602f2f91cc8a4e23e393556a",
                "CardId": "606163113a492b52f69c40de",
                "Date": "2021-03-29T05:18:09.138Z",
                "Id": "606163113a492b52f69c40df",
                "ListId": "605d2a706398646de27a1f8f",
                "Type": "createCard"
            },
            {
                "BoardId": "602f2f91cc8a4e23e393556a",
                "CardId": "6061628bedda298c34aa588d",
                "Date": "2021-03-29T05:15:55.706Z",
                "Id": "6061628bedda298c34aa588e",
                "ListId": "605d2a706398646de27a1f8f",
                "Type": "createCard"
            }
        ]
    }
}
```

#### Human Readable Output

>### Trello Actions
>
>|id|type|date|
>|---|---|---|
>| 606163113a492b52f69c40df | createCard | 2021-03-29T05:18:09.138Z |
>| 6061628bedda298c34aa588e | createCard | 2021-03-29T05:15:55.706Z |

### trello-list-labels

***
List all the labels in a given board

#### Base Command

`trello-list-labels`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| board_id | Optional - Board ID. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Trello.Labels.Id | Unknown | Label ID |
| Trello.Labels.Name | Unknown | Label Name |
| Trello.Labels.Color | Unknown | Label Color |

#### Command Example

```!trello-list-labels```

#### Context Example

```json
{
    "Trello": {
        "Labels": [
            {
                "Color": "green",
                "Id": "60596b0b080907067c65c0ee",
                "Name": "Low"
            },
            {
                "Color": "green",
                "Id": "603c6bd95984232d2a060953",
                "Name": "test-xsoar-label"
            },
            {
                "Color": "pink",
                "Id": "6059585c1b00040da553cd77",
                "Name": "LabelFromXsoar"
            },
            {
                "Color": "orange",
                "Id": "605973a683fc7129761f000b",
                "Name": "High"
            },
            {
                "Color": "red",
                "Id": "602f2f9186c6bc9cc5d3d1d1",
                "Name": ""
            },
            {
                "Color": "yellow",
                "Id": "60596d0a7dc72638aea5ef8d",
                "Name": "Medium"
            },
            {
                "Color": "orange",
                "Id": "602f2f9186c6bc9cc5d3d1d0",
                "Name": ""
            },
            {
                "Color": "green",
                "Id": "602f2f9186c6bc9cc5d3d1cb",
                "Name": ""
            },
            {
                "Color": "purple",
                "Id": "602f2f9186c6bc9cc5d3d1d4",
                "Name": ""
            },
            {
                "Color": "blue",
                "Id": "602f2f9186c6bc9cc5d3d1d6",
                "Name": ""
            },
            {
                "Color": "yellow",
                "Id": "602f2f9186c6bc9cc5d3d1ce",
                "Name": ""
            },
            {
                "Color": "green",
                "Id": "6059493cd83e275095800912",
                "Name": "Testlabel"
            },
            {
                "Color": null,
                "Id": "606162c31caee583b35a0772",
                "Name": "Test label"
            }
        ]
    }
}
```

#### Human Readable Output

>### Trello Labels
>
>|color|id|idBoard|name|
>|---|---|---|---|
>| green | 60596b0b080907067c65c0ee | 602f2f91cc8a4e23e393556a | Low |
>| green | 603c6bd95984232d2a060953 | 602f2f91cc8a4e23e393556a | test-xsoar-label |
>| pink | 6059585c1b00040da553cd77 | 602f2f91cc8a4e23e393556a | LabelFromXsoar |
>| orange | 605973a683fc7129761f000b | 602f2f91cc8a4e23e393556a | High |
>| red | 602f2f9186c6bc9cc5d3d1d1 | 602f2f91cc8a4e23e393556a |  |
>| yellow | 60596d0a7dc72638aea5ef8d | 602f2f91cc8a4e23e393556a | Medium |
>| orange | 602f2f9186c6bc9cc5d3d1d0 | 602f2f91cc8a4e23e393556a |  |
>| green | 602f2f9186c6bc9cc5d3d1cb | 602f2f91cc8a4e23e393556a |  |
>| purple | 602f2f9186c6bc9cc5d3d1d4 | 602f2f91cc8a4e23e393556a |  |
>| blue | 602f2f9186c6bc9cc5d3d1d6 | 602f2f91cc8a4e23e393556a |  |
>| yellow | 602f2f9186c6bc9cc5d3d1ce | 602f2f91cc8a4e23e393556a |  |
>| green | 6059493cd83e275095800912 | 602f2f91cc8a4e23e393556a | Testlabel |
>|  | 606162c31caee583b35a0772 | 602f2f91cc8a4e23e393556a | Test label |

### trello-create-label

***
Create a new trello label

#### Base Command

`trello-create-label`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| board_id | Optional - Board ID. | Optional |
| name | Name of Label. | Required |
| color | Label Color. Possible values are: yellow, purple, blue, red, green, orange, black, sky, pink, lime. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Trello.CreatedLabel.Id | Unknown | ID Of created label |
| Trello.CreatedLabel.Name | Unknown | Name of created label |

#### Command Example

Human Readable Output

trello-add-comment


Add a comment to a Trello card

Base Command

trello-add-comment

Input

Argument Name Description Required
text Comment to add. Required
card_id Card to add comment to. Optional

Context Output

Path Type Description
Trello.AddedComment.Id Unknown ID Of comment action
Trello.AddedComment.Date Unknown Date of comment

Command Example


#### Human Readable Output

### trello-list-cards

***
List all the trello cards

#### Base Command

`trello-list-cards`

#### Input

| **Argument Name** | **Description** | **Required** |
| --- | --- | --- |
| list_id | ID of list containing cards. | Optional |

#### Context Output

| **Path** | **Type** | **Description** |
| --- | --- | --- |
| Trello.Cards.Id | Unknown | Card ID |
| Trello.Cards.Name | Unknown | Card Name |
| Trello.Cards.IdList | Unknown | ID of parent list |
| Trello.Cards.Due | Date | Due date of card |
| Trello.Cards.Labels | Unknown | List of labels associated with card |
| Trello.Cards.Desc | Unknown | Card description |
| Trello.Cards.Start | Date | Card Start date |
| Trello.Cards.Labels.id | Unknown | Label ID |

#### Command Example

```!trello-list-cards list_id=605d2a706398646de27a1f8f```

#### Context Example

```json
{
    "Trello": {
        "Cards": [
            {
                "Desc": "",
                "Due": null,
                "Id": "6061628bedda298c34aa588d",
                "IdList": "605d2a706398646de27a1f8f",
                "Labels": [],
                "Name": "Temp card",
                "Start": null,
                "Url": "https://trello.com/c/oq5kliob/99-temp-card"
            },
            {
                "Desc": "",
                "Due": null,
                "Id": "606163113a492b52f69c40de",
                "IdList": "605d2a706398646de27a1f8f",
                "Labels": [],
                "Name": "This is a test card from XSOAR!",
                "Start": null,
                "Url": "https://trello.com/c/63F5dXGw/100-this-is-a-test-card-from-xsoar"
            }
        ]
    }
}

Human Readable Output

Trello Cards

id name url
6061628bedda298c34aa588d Temp card https://trello.com/c/oq5kliob/99-temp-card
606163113a492b52f69c40de This is a test card from XSOAR! https://trello.com/c/63F5dXGw/100-this-is-a-test-card-from-xsoar

Configuration parameters

  • url — Server URL (e.g. https://api.trello.com) (required)
  • isFetch — Fetch incidents
  • proxy — Use system proxy settings
  • board_id — Default and Fetch Board ID
  • credentials — API Key (required)
  • list_id — List to Fetch Incidents from
  • incidentType — Incident type
  • incidentFetchInterval — Incidents Fetch Interval

Commands (10)

  • trello-add-comment

    Add a comment to a Trello card

  • trello-create-card

    Create a new card.

  • trello-create-label

    Create a new trello label

  • trello-delete-card

    Delete a card

  • trello-list-actions

    List all actions on a board, such as card updates, additions, and deletes.

  • trello-list-boards

    List the boards available to the provided API Key

  • trello-list-cards

    List all the trello cards

  • trello-list-labels

    List all the labels in a given board

  • trello-list-lists

    List all the lists associated with the trello board.

  • trello-update-card

    Update an existing card

import demistomock as demisto  # noqa: F401
import urllib3
from CommonServerPython import *  # noqa: F401

from CommonServerUserPython import *

""" IMPORTS """

import json

import dateparser

# Disable insecure warnings
urllib3.disable_warnings()

""" CONSTANTS """
DATE_FORMAT = "%Y-%m-%dT%H:%M:%SZ"

LIST_BOARDS_SUFFIX = "/members/me/boards"
LISTS_SUFFIX = "/lists"
CARDS_SUFFIX = "/cards"
BOARDS_SUFFIX = "/boards"
ACTIONS = "/actions"
LABELS_SUFFIX = "/labels"
COMMENTS = "/actions/comments"

STANDARD_OUTPUTS = ["id", "name"]


class Client(BaseClient):
    """
    Client will implement the service API, and should not contain any Demisto logic.
    Should only do requests and return data.
    """

    def __init__(self, key, token, **kwargs):
        super().__init__(**kwargs)

        self.params = {"key": key, "token": token}

    def list_actions(self, board_id, actions_filter, since=None, before=None):
        """
        Returns all actions associated with a board.
        args

        board_id : str
            ID Of board to list actions of
        filter :
            CSV of action types to filter; https://developer.atlassian.com/cloud/trello/guides/rest-api/action-types/
        """
        params = self.params.copy()
        if actions_filter:
            params["filter"] = actions_filter

        if since:
            params["since"] = since

        if before:
            params["before"] = before

        data = self._http_request(method="GET", url_suffix=f"{BOARDS_SUFFIX}/{board_id}{ACTIONS}", params=params)
        return data

    def list_lists(self, board_id):
        """
        Return the lists associated with a board

        """
        data = self._http_request(method="GET", url_suffix=f"{BOARDS_SUFFIX}/{board_id}{LISTS_SUFFIX}", params=self.params)
        return data

    def list_boards(self):
        """
        List all the boards visble to this api key/user
        """
        data = self._http_request(method="GET", url_suffix=f"{LIST_BOARDS_SUFFIX}", params=self.params)
        return data

    def create_card(self, demisto_args):
        """
        Create a new card
        args

        board_id : str
            ID Of board to create the card in
        list_id
            ID Of list to create the card in
        demisto_args

        name : str
            Name of the card
        desc : str
            Description for the card
        """
        params = {**self.params, **demisto_args}
        demisto.results(params)
        data = self._http_request(method="POST", url_suffix=f"{CARDS_SUFFIX}", params=params)
        return data

    def list_cards(self, list_id):
        """
        Returns all cards in a list.
        """
        data = self._http_request(method="GET", url_suffix=f"{LISTS_SUFFIX}/{list_id}{CARDS_SUFFIX}", params=self.params)
        return data

    def update_card(self, card_id, demisto_args):
        """
        Update an existing card
        args

        card_id : str
            ID of card to update

        demisto_args
            -- same as create_card --
        """

        params = {**self.params, **demisto_args}
        data = self._http_request(method="PUT", url_suffix=f"{CARDS_SUFFIX}/{card_id}", params=params)
        return data

    def add_card_comment(self, card_id, demisto_args):
        """
        Update an existing card
        args

        card_id : str
            ID of card to update

        demisto_args
            -- same as create_card --
        """

        params = {**self.params, **demisto_args}
        data = self._http_request(method="POST", url_suffix=f"{CARDS_SUFFIX}/{card_id}{COMMENTS}", params=params)
        return data

    def delete_card(self, card_id):
        """
        Delete a card.
        args

        card_id : str
            ID of card to update

        demisto_args
            -- same as create_card --
        """

        data = self._http_request(method="DELETE", url_suffix=f"{CARDS_SUFFIX}/{card_id}", params=self.params)
        return data

    def get_card(self, card_id):
        """
        Get a single card.
        args

        card_id : str
            ID of card
        """

        data = self._http_request(method="GET", url_suffix=f"{CARDS_SUFFIX}/{card_id}", params=self.params)
        return data

    def list_labels(self, board_id):
        """
        Return all the labels for a board
        """
        data = self._http_request(method="GET", url_suffix=f"{BOARDS_SUFFIX}/{board_id}{LABELS_SUFFIX}", params=self.params)
        return data

    def create_label(self, board_id, demisto_args):
        """
        Create a label to be used on the board
        """
        params = {**self.params, **demisto_args}
        data = self._http_request(method="POST", url_suffix=f"{BOARDS_SUFFIX}/{board_id}{LABELS_SUFFIX}", params=params)
        return data


def flatten_action_data(result):
    """
    Given a result of an action query, flatten the result "data"
    """
    flatten_fields = ["id", "name"]
    for action in result:
        data = action.get("data")
        if data:
            for k, v in data.items():
                for f in flatten_fields:
                    if f in v:
                        action[f"{k}_{f}"] = v.get(f)

    return result


def normalise_card_fields(cards):
    """
    Adds XSOAR-like variations of card fields.
    """
    fields = {"id": "ID", "name": "Name", "url": "URL", "due": "Due", "labels": "Labels"}
    for card in cards:
        for k, v in fields.items():
            if k in card:
                card[v] = card[k]

    return cards


def capitalize(word):
    """
    Capitalize JUST the first letter of the string
    """
    word = word.replace(word[0], word[0].capitalize(), 1)
    return word


def select_outputs(result, output_keys):
    """
    Takes a result, either a list or dict, and trims all but the keys specified
    in output_keys

    Capitlizes the result
    """
    if type(result) is dict:  # noqa: RET503
        output = {capitalize(k): result[k] for k in output_keys}
        # output = camelize(output)
        return output
    elif type(result) is list:
        output = []  # type: ignore[assignment]
        for r in result:
            new_r = {capitalize(k): r[k] for k in output_keys}
            output.append(new_r)  # type: ignore[attr-defined]
        return output


def select_outputs_camelize(result, output_keys):
    """
    Takes a result, either a list or dict, and trims all but the keys specified
    in output_keys

    Camelizes any result.
    """
    special_keys = ["card_id", "list_id", "board_id"]
    if type(result) is dict:  # noqa: RET503
        output = {k: result[k] for k in output_keys}
        output = camelize(output, "_")
        return output
    elif type(result) is list:
        output = []
        for r in result:
            new_r = {k: r[k] for k in output_keys}
            # This is a hack to get special keys like card_id into the output when they may or may not exist
            for k in special_keys:
                if k in r:
                    new_r[k] = r[k]

            new_r = camelize(new_r, "_")
            output.append(new_r)
        return output


def list_boards(client):
    result = client.list_boards()
    output_keys = STANDARD_OUTPUTS + ["closed", "dateLastActivity", "url"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.Boards",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Boards", result, headers=["id", "name", "dateLastActivity"]),
        raw_response=result,
    )
    return r


def list_lists(client, board_id):
    result = client.list_lists(board_id)
    output_keys = STANDARD_OUTPUTS + ["closed", "idBoard"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.Lists",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Lists", result),
        raw_response=result,
    )
    return r


def list_labels(client, board_id):
    result = client.list_labels(board_id)
    output_keys = STANDARD_OUTPUTS + ["color"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.Labels",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Labels", result),
        raw_response=result,
    )
    return r


def add_comment(client, card_id, demisto_args):
    result = client.add_card_comment(card_id, demisto_args)
    output_keys = ["id", "date"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.AddedComment",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Comment Added", result),
        raw_response=result,
    )
    return r


def list_actions(client, board_id, actions_filter, since, before):
    result = client.list_actions(board_id, actions_filter, since, before)
    result = flatten_action_data(result)
    output_keys = ["id", "type", "date"]
    outputs = select_outputs_camelize(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.Actions",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Actions", result, headers=["id", "type", "date"]),
        raw_response=result,
    )
    return r


def create_card(client, list_id, demisto_args):
    del demisto_args["list_id"]
    demisto_args["idList"] = list_id
    result = client.create_card(demisto_args)
    output_keys = STANDARD_OUTPUTS + ["url", "idList"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.CreatedCard",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello created cards", result, headers=["id", "name"]),
        raw_response=result,
    )
    return r


def create_label(client, board_id, demisto_args):
    result = client.create_label(board_id, demisto_args)
    output_keys = STANDARD_OUTPUTS
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.CreatedLabel",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Created Label", result),
        raw_response=result,
    )
    return r


def list_cards(client, list_id):
    result = client.list_cards(list_id)
    result = normalise_card_fields(result)
    output_keys = STANDARD_OUTPUTS + ["url", "idList", "due", "labels", "desc", "start"]
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.Cards",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Cards", result, headers=STANDARD_OUTPUTS + ["url"]),
        raw_response=result,
    )
    return r


def update_card(client, card_id, demisto_args):
    del demisto_args["card_id"]
    result = client.update_card(card_id, demisto_args)
    output_keys = STANDARD_OUTPUTS
    outputs = select_outputs(result, output_keys)
    r = CommandResults(
        outputs_prefix="Trello.UpdatedCard",
        outputs=outputs,
        readable_output=tableToMarkdown("Trello Updated card", result, headers=["id", "name"] + list(demisto.args().keys())),
        raw_response=result,
    )
    return r


def delete_card(client, card_id):
    result = client.delete_card(card_id)
    r = CommandResults(
        outputs_prefix="Trello.DeletedCard",
        outputs=card_id,
        readable_output=f"Deleted card with ID {card_id}",
        raw_response=result,
    )
    return r


def test_module(client):
    """
    Returning 'ok' indicates that the integration works like it is supposed to. Connection to the service is successful.

    Args:
        client: HelloWorld client

    Returns:
        'ok' if test passed, anything else will fail the test.
    """

    result = client.list_boards()
    if result:
        return "ok"
    else:
        return "Test failed."


def fetch_incidents(client, last_run: dict, board_id: str, list_id_filter: str):
    """
    This function will execute each interval (default is 1 minute).

    In Trello, this fetches based on the Action API, specifically looking for createCard actions.

    Optionally, the user can specify list_id to only fetch cards in a specific list.

    Args:
        client (Client): Trello Client
        last_run (dict): The most recent fetch

    Returns:
        next_run: This will be last_run in the next fetch-incidents
        incidents: Incidents that will be created in Demisto
    """
    # Get the last fetch time, if exists
    last_fetch = last_run.get("last_fetch")
    since = None
    if last_fetch:
        since = last_fetch

    incidents = []
    actions = client.list_actions(board_id, "createCard,updateCard", since)

    actions = flatten_action_data(actions)

    # If we get no actions, means no new actions, simply return.
    if len(actions) == 0:
        return last_run, []

    # Update last run and add incident if the incident is newer than last fetch
    # Most recent action is first element of list
    last_action_id = actions[0].get("id")
    if last_action_id != last_fetch:
        last_fetch = last_action_id

    # Now, we list all the lists on the board
    lists = client.list_lists(board_id)
    cards: List = []
    for list_item in lists:
        list_id = list_item.get("id")
        # Finally, we get all the active cards
        cards_response = client.list_cards(list_id)
        cards = cards + normalise_card_fields(cards_response)

    # If there are no cards on the board, return
    if len(cards) == 0:
        return last_run, []

    new_cards = []
    for action in actions:
        # If it's a new type
        if action.get("type") == "createCard":
            card_id = action.get("card_id")
            if action.get("list_id") == list_id_filter or not list_id_filter:
                for card in cards:
                    if card_id == card.get("id"):
                        card["create_action"] = action
                        new_cards.append(card)
        # If it's updated due to a move
        elif action.get("type") == "updateCard":
            card_id = action.get("card_id")
            # Only support moved (updated) card actions when a list filter is given
            if list_id_filter and action.get("listAfter_id") == list_id_filter:
                for card in cards:
                    if card_id == card.get("id"):
                        card["create_action"] = action
                        new_cards.append(card)

    for card in new_cards:
        incident_created_time = dateparser.parse(card.get("create_action").get("date"))
        assert incident_created_time is not None, f'could not parse {card.get("create_action").get("date")}'
        incident = {
            "name": card.get("name"),
            "occurred": incident_created_time.strftime("%Y-%m-%dT%H:%M:%SZ"),
            "rawJSON": json.dumps(card),
        }

        incidents.append(incident)

    next_run = {"last_fetch": last_fetch}
    return next_run, incidents


def get_mapping_fields():
    fields = {"Trello": {"id": "str", "name": "str", "desc": "str"}}

    return fields


def get_remote_data(client, args):
    parsed_args = GetRemoteDataArgs(args)
    new_incident_data = client.get_card(parsed_args.remote_incident_id)
    demisto.info(f"Calling Remote data fetch for {parsed_args.remote_incident_id}")

    new_incident_data["dbotMirrorInstance"] = demisto.integrationInstance()

    # If the card is closed/archived, add a closeIncident entry
    entries = []
    if new_incident_data.get("closed"):
        demisto.debug("Trello card has been archived.")
        entries.append(
            {
                "Type": EntryType.NOTE,
                "Contents": {"dbotIncidentClose": True, "closeReason": "Trello Card archived."},
                "ContentsFormat": EntryFormat.JSON,
            }
        )
    return [new_incident_data] + entries


def get_board_id():
    if demisto.args().get("board_id"):
        return demisto.args().get("board_id")

    if demisto.params().get("board_id"):
        return demisto.params().get("board_id")

    raise DemistoException("Could not resolve board_id - please pass as argument or param to integration.")


def main():
    """
    PARSE AND VALIDATE INTEGRATION PARAMS
    """
    key = demisto.params().get("credentials").get("identifier")
    token = demisto.params().get("credentials").get("password")

    # get the service API url
    base_url = urljoin(demisto.params()["url"], "/1")

    verify_certificate = not demisto.params().get("insecure", False)

    proxy = demisto.params().get("proxy", False)

    LOG(f"Command being called is {demisto.command()}")
    try:
        client = Client(key, token, base_url=base_url, verify=verify_certificate, proxy=proxy)

        if demisto.command() == "test-module":
            return_results(test_module(client))
        elif demisto.command() == "fetch-incidents":
            board_id = get_board_id()

            # If the list is specified, we only fetch incidents belonging to that list (by ID)
            list_id = demisto.params().get("list_id")

            # Set and define the fetch incidents command to run after activated via integration settings.
            next_run, incidents = fetch_incidents(
                client=client, last_run=demisto.getLastRun(), board_id=board_id, list_id_filter=list_id
            )

            demisto.setLastRun(next_run)
            demisto.incidents(incidents)

        elif demisto.command() == "trello-list-boards":
            return_results(list_boards(client))
        elif demisto.command() == "trello-list-lists":
            board_id = get_board_id()
            return_results(list_lists(client, board_id))
        elif demisto.command() == "trello-list-labels":
            board_id = get_board_id()
            return_results(list_labels(client, board_id))
        elif demisto.command() == "trello-create-label":
            board_id = get_board_id()
            return_results(create_label(client, board_id, demisto.args()))
        elif demisto.command() == "trello-list-actions":
            board_id = get_board_id()
            filter_str = demisto.args().get("filter", None)
            since = demisto.args().get("since", None)
            before = demisto.args().get("before", None)
            return_results(list_actions(client, board_id, filter_str, since, before))
        elif demisto.command() == "trello-create-card":
            list_id = demisto.args().get("list_id")
            return_results(create_card(client, list_id, demisto.args()))
        elif demisto.command() == "trello-list-cards":
            list_id = demisto.args().get("list_id")
            return_results(list_cards(client, list_id))
        elif demisto.command() == "trello-update-card":
            card_id = demisto.args().get("card_id")
            return_results(update_card(client, card_id, demisto.args()))
        elif demisto.command() == "trello-add-comment":
            card_id = demisto.args().get("card_id")
            return_results(add_comment(client, card_id, demisto.args()))
        elif demisto.command() == "trello-delete-card":
            card_id = demisto.args().get("card_id")
            return_results(delete_card(client, card_id))
        elif demisto.command() == "trello-get-mapping-fields":
            demisto.results(get_mapping_fields())
        elif demisto.command() == "trello-get-remote-data":
            demisto.results(get_remote_data(client, demisto.args()))

    # Log exceptions
    except Exception as e:
        return_error(f"Failed to execute {demisto.command()} command. Error: {e!s}")


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