From a345e0be02af8b0f55bc474616f660265e20d160 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Thu, 12 Feb 2026 17:09:40 +0100 Subject: [PATCH] Add webhook calls ui Signed-off-by: Sacha Al Himdani --- .../core/WebhooksSettingsPageQuery.graphql.ts | 35 +- ...WebhooksSettingsPage_callsQuery.graphql.ts | 257 ++++ .../settings/WebhooksSettingsPage.tsx | 200 +++- pkg/coredata/webhook_call.go | 79 ++ pkg/coredata/webhook_call_order_field.go | 55 + pkg/probo/webhook_configuration_service.go | 52 + pkg/server/api/console/v1/schema.graphql | 58 + pkg/server/api/console/v1/schema/schema.go | 1043 +++++++++++++++++ pkg/server/api/console/v1/types/types.go | 19 + .../api/console/v1/types/webhook_call.go | 79 ++ pkg/server/api/console/v1/v1_resolver.go | 53 + 11 files changed, 1925 insertions(+), 5 deletions(-) create mode 100644 apps/console/src/__generated__/core/WebhooksSettingsPage_callsQuery.graphql.ts create mode 100644 pkg/coredata/webhook_call_order_field.go create mode 100644 pkg/server/api/console/v1/types/webhook_call.go diff --git a/apps/console/src/__generated__/core/WebhooksSettingsPageQuery.graphql.ts b/apps/console/src/__generated__/core/WebhooksSettingsPageQuery.graphql.ts index 47a84b08f..5b549801b 100644 --- a/apps/console/src/__generated__/core/WebhooksSettingsPageQuery.graphql.ts +++ b/apps/console/src/__generated__/core/WebhooksSettingsPageQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -20,6 +20,9 @@ export type WebhooksSettingsPageQuery$data = { readonly webhookConfigurations: { readonly edges: ReadonlyArray<{ readonly node: { + readonly calls: { + readonly totalCount: number; + }; readonly endpointUrl: string; readonly id: string; readonly selectedEvents: ReadonlyArray; @@ -98,6 +101,30 @@ v4 = [ "name": "selectedEvents", "storageKey": null }, + { + "alias": null, + "args": [ + { + "kind": "Literal", + "name": "first", + "value": 0 + } + ], + "concreteType": "WebhookCallConnection", + "kind": "LinkedField", + "name": "calls", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + } + ], + "storageKey": "calls(first:0)" + }, (v2/*: any*/) ], "storageKey": null @@ -238,7 +265,7 @@ return { ] }, "params": { - "cacheID": "e6aa5c6b8142b654e2a3b7bebec59f83", + "cacheID": "97b7c454586fe958e456fa7929b46686", "id": null, "metadata": { "connection": [ @@ -255,11 +282,11 @@ return { }, "name": "WebhooksSettingsPageQuery", "operationKind": "query", - "text": "query WebhooksSettingsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n webhookConfigurations(first: 50) {\n edges {\n node {\n id\n endpointUrl\n selectedEvents\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n" + "text": "query WebhooksSettingsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n webhookConfigurations(first: 50) {\n edges {\n node {\n id\n endpointUrl\n selectedEvents\n calls(first: 0) {\n totalCount\n }\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n }\n }\n }\n id\n }\n}\n" } }; })(); -(node as any).hash = "2a68dcb3a875a982b7d8300aa72c012b"; +(node as any).hash = "58012f55644dae31fe5e18c2c1e193a7"; export default node; diff --git a/apps/console/src/__generated__/core/WebhooksSettingsPage_callsQuery.graphql.ts b/apps/console/src/__generated__/core/WebhooksSettingsPage_callsQuery.graphql.ts new file mode 100644 index 000000000..807b4d3d5 --- /dev/null +++ b/apps/console/src/__generated__/core/WebhooksSettingsPage_callsQuery.graphql.ts @@ -0,0 +1,257 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type WebhookCallStatus = "FAILED" | "SUCCEEDED"; +export type WebhooksSettingsPage_callsQuery$variables = { + after?: string | null | undefined; + first?: number | null | undefined; + webhookConfigurationId: string; +}; +export type WebhooksSettingsPage_callsQuery$data = { + readonly node: { + readonly calls?: { + readonly edges: ReadonlyArray<{ + readonly node: { + readonly createdAt: string; + readonly endpointUrl: string; + readonly id: string; + readonly response: string | null | undefined; + readonly status: WebhookCallStatus; + }; + }>; + readonly pageInfo: { + readonly endCursor: string | null | undefined; + readonly hasNextPage: boolean; + }; + readonly totalCount: number; + }; + }; +}; +export type WebhooksSettingsPage_callsQuery = { + response: WebhooksSettingsPage_callsQuery$data; + variables: WebhooksSettingsPage_callsQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "after" +}, +v1 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "first" +}, +v2 = { + "defaultValue": null, + "kind": "LocalArgument", + "name": "webhookConfigurationId" +}, +v3 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "webhookConfigurationId" + } +], +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v5 = { + "kind": "InlineFragment", + "selections": [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "after", + "variableName": "after" + }, + { + "kind": "Variable", + "name": "first", + "variableName": "first" + } + ], + "concreteType": "WebhookCallConnection", + "kind": "LinkedField", + "name": "calls", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "totalCount", + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "PageInfo", + "kind": "LinkedField", + "name": "pageInfo", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "hasNextPage", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endCursor", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": null, + "concreteType": "WebhookCallEdge", + "kind": "LinkedField", + "name": "edges", + "plural": true, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "WebhookCall", + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v4/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "status", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "endpointUrl", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "createdAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "response", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } + ], + "type": "WebhookConfiguration", + "abstractKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/), + (v2/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "WebhooksSettingsPage_callsQuery", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v5/*: any*/) + ], + "storageKey": null + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v2/*: any*/), + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "WebhooksSettingsPage_callsQuery", + "selections": [ + { + "alias": null, + "args": (v3/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null + }, + (v5/*: any*/), + (v4/*: any*/) + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "9371a8918daebbb2a660b02649ba0241", + "id": null, + "metadata": {}, + "name": "WebhooksSettingsPage_callsQuery", + "operationKind": "query", + "text": "query WebhooksSettingsPage_callsQuery(\n $webhookConfigurationId: ID!\n $first: Int\n $after: CursorKey\n) {\n node(id: $webhookConfigurationId) {\n __typename\n ... on WebhookConfiguration {\n calls(first: $first, after: $after) {\n totalCount\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n status\n endpointUrl\n createdAt\n response\n }\n }\n }\n }\n id\n }\n}\n" + } +}; +})(); + +(node as any).hash = "de18874117b6272fdf2e22871adc4a5d"; + +export default node; diff --git a/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx b/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx index 37e230657..5e3d07548 100644 --- a/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx +++ b/apps/console/src/pages/organizations/settings/WebhooksSettingsPage.tsx @@ -1,5 +1,6 @@ import { useTranslate } from "@probo/i18n"; import { + Badge, Button, Card, Checkbox, @@ -17,11 +18,12 @@ import { useDialogRef, useToast, } from "@probo/ui"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { type PreloadedQuery, usePreloadedQuery, useRelayEnvironment } from "react-relay"; import { ConnectionHandler, fetchQuery, graphql } from "relay-runtime"; import { z } from "zod"; +import type { WebhooksSettingsPage_callsQuery } from "#/__generated__/core/WebhooksSettingsPage_callsQuery.graphql"; import type { WebhooksSettingsPage_createMutation } from "#/__generated__/core/WebhooksSettingsPage_createMutation.graphql"; import type { WebhooksSettingsPage_deleteMutation } from "#/__generated__/core/WebhooksSettingsPage_deleteMutation.graphql"; import type { WebhooksSettingsPage_signingSecretQuery } from "#/__generated__/core/WebhooksSettingsPage_signingSecretQuery.graphql"; @@ -43,6 +45,9 @@ export const webhooksSettingsPageQuery = graphql` id endpointUrl selectedEvents + calls(first: 0) { + totalCount + } } } } @@ -93,6 +98,35 @@ const signingSecretQuery = graphql` } `; +const webhookCallsQuery = graphql` + query WebhooksSettingsPage_callsQuery( + $webhookConfigurationId: ID! + $first: Int + $after: CursorKey + ) { + node(id: $webhookConfigurationId) { + ... on WebhookConfiguration { + calls(first: $first, after: $after) { + totalCount + pageInfo { + hasNextPage + endCursor + } + edges { + node { + id + status + endpointUrl + createdAt + response + } + } + } + } + } + } +`; + const deleteWebhookConfigurationMutation = graphql` mutation WebhooksSettingsPage_deleteMutation( $input: DeleteWebhookConfigurationInput! @@ -251,6 +285,151 @@ function WebhookFormDialog({ ); } +function CallStatusBadge({ status }: { status: string }) { + const { __ } = useTranslate(); + if (status === "SUCCEEDED") { + return {__("Succeeded")}; + } + return {__("Failed")}; +} + +function formatDate(iso: string) { + return new Date(iso).toLocaleString(); +} + +function WebhookCallsDialog({ + webhookConfigurationId, + endpointUrl, + onClose, +}: { + webhookConfigurationId: string; + endpointUrl: string; + onClose: () => void; +}) { + const { __ } = useTranslate(); + const environment = useRelayEnvironment(); + const dialogRef = useDialogRef(); + type CallNode = NonNullable["edges"][number]["node"]; + const [calls, setCalls] = useState([]); + const [loading, setLoading] = useState(true); + const [hasNextPage, setHasNextPage] = useState(false); + const [endCursor, setEndCursor] = useState(null); + const [totalCount, setTotalCount] = useState(0); + + const PAGE_SIZE = 20; + + const loadCalls = useCallback( + async (after?: string | null) => { + setLoading(true); + try { + const data = await fetchQuery( + environment, + webhookCallsQuery, + { + webhookConfigurationId, + first: PAGE_SIZE, + after: after ?? null, + }, + ).toPromise(); + + const connection = data?.node?.calls; + if (connection) { + const newCalls = connection.edges.map(e => e.node); + setCalls(prev => after ? [...prev, ...newCalls] : newCalls); + setHasNextPage(connection.pageInfo.hasNextPage); + setEndCursor(connection.pageInfo.endCursor ?? null); + setTotalCount(connection.totalCount); + } + } finally { + setLoading(false); + } + }, + [environment, webhookConfigurationId], + ); + + useEffect(() => { + void loadCalls(); + dialogRef.current?.open(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + return ( + + +

+ {endpointUrl} + {totalCount > 0 && ( + + {`(${totalCount} ${__("total")})`} + + )} +

+ {calls.length === 0 && !loading + ? ( +

+ {__("No webhook calls recorded yet.")} +

+ ) + : ( +
+ {calls.map(call => ( +
+
+ + + {formatDate(call.createdAt)} + +
+

+ {call.endpointUrl} +

+ {call.response && ( +
+ + {__("Response")} + +
+                          {(() => {
+                            try {
+                              return JSON.stringify(JSON.parse(call.response), null, 2);
+                            } catch {
+                              return call.response;
+                            }
+                          })()}
+                        
+
+ )} +
+ ))} +
+ )} + {loading && ( +
+ +
+ )} +
+ {hasNextPage && !loading && ( + + + + )} +
+ ); +} + export function WebhooksSettingsPage(props: { queryRef: PreloadedQuery; }) { @@ -262,6 +441,7 @@ export function WebhooksSettingsPage(props: { const [deletingId, setDeletingId] = useState(null); const [revealedSecrets, setRevealedSecrets] = useState>({}); const [loadingSecrets, setLoadingSecrets] = useState>(new Set()); + const [viewingCallsId, setViewingCallsId] = useState(null); const fetchSigningSecret = useCallback( async (webhookConfigurationId: string): Promise => { @@ -501,6 +681,12 @@ export function WebhooksSettingsPage(props: {
+ + + {viewingCallsId && (() => { + const webhook = webhooks.find(e => e.node.id === viewingCallsId); + if (!webhook) return null; + return ( + setViewingCallsId(null)} + /> + ); + })()}
); } diff --git a/pkg/coredata/webhook_call.go b/pkg/coredata/webhook_call.go index 82aba8993..560b2156a 100644 --- a/pkg/coredata/webhook_call.go +++ b/pkg/coredata/webhook_call.go @@ -18,11 +18,13 @@ import ( "context" "encoding/json" "fmt" + "maps" "time" "github.com/jackc/pgx/v5" "go.gearno.de/kit/pg" "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" ) type ( @@ -39,6 +41,83 @@ type ( WebhookCalls []*WebhookCall ) +func (w WebhookCall) CursorKey(orderBy WebhookCallOrderField) page.CursorKey { + switch orderBy { + case WebhookCallOrderFieldCreatedAt: + return page.NewCursorKey(w.ID, w.CreatedAt) + } + + panic(fmt.Sprintf("unsupported order by: %s", orderBy)) +} + +func (w *WebhookCalls) LoadByConfigurationID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + webhookConfigurationID gid.GID, + cursor *page.Cursor[WebhookCallOrderField], +) error { + q := ` +SELECT + id, + webhook_event_id, + webhook_configuration_id, + endpoint_url, + status, + response, + created_at +FROM + webhook_calls +WHERE + %s + AND webhook_configuration_id = @webhook_configuration_id + AND %s +` + q = fmt.Sprintf(q, scope.SQLFragment(), cursor.SQLFragment()) + + args := pgx.NamedArgs{"webhook_configuration_id": webhookConfigurationID} + maps.Copy(args, scope.SQLArguments()) + maps.Copy(args, cursor.SQLArguments()) + + rows, err := conn.Query(ctx, q, args) + if err != nil { + return fmt.Errorf("cannot query webhook calls: %w", err) + } + + calls, err := pgx.CollectRows(rows, pgx.RowToAddrOfStructByName[WebhookCall]) + if err != nil { + return fmt.Errorf("cannot collect webhook calls: %w", err) + } + + *w = calls + return nil +} + +func (w *WebhookCalls) CountByConfigurationID( + ctx context.Context, + conn pg.Conn, + scope Scoper, + webhookConfigurationID gid.GID, +) (int, error) { + q := ` +SELECT COUNT(*) +FROM webhook_calls +WHERE %s + AND webhook_configuration_id = @webhook_configuration_id +` + q = fmt.Sprintf(q, scope.SQLFragment()) + + args := pgx.StrictNamedArgs{"webhook_configuration_id": webhookConfigurationID} + maps.Copy(args, scope.SQLArguments()) + + var count int + if err := conn.QueryRow(ctx, q, args).Scan(&count); err != nil { + return 0, fmt.Errorf("cannot count webhook calls: %w", err) + } + + return count, nil +} + func (w *WebhookCall) Insert( ctx context.Context, conn pg.Conn, diff --git a/pkg/coredata/webhook_call_order_field.go b/pkg/coredata/webhook_call_order_field.go new file mode 100644 index 000000000..cf6a59364 --- /dev/null +++ b/pkg/coredata/webhook_call_order_field.go @@ -0,0 +1,55 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package coredata + +import ( + "fmt" +) + +type ( + WebhookCallOrderField string +) + +const ( + WebhookCallOrderFieldCreatedAt WebhookCallOrderField = "CREATED_AT" +) + +func (p WebhookCallOrderField) Column() string { + return string(p) +} + +func (p WebhookCallOrderField) String() string { + return string(p) +} + +func (p WebhookCallOrderField) IsValid() bool { + switch p { + case WebhookCallOrderFieldCreatedAt: + return true + } + return false +} + +func (p WebhookCallOrderField) MarshalText() ([]byte, error) { + return []byte(p.String()), nil +} + +func (p *WebhookCallOrderField) UnmarshalText(text []byte) error { + *p = WebhookCallOrderField(text) + if !p.IsValid() { + return fmt.Errorf("%s is not a valid WebhookCallOrderField", string(text)) + } + return nil +} diff --git a/pkg/probo/webhook_configuration_service.go b/pkg/probo/webhook_configuration_service.go index 72f955ddd..1eb174373 100644 --- a/pkg/probo/webhook_configuration_service.go +++ b/pkg/probo/webhook_configuration_service.go @@ -261,6 +261,58 @@ func (s WebhookConfigurationService) GetSigningSecret( return wc.DecryptSigningSecret(s.svc.encryptionKey) } +func (s WebhookConfigurationService) ListCallsForConfigurationID( + ctx context.Context, + webhookConfigurationID gid.GID, + cursor *page.Cursor[coredata.WebhookCallOrderField], +) (*page.Page[*coredata.WebhookCall, coredata.WebhookCallOrderField], error) { + var calls coredata.WebhookCalls + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) error { + if err := calls.LoadByConfigurationID(ctx, conn, s.svc.scope, webhookConfigurationID, cursor); err != nil { + return fmt.Errorf("cannot load webhook calls: %w", err) + } + + return nil + }, + ) + + if err != nil { + return nil, err + } + + return page.NewPage(calls, cursor), nil +} + +func (s WebhookConfigurationService) CountCallsForConfigurationID( + ctx context.Context, + webhookConfigurationID gid.GID, +) (int, error) { + var count int + + err := s.svc.pg.WithConn( + ctx, + func(conn pg.Conn) (err error) { + calls := &coredata.WebhookCalls{} + count, err = calls.CountByConfigurationID(ctx, conn, s.svc.scope, webhookConfigurationID) + + if err != nil { + return fmt.Errorf("cannot count webhook calls: %w", err) + } + + return nil + }, + ) + + if err != nil { + return 0, err + } + + return count, nil +} + func (s WebhookConfigurationService) Delete( ctx context.Context, webhookConfigurationID gid.GID, diff --git a/pkg/server/api/console/v1/schema.graphql b/pkg/server/api/console/v1/schema.graphql index 18543b1bd..e65428fb5 100644 --- a/pkg/server/api/console/v1/schema.graphql +++ b/pkg/server/api/console/v1/schema.graphql @@ -2293,6 +2293,14 @@ type WebhookConfiguration implements Node { createdAt: Datetime! updatedAt: Datetime! + calls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: WebhookCallOrder + ): WebhookCallConnection! @goField(forceResolver: true) + permission(action: String!): Boolean! @goField(forceResolver: true) } @@ -2310,6 +2318,56 @@ type WebhookConfigurationEdge { node: WebhookConfiguration! } +enum WebhookCallStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookCallStatus") { + SUCCEEDED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookCallStatusSucceeded") + FAILED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookCallStatusFailed") +} + +enum WebhookCallOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.WebhookCallOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.WebhookCallOrderFieldCreatedAt" + ) +} + +input WebhookCallOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookCallOrderBy" + ) { + field: WebhookCallOrderField! + direction: OrderDirection! +} + +type WebhookCall implements Node { + id: ID! + webhookEventId: ID! + webhookConfigurationId: ID! + endpointUrl: String! + status: WebhookCallStatus! + response: String + createdAt: Datetime! +} + +type WebhookCallConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookCallConnection" + ) { + edges: [WebhookCallEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type WebhookCallEdge { + cursor: CursorKey! + node: WebhookCall! +} + type StateOfApplicability implements Node { id: ID! name: String! diff --git a/pkg/server/api/console/v1/schema/schema.go b/pkg/server/api/console/v1/schema/schema.go index e5d01382f..7edfc6409 100644 --- a/pkg/server/api/console/v1/schema/schema.go +++ b/pkg/server/api/console/v1/schema/schema.go @@ -119,6 +119,7 @@ type ResolverRoot interface { VendorRiskAssessment() VendorRiskAssessmentResolver VendorService() VendorServiceResolver Viewer() ViewerResolver + WebhookCallConnection() WebhookCallConnectionResolver WebhookConfiguration() WebhookConfigurationResolver WebhookConfigurationConnection() WebhookConfigurationConnectionResolver } @@ -1929,7 +1930,29 @@ type ComplexityRoot struct { SignableDocuments func(childComplexity int, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) int } + WebhookCall struct { + CreatedAt func(childComplexity int) int + EndpointURL func(childComplexity int) int + ID func(childComplexity int) int + Response func(childComplexity int) int + Status func(childComplexity int) int + WebhookConfigurationID func(childComplexity int) int + WebhookEventID func(childComplexity int) int + } + + WebhookCallConnection struct { + Edges func(childComplexity int) int + PageInfo func(childComplexity int) int + TotalCount func(childComplexity int) int + } + + WebhookCallEdge struct { + Cursor func(childComplexity int) int + Node func(childComplexity int) int + } + WebhookConfiguration struct { + Calls func(childComplexity int, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookCallOrderBy) int CreatedAt func(childComplexity int) int EndpointURL func(childComplexity int) int ID func(childComplexity int) int @@ -2505,11 +2528,15 @@ type ViewerResolver interface { SignableDocuments(ctx context.Context, obj *types.Viewer, organizationID gid.GID, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.DocumentOrderBy) (*types.SignableDocumentConnection, error) SignableDocument(ctx context.Context, obj *types.Viewer, id gid.GID) (*types.SignableDocument, error) } +type WebhookCallConnectionResolver interface { + TotalCount(ctx context.Context, obj *types.WebhookCallConnection) (int, error) +} type WebhookConfigurationResolver interface { Organization(ctx context.Context, obj *types.WebhookConfiguration) (*types.Organization, error) SigningSecret(ctx context.Context, obj *types.WebhookConfiguration) (string, error) + Calls(ctx context.Context, obj *types.WebhookConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookCallOrderBy) (*types.WebhookCallConnection, error) Permission(ctx context.Context, obj *types.WebhookConfiguration, action string) (bool, error) } type WebhookConfigurationConnectionResolver interface { @@ -10100,6 +10127,92 @@ func (e *executableSchema) Complexity(ctx context.Context, typeName, field strin return e.complexity.Viewer.SignableDocuments(childComplexity, args["organizationId"].(gid.GID), args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.DocumentOrderBy)), true + case "WebhookCall.createdAt": + if e.complexity.WebhookCall.CreatedAt == nil { + break + } + + return e.complexity.WebhookCall.CreatedAt(childComplexity), true + case "WebhookCall.endpointUrl": + if e.complexity.WebhookCall.EndpointURL == nil { + break + } + + return e.complexity.WebhookCall.EndpointURL(childComplexity), true + case "WebhookCall.id": + if e.complexity.WebhookCall.ID == nil { + break + } + + return e.complexity.WebhookCall.ID(childComplexity), true + case "WebhookCall.response": + if e.complexity.WebhookCall.Response == nil { + break + } + + return e.complexity.WebhookCall.Response(childComplexity), true + case "WebhookCall.status": + if e.complexity.WebhookCall.Status == nil { + break + } + + return e.complexity.WebhookCall.Status(childComplexity), true + case "WebhookCall.webhookConfigurationId": + if e.complexity.WebhookCall.WebhookConfigurationID == nil { + break + } + + return e.complexity.WebhookCall.WebhookConfigurationID(childComplexity), true + case "WebhookCall.webhookEventId": + if e.complexity.WebhookCall.WebhookEventID == nil { + break + } + + return e.complexity.WebhookCall.WebhookEventID(childComplexity), true + + case "WebhookCallConnection.edges": + if e.complexity.WebhookCallConnection.Edges == nil { + break + } + + return e.complexity.WebhookCallConnection.Edges(childComplexity), true + case "WebhookCallConnection.pageInfo": + if e.complexity.WebhookCallConnection.PageInfo == nil { + break + } + + return e.complexity.WebhookCallConnection.PageInfo(childComplexity), true + case "WebhookCallConnection.totalCount": + if e.complexity.WebhookCallConnection.TotalCount == nil { + break + } + + return e.complexity.WebhookCallConnection.TotalCount(childComplexity), true + + case "WebhookCallEdge.cursor": + if e.complexity.WebhookCallEdge.Cursor == nil { + break + } + + return e.complexity.WebhookCallEdge.Cursor(childComplexity), true + case "WebhookCallEdge.node": + if e.complexity.WebhookCallEdge.Node == nil { + break + } + + return e.complexity.WebhookCallEdge.Node(childComplexity), true + + case "WebhookConfiguration.calls": + if e.complexity.WebhookConfiguration.Calls == nil { + break + } + + args, err := ec.field_WebhookConfiguration_calls_args(ctx, rawArgs) + if err != nil { + return 0, false + } + + return e.complexity.WebhookConfiguration.Calls(childComplexity, args["first"].(*int), args["after"].(*page.CursorKey), args["last"].(*int), args["before"].(*page.CursorKey), args["orderBy"].(*types.WebhookCallOrderBy)), true case "WebhookConfiguration.createdAt": if e.complexity.WebhookConfiguration.CreatedAt == nil { break @@ -10387,6 +10500,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler { ec.unmarshalInputVendorOrder, ec.unmarshalInputVendorRiskAssessmentOrder, ec.unmarshalInputVendorServiceOrder, + ec.unmarshalInputWebhookCallOrder, ec.unmarshalInputWebhookConfigurationOrder, ) first := true @@ -12780,6 +12894,14 @@ type WebhookConfiguration implements Node { createdAt: Datetime! updatedAt: Datetime! + calls( + first: Int + after: CursorKey + last: Int + before: CursorKey + orderBy: WebhookCallOrder + ): WebhookCallConnection! @goField(forceResolver: true) + permission(action: String!): Boolean! @goField(forceResolver: true) } @@ -12797,6 +12919,56 @@ type WebhookConfigurationEdge { node: WebhookConfiguration! } +enum WebhookCallStatus + @goModel(model: "go.probo.inc/probo/pkg/coredata.WebhookCallStatus") { + SUCCEEDED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookCallStatusSucceeded") + FAILED + @goEnum(value: "go.probo.inc/probo/pkg/coredata.WebhookCallStatusFailed") +} + +enum WebhookCallOrderField + @goModel( + model: "go.probo.inc/probo/pkg/coredata.WebhookCallOrderField" + ) { + CREATED_AT + @goEnum( + value: "go.probo.inc/probo/pkg/coredata.WebhookCallOrderFieldCreatedAt" + ) +} + +input WebhookCallOrder + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookCallOrderBy" + ) { + field: WebhookCallOrderField! + direction: OrderDirection! +} + +type WebhookCall implements Node { + id: ID! + webhookEventId: ID! + webhookConfigurationId: ID! + endpointUrl: String! + status: WebhookCallStatus! + response: String + createdAt: Datetime! +} + +type WebhookCallConnection + @goModel( + model: "go.probo.inc/probo/pkg/server/api/console/v1/types.WebhookCallConnection" + ) { + edges: [WebhookCallEdge!]! + pageInfo: PageInfo! + totalCount: Int! @goField(forceResolver: true) +} + +type WebhookCallEdge { + cursor: CursorKey! + node: WebhookCall! +} + type StateOfApplicability implements Node { id: ID! name: String! @@ -19757,6 +19929,37 @@ func (ec *executionContext) field_Viewer_signableDocuments_args(ctx context.Cont return args, nil } +func (ec *executionContext) field_WebhookConfiguration_calls_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { + var err error + args := map[string]any{} + arg0, err := graphql.ProcessArgField(ctx, rawArgs, "first", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["first"] = arg0 + arg1, err := graphql.ProcessArgField(ctx, rawArgs, "after", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["after"] = arg1 + arg2, err := graphql.ProcessArgField(ctx, rawArgs, "last", ec.unmarshalOInt2ᚖint) + if err != nil { + return nil, err + } + args["last"] = arg2 + arg3, err := graphql.ProcessArgField(ctx, rawArgs, "before", ec.unmarshalOCursorKey2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey) + if err != nil { + return nil, err + } + args["before"] = arg3 + arg4, err := graphql.ProcessArgField(ctx, rawArgs, "orderBy", ec.unmarshalOWebhookCallOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallOrderBy) + if err != nil { + return nil, err + } + args["orderBy"] = arg4 + return args, nil +} + func (ec *executionContext) field_WebhookConfiguration_permission_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { var err error args := map[string]any{} @@ -55226,6 +55429,8 @@ func (ec *executionContext) fieldContext_UpdateWebhookConfigurationPayload_webho return ec.fieldContext_WebhookConfiguration_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_WebhookConfiguration_updatedAt(ctx, field) + case "calls": + return ec.fieldContext_WebhookConfiguration_calls(ctx, field) case "permission": return ec.fieldContext_WebhookConfiguration_permission(ctx, field) } @@ -59721,6 +59926,386 @@ func (ec *executionContext) fieldContext_Viewer_signableDocument(ctx context.Con return fc, nil } +func (ec *executionContext) _WebhookCall_id(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_id, + func(ctx context.Context) (any, error) { + return obj.ID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_id(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_webhookEventId(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_webhookEventId, + func(ctx context.Context) (any, error) { + return obj.WebhookEventID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_webhookEventId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_webhookConfigurationId(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_webhookConfigurationId, + func(ctx context.Context) (any, error) { + return obj.WebhookConfigurationID, nil + }, + nil, + ec.marshalNID2goᚗproboᚗincᚋproboᚋpkgᚋgidᚐGID, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_webhookConfigurationId(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type ID does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_endpointUrl(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_endpointUrl, + func(ctx context.Context) (any, error) { + return obj.EndpointURL, nil + }, + nil, + ec.marshalNString2string, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_endpointUrl(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_status(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_status, + func(ctx context.Context) (any, error) { + return obj.Status, nil + }, + nil, + ec.marshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_status(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type WebhookCallStatus does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_response(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_response, + func(ctx context.Context) (any, error) { + return obj.Response, nil + }, + nil, + ec.marshalOString2ᚖstring, + true, + false, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_response(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type String does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCall_createdAt(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCall) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCall_createdAt, + func(ctx context.Context) (any, error) { + return obj.CreatedAt, nil + }, + nil, + ec.marshalNDatetime2timeᚐTime, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCall_createdAt(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCall", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Datetime does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCallConnection_edges(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCallConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCallConnection_edges, + func(ctx context.Context) (any, error) { + return obj.Edges, nil + }, + nil, + ec.marshalNWebhookCallEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallEdgeᚄ, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCallConnection_edges(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCallConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "cursor": + return ec.fieldContext_WebhookCallEdge_cursor(ctx, field) + case "node": + return ec.fieldContext_WebhookCallEdge_node(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type WebhookCallEdge", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCallConnection_pageInfo(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCallConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCallConnection_pageInfo, + func(ctx context.Context) (any, error) { + return obj.PageInfo, nil + }, + nil, + ec.marshalNPageInfo2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐPageInfo, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCallConnection_pageInfo(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCallConnection", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "hasNextPage": + return ec.fieldContext_PageInfo_hasNextPage(ctx, field) + case "hasPreviousPage": + return ec.fieldContext_PageInfo_hasPreviousPage(ctx, field) + case "startCursor": + return ec.fieldContext_PageInfo_startCursor(ctx, field) + case "endCursor": + return ec.fieldContext_PageInfo_endCursor(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type PageInfo", field.Name) + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCallConnection_totalCount(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCallConnection) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCallConnection_totalCount, + func(ctx context.Context) (any, error) { + return ec.resolvers.WebhookCallConnection().TotalCount(ctx, obj) + }, + nil, + ec.marshalNInt2int, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCallConnection_totalCount(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCallConnection", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type Int does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCallEdge_cursor(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCallEdge) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCallEdge_cursor, + func(ctx context.Context) (any, error) { + return obj.Cursor, nil + }, + nil, + ec.marshalNCursorKey2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐCursorKey, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCallEdge_cursor(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCallEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + return nil, errors.New("field of type CursorKey does not have child fields") + }, + } + return fc, nil +} + +func (ec *executionContext) _WebhookCallEdge_node(ctx context.Context, field graphql.CollectedField, obj *types.WebhookCallEdge) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookCallEdge_node, + func(ctx context.Context) (any, error) { + return obj.Node, nil + }, + nil, + ec.marshalNWebhookCall2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCall, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookCallEdge_node(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookCallEdge", + Field: field, + IsMethod: false, + IsResolver: false, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "id": + return ec.fieldContext_WebhookCall_id(ctx, field) + case "webhookEventId": + return ec.fieldContext_WebhookCall_webhookEventId(ctx, field) + case "webhookConfigurationId": + return ec.fieldContext_WebhookCall_webhookConfigurationId(ctx, field) + case "endpointUrl": + return ec.fieldContext_WebhookCall_endpointUrl(ctx, field) + case "status": + return ec.fieldContext_WebhookCall_status(ctx, field) + case "response": + return ec.fieldContext_WebhookCall_response(ctx, field) + case "createdAt": + return ec.fieldContext_WebhookCall_createdAt(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type WebhookCall", field.Name) + }, + } + return fc, nil +} + func (ec *executionContext) _WebhookConfiguration_id(ctx context.Context, field graphql.CollectedField, obj *types.WebhookConfiguration) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -60002,6 +60587,55 @@ func (ec *executionContext) fieldContext_WebhookConfiguration_updatedAt(_ contex return fc, nil } +func (ec *executionContext) _WebhookConfiguration_calls(ctx context.Context, field graphql.CollectedField, obj *types.WebhookConfiguration) (ret graphql.Marshaler) { + return graphql.ResolveField( + ctx, + ec.OperationContext, + field, + ec.fieldContext_WebhookConfiguration_calls, + func(ctx context.Context) (any, error) { + fc := graphql.GetFieldContext(ctx) + return ec.resolvers.WebhookConfiguration().Calls(ctx, obj, fc.Args["first"].(*int), fc.Args["after"].(*page.CursorKey), fc.Args["last"].(*int), fc.Args["before"].(*page.CursorKey), fc.Args["orderBy"].(*types.WebhookCallOrderBy)) + }, + nil, + ec.marshalNWebhookCallConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallConnection, + true, + true, + ) +} + +func (ec *executionContext) fieldContext_WebhookConfiguration_calls(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) { + fc = &graphql.FieldContext{ + Object: "WebhookConfiguration", + Field: field, + IsMethod: true, + IsResolver: true, + Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) { + switch field.Name { + case "edges": + return ec.fieldContext_WebhookCallConnection_edges(ctx, field) + case "pageInfo": + return ec.fieldContext_WebhookCallConnection_pageInfo(ctx, field) + case "totalCount": + return ec.fieldContext_WebhookCallConnection_totalCount(ctx, field) + } + return nil, fmt.Errorf("no field named %q was found under type WebhookCallConnection", field.Name) + }, + } + defer func() { + if r := recover(); r != nil { + err = ec.Recover(ctx, r) + ec.Error(ctx, err) + } + }() + ctx = graphql.WithFieldContext(ctx, fc) + if fc.Args, err = ec.field_WebhookConfiguration_calls_args(ctx, field.ArgumentMap(ec.Variables)); err != nil { + ec.Error(ctx, err) + return fc, err + } + return fc, nil +} + func (ec *executionContext) _WebhookConfiguration_permission(ctx context.Context, field graphql.CollectedField, obj *types.WebhookConfiguration) (ret graphql.Marshaler) { return graphql.ResolveField( ctx, @@ -60213,6 +60847,8 @@ func (ec *executionContext) fieldContext_WebhookConfigurationEdge_node(_ context return ec.fieldContext_WebhookConfiguration_createdAt(ctx, field) case "updatedAt": return ec.fieldContext_WebhookConfiguration_updatedAt(ctx, field) + case "calls": + return ec.fieldContext_WebhookConfiguration_calls(ctx, field) case "permission": return ec.fieldContext_WebhookConfiguration_permission(ctx, field) } @@ -69889,6 +70525,40 @@ func (ec *executionContext) unmarshalInputVendorServiceOrder(ctx context.Context return it, nil } +func (ec *executionContext) unmarshalInputWebhookCallOrder(ctx context.Context, obj any) (types.WebhookCallOrderBy, error) { + var it types.WebhookCallOrderBy + asMap := map[string]any{} + for k, v := range obj.(map[string]any) { + asMap[k] = v + } + + fieldsInOrder := [...]string{"field", "direction"} + for _, k := range fieldsInOrder { + v, ok := asMap[k] + if !ok { + continue + } + switch k { + case "field": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("field")) + data, err := ec.unmarshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField(ctx, v) + if err != nil { + return it, err + } + it.Field = data + case "direction": + ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("direction")) + data, err := ec.unmarshalNOrderDirection2goᚗproboᚗincᚋproboᚋpkgᚋpageᚐOrderDirection(ctx, v) + if err != nil { + return it, err + } + it.Direction = data + } + } + + return it, nil +} + func (ec *executionContext) unmarshalInputWebhookConfigurationOrder(ctx context.Context, obj any) (types.WebhookConfigurationOrderBy, error) { var it types.WebhookConfigurationOrderBy asMap := map[string]any{} @@ -69938,6 +70608,13 @@ func (ec *executionContext) _Node(ctx context.Context, sel ast.SelectionSet, obj return graphql.Null } return ec._WebhookConfiguration(ctx, sel, obj) + case types.WebhookCall: + return ec._WebhookCall(ctx, sel, &obj) + case *types.WebhookCall: + if obj == nil { + return graphql.Null + } + return ec._WebhookCall(ctx, sel, obj) case types.VendorService: return ec._VendorService(ctx, sel, &obj) case *types.VendorService: @@ -90685,6 +91362,196 @@ func (ec *executionContext) _Viewer(ctx context.Context, sel ast.SelectionSet, o return out } +var webhookCallImplementors = []string{"WebhookCall", "Node"} + +func (ec *executionContext) _WebhookCall(ctx context.Context, sel ast.SelectionSet, obj *types.WebhookCall) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, webhookCallImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("WebhookCall") + case "id": + out.Values[i] = ec._WebhookCall_id(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "webhookEventId": + out.Values[i] = ec._WebhookCall_webhookEventId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "webhookConfigurationId": + out.Values[i] = ec._WebhookCall_webhookConfigurationId(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "endpointUrl": + out.Values[i] = ec._WebhookCall_endpointUrl(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "status": + out.Values[i] = ec._WebhookCall_status(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "response": + out.Values[i] = ec._WebhookCall_response(ctx, field, obj) + case "createdAt": + out.Values[i] = ec._WebhookCall_createdAt(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var webhookCallConnectionImplementors = []string{"WebhookCallConnection"} + +func (ec *executionContext) _WebhookCallConnection(ctx context.Context, sel ast.SelectionSet, obj *types.WebhookCallConnection) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, webhookCallConnectionImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("WebhookCallConnection") + case "edges": + out.Values[i] = ec._WebhookCallConnection_edges(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "pageInfo": + out.Values[i] = ec._WebhookCallConnection_pageInfo(ctx, field, obj) + if out.Values[i] == graphql.Null { + atomic.AddUint32(&out.Invalids, 1) + } + case "totalCount": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._WebhookCallConnection_totalCount(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + +var webhookCallEdgeImplementors = []string{"WebhookCallEdge"} + +func (ec *executionContext) _WebhookCallEdge(ctx context.Context, sel ast.SelectionSet, obj *types.WebhookCallEdge) graphql.Marshaler { + fields := graphql.CollectFields(ec.OperationContext, sel, webhookCallEdgeImplementors) + + out := graphql.NewFieldSet(fields) + deferred := make(map[string]*graphql.FieldSet) + for i, field := range fields { + switch field.Name { + case "__typename": + out.Values[i] = graphql.MarshalString("WebhookCallEdge") + case "cursor": + out.Values[i] = ec._WebhookCallEdge_cursor(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + case "node": + out.Values[i] = ec._WebhookCallEdge_node(ctx, field, obj) + if out.Values[i] == graphql.Null { + out.Invalids++ + } + default: + panic("unknown field " + strconv.Quote(field.Name)) + } + } + out.Dispatch(ctx) + if out.Invalids > 0 { + return graphql.Null + } + + atomic.AddInt32(&ec.deferred, int32(len(deferred))) + + for label, dfs := range deferred { + ec.processDeferredGroup(graphql.DeferredGroup{ + Label: label, + Path: graphql.GetPath(ctx), + FieldSet: dfs, + Context: ctx, + }) + } + + return out +} + var webhookConfigurationImplementors = []string{"WebhookConfiguration", "Node"} func (ec *executionContext) _WebhookConfiguration(ctx context.Context, sel ast.SelectionSet, obj *types.WebhookConfiguration) graphql.Marshaler { @@ -90790,6 +91657,42 @@ func (ec *executionContext) _WebhookConfiguration(ctx context.Context, sel ast.S if out.Values[i] == graphql.Null { atomic.AddUint32(&out.Invalids, 1) } + case "calls": + field := field + + innerFunc := func(ctx context.Context, fs *graphql.FieldSet) (res graphql.Marshaler) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + } + }() + res = ec._WebhookConfiguration_calls(ctx, field, obj) + if res == graphql.Null { + atomic.AddUint32(&fs.Invalids, 1) + } + return res + } + + if field.Deferrable != nil { + dfs, ok := deferred[field.Deferrable.Label] + di := 0 + if ok { + dfs.AddField(field) + di = len(dfs.Values) - 1 + } else { + dfs = graphql.NewFieldSet([]graphql.CollectedField{field}) + deferred[field.Deferrable.Label] = dfs + } + dfs.Concurrently(di, func(ctx context.Context) graphql.Marshaler { + return innerFunc(ctx, dfs) + }) + + // don't run the out.Concurrently() call below + out.Values[i] = graphql.Null + continue + } + + out.Concurrently(i, func(ctx context.Context) graphql.Marshaler { return innerFunc(ctx, out) }) case "permission": field := field @@ -99879,6 +100782,138 @@ func (ec *executionContext) marshalNViewer2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋs return ec._Viewer(ctx, sel, v) } +func (ec *executionContext) marshalNWebhookCall2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCall(ctx context.Context, sel ast.SelectionSet, v *types.WebhookCall) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._WebhookCall(ctx, sel, v) +} + +func (ec *executionContext) marshalNWebhookCallConnection2goᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallConnection(ctx context.Context, sel ast.SelectionSet, v types.WebhookCallConnection) graphql.Marshaler { + return ec._WebhookCallConnection(ctx, sel, &v) +} + +func (ec *executionContext) marshalNWebhookCallConnection2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallConnection(ctx context.Context, sel ast.SelectionSet, v *types.WebhookCallConnection) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._WebhookCallConnection(ctx, sel, v) +} + +func (ec *executionContext) marshalNWebhookCallEdge2ᚕᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallEdgeᚄ(ctx context.Context, sel ast.SelectionSet, v []*types.WebhookCallEdge) graphql.Marshaler { + ret := make(graphql.Array, len(v)) + var wg sync.WaitGroup + isLen1 := len(v) == 1 + if !isLen1 { + wg.Add(len(v)) + } + for i := range v { + i := i + fc := &graphql.FieldContext{ + Index: &i, + Result: &v[i], + } + ctx := graphql.WithFieldContext(ctx, fc) + f := func(i int) { + defer func() { + if r := recover(); r != nil { + ec.Error(ctx, ec.Recover(ctx, r)) + ret = nil + } + }() + if !isLen1 { + defer wg.Done() + } + ret[i] = ec.marshalNWebhookCallEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallEdge(ctx, sel, v[i]) + } + if isLen1 { + f(i) + } else { + go f(i) + } + + } + wg.Wait() + + for _, e := range ret { + if e == graphql.Null { + return graphql.Null + } + } + + return ret +} + +func (ec *executionContext) marshalNWebhookCallEdge2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallEdge(ctx context.Context, sel ast.SelectionSet, v *types.WebhookCallEdge) graphql.Marshaler { + if v == nil { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + return graphql.Null + } + return ec._WebhookCallEdge(ctx, sel, v) +} + +func (ec *executionContext) unmarshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField(ctx context.Context, v any) (coredata.WebhookCallOrderField, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField(ctx context.Context, sel ast.SelectionSet, v coredata.WebhookCallOrderField) graphql.Marshaler { + _ = sel + res := graphql.MarshalString(marshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField = map[string]coredata.WebhookCallOrderField{ + "CREATED_AT": coredata.WebhookCallOrderFieldCreatedAt, + } + marshalNWebhookCallOrderField2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallOrderField = map[coredata.WebhookCallOrderField]string{ + coredata.WebhookCallOrderFieldCreatedAt: "CREATED_AT", + } +) + +func (ec *executionContext) unmarshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus(ctx context.Context, v any) (coredata.WebhookCallStatus, error) { + tmp, err := graphql.UnmarshalString(v) + res := unmarshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus[tmp] + return res, graphql.ErrorOnPath(ctx, err) +} + +func (ec *executionContext) marshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus(ctx context.Context, sel ast.SelectionSet, v coredata.WebhookCallStatus) graphql.Marshaler { + _ = sel + res := graphql.MarshalString(marshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus[v]) + if res == graphql.Null { + if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { + graphql.AddErrorf(ctx, "the requested element is null which the schema does not allow") + } + } + return res +} + +var ( + unmarshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus = map[string]coredata.WebhookCallStatus{ + "SUCCEEDED": coredata.WebhookCallStatusSucceeded, + "FAILED": coredata.WebhookCallStatusFailed, + } + marshalNWebhookCallStatus2goᚗproboᚗincᚋproboᚋpkgᚋcoredataᚐWebhookCallStatus = map[coredata.WebhookCallStatus]string{ + coredata.WebhookCallStatusSucceeded: "SUCCEEDED", + coredata.WebhookCallStatusFailed: "FAILED", + } +) + func (ec *executionContext) marshalNWebhookConfiguration2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookConfiguration(ctx context.Context, sel ast.SelectionSet, v *types.WebhookConfiguration) graphql.Marshaler { if v == nil { if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) { @@ -102154,6 +103189,14 @@ func (ec *executionContext) unmarshalOVendorServiceOrder2ᚖgoᚗproboᚗincᚋp return &res, graphql.ErrorOnPath(ctx, err) } +func (ec *executionContext) unmarshalOWebhookCallOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookCallOrderBy(ctx context.Context, v any) (*types.WebhookCallOrderBy, error) { + if v == nil { + return nil, nil + } + res, err := ec.unmarshalInputWebhookCallOrder(ctx, v) + return &res, graphql.ErrorOnPath(ctx, err) +} + func (ec *executionContext) unmarshalOWebhookConfigurationOrder2ᚖgoᚗproboᚗincᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐWebhookConfigurationOrderBy(ctx context.Context, v any) (*types.WebhookConfigurationOrderBy, error) { if v == nil { return nil, nil diff --git a/pkg/server/api/console/v1/types/types.go b/pkg/server/api/console/v1/types/types.go index ebb053c4b..b1d49d5c9 100644 --- a/pkg/server/api/console/v1/types/types.go +++ b/pkg/server/api/console/v1/types/types.go @@ -2623,6 +2623,24 @@ type Viewer struct { SignableDocument *SignableDocument `json:"signableDocument,omitempty"` } +type WebhookCall struct { + ID gid.GID `json:"id"` + WebhookEventID gid.GID `json:"webhookEventId"` + WebhookConfigurationID gid.GID `json:"webhookConfigurationId"` + EndpointURL string `json:"endpointUrl"` + Status coredata.WebhookCallStatus `json:"status"` + Response *string `json:"response,omitempty"` + CreatedAt time.Time `json:"createdAt"` +} + +func (WebhookCall) IsNode() {} +func (this WebhookCall) GetID() gid.GID { return this.ID } + +type WebhookCallEdge struct { + Cursor page.CursorKey `json:"cursor"` + Node *WebhookCall `json:"node"` +} + type WebhookConfiguration struct { ID gid.GID `json:"id"` Organization *Organization `json:"organization,omitempty"` @@ -2631,6 +2649,7 @@ type WebhookConfiguration struct { SelectedEvents []coredata.WebhookEventType `json:"selectedEvents"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` + Calls *WebhookCallConnection `json:"calls"` Permission bool `json:"permission"` } diff --git a/pkg/server/api/console/v1/types/webhook_call.go b/pkg/server/api/console/v1/types/webhook_call.go new file mode 100644 index 000000000..f48c5b0b2 --- /dev/null +++ b/pkg/server/api/console/v1/types/webhook_call.go @@ -0,0 +1,79 @@ +// Copyright (c) 2025 Probo Inc . +// +// Permission to use, copy, modify, and/or distribute this software for any +// purpose with or without fee is hereby granted, provided that the above +// copyright notice and this permission notice appear in all copies. +// +// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +// PERFORMANCE OF THIS SOFTWARE. + +package types + +import ( + "go.probo.inc/probo/pkg/coredata" + "go.probo.inc/probo/pkg/gid" + "go.probo.inc/probo/pkg/page" +) + +type ( + WebhookCallOrderBy OrderBy[coredata.WebhookCallOrderField] + + WebhookCallConnection struct { + TotalCount int + Edges []*WebhookCallEdge + PageInfo PageInfo + + Resolver any + ParentID gid.GID + } +) + +func NewWebhookCallConnection( + p *page.Page[*coredata.WebhookCall, coredata.WebhookCallOrderField], + parentType any, + parentID gid.GID, +) *WebhookCallConnection { + var edges = make([]*WebhookCallEdge, len(p.Data)) + + for i := range edges { + edges[i] = NewWebhookCallEdge(p.Data[i], p.Cursor.OrderBy.Field) + } + + return &WebhookCallConnection{ + Edges: edges, + PageInfo: *NewPageInfo(p), + + Resolver: parentType, + ParentID: parentID, + } +} + +func NewWebhookCallEdge(wc *coredata.WebhookCall, orderBy coredata.WebhookCallOrderField) *WebhookCallEdge { + return &WebhookCallEdge{ + Cursor: wc.CursorKey(orderBy), + Node: NewWebhookCall(wc), + } +} + +func NewWebhookCall(wc *coredata.WebhookCall) *WebhookCall { + var response *string + if len(wc.Response) > 0 { + s := string(wc.Response) + response = &s + } + + return &WebhookCall{ + ID: wc.ID, + WebhookEventID: wc.WebhookEventID, + WebhookConfigurationID: wc.WebhookConfigurationID, + EndpointURL: wc.EndpointURL, + Status: wc.Status, + Response: response, + CreatedAt: wc.CreatedAt, + } +} diff --git a/pkg/server/api/console/v1/v1_resolver.go b/pkg/server/api/console/v1/v1_resolver.go index 077696ac7..5bbe1cd7a 100644 --- a/pkg/server/api/console/v1/v1_resolver.go +++ b/pkg/server/api/console/v1/v1_resolver.go @@ -8559,6 +8559,23 @@ func (r *viewerResolver) SignableDocument(ctx context.Context, obj *types.Viewer }, nil } +// TotalCount is the resolver for the totalCount field. +func (r *webhookCallConnectionResolver) TotalCount(ctx context.Context, obj *types.WebhookCallConnection) (int, error) { + if err := r.authorize(ctx, obj.ParentID, probo.ActionWebhookConfigurationGet); err != nil { + return 0, err + } + + prb := r.ProboService(ctx, obj.ParentID.TenantID()) + + count, err := prb.WebhookConfigurations.CountCallsForConfigurationID(ctx, obj.ParentID) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot count webhook calls", log.Error(err)) + return 0, gqlutils.Internal(ctx) + } + + return count, nil +} + // Organization is the resolver for the organization field. func (r *webhookConfigurationResolver) Organization(ctx context.Context, obj *types.WebhookConfiguration) (*types.Organization, error) { if err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil { @@ -8593,6 +8610,36 @@ func (r *webhookConfigurationResolver) SigningSecret(ctx context.Context, obj *t return signingSecret, nil } +// Calls is the resolver for the calls field. +func (r *webhookConfigurationResolver) Calls(ctx context.Context, obj *types.WebhookConfiguration, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.WebhookCallOrderBy) (*types.WebhookCallConnection, error) { + if err := r.authorize(ctx, obj.ID, probo.ActionWebhookConfigurationGet); err != nil { + return nil, err + } + + prb := r.ProboService(ctx, obj.ID.TenantID()) + + pageOrderBy := page.OrderBy[coredata.WebhookCallOrderField]{ + Field: coredata.WebhookCallOrderFieldCreatedAt, + Direction: page.OrderDirectionDesc, + } + if orderBy != nil { + pageOrderBy = page.OrderBy[coredata.WebhookCallOrderField]{ + Field: orderBy.Field, + Direction: orderBy.Direction, + } + } + + cursor := types.NewCursor(first, after, last, before, pageOrderBy) + + page, err := prb.WebhookConfigurations.ListCallsForConfigurationID(ctx, obj.ID, cursor) + if err != nil { + r.logger.ErrorCtx(ctx, "cannot list webhook calls", log.Error(err)) + return nil, gqlutils.Internal(ctx) + } + + return types.NewWebhookCallConnection(page, r, obj.ID), nil +} + // Permission is the resolver for the permission field. func (r *webhookConfigurationResolver) Permission(ctx context.Context, obj *types.WebhookConfiguration, action string) (bool, error) { return r.Resolver.Permission(ctx, obj, action) @@ -8922,6 +8969,11 @@ func (r *Resolver) VendorService() schema.VendorServiceResolver { return &vendor // Viewer returns schema.ViewerResolver implementation. func (r *Resolver) Viewer() schema.ViewerResolver { return &viewerResolver{r} } +// WebhookCallConnection returns schema.WebhookCallConnectionResolver implementation. +func (r *Resolver) WebhookCallConnection() schema.WebhookCallConnectionResolver { + return &webhookCallConnectionResolver{r} +} + // WebhookConfiguration returns schema.WebhookConfigurationResolver implementation. func (r *Resolver) WebhookConfiguration() schema.WebhookConfigurationResolver { return &webhookConfigurationResolver{r} @@ -9004,5 +9056,6 @@ type vendorDataPrivacyAgreementResolver struct{ *Resolver } type vendorRiskAssessmentResolver struct{ *Resolver } type vendorServiceResolver struct{ *Resolver } type viewerResolver struct{ *Resolver } +type webhookCallConnectionResolver struct{ *Resolver } type webhookConfigurationResolver struct{ *Resolver } type webhookConfigurationConnectionResolver struct{ *Resolver }