From 3c12a398ae2f9bb3846a33c7d04b5cd23ba3f7f4 Mon Sep 17 00:00:00 2001 From: Nicholas Tindle Date: Fri, 6 Sep 2024 14:22:24 -0500 Subject: [PATCH] feat: marketplace analytics (#7998) --- .../marketplace/AgentDetailContent.tsx | 18 +++++++++-- .../src/components/marketplace/actions.ts | 9 ++++++ .../src/lib/marketplace-api/client.ts | 8 +++++ .../src/lib/marketplace-api/types.ts | 31 +++++++++++++++++++ rnd/market/market/app.py | 4 +++ rnd/market/market/db.py | 22 ++++++++++++- rnd/market/market/model.py | 29 +++++++++++++++++ rnd/market/market/routes/analytics.py | 26 ++++++++++++++++ .../migrations/20240905162237_/migration.sql | 19 ++++++++++++ rnd/market/schema.prisma | 17 ++++++++++ 10 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 rnd/autogpt_builder/src/components/marketplace/actions.ts create mode 100644 rnd/market/market/routes/analytics.py create mode 100644 rnd/market/migrations/20240905162237_/migration.sql diff --git a/rnd/autogpt_builder/src/components/marketplace/AgentDetailContent.tsx b/rnd/autogpt_builder/src/components/marketplace/AgentDetailContent.tsx index a57eaa586..aeaf9163e 100644 --- a/rnd/autogpt_builder/src/components/marketplace/AgentDetailContent.tsx +++ b/rnd/autogpt_builder/src/components/marketplace/AgentDetailContent.tsx @@ -11,7 +11,10 @@ import { ChevronUp, } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { AgentDetailResponse } from "@/lib/marketplace-api"; +import { + AgentDetailResponse, + InstallationLocation, +} from "@/lib/marketplace-api"; import dynamic from "next/dynamic"; import { Node, Edge } from "@xyflow/react"; import MarketplaceAPI from "@/lib/marketplace-api"; @@ -32,6 +35,7 @@ const Background = dynamic( import "@xyflow/react/dist/style.css"; import { beautifyString } from "@/lib/utils"; +import { makeAnalyticsEvent } from "./actions"; function convertGraphToReactFlow(graph: any): { nodes: Node[]; edges: Edge[] } { const nodes: Node[] = graph.nodes.map((node: any) => { @@ -96,8 +100,16 @@ async function installGraph(id: string): Promise { nodes: agent.graph.nodes, links: agent.graph.links, }; - await serverAPI.createTemplate(data); - console.log(`Agent installed successfully`); + const result = await serverAPI.createTemplate(data); + makeAnalyticsEvent({ + event_name: "agent_installed_from_marketplace", + event_data: { + marketplace_agent_id: id, + installed_agent_id: result.id, + installation_location: InstallationLocation.CLOUD, + }, + }); + console.log(`Agent installed successfully`, result); } catch (error) { console.error(`Error installing agent:`, error); throw error; diff --git a/rnd/autogpt_builder/src/components/marketplace/actions.ts b/rnd/autogpt_builder/src/components/marketplace/actions.ts new file mode 100644 index 000000000..2fc158d4a --- /dev/null +++ b/rnd/autogpt_builder/src/components/marketplace/actions.ts @@ -0,0 +1,9 @@ +"use server"; + +import MarketplaceAPI, { AnalyticsEvent } from "@/lib/marketplace-api"; + +export async function makeAnalyticsEvent(event: AnalyticsEvent) { + const apiUrl = process.env.AGPT_SERVER_API_URL; + const api = new MarketplaceAPI(); + await api.makeAnalyticsEvent(event); +} diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/client.ts b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts index 9cc62c39b..a7b978677 100644 --- a/rnd/autogpt_builder/src/lib/marketplace-api/client.ts +++ b/rnd/autogpt_builder/src/lib/marketplace-api/client.ts @@ -8,6 +8,7 @@ import { AgentWithRank, FeaturedAgentResponse, UniqueCategoriesResponse, + AnalyticsEvent, } from "./types"; export default class MarketplaceAPI { @@ -193,6 +194,13 @@ export default class MarketplaceAPI { return this._get("/admin/categories"); } + async makeAnalyticsEvent(event: AnalyticsEvent) { + if (event.event_name === "agent_installed_from_marketplace") { + return this._post("/analytics/agent-installed", event.event_data); + } + throw new Error("Invalid event name"); + } + private async _get(path: string) { return this._request("GET", path); } diff --git a/rnd/autogpt_builder/src/lib/marketplace-api/types.ts b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts index 145c973e4..87772e122 100644 --- a/rnd/autogpt_builder/src/lib/marketplace-api/types.ts +++ b/rnd/autogpt_builder/src/lib/marketplace-api/types.ts @@ -76,3 +76,34 @@ export type AgentResponse = Agent; export type UniqueCategoriesResponse = { unique_categories: string[]; }; + +export enum InstallationLocation { + LOCAL = "local", + CLOUD = "cloud", +} + +export type AgentInstalledFromMarketplaceEventData = { + marketplace_agent_id: string; + installed_agent_id: string; + installation_location: InstallationLocation; +}; + +export type AgentInstalledFromTemplateEventData = { + template_id: string; + installed_agent_id: string; + installation_location: InstallationLocation; +}; + +export interface AgentInstalledFromMarketplaceEvent { + event_name: "agent_installed_from_marketplace"; + event_data: AgentInstalledFromMarketplaceEventData; +} + +export interface AgentInstalledFromTemplateEvent { + event_name: "agent_installed_from_template"; + event_data: AgentInstalledFromTemplateEventData; +} + +export type AnalyticsEvent = + | AgentInstalledFromMarketplaceEvent + | AgentInstalledFromTemplateEvent; diff --git a/rnd/market/market/app.py b/rnd/market/market/app.py index 0d8a7ba62..a6fa4752c 100644 --- a/rnd/market/market/app.py +++ b/rnd/market/market/app.py @@ -18,6 +18,7 @@ import market.routes.admin import market.routes.agents import market.routes.search import market.routes.submissions +import market.routes.analytics dotenv.load_dotenv() @@ -75,6 +76,9 @@ app.include_router(market.routes.agents.router, tags=["agents"]) app.include_router(market.routes.search.router, tags=["search"]) app.include_router(market.routes.submissions.router, tags=["submissions"]) app.include_router(market.routes.admin.router, prefix="/admin", tags=["admin"]) +app.include_router( + market.routes.analytics.router, prefix="/analytics", tags=["analytics"] +) @app.get("/health") diff --git a/rnd/market/market/db.py b/rnd/market/market/db.py index 6ca92722f..a85720eee 100644 --- a/rnd/market/market/db.py +++ b/rnd/market/market/db.py @@ -634,10 +634,30 @@ FROM ( model=market.model.CategoriesResponse, ) if not categories: - raise AgentQueryError("No categories found") + return market.model.CategoriesResponse(unique_categories=[]) return categories except prisma.errors.PrismaError as e: raise AgentQueryError(f"Database query failed: {str(e)}") + except Exception as e: + # raise AgentQueryError(f"Unexpected error occurred: {str(e)}") + return market.model.CategoriesResponse(unique_categories=[]) + + +async def create_agent_installed_event( + event_data: market.model.AgentInstalledFromMarketplaceEventData, +): + try: + await prisma.models.InstallTracker.prisma().create( + data={ + "installedAgentId": event_data.installed_agent_id, + "marketplaceAgentId": event_data.marketplace_agent_id, + "installationLocation": prisma.enums.InstallationLocation( + event_data.installation_location.name + ), + } + ) + except prisma.errors.PrismaError as e: + raise AgentQueryError(f"Database query failed: {str(e)}") except Exception as e: raise AgentQueryError(f"Unexpected error occurred: {str(e)}") diff --git a/rnd/market/market/model.py b/rnd/market/market/model.py index 864b17787..66dda0d0b 100644 --- a/rnd/market/market/model.py +++ b/rnd/market/market/model.py @@ -4,6 +4,35 @@ import typing import prisma.enums import pydantic +from enum import Enum +from typing import Literal, Union + +class InstallationLocation(str, Enum): + LOCAL = "local" + CLOUD = "cloud" + +class AgentInstalledFromMarketplaceEventData(pydantic.BaseModel): + marketplace_agent_id: str + installed_agent_id: str + installation_location: InstallationLocation + +class AgentInstalledFromTemplateEventData(pydantic.BaseModel): + template_id: str + installed_agent_id: str + installation_location: InstallationLocation + +class AgentInstalledFromMarketplaceEvent(pydantic.BaseModel): + event_name: Literal["agent_installed_from_marketplace"] + event_data: AgentInstalledFromMarketplaceEventData + +class AgentInstalledFromTemplateEvent(pydantic.BaseModel): + event_name: Literal["agent_installed_from_template"] + event_data: AgentInstalledFromTemplateEventData + +AnalyticsEvent = Union[AgentInstalledFromMarketplaceEvent, AgentInstalledFromTemplateEvent] + +class AnalyticsRequest(pydantic.BaseModel): + event: AnalyticsEvent class AddAgentRequest(pydantic.BaseModel): graph: dict[str, typing.Any] diff --git a/rnd/market/market/routes/analytics.py b/rnd/market/market/routes/analytics.py new file mode 100644 index 000000000..87adbca07 --- /dev/null +++ b/rnd/market/market/routes/analytics.py @@ -0,0 +1,26 @@ +import fastapi + +import market.db +import market.model + +router = fastapi.APIRouter() + + +@router.post("/agent-installed") +async def agent_installed_endpoint( + event_data: market.model.AgentInstalledFromMarketplaceEventData, +): + """ + Endpoint to track agent installation events from the marketplace. + + Args: + event_data (market.model.AgentInstalledFromMarketplaceEventData): The event data. + """ + try: + await market.db.create_agent_installed_event(event_data) + except market.db.AgentQueryError as e: + raise fastapi.HTTPException(status_code=400, detail=str(e)) + except Exception as e: + raise fastapi.HTTPException( + status_code=500, detail=f"An unexpected error occurred: {e}" + ) diff --git a/rnd/market/migrations/20240905162237_/migration.sql b/rnd/market/migrations/20240905162237_/migration.sql new file mode 100644 index 000000000..ca2dc97bc --- /dev/null +++ b/rnd/market/migrations/20240905162237_/migration.sql @@ -0,0 +1,19 @@ +-- CreateEnum +CREATE TYPE "InstallationLocation" AS ENUM ('LOCAL', 'CLOUD'); + +-- CreateTable +CREATE TABLE "InstallTracker" ( + "id" UUID NOT NULL DEFAULT gen_random_uuid(), + "marketplaceAgentId" UUID NOT NULL, + "installedAgentId" UUID NOT NULL, + "installationLocation" "InstallationLocation" NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + + CONSTRAINT "InstallTracker_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "InstallTracker_marketplaceAgentId_installedAgentId_key" ON "InstallTracker"("marketplaceAgentId", "installedAgentId"); + +-- AddForeignKey +ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/rnd/market/schema.prisma b/rnd/market/schema.prisma index fe146e95c..f89af707d 100644 --- a/rnd/market/schema.prisma +++ b/rnd/market/schema.prisma @@ -40,6 +40,7 @@ model Agents { graph Json AnalyticsTracker AnalyticsTracker[] FeaturedAgent FeaturedAgent? + InstallTracker InstallTracker[] @@id(name: "graphVersionId", [id, version]) } @@ -52,6 +53,22 @@ model AnalyticsTracker { downloads Int } +enum InstallationLocation { + LOCAL + CLOUD +} + +model InstallTracker { + id String @id @default(dbgenerated("gen_random_uuid()")) @db.Uuid + marketplaceAgentId String @db.Uuid + marketplaceAgent Agents @relation(fields: [marketplaceAgentId], references: [id]) + installedAgentId String @db.Uuid + installationLocation InstallationLocation + createdAt DateTime @default(now()) + + @@unique([marketplaceAgentId, installedAgentId]) +} + model FeaturedAgent { id String @id @unique @default(dbgenerated("gen_random_uuid()")) @db.Uuid agentId String @unique @db.Uuid