Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
f531bd2
basic tools for workspaces, projects, teams, and tasks
byrro Apr 25, 2025
b2c932f
more task tools
byrro Apr 25, 2025
40e4624
add several tools; refactor others; make tools more ergonomic
byrro May 2, 2025
5b4fddc
subtasks & attachment tools; include subtasks when retrieving tasks
byrro May 2, 2025
088170f
fix reference to toolkit pkg name
byrro May 2, 2025
3847a15
make mypy happy
byrro May 2, 2025
b214001
fix case
byrro May 2, 2025
61d8e0f
fix issues in search tasks
byrro May 5, 2025
3196d64
bump required arcade-ai version
byrro May 5, 2025
b457ba6
return results count
byrro May 5, 2025
f1437fc
evals
byrro May 5, 2025
dfd7cd9
various adjustments; unit tests
byrro May 5, 2025
ee6852e
fix bug in filter by project name
byrro May 5, 2025
b371a64
improve tag name filtering
byrro May 5, 2025
2e4e48a
improve annotation about auto-creating tags
byrro May 5, 2025
0d509e6
fix task association to project name
byrro May 5, 2025
0081224
remove debug msg
byrro May 5, 2025
2c74bb8
Make search tags/projects by name utility functions, instead of tools
byrro May 7, 2025
3a5773f
rename gid to id
byrro May 7, 2025
6975778
rename gid to id
byrro May 7, 2025
e57f263
sanitize offset to always be a str
byrro May 7, 2025
7c32444
make sure limit is always between 1 and 100
byrro May 7, 2025
0865bef
make max items scanned an arg in find tags/projects funcs
byrro May 7, 2025
ee42f05
include msg about the limitation in find by name
byrro May 7, 2025
ac6cc9a
cannot update a task parent
byrro May 7, 2025
3c016f5
update evals; default workspace_id in list_users
byrro May 7, 2025
56fd00f
fix mypy errors
byrro May 7, 2025
8a763fa
make annotations more clear in args with list of values
byrro May 7, 2025
31053d8
various fixes and improvements
byrro May 7, 2025
792626b
make completed optional when searching for tasks
byrro May 8, 2025
66396e0
fix type errors; handle missing workspace_id in search_tasks
byrro May 8, 2025
92b97f0
add some missing tools
byrro May 8, 2025
cf4d3d2
update annotation
byrro May 8, 2025
7fe92f1
fix test
byrro May 8, 2025
cec77a3
use well-known provider name
byrro May 8, 2025
6b1c2ec
add asana to docker txt
byrro May 8, 2025
29f83c6
update auth class
byrro May 8, 2025
c9de17d
rename search_tasks tool; update evals
byrro May 9, 2025
e0880c5
create tool to mark task as completed
byrro May 9, 2025
cd9e7b6
make mypy happy
byrro May 12, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docker/toolkits.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
arcade-asana
arcade-code-sandbox
arcade-dropbox
arcade-github
Expand Down
18 changes: 18 additions & 0 deletions toolkits/asana/.pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
files: ^./
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v4.4.0"
hooks:
- id: check-case-conflict
- id: check-merge-conflict
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.7
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
46 changes: 46 additions & 0 deletions toolkits/asana/.ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
target-version = "py39"
line-length = 100
fix = true

[lint]
select = [
# flake8-2020
"YTT",
# flake8-bandit
"S",
# flake8-bugbear
"B",
# flake8-builtins
"A",
# flake8-comprehensions
"C4",
# flake8-debugger
"T10",
# flake8-simplify
"SIM",
# isort
"I",
# mccabe
"C90",
# pycodestyle
"E", "W",
# pyflakes
"F",
# pygrep-hooks
"PGH",
# pyupgrade
"UP",
# ruff
"RUF",
# tryceratops
"TRY",
]

[lint.per-file-ignores]
"*" = ["TRY003", "B904"]
"**/tests/*" = ["S101", "E501"]
"**/evals/*" = ["S101", "E501"]

[format]
preview = true
skip-magic-trailing-comma = false
21 changes: 21 additions & 0 deletions toolkits/asana/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2025 Arcade

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
53 changes: 53 additions & 0 deletions toolkits/asana/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
.PHONY: help

help:
@echo "🛠️ dropbox Commands:\n"
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

.PHONY: install
install: ## Install the poetry environment and install the pre-commit hooks
@echo "📦 Checking if Poetry is installed"
@if ! command -v poetry &> /dev/null; then \
echo "📦 Installing Poetry with pip"; \
pip install poetry==1.8.5; \
else \
echo "📦 Poetry is already installed"; \
fi
@echo "🚀 Installing package in development mode with all extras"
poetry install --all-extras

.PHONY: build
build: clean-build ## Build wheel file using poetry
@echo "🚀 Creating wheel file"
poetry build

.PHONY: clean-build
clean-build: ## clean build artifacts
@echo "🗑️ Cleaning dist directory"
rm -rf dist

.PHONY: test
test: ## Test the code with pytest
@echo "🚀 Testing code: Running pytest"
@poetry run pytest -W ignore -v --cov --cov-config=pyproject.toml --cov-report=xml

.PHONY: coverage
coverage: ## Generate coverage report
@echo "coverage report"
coverage report
@echo "Generating coverage report"
coverage html

