Add webhook calls ui

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-02-12 17:09:40 +01:00
parent f9de9c4834
commit a345e0be02
11 changed files with 1925 additions and 5 deletions

View File

@@ -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!

File diff suppressed because it is too large Load Diff

View File

@@ -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"`
}

View File

@@ -0,0 +1,79 @@
// Copyright (c) 2025 Probo Inc <hello@getprobo.com>.
//
// 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,
}
}

View File

@@ -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 }