Examples
Whole scripts to start from, in shell, Python and JavaScript.
Each example reads the workspace address and a personal token from the environment:
export ALBA_URL=https://your-workspace.albaticket.com
export ALBA_TOKEN=alba_pat_…
Make the token on your AI agents page with the least access the script needs: Read for the first and third, Read and change for the others. The scripts use the REST API, which an administrator switches on for the workspace under Modules and features; while it is off they are answered 403 feature_disabled.
My open tickets, for a stand-up
Shell, with curl and jq:
#!/bin/sh
set -eu
curl -sf "$ALBA_URL/api/v1/tickets?assignee=me&status=open&sort=priority&limit=100" \
-H "Authorization: Bearer $ALBA_TOKEN" |
jq -r '.tickets[] | "\(.key)\t\(.status.name)\t\(.priority // "-")\t\(.summary)"' |
column -t -s "$(printf '\t')"
Raise a ticket from an alert, once
Python 3, standard library only. It looks for an open ticket with the alert's label before creating one, so an alert that keeps firing does not open a ticket each time:
import json, os, urllib.error, urllib.parse, urllib.request
BASE = os.environ["ALBA_URL"] + "/api/v1"
TOKEN = os.environ["ALBA_TOKEN"]
def call(method, path, body=None, **query):
url = BASE + path + ("?" + urllib.parse.urlencode(query) if query else "")
data = json.dumps(body).encode() if body is not None else None
request = urllib.request.Request(url, data=data, method=method)
request.add_header("Authorization", "Bearer " + TOKEN)
request.add_header("Content-Type", "application/json")
try:
with urllib.request.urlopen(request) as response:
return json.load(response)
except urllib.error.HTTPError as error:
message = json.load(error)["error"]["message"]
raise SystemExit(f"{method} {path}: {error.code} {message}")
def raise_alert(name, detail):
label = "alert-" + name
found = call("GET", "/tickets", project="OPS", label=label, status="open", limit=1)
if found["total"] > 0:
key = found["tickets"][0]["key"]
call("POST", f"/tickets/{key}/comments", {"body": f"Fired again:\n\n```\n{detail}\n```"})
return key
ticket = call("POST", "/tickets", {
"project": "OPS",
"summary": f"Alert: {name}",
"description": f"```\n{detail}\n```",
"type": "Bug",
"priority": "High",
"labels": [label],
})
return ticket["key"]
if __name__ == "__main__":
print(raise_alert("disk-full", "/var is 97% full on web-3"))
Every ticket a search finds
Python 3, paging with limit and offset until total is reached:
import json, os, urllib.parse, urllib.request
BASE = os.environ["ALBA_URL"] + "/api/v1"
TOKEN = os.environ["ALBA_TOKEN"]
def tickets(**filters):
offset = 0
while True:
query = urllib.parse.urlencode({**filters, "limit": 100, "offset": offset})
request = urllib.request.Request(f"{BASE}/tickets?{query}")
request.add_header("Authorization", "Bearer " + TOKEN)
with urllib.request.urlopen(request) as response:
page = json.load(response)
yield from page["tickets"]
offset += len(page["tickets"])
if not page["tickets"] or offset >= page["total"]:
return
for ticket in tickets(project="WEB", status="done", sort="key"):
print(ticket["key"], ticket["summary"])
Resolve what a release shipped
JavaScript (Node.js 18 or later, which has fetch), for the end of a deployment pipeline. It resolves every ticket key mentioned in the release notes and says which release it went out in. A ticket that is already resolved, or that has no such transition, is reported and skipped:
const base = `${process.env.ALBA_URL}/api/v1`
const headers = {
Authorization: `Bearer ${process.env.ALBA_TOKEN}`,
"Content-Type": "application/json",
}
async function call(method, path, body) {
const response = await fetch(base + path, {method, headers, body: body && JSON.stringify(body)})
const data = await response.json()
if (!response.ok) throw new Error(`${response.status} ${data.error.message}`)
return data
}
const release = process.argv[2]
const notes = process.argv[3]
const keys = [...new Set(notes.match(/\b[A-Z][A-Z0-9_]+-\d+\b/g) ?? [])]
for (const key of keys) {
try {
const ticket = await call("POST", `/tickets/${key}/transitions`, {
transition: "Resolved",
resolution: "Done",
comment: `Released in **${release}**.`,
})
console.log(`${key}: ${ticket.status.name}`)
} catch (error) {
console.log(`${key}: skipped (${error.message})`)
}
}
Run it as node resolve.mjs 2.5.0 "$(cat RELEASE_NOTES.md)".
Move a card when a pull request opens
Shell, for a CI job. It moves the ticket named in the branch (WEB-142-fix-checkout) into the board's review column:
#!/bin/sh
set -eu
key=$(printf '%s' "$BRANCH_NAME" | grep -oE '^[A-Z][A-Z0-9_]+-[0-9]+' || true)
[ -n "$key" ] || exit 0
curl -sf "$ALBA_URL/api/v1/boards/3/moves" \
-H "Authorization: Bearer $ALBA_TOKEN" \
-H "Content-Type: application/json" \
-d "{\"key\": \"$key\", \"column\": \"Review\"}" |
jq -r '"\(.key) is now \(.status.name)"'
A workspace with a GitHub or GitLab integration can do much of this without a token: see GitHub and GitLab in the user guide.
Close a sprint and open the next
Shell. Completes the active sprint, moving what is unfinished into the next one, then starts it for two weeks. The token's member must be able to manage the board's sprints:
#!/bin/sh
set -eu
board=3
api="$ALBA_URL/api/v1/boards/$board/sprints"
auth="Authorization: Bearer $ALBA_TOKEN"
sprints=$(curl -sf "$api" -H "$auth")
active=$(printf '%s' "$sprints" | jq -r '.sprints[] | select(.state == "active") | .id')
next=$(printf '%s' "$sprints" | jq -r '[.sprints[] | select(.state == "future")][0].id')
[ "$active" ] && [ "$next" != null ] || { echo "Needs an active sprint and a future one" >&2; exit 1; }
curl -sf -X POST "$api/$active/complete" -H "$auth" -H "Content-Type: application/json" \
-d "{\"move_open_to\": \"$next\"}" | jq -r '"Closed \(.name), moved \(.moved | length)"'
end=$(date -u -d "+14 days" +%F 2>/dev/null || date -u -v+14d +%F)
curl -sf -X POST "$api/$next/start" -H "$auth" -H "Content-Type: application/json" \
-d "{\"end_date\": \"$end\"}" | jq -r '"Started \(.name) until \(.end_at)"'