From 570ea43f4baeed91a02a4a9fa791a8f3272296f7 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Wed, 19 Jun 2024 10:27:00 -0400 Subject: [PATCH 1/4] feat: add create event to rest api --- .../openapi/components/schemas/_index.yaml | 2 + .../openapi/components/schemas/event.yaml | 15 + api-contracts/openapi/paths/event/event.yaml | 49 ++ api/v1/server/handlers/events/create.go | 57 +++ api/v1/server/oas/gen/openapi.gen.go | 438 +++++++++++------- frontend/app/src/lib/api/generated/Api.ts | 21 + .../src/lib/api/generated/data-contracts.ts | 9 + internal/services/ingestor/ingestor.go | 6 +- pkg/client/rest/gen.go | 185 ++++++++ 9 files changed, 619 insertions(+), 163 deletions(-) create mode 100644 api/v1/server/handlers/events/create.go diff --git a/api-contracts/openapi/components/schemas/_index.yaml b/api-contracts/openapi/components/schemas/_index.yaml index 55fe68e663..3e75e4186b 100644 --- a/api-contracts/openapi/components/schemas/_index.yaml +++ b/api-contracts/openapi/components/schemas/_index.yaml @@ -74,6 +74,8 @@ Event: $ref: "./event.yaml#/Event" EventData: $ref: "./event.yaml#/EventData" +CreateEventRequest: + $ref: "./event.yaml#/CreateEventRequest" EventWorkflowRunSummary: $ref: "./event.yaml#/EventWorkflowRunSummary" EventOrderByField: diff --git a/api-contracts/openapi/components/schemas/event.yaml b/api-contracts/openapi/components/schemas/event.yaml index 0a681b38a4..adccc3cc69 100644 --- a/api-contracts/openapi/components/schemas/event.yaml +++ b/api-contracts/openapi/components/schemas/event.yaml @@ -30,6 +30,21 @@ EventData: required: - data +CreateEventRequest: + properties: + key: + type: string + description: The key for the event. + data: + type: object + description: The data for the event. + additionalMetadata: + type: object + description: Additional metadata for the event. + required: + - key + - data + ReplayEventRequest: properties: eventIds: diff --git a/api-contracts/openapi/paths/event/event.yaml b/api-contracts/openapi/paths/event/event.yaml index 9d58f0d889..d00fa3a9d9 100644 --- a/api-contracts/openapi/paths/event/event.yaml +++ b/api-contracts/openapi/paths/event/event.yaml @@ -98,6 +98,55 @@ withTenant: summary: List events tags: - Event + post: + x-resources: ["tenant"] + description: Creates a new event. + operationId: event:create + parameters: + - description: The tenant id + in: path + name: tenant + required: true + schema: + type: string + format: uuid + minLength: 36 + maxLength: 36 + requestBody: + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/CreateEventRequest" + description: The event to create + required: true + responses: + "200": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/Event" + description: Successfully created the event + "400": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: A malformed or bad request + "403": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Forbidden + "429": + content: + application/json: + schema: + $ref: "../../components/schemas/_index.yaml#/APIErrors" + description: Resource limit exceeded + summary: Create event + tags: + - Event eventData: get: diff --git a/api/v1/server/handlers/events/create.go b/api/v1/server/handlers/events/create.go new file mode 100644 index 0000000000..f03e3d4c75 --- /dev/null +++ b/api/v1/server/handlers/events/create.go @@ -0,0 +1,57 @@ +package events + +import ( + "encoding/json" + + "github.com/labstack/echo/v4" + + "github.com/hatchet-dev/hatchet/api/v1/server/oas/apierrors" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/gen" + "github.com/hatchet-dev/hatchet/api/v1/server/oas/transformers" + "github.com/hatchet-dev/hatchet/pkg/repository/metered" + "github.com/hatchet-dev/hatchet/pkg/repository/prisma/db" + "github.com/hatchet-dev/hatchet/pkg/repository/prisma/sqlchelpers" +) + +func (t *EventService) EventCreate(ctx echo.Context, request gen.EventCreateRequestObject) (gen.EventCreateResponseObject, error) { + tenant := ctx.Get("tenant").(*db.TenantModel) + + // marshal the data object to bytes + dataBytes, err := json.Marshal(request.Body.Data) + + if err != nil { + return nil, err + } + + var additionalMetadata []byte + + if request.Body.AdditionalMetadata != nil { + additionalMetadata, err = json.Marshal(request.Body.AdditionalMetadata) + + if err != nil { + return nil, err + } + } + + newEvent, err := t.config.Ingestor.IngestEvent(ctx.Request().Context(), tenant.ID, request.Body.Key, dataBytes, additionalMetadata) + + if err != nil { + if err == metered.ErrResourceExhausted { + return gen.EventCreate429JSONResponse( + apierrors.NewAPIErrors("Event limit exceeded"), + ), nil + } + + return nil, err + } + + dbNewEvent, err := t.config.APIRepository.Event().GetEventById(sqlchelpers.UUIDToStr(newEvent.ID)) + + if err != nil { + return nil, err + } + + return gen.EventCreate200JSONResponse( + *transformers.ToEvent(dbNewEvent), + ), nil +} diff --git a/api/v1/server/oas/gen/openapi.gen.go b/api/v1/server/oas/gen/openapi.gen.go index 81376b7a80..649eab51d1 100644 --- a/api/v1/server/oas/gen/openapi.gen.go +++ b/api/v1/server/oas/gen/openapi.gen.go @@ -239,6 +239,18 @@ type CreateAPITokenResponse struct { Token string `json:"token"` } +// CreateEventRequest defines model for CreateEventRequest. +type CreateEventRequest struct { + // AdditionalMetadata Additional metadata for the event. + AdditionalMetadata *map[string]interface{} `json:"additionalMetadata,omitempty"` + + // Data The data for the event. + Data map[string]interface{} `json:"data"` + + // Key The key for the event. + Key string `json:"key"` +} + // CreatePullRequestFromStepRun defines model for CreatePullRequestFromStepRun. type CreatePullRequestFromStepRun struct { BranchName string `json:"branchName"` @@ -1252,6 +1264,9 @@ type AlertEmailGroupCreateJSONRequestBody = CreateTenantAlertEmailGroupRequest // ApiTokenCreateJSONRequestBody defines body for ApiTokenCreate for application/json ContentType. type ApiTokenCreateJSONRequestBody = CreateAPITokenRequest +// EventCreateJSONRequestBody defines body for EventCreate for application/json ContentType. +type EventCreateJSONRequestBody = CreateEventRequest + // EventUpdateReplayJSONRequestBody defines body for EventUpdateReplay for application/json ContentType. type EventUpdateReplayJSONRequestBody = ReplayEventRequest @@ -1377,6 +1392,9 @@ type ServerInterface interface { // List events // (GET /api/v1/tenants/{tenant}/events) EventList(ctx echo.Context, tenant openapi_types.UUID, params EventListParams) error + // Create event + // (POST /api/v1/tenants/{tenant}/events) + EventCreate(ctx echo.Context, tenant openapi_types.UUID) error // List event keys // (GET /api/v1/tenants/{tenant}/events/keys) EventKeyList(ctx echo.Context, tenant openapi_types.UUID) error @@ -2158,6 +2176,26 @@ func (w *ServerInterfaceWrapper) EventList(ctx echo.Context) error { return err } +// EventCreate converts echo context to params. +func (w *ServerInterfaceWrapper) EventCreate(ctx echo.Context) error { + var err error + // ------------- Path parameter "tenant" ------------- + var tenant openapi_types.UUID + + err = runtime.BindStyledParameterWithLocation("simple", false, "tenant", runtime.ParamLocationPath, ctx.Param("tenant"), &tenant) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter tenant: %s", err)) + } + + ctx.Set(BearerAuthScopes, []string{}) + + ctx.Set(CookieAuthScopes, []string{}) + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.EventCreate(ctx, tenant) + return err +} + // EventKeyList converts echo context to params. func (w *ServerInterfaceWrapper) EventKeyList(ctx echo.Context) error { var err error @@ -3239,6 +3277,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.GET(baseURL+"/api/v1/tenants/:tenant/api-tokens", wrapper.ApiTokenList) router.POST(baseURL+"/api/v1/tenants/:tenant/api-tokens", wrapper.ApiTokenCreate) router.GET(baseURL+"/api/v1/tenants/:tenant/events", wrapper.EventList) + router.POST(baseURL+"/api/v1/tenants/:tenant/events", wrapper.EventCreate) router.GET(baseURL+"/api/v1/tenants/:tenant/events/keys", wrapper.EventKeyList) router.POST(baseURL+"/api/v1/tenants/:tenant/events/replay", wrapper.EventUpdateReplay) router.GET(baseURL+"/api/v1/tenants/:tenant/invites", wrapper.TenantInviteList) @@ -4327,6 +4366,51 @@ func (response EventList403JSONResponse) VisitEventListResponse(w http.ResponseW return json.NewEncoder(w).Encode(response) } +type EventCreateRequestObject struct { + Tenant openapi_types.UUID `json:"tenant"` + Body *EventCreateJSONRequestBody +} + +type EventCreateResponseObject interface { + VisitEventCreateResponse(w http.ResponseWriter) error +} + +type EventCreate200JSONResponse Event + +func (response EventCreate200JSONResponse) VisitEventCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(200) + + return json.NewEncoder(w).Encode(response) +} + +type EventCreate400JSONResponse APIErrors + +func (response EventCreate400JSONResponse) VisitEventCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(400) + + return json.NewEncoder(w).Encode(response) +} + +type EventCreate403JSONResponse APIErrors + +func (response EventCreate403JSONResponse) VisitEventCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(403) + + return json.NewEncoder(w).Encode(response) +} + +type EventCreate429JSONResponse APIErrors + +func (response EventCreate429JSONResponse) VisitEventCreateResponse(w http.ResponseWriter) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(429) + + return json.NewEncoder(w).Encode(response) +} + type EventKeyListRequestObject struct { Tenant openapi_types.UUID `json:"tenant"` } @@ -6125,6 +6209,8 @@ type StrictServerInterface interface { EventList(ctx echo.Context, request EventListRequestObject) (EventListResponseObject, error) + EventCreate(ctx echo.Context, request EventCreateRequestObject) (EventCreateResponseObject, error) + EventKeyList(ctx echo.Context, request EventKeyListRequestObject) (EventKeyListResponseObject, error) EventUpdateReplay(ctx echo.Context, request EventUpdateReplayRequestObject) (EventUpdateReplayResponseObject, error) @@ -6961,6 +7047,37 @@ func (sh *strictHandler) EventList(ctx echo.Context, tenant openapi_types.UUID, return nil } +// EventCreate operation middleware +func (sh *strictHandler) EventCreate(ctx echo.Context, tenant openapi_types.UUID) error { + var request EventCreateRequestObject + + request.Tenant = tenant + + var body EventCreateJSONRequestBody + if err := ctx.Bind(&body); err != nil { + return err + } + request.Body = &body + + handler := func(ctx echo.Context, request interface{}) (interface{}, error) { + return sh.ssi.EventCreate(ctx, request.(EventCreateRequestObject)) + } + for _, middleware := range sh.middlewares { + handler = middleware(handler, "EventCreate") + } + + response, err := handler(ctx, request) + + if err != nil { + return err + } else if validResponse, ok := response.(EventCreateResponseObject); ok { + return validResponse.VisitEventCreateResponse(ctx.Response()) + } else if response != nil { + return fmt.Errorf("Unexpected response type: %T", response) + } + return nil +} + // EventKeyList operation middleware func (sh *strictHandler) EventKeyList(ctx echo.Context, tenant openapi_types.UUID) error { var request EventKeyListRequestObject @@ -8226,166 +8343,167 @@ func (sh *strictHandler) WorkflowVersionGetDefinition(ctx echo.Context, workflow // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ - "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", - "W3R/AQA=", + "H4sIAAAAAAAC/+x9+2/buNLovyLoXuCeAzhxk7Z79gT4fkgTt+tv0yTXTrY4dxEEjETb3MiSlqTz+Ir8", + "7xd8SZRESpRfcVr9sptafAyH8+JwZvjdD5J5msQwpsQ/+u6TYAbngP95fDkcYJxg9neKkxRiiiD/EiQh", + "ZP8PIQkwSilKYv/IB16wIDSZe78BGswg9SDr7fHGPR8+gXkaQf/o4MO7dz1/kuA5oP6Rv0Ax/eWD3/Pp", + "cwr9Ix/FFE4h9l96xeGrs2n/9iYJ9ugMETGnPp1/nDd8gBKmOSQETGE+K6EYxVM+aRKQ2wjF96Yp2e8e", + "TTw6g16YBIs5jCkwANDz0MRD1INPiFBSAGeK6Gxxtx8k8/5M4GkvhA/qbxNEEwSjsAoNg4F/8ugMUG1y", + "DxEPEJIECFAYeo+Izjg8IE0jFIC7qLAdfgzmBkS89HwM/14gDEP/6M/C1DdZ4+TuLxhQBqOiFVIlFpj9", + "jiic8z/+N4YT/8j/X/2c9vqS8PoZ1b1k0wCMwXMFJDmuBZqvkIIqLGBBZw4AsM7HrOlLz08TQmfJ1LHX", + "pWzNOj5HSXycpkPL3l2y72xTvOEpJ58FgbwPow0vYHtKFmmaYFrYroPD9x8+/vKvX/fYH6X/sN///e7g", + "0LidNiwdS5wUMcXXJf6sgi7hgqHHBiVeMvEYZmFMUcDZQYf4T/8OEBT4PX+aJNMIsh3LKKFC7JUtt4E9", + "ZHICAyUcSjQXMzI3IP5xBukMSlZF+RCMZ2QnL4k5f6OYUBAHGm/cJUkEQcyA4ExjxA37whAihshhrMqA", + "RqaTnKkWU0PplzmRlgg+Rb8lhFooMCH0t2TqHV8OvRlrpcM4ozQlR/2+pP99+YURp0lIgRT9Dp+b57mH", + "z4Vp0tn9bU664C4I4cSZfEeQJAscQDOzBxgyAXhsWT1Fc6iJTizH8h4B8WTXAqSH7w4P9w4O9w7eXx2+", + "O3r3y9GHX/d//fXX/+dryiwEFO6xgU0oQhZBgEJBLxoQPQ/F3vW1EAxsaB2Qu7vDgw+/vvvX3uGHX+De", + "h/fg4x44/BjufTj41y8H4UEwmfwb6kAtFoitZA6ezmA8Zcz+/peeP0ex/s8KtIs0XBZ7ESDUk/3XicIS", + "e/BV5Zusg2xhlavkHpqkxVOKMCSmpX6bQSENGO1S1t2Trfed930OKQiBoNAGFVIgaKuYuSqJmQy2/eI2", + "H3782ITDDLZeJm0yZBiRGAQwpcP4AVE4gn8voJAtRXwi/llgtiXRtiHSnv+0l4AU7TELcwrjPfhEMdij", + "YMqheAARYvviH2Ur7nFWeKkQkoDXtN4TTl6KdKwrNu/TsdglYRqutE18fBf4SJrEBFYBpIryq5RUAKse", + "DDGKHY7BA4ypFUkgDBGbF0RfNY4ooSxr4ynSzPAH2egakPns5rHY4hwHuDepLtb/Hj5bu1uQJDQcB+km", + "w8zlIookYj7jZD6mMB0tDKLoDoM4mJ1LcqqfSGubTzQ+H2uWkXUvaJKi4BjbSGIO/ieJPSWNPDaH94/j", + "0fk/lcgZn489PkYVH0uw5RzF/3XQm4On/zr8+EuVPzNg7ZR3BWMQ0+MIYjqYAxR9wckita4esibExLAR", + "IpStUbRQZjlmRzhHm3WJ5YfoAfb4jNW1S1CbVt4gkcXgxr3mn9S2srWy862QiGvZW7Wuno+TCDbpQLGa", + "r3B+B/GItTfiw5eDNWGlpbwu61XKB1kHFvgySLSYmidlX9Y/aU/6FLiEeLEcLjhQJjxyWb5NIb6SDF7J", + "yhIodyPNvL3pXM/AHZ4Wt7Lsi5GeGutCHhN8P4mSx9EiHi/mc4CfmyDjW/Wt2q3G2BNKKlvIjdrwU2A6", + "SbXRr94//nt8ce7dPVNI/tmsLTM9yaf/fTUaUGOcIRPTp2CK4sxhUIfQy6xlZlFx+fXo7sbKllP1aShA", + "dwXKGhAvcAjxp+dThGGgQILxYs52DpDAFz5aTX6U9kL2/6w8mKpvfmqzdh1DgIOZUd/a6L2CywlARi8Q", + "F/QLpmMYq4pWHl7ExUOd3TGdwjhksDQMLJu1GfnvBVw0QyxatRkXL+LYAWLZrM3IZBEEEIbNQGcN3Udn", + "dPgFUmktn6LJxH7CCdFk4k742pCNjmYxMpNRX7jf7jhNhzGhIIos3kcQBMkiprfgAVCAbxc4MpKxahab", + "rf2ej7RZbgmkFMVTYh1uaQVo1xJ2AErQ90xrNlkVAoOf+MnFdvqpQQi5DeEELCKqfc68ssbjkYJP62qH", + "awTTpAoVhmlih4l/TR5jiJtPbFrbnjasCaD/Tu4MNF53IcbVsXYlJo2Qv5K7/Q15pSpjEgrTdjxYZb6i", + "eVU9a6E5TBbUvHz5sWnpDxATlMTGGezMkIGlD5C5zcTSLTtpPOoHIA5gFCkXq5svMeuU3czam4wgIIJQ", + "DFeKMSKzdlP/JSiybkcZ0YqWlt1bgegwJEW+zzFMKMC03WIIBXRBHNbDzAvRVtL3aBG3VjNLUHlwD3E9", + "C7RZrnamaAJZs6tKPZfnl+IgikCyXbBzzTjbJmU5Xg7OT4fnX/yeP7o+Pxd/ja9PTgaD08Gp3/M/Hw/P", + "+B8nx+cngzP2t8nEPEPxfS7zCaIJfrZ6C6aIsla51qpKHpyN4gm9YxQ8cqBzq/dBG4bJlbpBLpTKqR2F", + "KxvjMLputx1lDeC0ukWq+NkLUxbxUVpYr4R1E42wA5T5Ntg10qDc1cCnchLuXyd283Orx7bsKst4cmMQ", + "Gy3VXQHfbEY3meEaiHI+G03oRiYsLLoFeJLuLBShyY4lx+e2pmV07bagbs+0Vs6Ta0M3Y1yf4EbCVrxg", + "IK9MSkVo1khD4wgE99/g3SxJ7l99kRos61piMj1DMWwVNME0Av/MThfM3FB2dpRMvQjFsM2VuIi/M87B", + "hpMNGk8utt6ihcHPXMKWHj6QBwVmM9zkqDqDDzDSLZHTwadrZn0Mzz9f+D3/2/Ho3O/5g9HoYmQ2ObRx", + "MoeY0/4XIDCJDPn99f2JiqzMekl8XMGnWByhpVdRdq7xKxoQoMcwfPeDBcYwprcpp93Dnh/DJ/Wv9z0/", + "Xsz5P4h/dPDupVfmrEJnU0yNbOGlggqziQ+dHHEaLMa4NPhUHfm928j5uoyhQAkFke72ZE25tz5ChIq7", + "qzz6952L388gsXTFVacKPwECc0u9ssday98gCN1aDk+1FrofOG9yzpff2IwdaGALHS3aF8e4QjSy+6KE", + "vX5e564STS7cfVZ6h8osZUwZYDVhyrYVPctmGtB4UySLDLdKHiQpZEowiBJSCJ/MsTGCjLx+nnCmEUwj", + "8FwfpsOv14ZhUehvO/qwPvJbQXjDl4QXsfSz1GxhujD5jiqYY83YqCW70jDgFBJ6jS1BFdejM48mHoFx", + "yENlpGlBPJps5vLadoJfxOjvBfRQCGOKJgji7BZVXk7LEE4R0aMHRN/BKImnCuLydlY3bHMBRW4+ptog", + "oYIFXbV5ZyCOYWTDofzsodB8lU/Y4N6jGN24vXIEu99HTcGjT5acZCUaAnPb6tm3FZbOutvXzQdfZdE7", + "Qf1u9KkQkaG7SBc9jQyNJExharpqVPLJQHQoCjEs+sMaBO2GfPcpwCq/zB0SDEEI7iJod06K71n8PPQI", + "hWkjmax0pWSZwU4B2ioK5KBc4HIDhfles/UbuEI6poM0KRi/ms26posmToTalUJLGliOhqEV5GXuvfI+", + "NegqGxiFazOHWxd5SZi1Xz8PJgtqA3FJ9uRRKMcTKk4Rbshc+y2e6FKzM243fZLFild9rhfYrK1NtjgI", + "njYrzrrUrPgxwbbLQydNlVFgtrLamzo9lKYioWRQZ5WQk5ApZDNeEoymKAZR8wJEGGPWXhv3JofMEsnK", + "41XMGFQsVlkrCs0dNH+mgeSVdHQgQHk04z0YZcEHiBF9btN7rPrkF9Y19PcZYULHUIhYdxo8A+16mVK3", + "itOXxtVhz3CoIUR314qdrKHNXQm3LBCk0UdqIAPNoTEa/N/rwfXg9Pb84vbbxej3wcjv5T+Ojq8Gt2fD", + "r8Mrv+ePT34bnF6fDc+/3F4Nvw5Oby+u2c/H4/Hwyzm/IB9fHY+uxJ358Hw4/q14fT4aXI3+I67X85v0", + "ns/Guri+uh0NPo8Gss9ooI2qTzY+u2AtzwbH42zM4eD09tN/bq/HA7OX3EjJGgo0b7uEbjS8Gp4cn9WN", + "VhdGIP+6FWv4OjgvoWmlMIOrLLK8ZLZHEMssh4ElF+WbylROPN5aHVnmvBfZN6YlgxhEzxQF5CKlFwta", + "M2p+BpoB4iUpO3hJOzcbxDzHxtMZbRkQK6dQNCc/WrMhjPlF200s2kyIZ01+kXHNOyBFzXthysOaJnuC", + "5PwRm4BLWK03iqdjGdS6PRYV2fyDpxSxXeZhHByY+vFFLzEN8R55VjKPSPEAhh5IU5yAYIbiqUhP5giu", + "m1/lRwkiOUNzRJeEQixZ5X9X4YnY2LW40I6HnwGKFhg6gMJdqzogKsaMR5fzmH7znBEgYqn2S26ev84T", + "3EEsd/YREDYlbXHFDZ4UkX3mZ6U4sOS2zMGTN1FNPEC9xxkKZhlVrdfZZ5cERoDtcmGY3YpsJtXwJUtF", + "tztmtAIEYpitJucvl8/Y5LOUDGXzuKrPdqyJFnU+Vz5CITV9CY1ZSMTM90pPKmugnZ1RJZKU22kQsadV", + "+F+NoNzzFxnrNbW+JhCLHpeLuwgFdaTAx6tJydVh3plNl/u3zKaP5D6pw8TFt3N+Ejs+/To893v+18HX", + "T5bjTVHv6oNk57nBH+IMwn74fHbx7XZ0fc7PORfn+blu4DA61+omwwbg+R8gWljkA//uPbAGZhHENToT", + "3Y8A8zSTiroXvfeNwSRMuY7gBEVRk/LloWV8OKZ9Me8DQ3fRzvvWLFSMbV+iGf7VUhiybW8m0IxIXnr+", + "g30VKmioacPMq3lEcZg8WuwSSCH2RItMU4ixvH+gfbjvHXgheO55B94jhPfs//MkprN/LnlDlqGnsHVq", + "8XbBohB1mUQoMGSBCgu07lCWVS8STQ1qsYVgKbJfUyyDBM64OoymU4g187hlaZWq+7JtGMQ1r6j0MxbX", + "0FfeEB+0lroWVuWpA2Lf/zfsT+oOxK97IN7gQdWdXZM5ojGKeuFCRfKv7i40RZFeE5PN3nhmBWGIISH6", + "2bVgh6jDUPUIyz78BsjMJA9ngMz0If8PKU0nJaRQ5aJe6FiU3vROZoBaJ/wD4ux2zc6v/ATOuPVBNme/", + "IlyEwUwzM0AuASGPCXadA3ip7OARSLfoWQ4RSSPwXCAZtX+tD7tF7N5YCOxkBuIpVAiyFyGCj3YkciqH", + "jznWlE1ihn0JxahG5utOawHJgKjF32owVNJY5JdeAU82lJ8lUxQvX/5qOf5eqRrWzmFcrTFtwvUIThGh", + "7P9vCN1uusQiGHZwt1S1TtdN0w1QMkMpeauOmIpjaovafBNaRkxm2rZvPI7HFvNpcYvLj+LgLyKBvADE", + "XgoxW99+q/tG8ABQxCxSFbnXUDuoOi18gsGCQi9IYumoiJ7NngiGQV6vDg8b6iRzLweaxjD08k47UDE5", + "AjyRBdM7COgxrb22yLHE/V0ExtQD3kz13l9v5Wk2B+N3GEM8IBTcRTyicYcgnIMnO43NwROaL+bro7WN", + "hzAIqPbNEZABjFVJK2ILbWZteLCxPL2p1yXygVetP5LHRRqCKfi36mKUr/r45Gr4x4Dnvso/b9rGWtgF", + "3g4oJil5jRFa6nBuqs6URsnzHDZfxagxTrMeJ0k8QdPGR08sNZ7UUX/fUrfHss3si2kIJxzJWj9lsuLu", + "/dZVZrbCkFYMKQvMIAvBdHkMqTVeAaNylUWk2lElG+8P0TFL3qlzL7uyHRv3RMlNkxN9Cqn2PQt/Krk0", + "Yml/SM/QFFLCcRfkXb0p65sdZzVC2Lde4owpBhROn23SSnz1aCK8Japmvz6ruOzhLxWAYCaukZQ4EzF8", + "t8Pz28vRxZfRYDz2e/7p6OLy9nzwbTC+8ns+D7HM//lldHF9eTu6uD4/vR1dfBqeG6/jWiq1XG8VfXfl", + "UonvD80p04V9l1OXEdgzbmQdVVRk1M9RHmlqK/W4VGEb42i2CJDczhXjecdp6um1k5wSMjcQIdSiXJN9", + "yTcabYm8+rJbNCP+4alxa1Rvs6GwUpralm0Mbkc4PXqkWn+FFKPAEK7Iperv8PlEZTUYlFmpTkNVKN/D", + "Z2K2mNXwTKjUTFGy0JmQBx5JYYAmKMgn8f6RAkJg6D0g4E1QRCH+p2MZiG/FSnYu16D5r5daa4oX0PSU", + "hDjIW+snrDe3LLuQbQWjSNBxJ7U8uWxtAcUqaUzIsG2ftMXcYz25ZdsgbKzmpF7XOstGq08jE3ECMPz0", + "3GLwK62XVudRGpctbVHDCKtXi/xDq7IqcVdc7E29fNiRg6R2zHGX8+uqfSktV3N2SmUyhbG2a9MItSRg", + "LARnKDuWxJeaRKkmEuMkHjPDfWGpfQMfHA7eWeaVLCGymUT3lqSfdWqgZ3LCE66t1y6FKqtFGt1kNZbS", + "tE2LsFowPIuvDdWpoU5ExyZ+KjWvzC+5x5jfqTjP+FFymPGbYlTjx5x3DZ/rVnMFpib8RQlej8toZZ+K", + "+bJKQFhHIFJGnGAmcydmMVGTwn6LLJzXNKHMt5xYqiHd2tKYV5yWmFfYXr+U8GaqifBQSfFvMXCGn/Xa", + "kELpm9GX2wG30lPWHs2aMVPmlYKrywUTundM86qu4itdpZoDDktlF2yOoR5/ypepzau8zonNhmxLF0Rz", + "ZJoFhvzoJHYeNde669FZ9WmpcxXMCpOFgW6aSeoUsuOeuTYYBo/FzwYXFHj0/nP89cwLs4btpWpxHgeg", + "zc/kbokKfwIq4QUbggVG9HmcP599BwGGWL2yLd7VZsdU/nO+wBmlPIk1SJJ7BFVzxDAkflIu/CO/8la8", + "fHyZ30+heJKYkaye5T++HPJqC7yKpF/8Ndsl/2D/3f47vskpjEGK/CP//f7B/jtusNIZX1ofpKgfoQco", + "bwiq835RNwCsVQwJ8bKTHKPBzA/qn8nvX/i6sDxw8VkO372rDvwbBBGdcTH60fT9PKHZnIWd8Y/+vOn5", + "RD0ZxSDMG6q7oD/l+MEMBvf+DevP14ohCJ+bF8uaobrVjlSDdS6XA8ejcfkjvR7FYDKRmVt1q8+gbVz+", + "w0EfyFDhPR63ssddeqT/nf+s//YiYIwgNViSp/x34oHsiTwekS6ic3j3CsZK2QdiBHF4AjxRhYFdk+5Y", + "mcHjByHOX4yec+6qLMXX5YNwyQkZs/LJ6uWmsvcfqtgaL4IAEjJZRNGzJ1AaFt4XrCDvped/EFQSJDGV", + "52KQppF8nr//lywskq+jQfLzKmAyAqvsr5+DiGEBhl6CvTsQejivL//h3fu1g2GC4nOC71AYQhE4ntO3", + "oJM6MlMUL9Mjb3r+014WvM9fnxUfegbCuOFHAGq69RJpG6uQuBjhxyBxUcw3EbJzLcTgkJlkIJNabNFE", + "PlpfwcaLWUSvZSGWahZV2AtiQD5134kBNzEgqGVzYkBXkCnaE5lI/e/Z31wbpgkxGA0j+JDc80oT2UPs", + "8t4sm7EkJlLEk6TEmkR3FymRDW+RCQrWnVJ3mC9P0rl68uYHJmrShqol6bCNvZI7p8g4/62OkrMtL1Cw", + "cBX1v/P/v/TV2dBm8vK9yd4BBnH+Pm+RbrP3hYXN20iv4jVhmwoT7v5tkur6aC5/ablJzGNIMYIPkgEE", + "Rvh+dFxQOMJomMl5QFz21NC/oKEC7YuolT2Qpn094oZYGeAMEWqL06me+7IAIdZtWGq6MXpzeBCsHSEW", + "F7lLtHiwHTCuY7CgswSj/4GhmPjjdib+CuksCb04YVZMlDzCsHy8/17wIP1581I47zeRq+Id0cSNN/rf", + "p7M9/ZeXPg+xc+aZLCAPwQaW4Q+uuSgPHRyrDimB/Ua1ie05unYsXdiDjqPfLkeXmKnM0BVtWGaClVie", + "/87+2uORtS/5vxnLvfTv5JuMzqIh61ArFj7lrd6aZOi5RChbgcxRXQti20nVm+n2OWUL9ym3IwErb362", + "E4IZtXUC8O0KQE1krEP49R+1Z4OMHhxt7mmU3IFIvVhjEVrCcfOFN/2WtWy+AyoQbooT9g8YZpN1NLtL", + "NFu8ZRMUAkwU0mxxKwrsf5d/vDjRonR2utCicHrmtNioROWgVv35qJH1Vi3qjmN+OI6p0HEdx8xhvbOS", + "ZG8DZxmIKgCCK4KYlx8scorKJbHf1a8LfTIBq43JkkWr7AoxNwQb6CH/ch+zXB2un1XnvSQVCTHyCq60", + "yX1UeoTcfpwAUeQVWts2WDjlCg03arPKHS88YN5q81VBxcLqdokQikZaaROq+69vMn/7r/+d/88hmMQb", + "628FVrZYfwDSPXakMKZV2XEQdzJIpIiTTt29vrorB6aUqVbxBP+97u5CEF2RY2LS/05i4sQtxfcuq/wS", + "kxZsUno808ooUqTuHJuUkNExyg4ySoVgM1Y5H9cySkwMbCI+v6hLbvuBis2r7porLNI6RMvGGRm0W3Uz", + "iltT8YbeUlfsGgyHHz8WgDjojmzdkc3pyEYoTPfwgisv+edLP8AQULiXYjtnnvAmHvDSRRSpnZGhU1mO", + "QIVpRc64YFwxwiV2YWBVi8uu3CTsby+UUqJhEanU0s84mWeVw8xBlLxwgUcTLzDtwlYDKNuCX5AwAnxx", + "miqs4OcOsGGzftjOrOcJY9pFXNb7kr1LZKUESZbcU6f5FUc2i5tQPl5bH+OGJhNV0kRJgztIH6EsNzVP", + "CFWl+9g3EAu6miBM+C/7NnH0BVL+fO5bkkMb4uYvkGoPCi95j8e3s+PgV+ZgxjehIOsNsW2e1Gz3/ok2", + "Jb61MSLrMRCDvhFe7NWUYaKJR+5RqmD7ewHxcw5cMpkQ7tE2gIJi+ssHY0Wm+ulElb27Z8uU/HPLGTcp", + "bCrPQy8Rj0s6QbMtQVPguUdA+NHEJHnKjK+xfeY1YD8xC3E9kihKpvVyiHhRMvUiFMOyLKpaBWfJ9AzF", + "4n3ETgzthhjq2Z+ziuADjAibV5Syq5mYtyzMXHtHJOmA9fqMYBTaVk4gwMHM47NpcEwSbAFEdGgLyFj0", + "MgDxjb/Dmng8Td6+fv7507NYS8vJL/S+FjyI6UOEYSCdhDVQnGrNloEk77/h6DZNGjTpJ0aSnXKyJIpw", + "rZBJYU0XnCXT9mpAfCZN/iniAf6OjyWZT0TeiKb+Jt07xQfk6lNjM7fOKyTCtvLY0OxJ2y7PVXeaZMTW", + "lNVqoujsckRUS6zJbufJfk+IUBRP6wn87VyUbCFd3Y0J8yI9r5qY3vHj2vLOW2SZ1/KluQZLfdQbyKxV", + "Ww48aapH4Xoc2QkO3maxhiU8B/ZN6HinYK7VUas7M/VamGjtC7Vk1tvPqtx0C3N9tVicTdCDV67FUtWA", + "XS0WVxt1pVosblqyTyBl/yfNddtUF091qa/EopELiqdj2cexvMVPoiY1xKygI/U96VipcMNmRdPa+Cgr", + "aFR/0ZbVFyJu9Ys6ezLLAuD4WDZpVcU6db6+svGYFUEi7SojNRmMSxTr6mxEjgBF65pZuPnQsHzSjr/W", + "xV+SEZYsPVavcByiOghP6iqEdojelrJjb0XX/MzXqPfw2ekSlbUrzOpUgp+TAS+SXX3OxA6T9hSdE2y5", + "rGgNoPYm3nIg4kUsy01DJ1hVW+frT/NzQa90Jc3383UupPnUO3AdrcOhX0bXEEuW/HwPn70HEC2glwKE", + "K/SSvab2J2O3gyPe9MDvsX8din8dMvFuWo/hET4jMzS9MrTxCozdjfparGyo4iUd6y66umPryoh25jRH", + "gHyWp9bFKpK8XudKXz631sJ/CtUDbT93ROXhv7czq3rQRZp68CmAMISWVAyVD+jM581Gfp8bc46WvrAQ", + "Haz93+Fz51zKTd6l9BxHdqfrTLrOkyeQdfIBhmkEnuuqxLPv+j2+6GjhAFUbng/682pJgQB3LYlCbglj", + "hbcta0rHQAIGXGeS7rC+FGS3hGFcJyhQ/IAobBvmo3qZry6H/GunK9WNpYaPpe4qFba7G0pTEE9OixuK", + "3BET1NJ6d3DUYnUEStxCdARuXzUuR4C7TDiOJIyOLc0xOBnfrCdgQPK5+mFP/Lvdg4AOrNz6CcDdutUp", + "8lU9bHsZOt66bm3kXsP7hjvGvabqVNn+2HKIivvY5t1AB05442WodpATNpsAspzefbUUEEfONTxJuMuc", + "K1MzWnNuneabw/mdfES9xRlN9TKz+Ff+tTujKWrU8LHUGU1huzMGTWe0nBbXYwvK8frfxR8upUmBBMKb", + "4GTeFHwtqOHHMAXlsm2wic/bL6C6dt5dxgb8Obj2DRR1ypi0sDFrkxeqx16aRChwempfemRFB5eEDeXE", + "veQ9unSNvgktyynX0m50SnbrWU+iLnhtokah5jipLZXfmZ0iR0PHSZs8phKqu+rFO1RYXOMFy8MUDUX4", + "HRixTyjA1MqOY/ZV6LGL4wWdecaaxdcEYnFa5QBdMITynm+RM9+/O2wo+s1RJtVKASszCEJ5uo4SQTBF", + "WinP/dLqHTmO0uKMihDYDixNB015c6XK9sRUaL6Tw1IOn48LD/S0kMRlLHeyeOdkcZURnN54aEzXc3js", + "pLsX5ggo8ldtlt76aLY4qfP9bvdqyw4ztJXzHDm6VqMaCrDWFm/X67U/C861lWF/sz7EH70uvOuDDkW3", + "RFb4t/Mb7lqNZsaYa63L7CQn+gGIAxjZA62PKYXzlPI4YNHWoWy8fE5GDN1JkLctQUJEeGyGFCGCCKLd", + "szFeOR+iiVG2xdAYso41iROsgzMP8+YdC+9iKgdexHKrGiJnsrepxP2QabkvO2GpdIkcNfKFb/hrCJR8", + "TbWvQYlmju/KfIF0LIbtRMvrWQdyvOTuLxjQJU8Sct+7A8VOHyjULm1Eajwm+B7ieo9DFHmiWUMxvG+8", + "UefeF9Vllorr6+pzWSpDSgIsPb+2/E2WQrTUmPo/m/xvhSJFjQwhiwy9ZXdcYcE20HQMvmGuldu1JNt2", + "7jkz52a4afd6YoGmlufnfoobrq2ZitMfZyWlp5eN5rBGLmwQ7dFe0rH6pgAsvI9NqLjntFWEg87lx7TN", + "G/OOm7/+1+llyWqeKveiQLqd/CldxRexs3EJRJyMad7SzXroDOpcNXcm9foVczuecGSCxrupE+VmvwM0", + "mJWLf5I6bft27qY25D/WcEEEMly9yPJyo2yzkrU7lfMXv9i/HnOAhyEpVM5cCcHV2rIt/WLyQsxgxnfi", + "Qo+AEWRTJpmy1Kj3SblKDj64qw7loDifwrvS2btaOluvW8bmnEKabe2+ZWLefhj62zoTuUOmumweuBRg", + "hjTLua0Elmj8TRfGW4LPcONihE1eMW0Wrp2uN97Vt65B3Qrmf6fTG3xzmzkGcHfcnO1IQJriWgVojPSL", + "jCm7t/G1f5UzvlVd36nCThVuVxV22qZ6tlZSZAWNo4RXp3k2onkWBGLSDxYYy6XUF1iQDT3WzZia+gXS", + "EznYBmmMp2C2IyoOcZcV9PpZQa4ZuIzIS+RWzMCtkvEU0dnibg+kaT8AUXRXV/zgJGFSmMIWCddf+PDH", + "acqTrk/UDK1zmgM59fazmtWi6xKba9G63gz2IkJVFvuPkiFuQ+QGaXOHCXNtdLgRGtxNAlyZ3pJkGsHN", + "0Bsf+genN4G+NdNbjrgfjt6aXiXIn/ApFoHnjgkn85KNoNchJf4uPQOgPZvzU70B4HJ4cVWrbm8EWGmv", + "D4IAprQmpZN/b1dSWfTxN3MXLQavVAG23B/XUJ9YeVfrvj5RkSOpsda9nb4w5PfiNSmG7Hs7+hJ9/E3l", + "yrHB10BfYuUdfTUkqjEkLUFfUTJFNZmrZ8mUeCj2ANeN+zUGxhkfaEN1y5kKZuNv6cl2Jz9PlEynMPRQ", + "V/Rlt9w7RbXOqMbVjxMl02RBG5ghWVA3bmBD7QiNMlA6In07PkhBPa5kK8tSz1Da4gikdXI7BunFs3k3", + "GZO1UQI3T9r+PKSjqDsTLXMm0jHYTJIpIOQxwaFdlsqXL4Qk9VT7OpF6qcbcnI1xMgPxNJtol4yNgEMW", + "ZojqxPkbEueCrIqU7sBEGE6ZIMN1hz7RgtRaJCf6W3mbYBsFxi4xjEJedw37Jux0RUKuNo8obL2JG4a8", + "vPXPcNMq0/NF4q18Gqchhd5gG4qUfsdUeTFGbZLqll+YWXf9iCXijnZMOu1M4YgWdSN6inQqBC4iWrPc", + "crf3n7JAJ6foVffHn7RQzPos7e0/suT4GlIGYFd+aDfeQdIoZpkcaV673KVuihMntNACu8cG6w9HXTIG", + "tdMG5rDT5Um8QSf0IxTf74lQnxoPMIrvPeCJZh6GaUIQTfCzRxONUay8IX3DKL4X4T9vilHWf3bMETHK", + "MOmaEB1ZdmKrVTadmZxBKzm8CnGnRl9ZjXKuNlHShkSNS1oVI5Vi/lQmCB4gJqYXRDQN3CKNahfkiyG9", + "RSRqVspLuOd4Lp/haQaIv5DHk2xyENQGWUHhnX6HzwVgXsMMWTEjRpJclxSzKyKrkIozz7h9QwKLYjSd", + "1jmir0QDD3gxfFyu0KL740c7KbGuDOyy7w0n3ANKFow6YNgT1b8AhYRmPIWIN4G81L8tFTMX+Dtu0Eky", + "0Ha1TZX0UtG+7ZtxLvUj1cNPXfXIHRCJPf/D4b+3M6t6mFgWVYFPAYRh5cUpJQcbamc21YBuIZqlbCCu", + "tW+V1HESy3+Ixm/IpfMjyOUNSzm5qSuagp282ykTMCfFDZmASs70QzhBMVL3lm1ETt6zrfQ5zefs5NAP", + "Joe0vV3xcKpRZiecdlA46Ru0vJwqR0PcQYAhzqIhesb4CIgflLxY4Mg/8v2Xm5f/HwAA///oVBWNdoQB", + "AA==", } // GetSwagger returns the content of the embedded swagger specification file diff --git a/frontend/app/src/lib/api/generated/Api.ts b/frontend/app/src/lib/api/generated/Api.ts index d63ea2f2ec..5ce6ad544a 100644 --- a/frontend/app/src/lib/api/generated/Api.ts +++ b/frontend/app/src/lib/api/generated/Api.ts @@ -16,11 +16,13 @@ import { AcceptInviteRequest, CreateAPITokenRequest, CreateAPITokenResponse, + CreateEventRequest, CreatePullRequestFromStepRun, CreateSNSIntegrationRequest, CreateTenantAlertEmailGroupRequest, CreateTenantInviteRequest, CreateTenantRequest, + Event, EventData, EventKey, EventKeyList, @@ -874,6 +876,25 @@ export class Api extends HttpClient + this.request({ + path: `/api/v1/tenants/${tenant}/events`, + method: "POST", + body: data, + secure: true, + type: ContentType.Json, + format: "json", + ...params, + }); /** * @description Replays a list of events. * diff --git a/frontend/app/src/lib/api/generated/data-contracts.ts b/frontend/app/src/lib/api/generated/data-contracts.ts index 392f255de1..23d6b7653b 100644 --- a/frontend/app/src/lib/api/generated/data-contracts.ts +++ b/frontend/app/src/lib/api/generated/data-contracts.ts @@ -391,6 +391,15 @@ export interface EventData { data: string; } +export interface CreateEventRequest { + /** The key for the event. */ + key: string; + /** The data for the event. */ + data: object; + /** Additional metadata for the event. */ + additionalMetadata?: object; +} + export interface EventWorkflowRunSummary { /** * The number of pending runs. diff --git a/internal/services/ingestor/ingestor.go b/internal/services/ingestor/ingestor.go index 0b2ff580f6..c3a78b0407 100644 --- a/internal/services/ingestor/ingestor.go +++ b/internal/services/ingestor/ingestor.go @@ -18,7 +18,7 @@ import ( type Ingestor interface { contracts.EventsServiceServer - IngestEvent(ctx context.Context, tenantId, eventName string, data []byte, metadata *[]byte) (*dbsqlc.Event, error) + IngestEvent(ctx context.Context, tenantId, eventName string, data []byte, metadata []byte) (*dbsqlc.Event, error) IngestReplayedEvent(ctx context.Context, tenantId string, replayedEvent *dbsqlc.Event) (*dbsqlc.Event, error) } @@ -112,7 +112,7 @@ func NewIngestor(fs ...IngestorOptFunc) (Ingestor, error) { }, nil } -func (i *IngestorImpl) IngestEvent(ctx context.Context, tenantId, key string, data []byte, metadata *[]byte) (*dbsqlc.Event, error) { +func (i *IngestorImpl) IngestEvent(ctx context.Context, tenantId, key string, data []byte, metadata []byte) (*dbsqlc.Event, error) { ctx, span := telemetry.NewSpan(ctx, "ingest-event") defer span.End() @@ -120,7 +120,7 @@ func (i *IngestorImpl) IngestEvent(ctx context.Context, tenantId, key string, da TenantId: tenantId, Key: key, Data: data, - AdditionalMetadata: *metadata, + AdditionalMetadata: metadata, }) if err == metered.ErrResourceExhausted { diff --git a/pkg/client/rest/gen.go b/pkg/client/rest/gen.go index 5413e66f99..220326ccc3 100644 --- a/pkg/client/rest/gen.go +++ b/pkg/client/rest/gen.go @@ -236,6 +236,18 @@ type CreateAPITokenResponse struct { Token string `json:"token"` } +// CreateEventRequest defines model for CreateEventRequest. +type CreateEventRequest struct { + // AdditionalMetadata Additional metadata for the event. + AdditionalMetadata *map[string]interface{} `json:"additionalMetadata,omitempty"` + + // Data The data for the event. + Data map[string]interface{} `json:"data"` + + // Key The key for the event. + Key string `json:"key"` +} + // CreatePullRequestFromStepRun defines model for CreatePullRequestFromStepRun. type CreatePullRequestFromStepRun struct { BranchName string `json:"branchName"` @@ -1249,6 +1261,9 @@ type AlertEmailGroupCreateJSONRequestBody = CreateTenantAlertEmailGroupRequest // ApiTokenCreateJSONRequestBody defines body for ApiTokenCreate for application/json ContentType. type ApiTokenCreateJSONRequestBody = CreateAPITokenRequest +// EventCreateJSONRequestBody defines body for EventCreate for application/json ContentType. +type EventCreateJSONRequestBody = CreateEventRequest + // EventUpdateReplayJSONRequestBody defines body for EventUpdateReplay for application/json ContentType. type EventUpdateReplayJSONRequestBody = ReplayEventRequest @@ -1457,6 +1472,11 @@ type ClientInterface interface { // EventList request EventList(ctx context.Context, tenant openapi_types.UUID, params *EventListParams, reqEditors ...RequestEditorFn) (*http.Response, error) + // EventCreateWithBody request with any body + EventCreateWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + EventCreate(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + // EventKeyList request EventKeyList(ctx context.Context, tenant openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) @@ -2036,6 +2056,30 @@ func (c *Client) EventList(ctx context.Context, tenant openapi_types.UUID, param return c.Client.Do(req) } +func (c *Client) EventCreateWithBody(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEventCreateRequestWithBody(c.Server, tenant, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) EventCreate(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewEventCreateRequest(c.Server, tenant, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + func (c *Client) EventKeyList(ctx context.Context, tenant openapi_types.UUID, reqEditors ...RequestEditorFn) (*http.Response, error) { req, err := NewEventKeyListRequest(c.Server, tenant) if err != nil { @@ -4060,6 +4104,53 @@ func NewEventListRequest(server string, tenant openapi_types.UUID, params *Event return req, nil } +// NewEventCreateRequest calls the generic EventCreate builder with application/json body +func NewEventCreateRequest(server string, tenant openapi_types.UUID, body EventCreateJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewEventCreateRequestWithBody(server, tenant, "application/json", bodyReader) +} + +// NewEventCreateRequestWithBody generates requests for EventCreate with any type of body +func NewEventCreateRequestWithBody(server string, tenant openapi_types.UUID, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "tenant", runtime.ParamLocationPath, tenant) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/api/v1/tenants/%s/events", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + // NewEventKeyListRequest generates requests for EventKeyList func NewEventKeyListRequest(server string, tenant openapi_types.UUID) (*http.Request, error) { var err error @@ -6297,6 +6388,11 @@ type ClientWithResponsesInterface interface { // EventListWithResponse request EventListWithResponse(ctx context.Context, tenant openapi_types.UUID, params *EventListParams, reqEditors ...RequestEditorFn) (*EventListResponse, error) + // EventCreateWithBodyWithResponse request with any body + EventCreateWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventCreateResponse, error) + + EventCreateWithResponse(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*EventCreateResponse, error) + // EventKeyListWithResponse request EventKeyListWithResponse(ctx context.Context, tenant openapi_types.UUID, reqEditors ...RequestEditorFn) (*EventKeyListResponse, error) @@ -7136,6 +7232,31 @@ func (r EventListResponse) StatusCode() int { return 0 } +type EventCreateResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Event + JSON400 *APIErrors + JSON403 *APIErrors + JSON429 *APIErrors +} + +// Status returns HTTPResponse.Status +func (r EventCreateResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r EventCreateResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + type EventKeyListResponse struct { Body []byte HTTPResponse *http.Response @@ -8580,6 +8701,23 @@ func (c *ClientWithResponses) EventListWithResponse(ctx context.Context, tenant return ParseEventListResponse(rsp) } +// EventCreateWithBodyWithResponse request with arbitrary body returning *EventCreateResponse +func (c *ClientWithResponses) EventCreateWithBodyWithResponse(ctx context.Context, tenant openapi_types.UUID, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*EventCreateResponse, error) { + rsp, err := c.EventCreateWithBody(ctx, tenant, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseEventCreateResponse(rsp) +} + +func (c *ClientWithResponses) EventCreateWithResponse(ctx context.Context, tenant openapi_types.UUID, body EventCreateJSONRequestBody, reqEditors ...RequestEditorFn) (*EventCreateResponse, error) { + rsp, err := c.EventCreate(ctx, tenant, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseEventCreateResponse(rsp) +} + // EventKeyListWithResponse request returning *EventKeyListResponse func (c *ClientWithResponses) EventKeyListWithResponse(ctx context.Context, tenant openapi_types.UUID, reqEditors ...RequestEditorFn) (*EventKeyListResponse, error) { rsp, err := c.EventKeyList(ctx, tenant, reqEditors...) @@ -10202,6 +10340,53 @@ func ParseEventListResponse(rsp *http.Response) (*EventListResponse, error) { return response, nil } +// ParseEventCreateResponse parses an HTTP response from a EventCreateWithResponse call +func ParseEventCreateResponse(rsp *http.Response) (*EventCreateResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &EventCreateResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Event + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 400: + var dest APIErrors + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON400 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 403: + var dest APIErrors + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON403 = &dest + + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 429: + var dest APIErrors + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON429 = &dest + + } + + return response, nil +} + // ParseEventKeyListResponse parses an HTTP response from a EventKeyListWithResponse call func ParseEventKeyListResponse(rsp *http.Response) (*EventKeyListResponse, error) { bodyBytes, err := io.ReadAll(rsp.Body) From d222862cf56b7c0ec0e294ad5e76ae51f1b4c487 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Wed, 19 Jun 2024 10:27:41 -0400 Subject: [PATCH 2/4] fix: ingestor usage --- internal/services/ingestor/server.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/services/ingestor/server.go b/internal/services/ingestor/server.go index b5dd2ea411..81ae50feb9 100644 --- a/internal/services/ingestor/server.go +++ b/internal/services/ingestor/server.go @@ -29,7 +29,7 @@ func (i *IngestorImpl) Push(ctx context.Context, req *contracts.PushEventRequest if req.AdditionalMetadata != nil { additionalMeta = []byte(*req.AdditionalMetadata) } - event, err := i.IngestEvent(ctx, tenantId, req.Key, []byte(req.Payload), &additionalMeta) + event, err := i.IngestEvent(ctx, tenantId, req.Key, []byte(req.Payload), additionalMeta) if err == metered.ErrResourceExhausted { return nil, status.Errorf(codes.ResourceExhausted, "resource exhausted: event limit exceeded for tenant") From 97c415e40e5baad20d222cdff85cd9d6fddb3636 Mon Sep 17 00:00:00 2001 From: Alexander Belanger Date: Wed, 19 Jun 2024 11:41:44 -0400 Subject: [PATCH 3/4] feat: add create event to the UI --- .../events/components/create-event-form.tsx | 113 ++++++++++++++++++ frontend/app/src/pages/main/events/index.tsx | 48 ++++++++ 2 files changed, 161 insertions(+) create mode 100644 frontend/app/src/pages/main/events/components/create-event-form.tsx diff --git a/frontend/app/src/pages/main/events/components/create-event-form.tsx b/frontend/app/src/pages/main/events/components/create-event-form.tsx new file mode 100644 index 0000000000..5e7c7ed9d0 --- /dev/null +++ b/frontend/app/src/pages/main/events/components/create-event-form.tsx @@ -0,0 +1,113 @@ +import { cn } from '@/lib/utils'; +import { Button } from '@/components/ui/button'; +import { Label } from '@/components/ui/label'; +import { Controller, useForm } from 'react-hook-form'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { z } from 'zod'; +import { Spinner } from '@/components/ui/loading.tsx'; +import { + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { CodeEditor } from '@/components/ui/code-editor'; +import { Input } from '@/components/ui/input'; +import { useState } from 'react'; + +const schema = z.object({ + key: z.string().min(1).max(255), + data: z.object({}).passthrough(), + additionalMetadata: z.any(), +}); + +interface CreateEventFormProps { + className?: string; + onSubmit: (opts: z.infer) => void; + isLoading: boolean; + fieldErrors?: Record; +} + +export function CreateEventForm({ className, ...props }: CreateEventFormProps) { + const [code, setCode] = useState('{}'); + + const { + register, + handleSubmit, + control, + formState: { errors }, + } = useForm>({ + resolver: zodResolver(schema), + }); + + const keyError = errors.key?.message?.toString() || props.fieldErrors?.name; + const dataError = errors.data?.message?.toString() || props.fieldErrors?.data; + + return ( + + + Create a new event + +
+
{ + props.onSubmit(d); + })} + > +
+
+ + + {keyError && ( +
{keyError}
+ )} +
+
+ + { + return ( + { + setCode(value); + + // if this is valid JSON, set it as the value + if (!value) { + field.onChange({}); + return; + } + + try { + JSON.parse(value); + field.onChange(JSON.parse(value)); + } catch (e) {} // eslint-disable-line no-empty + }} + language="json" + /> + ); + }} + /> + {dataError && ( +
{dataError}
+ )} +
+ +
+
+
+
+ ); +} diff --git a/frontend/app/src/pages/main/events/index.tsx b/frontend/app/src/pages/main/events/index.tsx index f8bb753721..6bcc249b15 100644 --- a/frontend/app/src/pages/main/events/index.tsx +++ b/frontend/app/src/pages/main/events/index.tsx @@ -12,6 +12,7 @@ import { } from '@tanstack/react-table'; import { useMutation, useQuery } from '@tanstack/react-query'; import api, { + CreateEventRequest, Event, EventOrderByDirection, EventOrderByField, @@ -37,11 +38,13 @@ import { Button } from '@/components/ui/button'; import { ArrowPathIcon, ArrowPathRoundedSquareIcon, + PlusCircleIcon, } from '@heroicons/react/24/outline'; import { useApiError } from '@/lib/hooks'; import { Loading } from '@/components/ui/loading.tsx'; import { TenantContextType } from '@/lib/outlet'; import RelativeDate from '@/components/molecules/relative-date'; +import { CreateEventForm } from './components/create-event-form'; export default function Events() { return ( @@ -59,11 +62,20 @@ export default function Events() { function EventsTable() { const [selectedEvent, setSelectedEvent] = useState(null); + const [showCreateEvent, setShowCreateEvent] = useState(false); const { tenant } = useOutletContext(); const [searchParams, setSearchParams] = useSearchParams(); const [rotate, setRotate] = useState(false); const { handleApiError } = useApiError({}); + const [createEventFieldErrors, setCreateEventFieldErrors] = useState< + Record + >({}); + const createEventApiError = useApiError({ + setFieldErrors: setCreateEventFieldErrors, + }); + const handleCreateEventApiError = createEventApiError.handleApiError; + invariant(tenant); useEffect(() => { @@ -199,6 +211,16 @@ function EventsTable() { onError: handleApiError, }); + const createEventMutation = useMutation({ + mutationKey: ['event:create', tenant.metadata.id], + mutationFn: async (input: CreateEventRequest) => { + const res = await api.eventCreate(tenant.metadata.id, input); + + return res.data; + }, + onError: handleCreateEventApiError, + }); + const { data: eventKeys, isLoading: eventKeysIsLoading, @@ -295,6 +317,18 @@ function EventsTable() { className={`h-4 w-4 transition-transform ${rotate ? 'rotate-180' : ''}`} /> , + , ]; return ( @@ -309,6 +343,20 @@ function EventsTable() { > {selectedEvent && } + { + if (!open) { + setShowCreateEvent(false); + } + }} + > + + Date: Wed, 19 Jun 2024 14:30:59 -0400 Subject: [PATCH 4/4] fix: address PR review --- .../app/src/pages/main/events/components/create-event-form.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/app/src/pages/main/events/components/create-event-form.tsx b/frontend/app/src/pages/main/events/components/create-event-form.tsx index 5e7c7ed9d0..59acf15ed0 100644 --- a/frontend/app/src/pages/main/events/components/create-event-form.tsx +++ b/frontend/app/src/pages/main/events/components/create-event-form.tsx @@ -88,7 +88,6 @@ export function CreateEventForm({ className, ...props }: CreateEventFormProps) { } try { - JSON.parse(value); field.onChange(JSON.parse(value)); } catch (e) {} // eslint-disable-line no-empty }}