Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
14 changes: 14 additions & 0 deletions web/oss/src/components/Sidebar/SettingsSidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
Buildings,
ClockCounterClockwise,
Key,
Lightning,
Link,
Receipt,
Sparkle,
Expand Down Expand Up @@ -46,6 +47,7 @@ const SettingsSidebar: FC<SettingsSidebarProps> = ({lastPath}) => {
const canShowUsageBilling = isEE() && isOwner
const billingEnabled = isBillingEnabled()
const canShowTools = isToolsEnabled()
const canShowTriggers = isToolsEnabled()
// Audit Log is an EE feature. Within EE the tab is gated by `view_events`;
// the page content is gated separately by the `Flag.AUDIT` entitlement.
const canShowAuditLog = isEE() && canViewEvents
Expand All @@ -57,6 +59,7 @@ const SettingsSidebar: FC<SettingsSidebarProps> = ({lastPath}) => {
(requestedTab === "organization" && !canShowOrganization) ||
(requestedTab === "billing" && !canShowUsageBilling) ||
(requestedTab === "tools" && !canShowTools) ||
(requestedTab === "triggers" && !canShowTriggers) ||
(requestedTab === "apiKeys" && !canViewApiKeys) ||
(requestedTab === "auditLog" && !canShowAuditLog) ||
(requestedTab === "account" && !canShowAccount)
Expand All @@ -69,6 +72,7 @@ const SettingsSidebar: FC<SettingsSidebarProps> = ({lastPath}) => {
canShowUsageBilling,
canShowOrganization,
canShowTools,
canShowTriggers,
canViewApiKeys,
canShowAuditLog,
canShowAccount,
Expand Down Expand Up @@ -107,6 +111,15 @@ const SettingsSidebar: FC<SettingsSidebarProps> = ({lastPath}) => {
},
]
: []),
...(canShowTriggers
? [
{
key: "triggers",
title: "Triggers",
icon: <Lightning size={16} className="mt-0.5" />,
},
]
: []),
{
key: "automations",
title: "Automations",
Expand Down Expand Up @@ -156,6 +169,7 @@ const SettingsSidebar: FC<SettingsSidebarProps> = ({lastPath}) => {
billingEnabled,
canShowOrganization,
canShowTools,
canShowTriggers,
canViewApiKeys,
canShowAuditLog,
canShowAccount,
Expand Down
9 changes: 9 additions & 0 deletions web/oss/src/components/pages/settings/Triggers/Triggers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import GatewayTriggersSection from "./components/GatewayTriggersSection"

export default function Triggers() {
return (
<div className="flex flex-col gap-6">
<GatewayTriggersSection />
</div>
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import {useCallback, useMemo} from "react"

import {
eventsDrawerAtom,
useTriggerConnectionsQuery,
type TriggerConnection,
} from "@agenta/entities/gatewayTrigger"
import {ConnectionStatusBadge} from "@agenta/entity-ui/gatewayTool"
import {TriggerEventsDrawer} from "@agenta/entity-ui/gatewayTrigger"
import {Lightning} from "@phosphor-icons/react"
import {Button, Empty, Table, Tag, Tooltip, Typography} from "antd"
import type {ColumnsType} from "antd/es/table"
import {useSetAtom} from "jotai"

import {formatDay} from "@/oss/lib/helpers/dateTimeHelper"

const DEFAULT_PROVIDER = "composio"

export default function GatewayTriggersSection() {
const {connections, isLoading} = useTriggerConnectionsQuery()
const setEventsDrawer = useSetAtom(eventsDrawerAtom)

const openEvents = useCallback(
(record: TriggerConnection) => {
setEventsDrawer({
providerKey: record.provider_key ?? DEFAULT_PROVIDER,
integrationKey: record.integration_key,
integrationName: record.name ?? record.slug ?? record.integration_key,
connectionId: record.id ?? undefined,
})
},
[setEventsDrawer],
)

const columns: ColumnsType<TriggerConnection> = useMemo(
() => [
{
title: "Integration",
key: "integration",
onHeaderCell: () => ({style: {minWidth: 160}}),
render: (_, record) => (
<Tag
bordered={false}
color="default"
className="bg-[var(--ag-c-0517290F)] px-2 py-[1px]"
>
{record.integration_key}
</Tag>
),
},
{
title: "Name",
key: "name",
onHeaderCell: () => ({style: {minWidth: 160}}),
render: (_, record) => (
<Typography.Text>{record.name || record.slug}</Typography.Text>
),
},
{
title: "Status",
key: "status",
onHeaderCell: () => ({style: {minWidth: 120}}),
render: (_, record) => <ConnectionStatusBadge connection={record} />,
},
{
title: "Created at",
dataIndex: "created_at",
key: "created_at",
onHeaderCell: () => ({style: {minWidth: 160}}),
render: (value: string) =>
value ? formatDay({date: value, outputFormat: "YYYY-MM-DD HH:mm"}) : "-",
},
{
title: "",
key: "actions",
width: 120,
fixed: "right",
align: "right",
render: (_, record) => (
<Button
size="small"
icon={<Lightning size={14} />}
onClick={(e) => {
e.stopPropagation()
openEvents(record)
}}
>
Events
</Button>
),
},
],
[openEvents],
)

return (
<>
<section className="flex flex-col gap-2">
<div className="flex items-center gap-2">
<Typography.Text className="text-sm font-medium">
Trigger integrations
</Typography.Text>
<Tooltip title="Browse the events of a connected integration">
<Lightning size={14} />
</Tooltip>
</div>

<Typography.Text type="secondary" className="text-xs">
Triggers reuse the same connections as tools. Connect an integration under
Tools, then browse its events here.
</Typography.Text>

<Table<TriggerConnection>
className="ph-no-capture"
columns={columns}
dataSource={connections}
rowKey={(record) => record.id ?? record.slug ?? record.integration_key}
bordered
pagination={false}
loading={isLoading}
locale={{
emptyText: <Empty description="No connected integrations yet" />,
}}
onRow={(record) => ({
onClick: () => openEvents(record),
className: "cursor-pointer",
})}
/>
</section>

<TriggerEventsDrawer />
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ const Tools = dynamic(() => import("@/oss/components/pages/settings/Tools/Tools"
ssr: false,
})

const Triggers = dynamic(() => import("@/oss/components/pages/settings/Triggers/Triggers"), {
ssr: false,
})

const Organization = dynamic(() => import("@/oss/components/pages/settings/Organization"), {
ssr: false,
})
Expand Down Expand Up @@ -71,12 +75,14 @@ export const Settings: React.FC<SettingsProps> = ({AuditLogComponent}) => {
const canShowBilling = isEE() && isOwner
const billingEnabled = isBillingEnabled()
const canShowTools = isToolsEnabled()
const canShowTriggers = isToolsEnabled()
const canShowAuditLog = isEE() && canViewEvents
const canShowAccount = isEE()
const resolvedTab =
(tab === "organization" && !canShowOrganization) ||
(tab === "billing" && !canShowBilling) ||
(tab === "tools" && !canShowTools) ||
(tab === "triggers" && !canShowTriggers) ||
(tab === "apiKeys" && !canViewApiKeys) ||
(tab === "auditLog" && !canShowAuditLog) ||
(tab === "account" && !canShowAccount)
Expand Down Expand Up @@ -124,6 +130,8 @@ export const Settings: React.FC<SettingsProps> = ({AuditLogComponent}) => {
return "Providers & Models"
case "tools":
return "Tools"
case "triggers":
return "Triggers"
case "apiKeys":
return "API Keys"
case "automations":
Expand Down Expand Up @@ -177,6 +185,8 @@ export const Settings: React.FC<SettingsProps> = ({AuditLogComponent}) => {
return {content: <Secrets />, title: "Providers & Models"}
case "tools":
return {content: <Tools />, title: "Tools"}
case "triggers":
return {content: <Triggers />, title: "Triggers"}
case "apiKeys":
return {content: <APIKeys />, title: "API Keys"}
case "billing":
Expand Down
1 change: 1 addition & 0 deletions web/packages/agenta-entities/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"./event/state": "./src/event/state/index.ts",
"./secret": "./src/secret/index.ts",
"./gatewayTool": "./src/gatewayTool/index.ts",
"./gatewayTrigger": "./src/gatewayTrigger/index.ts",
"./environment": "./src/environment/index.ts",
"./simpleQueue": "./src/simpleQueue/index.ts",
"./simpleQueue/etl": "./src/simpleQueue/etl/index.ts",
Expand Down
119 changes: 119 additions & 0 deletions web/packages/agenta-entities/src/gatewayTrigger/api/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Gateway-trigger API functions.
*
* Catalog browse + connection list over the `/triggers/*` endpoints. Each
* response is validated against the frozen zod schema at the boundary
* (`safeParseWithLogging`), so a backend drift surfaces as a logged parse
* failure rather than a downstream crash.
*
* `/triggers/connections/query` reads the same shared `gateway_connections`
* rows as `/tools/connections/query` (WP0); the connection shape is reused
* from gatewayTool so the two lists stay byte-compatible (F2).
*/

import {safeParseWithLogging} from "../../shared"
import {
triggerCatalogEventResponseSchema,
triggerCatalogEventsResponseSchema,
triggerCatalogProviderResponseSchema,
triggerCatalogProvidersResponseSchema,
triggerConnectionsResponseSchema,
type TriggerCatalogEventResponse,
type TriggerCatalogEventsResponse,
type TriggerCatalogProviderResponse,
type TriggerCatalogProvidersResponse,
type TriggerConnectionsResponse,
} from "../core/types"

import {axios, projectScopedParams, triggersBaseUrl} from "./client"

// --- Catalog browse ---

export const fetchTriggerProviders = async (): Promise<TriggerCatalogProvidersResponse> => {
const {data} = await axios.get(`${triggersBaseUrl()}/catalog/providers/`, projectScopedParams())
return (
safeParseWithLogging(
triggerCatalogProvidersResponseSchema,
data,
"[fetchTriggerProviders]",
) ?? {count: 0, providers: []}
)
}

export const fetchTriggerProvider = async (
providerKey: string,
): Promise<TriggerCatalogProviderResponse> => {
const {data} = await axios.get(
`${triggersBaseUrl()}/catalog/providers/${providerKey}`,
projectScopedParams(),
)
return (
safeParseWithLogging(
triggerCatalogProviderResponseSchema,
data,
"[fetchTriggerProvider]",
) ?? {count: 0, provider: null}
)
}

export const fetchTriggerEvents = async (
providerKey: string,
integrationKey: string,
params?: {query?: string; limit?: number; cursor?: string},
): Promise<TriggerCatalogEventsResponse> => {
const {data} = await axios.get(
`${triggersBaseUrl()}/catalog/providers/${providerKey}/integrations/${integrationKey}/events/`,
projectScopedParams({
query: params?.query,
limit: params?.limit,
cursor: params?.cursor,
}),
)
return (
safeParseWithLogging(triggerCatalogEventsResponseSchema, data, "[fetchTriggerEvents]") ?? {
count: 0,
total: 0,
cursor: null,
events: [],
}
)
}

export const fetchTriggerEvent = async (
providerKey: string,
integrationKey: string,
eventKey: string,
): Promise<TriggerCatalogEventResponse> => {
const {data} = await axios.get(
`${triggersBaseUrl()}/catalog/providers/${providerKey}/integrations/${integrationKey}/events/${eventKey}`,
projectScopedParams(),
)
return (
safeParseWithLogging(triggerCatalogEventResponseSchema, data, "[fetchTriggerEvent]") ?? {
count: 0,
event: null,
}
)
}

// --- Connections (shared rows, WP0 view; F2) ---

export const queryTriggerConnections = async (params?: {
provider_key?: string
integration_key?: string
}): Promise<TriggerConnectionsResponse> => {
const {data} = await axios.post(
`${triggersBaseUrl()}/connections/query`,
{
provider_key: params?.provider_key,
integration_key: params?.integration_key,
},
projectScopedParams(),
)
const validated = safeParseWithLogging(
triggerConnectionsResponseSchema,
data,
"[queryTriggerConnections]",
)
return (validated as TriggerConnectionsResponse | null) ?? {count: 0, connections: []}
}
Loading
Loading