Files
airano-ir 9905a28eb0 feat: v3.4.0 — OpenPanel plugin public release (42 tools)
OpenPanel product analytics plugin fully reviewed, tested, and published.
Works with both self-hosted and cloud (openpanel.dev) instances.

- 42 tools: event tracking, data export, analytics, project/client management
- All tools use public REST APIs (Track, Export, Insights, Manage)
- Client modes: write (tracking), read (analytics), root (full access)
- Service page with description, setup notes, WordPress plugin download
- Dynamic URL hints in Add Site form
- 62 unit tests
- ENABLED_PLUGINS default now includes openpanel

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:48:12 +02:00

756 lines
27 KiB
Python

"""Events Handler - OpenPanel event tracking operations (11 tools)"""
import json
from typing import Any
from plugins.openpanel.client import OpenPanelClient
def get_tool_specifications() -> list[dict[str, Any]]:
"""Return tool specifications for ToolGenerator (11 tools)"""
return [
{
"name": "track_event",
"method_name": "track_event",
"description": "Track a custom event with properties. Events can have any custom properties for analytics.",
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "Event name (e.g., 'button_clicked', 'purchase_completed', 'page_viewed')",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Custom properties for the event (e.g., {"product_id": "123", "price": 99.99})',
},
"profile_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User/profile ID to associate with the event",
},
"timestamp": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Event timestamp (ISO 8601 format, defaults to now)",
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP for geolocation tracking",
},
"user_agent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User agent for device detection",
},
},
"required": ["name"],
},
"scope": "write",
},
{
"name": "track_page_view",
"method_name": "track_page_view",
"description": "Track a page view event with URL and referrer information.",
"schema": {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Page path (e.g., '/products/123', '/blog/post-title')",
},
"title": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Page title",
},
"referrer": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Referrer URL",
},
"profile_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User/profile ID",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Additional properties",
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP for geolocation",
},
"user_agent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User agent for device detection",
},
},
"required": ["path"],
},
"scope": "write",
},
{
"name": "track_screen_view",
"method_name": "track_screen_view",
"description": "Track a screen view event for mobile applications.",
"schema": {
"type": "object",
"properties": {
"screen_name": {
"type": "string",
"description": "Screen name (e.g., 'HomeScreen', 'ProductDetail')",
},
"screen_class": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Screen class/component name",
},
"profile_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User/profile ID",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Additional properties",
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP",
},
"user_agent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User agent",
},
},
"required": ["screen_name"],
},
"scope": "write",
},
{
"name": "identify_user",
"method_name": "identify_user",
"description": "Identify a user and set their profile properties. Use this to associate events with users.",
"schema": {
"type": "object",
"properties": {
"profile_id": {
"type": "string",
"description": "Unique identifier for the user",
},
"email": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User's email address",
},
"first_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User's first name",
},
"last_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User's last name",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Additional profile properties (e.g., {"plan": "premium", "company": "Acme"})',
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP",
},
"user_agent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User agent",
},
},
"required": ["profile_id"],
},
"scope": "write",
},
{
"name": "set_user_properties",
"method_name": "set_user_properties",
"description": "Update properties for an existing user profile.",
"schema": {
"type": "object",
"properties": {
"profile_id": {"type": "string", "description": "User's profile ID"},
"properties": {
"type": "object",
"description": "Properties to set/update on the profile",
},
},
"required": ["profile_id", "properties"],
},
"scope": "write",
},
{
"name": "increment_property",
"method_name": "increment_property",
"description": "Increment a numeric property on a user profile. IMPORTANT: Profile must exist first (use identify_user). Property must be numeric.",
"schema": {
"type": "object",
"properties": {
"profile_id": {"type": "string", "description": "User's profile ID"},
"property_name": {
"type": "string",
"description": "Name of the property to increment",
},
"value": {
"type": "integer",
"description": "Amount to increment by",
"default": 1,
},
},
"required": ["profile_id", "property_name"],
},
"scope": "write",
},
{
"name": "decrement_property",
"method_name": "decrement_property",
"description": "Decrement a numeric property on a user profile. IMPORTANT: Profile must exist first (use identify_user). Property must be numeric.",
"schema": {
"type": "object",
"properties": {
"profile_id": {"type": "string", "description": "User's profile ID"},
"property_name": {
"type": "string",
"description": "Name of the property to decrement",
},
"value": {
"type": "integer",
"description": "Amount to decrement by",
"default": 1,
},
},
"required": ["profile_id", "property_name"],
},
"scope": "write",
},
{
"name": "create_group",
"method_name": "create_group",
"description": "Create or update a group (e.g., company, workspace, team). Groups can be associated with events and profiles.",
"schema": {
"type": "object",
"properties": {
"group_id": {
"type": "string",
"description": "Unique group identifier",
},
"group_type": {
"type": "string",
"description": "Group category (e.g., 'company', 'workspace', 'team')",
},
"name": {
"type": "string",
"description": "Display name for the group",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": 'Custom group properties (e.g., {"plan": "enterprise", "size": 50})',
},
},
"required": ["group_id", "group_type", "name"],
},
"scope": "write",
},
{
"name": "assign_group",
"method_name": "assign_group",
"description": "Assign a user to one or more groups. If no profile_id is provided, falls back to device ID.",
"schema": {
"type": "object",
"properties": {
"group_ids": {
"type": "array",
"items": {"type": "string"},
"description": "List of group IDs to assign the user to",
"minItems": 1,
},
"profile_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User profile ID (falls back to device ID if not provided)",
},
},
"required": ["group_ids"],
},
"scope": "write",
},
{
"name": "track_revenue",
"method_name": "track_revenue",
"description": "Track a revenue/purchase event with amount and currency.",
"schema": {
"type": "object",
"properties": {
"amount": {"type": "number", "description": "Revenue amount"},
"currency": {
"type": "string",
"description": "Currency code (e.g., 'USD', 'EUR', 'IRR')",
"default": "USD",
},
"product_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Product ID",
},
"product_name": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Product name",
},
"order_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Order/transaction ID",
},
"profile_id": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User's profile ID",
},
"properties": {
"anyOf": [{"type": "object"}, {"type": "null"}],
"description": "Additional properties",
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP",
},
},
"required": ["amount"],
},
"scope": "write",
},
{
"name": "track_batch",
"method_name": "track_batch",
"description": "Track multiple events in a single request for efficiency.",
"schema": {
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"properties": {"type": "object"},
"profile_id": {"type": "string"},
"timestamp": {"type": "string"},
},
"required": ["name"],
},
"description": "Array of events to track",
"minItems": 1,
"maxItems": 100,
},
"client_ip": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "Client IP for all events",
},
"user_agent": {
"anyOf": [{"type": "string"}, {"type": "null"}],
"description": "User agent for all events",
},
},
"required": ["events"],
},
"scope": "write",
},
]
# =====================
# Event Tracking Functions (10)
# =====================
async def track_event(
client: OpenPanelClient,
name: str,
properties: dict[str, Any] | None = None,
profile_id: str | None = None,
timestamp: str | None = None,
client_ip: str | None = None,
user_agent: str | None = None,
) -> str:
"""Track a custom event"""
try:
result = await client.track_event(
name=name,
properties=properties,
profile_id=profile_id,
timestamp=timestamp,
client_ip=client_ip,
user_agent=user_agent,
)
return json.dumps(
{
"success": True,
"event": name,
"profile_id": profile_id,
"message": f"Event '{name}' tracked successfully",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def track_page_view(
client: OpenPanelClient,
path: str,
title: str | None = None,
referrer: str | None = None,
profile_id: str | None = None,
properties: dict[str, Any] | None = None,
client_ip: str | None = None,
user_agent: str | None = None,
) -> str:
"""Track a page view event"""
try:
event_properties = {
"path": path,
}
if title:
event_properties["title"] = title
if referrer:
event_properties["referrer"] = referrer
if properties:
event_properties.update(properties)
result = await client.track_event(
name="page_view",
properties=event_properties,
profile_id=profile_id,
client_ip=client_ip,
user_agent=user_agent,
)
return json.dumps(
{
"success": True,
"event": "page_view",
"path": path,
"message": f"Page view tracked for '{path}'",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def track_screen_view(
client: OpenPanelClient,
screen_name: str,
screen_class: str | None = None,
profile_id: str | None = None,
properties: dict[str, Any] | None = None,
client_ip: str | None = None,
user_agent: str | None = None,
) -> str:
"""Track a screen view event for mobile"""
try:
event_properties = {
"screen_name": screen_name,
}
if screen_class:
event_properties["screen_class"] = screen_class
if properties:
event_properties.update(properties)
result = await client.track_event(
name="screen_view",
properties=event_properties,
profile_id=profile_id,
client_ip=client_ip,
user_agent=user_agent,
)
return json.dumps(
{
"success": True,
"event": "screen_view",
"screen_name": screen_name,
"message": f"Screen view tracked for '{screen_name}'",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def identify_user(
client: OpenPanelClient,
profile_id: str,
email: str | None = None,
first_name: str | None = None,
last_name: str | None = None,
properties: dict[str, Any] | None = None,
client_ip: str | None = None,
user_agent: str | None = None,
) -> str:
"""Identify a user with profile data"""
try:
result = await client.identify_user(
profile_id=profile_id,
email=email,
first_name=first_name,
last_name=last_name,
properties=properties,
client_ip=client_ip,
user_agent=user_agent,
)
return json.dumps(
{
"success": True,
"profile_id": profile_id,
"email": email,
"message": f"User '{profile_id}' identified successfully",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def set_user_properties(
client: OpenPanelClient, profile_id: str, properties: dict[str, Any]
) -> str:
"""Update properties for a user profile"""
try:
result = await client.identify_user(profile_id=profile_id, properties=properties)
return json.dumps(
{
"success": True,
"profile_id": profile_id,
"properties_set": list(properties.keys()),
"message": f"Properties updated for user '{profile_id}'",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def increment_property(
client: OpenPanelClient, profile_id: str, property_name: str, value: int = 1
) -> str:
"""Increment a numeric property on a profile"""
try:
result = await client.increment_property(
profile_id=profile_id, property_name=property_name, value=value
)
return json.dumps(
{
"success": True,
"profile_id": profile_id,
"property": property_name,
"increment_by": value,
"message": f"Property '{property_name}' incremented by {value}",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_str = str(e)
hint = ""
if "500" in error_str:
hint = " (Hint: Profile must exist and property must be numeric. Try identifying the user first with identify_user.)"
return json.dumps(
{
"success": False,
"error": error_str + hint,
"profile_id": profile_id,
"property": property_name,
},
indent=2,
ensure_ascii=False,
)
async def decrement_property(
client: OpenPanelClient, profile_id: str, property_name: str, value: int = 1
) -> str:
"""Decrement a numeric property on a profile"""
try:
result = await client.decrement_property(
profile_id=profile_id, property_name=property_name, value=value
)
return json.dumps(
{
"success": True,
"profile_id": profile_id,
"property": property_name,
"decrement_by": value,
"message": f"Property '{property_name}' decremented by {value}",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
error_str = str(e)
hint = ""
if "500" in error_str:
hint = " (Hint: Profile must exist and property must be numeric. Try identifying the user first with identify_user.)"
return json.dumps(
{
"success": False,
"error": error_str + hint,
"profile_id": profile_id,
"property": property_name,
},
indent=2,
ensure_ascii=False,
)
async def create_group(
client: OpenPanelClient,
group_id: str,
group_type: str,
name: str,
properties: dict[str, Any] | None = None,
) -> str:
"""Create or update a group"""
try:
result = await client.track_group(
group_id=group_id, group_type=group_type, name=name, properties=properties
)
return json.dumps(
{
"success": True,
"group_id": group_id,
"group_type": group_type,
"name": name,
"message": f"Group '{name}' ({group_type}) created/updated",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def assign_group(
client: OpenPanelClient,
group_ids: list[str],
profile_id: str | None = None,
) -> str:
"""Assign a user to groups"""
try:
result = await client.assign_group(group_ids=group_ids, profile_id=profile_id)
return json.dumps(
{
"success": True,
"group_ids": group_ids,
"profile_id": profile_id,
"message": f"User assigned to {len(group_ids)} group(s)",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def track_revenue(
client: OpenPanelClient,
amount: float,
currency: str = "USD",
product_id: str | None = None,
product_name: str | None = None,
order_id: str | None = None,
profile_id: str | None = None,
properties: dict[str, Any] | None = None,
client_ip: str | None = None,
) -> str:
"""Track a revenue/purchase event"""
try:
event_properties = {
"amount": amount,
"currency": currency,
}
if product_id:
event_properties["product_id"] = product_id
if product_name:
event_properties["product_name"] = product_name
if order_id:
event_properties["order_id"] = order_id
if properties:
event_properties.update(properties)
result = await client.track_event(
name="revenue", properties=event_properties, profile_id=profile_id, client_ip=client_ip
)
return json.dumps(
{
"success": True,
"event": "revenue",
"amount": amount,
"currency": currency,
"order_id": order_id,
"message": f"Revenue of {amount} {currency} tracked",
"response": result,
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)
async def track_batch(
client: OpenPanelClient,
events: list[dict[str, Any]],
client_ip: str | None = None,
user_agent: str | None = None,
) -> str:
"""Track multiple events in batch"""
try:
results = []
errors = []
for event in events:
try:
await client.track_event(
name=event.get("name"),
properties=event.get("properties"),
profile_id=event.get("profile_id"),
timestamp=event.get("timestamp"),
client_ip=client_ip,
user_agent=user_agent,
)
results.append({"name": event.get("name"), "success": True})
except Exception as e:
errors.append({"name": event.get("name"), "error": str(e)})
return json.dumps(
{
"success": len(errors) == 0,
"total": len(events),
"tracked": len(results),
"failed": len(errors),
"results": results,
"errors": errors if errors else None,
"message": f"Batch tracked {len(results)}/{len(events)} events",
},
indent=2,
ensure_ascii=False,
)
except Exception as e:
return json.dumps({"success": False, "error": str(e)}, indent=2, ensure_ascii=False)