.PHONY: bump-version
bump-version: ## Bump the version in the pyproject.toml file
@echo "🚀 Bumping version in pyproject.toml"
poetry version patch

.PHONY: check
check: ## Run code quality tools.
@echo "🚀 Checking Poetry lock file consistency with 'pyproject.toml': Running poetry check"
@poetry check
@echo "🚀 Linting code: Running pre-commit"
@poetry run pre-commit run -a
@echo "🚀 Static type checking: Running mypy"
@poetry run mypy --config-file=pyproject.toml
Empty file.
139 changes: 139 additions & 0 deletions toolkits/asana/arcade_asana/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import os
from enum import Enum

ASANA_BASE_URL = "https://app.asana.com/api"
ASANA_API_VERSION = "1.0"

try:
ASANA_MAX_CONCURRENT_REQUESTS = int(os.getenv("ASANA_MAX_CONCURRENT_REQUESTS", 3))
except ValueError:
ASANA_MAX_CONCURRENT_REQUESTS = 3

try:
ASANA_MAX_TIMEOUT_SECONDS = int(os.getenv("ASANA_MAX_TIMEOUT_SECONDS", 20))
except ValueError:
ASANA_MAX_TIMEOUT_SECONDS = 20

MAX_PROJECTS_TO_SCAN_BY_NAME = 1000
MAX_TAGS_TO_SCAN_BY_NAME = 1000

PROJECT_OPT_FIELDS = [
"gid",
"resource_type",
"name",
"workspace",
"color",
"created_at",
"current_status_update",
"due_on",
"members",
"notes",
"completed",
"completed_at",
"completed_by",
"owner",
"team",
"workspace",
"permalink_url",
]

TASK_OPT_FIELDS = [
"gid",
"name",
"notes",
"completed",
"completed_at",
"completed_by",
"created_at",
"created_by",
"due_on",
"start_on",
"owner",
"team",
"workspace",
"permalink_url",
"approval_status",
"assignee",
"assignee_status",
"dependencies",
"dependents",
"memberships",
"num_subtasks",
"resource_type",
"custom_type",
"custom_type_status_option",
"parent",
"tags",
"workspace",
]


TAG_OPT_FIELDS = [
"gid",
"name",
"workspace",
]

TEAM_OPT_FIELDS = [
"gid",
"name",
"description",
"organization",
"permalink_url",
]

USER_OPT_FIELDS = [
"gid",
"resource_type",
"name",
"email",
"photo",
"workspaces",
]

WORKSPACE_OPT_FIELDS = [
"gid",
"resource_type",
"name",
"email_domains",
"is_organization",
]


class TaskSortBy(Enum):
DUE_DATE = "due_date"
CREATED_AT = "created_at"
COMPLETED_AT = "completed_at"
MODIFIED_AT = "modified_at"
LIKES = "likes"


class SortOrder(Enum):
ASCENDING = "ascending"
DESCENDING = "descending"


class TagColor(Enum):
DARK_GREEN = "dark-green"
DARK_RED = "dark-red"
DARK_BLUE = "dark-blue"
DARK_PURPLE = "dark-purple"
DARK_PINK = "dark-pink"
DARK_ORANGE = "dark-orange"
DARK_TEAL = "dark-teal"
DARK_BROWN = "dark-brown"
DARK_WARM_GRAY = "dark-warm-gray"
LIGHT_GREEN = "light-green"
LIGHT_RED = "light-red"
LIGHT_BLUE = "light-blue"
LIGHT_PURPLE = "light-purple"
LIGHT_PINK = "light-pink"
LIGHT_ORANGE = "light-orange"
LIGHT_TEAL = "light-teal"
LIGHT_BROWN = "light-brown"
LIGHT_WARM_GRAY = "light-warm-gray"


class ReturnType(Enum):
FULL_ITEMS_DATA = "full_items_data"
ITEMS_COUNT = "items_count"
26 changes: 26 additions & 0 deletions toolkits/asana/arcade_asana/decorators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
from functools import wraps
from typing import Any, Callable


def clean_asana_response(func: Callable[..., Any]) -> Callable[..., Any]:
def response_cleaner(data: dict[str, Any]) -> dict[str, Any]:
if "gid" in data:
data["id"] = data["gid"]
del data["gid"]

for k, v in data.items():
if isinstance(v, dict):
data[k] = response_cleaner(v)
elif isinstance(v, list):
data[k] = [
item if not isinstance(item, dict) else response_cleaner(item) for item in v
]

return data

@wraps(func)
async def wrapper(*args: Any, **kwargs: Any) -> Any:
response = await func(*args, **kwargs)
return response_cleaner(response)

return wrapper
14 changes: 14 additions & 0 deletions toolkits/asana/arcade_asana/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from arcade.sdk.errors import ToolExecutionError


class AsanaToolExecutionError(ToolExecutionError):
pass


class PaginationTimeoutError(AsanaToolExecutionError):
def __init__(self, timeout_seconds: int, tool_name: str):
message = f"Pagination timed out after {timeout_seconds} seconds"
super().__init__(
message=message,
developer_message=f"{message} while calling the tool {tool_name}",
)
Loading