mirror of
https://github.com/SigNoz/signoz.git
synced 2026-09-22 03:10:41 +01:00
<!--A few plain bullets saying what changed and why, for a reviewer skimming it - not a wall of text, not a restatement of the diff, not generated boilerplate.--> #### Description Until now every org started with no span mapper groups and we had to create `llm`, `agent` and `tool` by hand. This PR ships them as defaults. - The three groups live as JSON in the binary. On startup and when a new org is created, they get seeded. When we change a definition and bump its version, the next release updates the org's copy in place. - Anything SigNoz ships is marked `origin: system` and can only be switched on or off. Users can't rename or delete these groups and mappers, and can't take their names. Anything the user adds is theirs to edit or remove, including new mappers in a shipped group or new sources on a shipped mapper. - Upgrades keep the user's on/off choices and never touch their items. - Condition substrings and sources now carry `enabled` and `origin`, so a substring is an object instead of a plain string. Disabled ones are left out of the collector config. - - Migration 127 only adds the `origin` and `version` columns. Stored JSON is not rewritten because these tables are empty on every instance. - Fixes creating a group or mapper with `enabled: false` being saved as true (the bun `default:true` tag turned false into SQL `DEFAULT`) Frontend: no UI changes. Generated client regenerated; drafts carry `enabled` and `origin` so saves from the existing screens round-trip shipped items intact. <!--Reference issues using `Closes #issue-number` to enable automatic closure on merge. --> #### Issues closed by this PR Closes https://github.com/SigNoz/engineering-pod/issues/5329 <!--Anything reviewers should keep in mind while reviewing --> #### Additional Information * Frontend follow-up: toggles for sources and substrings, "Default" badge and read-only rows for shipped items, hide rename/delete on system groups and mappers. * Deferred: per-group upgrade changelog ( will come back to this later) --------- Co-authored-by: Gaurav Tewari <gauravtewari111@gmail.com> Co-authored-by: Gaurav Tewari <tewarig@users.noreply.github.com>
333 lines
8.1 KiB
Go
333 lines
8.1 KiB
Go
package implspanmapper
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"time"
|
|
|
|
"github.com/SigNoz/signoz/pkg/errors"
|
|
"github.com/SigNoz/signoz/pkg/http/binding"
|
|
"github.com/SigNoz/signoz/pkg/http/render"
|
|
"github.com/SigNoz/signoz/pkg/modules/spanmapper"
|
|
"github.com/SigNoz/signoz/pkg/types/authtypes"
|
|
"github.com/SigNoz/signoz/pkg/types/spantypes"
|
|
"github.com/SigNoz/signoz/pkg/valuer"
|
|
"github.com/gorilla/mux"
|
|
)
|
|
|
|
type handler struct {
|
|
module spanmapper.Module
|
|
}
|
|
|
|
func NewHandler(module spanmapper.Module) spanmapper.Handler {
|
|
return &handler{module: module}
|
|
}
|
|
|
|
// ListGroups handles GET /api/v1/span_mapper_groups.
|
|
func (h *handler) ListGroups(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
var q spantypes.ListSpanMapperGroupsQuery
|
|
if err := binding.Query.BindQuery(r.URL.Query(), &q); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
groups, err := h.module.ListGroups(ctx, orgID, &q)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMapperGroups(groups))
|
|
}
|
|
|
|
// CreateGroup handles POST /api/v1/span_mapper_groups.
|
|
func (h *handler) CreateGroup(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
req := new(spantypes.PostableSpanMapperGroup)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
if err := req.Validate(); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
group := spantypes.NewSpanMapperGroup(orgID, claims.Email, req)
|
|
|
|
if err := h.module.CreateGroup(ctx, orgID, group); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
render.Success(rw, http.StatusCreated, group)
|
|
}
|
|
|
|
// UpdateGroup handles PUT /api/v1/span_mapper_groups/{id}.
|
|
func (h *handler) UpdateGroup(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
id, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(spantypes.UpdatableSpanMapperGroup)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if err := h.module.UpdateGroup(ctx, orgID, id, req.Name, req.Condition, req.Enabled, claims.Email); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// DeleteGroup handles DELETE /api/v1/span_mapper_groups/{id}.
|
|
func (h *handler) DeleteGroup(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
id, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if err := h.module.DeleteGroup(ctx, orgID, id); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// ListMappers handles GET /api/v1/span_mapper_groups/{id}/span_mappers.
|
|
func (h *handler) ListMappers(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
groupID, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
mappers, err := h.module.ListMappers(ctx, orgID, groupID)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, spantypes.NewGettableSpanMappers(mappers))
|
|
}
|
|
|
|
// CreateMapper handles POST /api/v1/span_mapper_groups/{id}/span_mappers.
|
|
func (h *handler) CreateMapper(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
groupID, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(spantypes.PostableSpanMapper)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
if err := req.Validate(); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
mapper := spantypes.NewSpanMapper(groupID, claims.Email, req)
|
|
|
|
if err := h.module.CreateMapper(ctx, orgID, groupID, mapper); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusCreated, mapper)
|
|
}
|
|
|
|
// UpdateMapper handles PUT /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
|
|
func (h *handler) UpdateMapper(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
groupID, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
mapperID, err := mapperIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
req := new(spantypes.UpdatableSpanMapper)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if err := h.module.UpdateMapper(ctx, orgID, groupID, mapperID, req.FieldContext, req.Config, req.Enabled, claims.Email); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// DeleteMapper handles DELETE /api/v1/span_mapper_groups/{groupId}/span_mappers/{mapperId}.
|
|
func (h *handler) DeleteMapper(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
groupID, err := groupIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
mapperID, err := mapperIDFromPath(r)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
if err := h.module.DeleteMapper(ctx, orgID, groupID, mapperID); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusNoContent, nil)
|
|
}
|
|
|
|
// TestMappers handles POST /api/v1/span_mapper_groups/test.
|
|
func (h *handler) TestMappers(rw http.ResponseWriter, r *http.Request) {
|
|
ctx, cancel := context.WithTimeout(r.Context(), 10*time.Second)
|
|
defer cancel()
|
|
|
|
claims, err := authtypes.ClaimsFromContext(ctx)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
orgID := valuer.MustNewUUID(claims.OrgID)
|
|
|
|
req := new(spantypes.PostableSpanMapperTest)
|
|
if err := binding.JSON.BindBody(r.Body, req); err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
groups := spantypes.NewSpanMapperGroupsWithMappersFromPostable(orgID, req.Groups)
|
|
out, collectorLogs, err := h.module.TestMappers(ctx, orgID, req.Spans, groups)
|
|
if err != nil {
|
|
render.Error(rw, err)
|
|
return
|
|
}
|
|
|
|
render.Success(rw, http.StatusOK, &spantypes.GettableSpanMapperTest{Spans: out, CollectorLogs: collectorLogs})
|
|
}
|
|
|
|
// groupIDFromPath extracts and validates the {id} or {groupId} path variable.
|
|
func groupIDFromPath(r *http.Request) (valuer.UUID, error) {
|
|
vars := mux.Vars(r)
|
|
raw := vars["groupId"]
|
|
id, err := valuer.NewUUID(raw)
|
|
if err != nil {
|
|
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "group id is not a valid uuid")
|
|
}
|
|
return id, nil
|
|
}
|
|
|
|
// mapperIDFromPath extracts and validates the {mapperId} path variable.
|
|
func mapperIDFromPath(r *http.Request) (valuer.UUID, error) {
|
|
raw := mux.Vars(r)["mapperId"]
|
|
id, err := valuer.NewUUID(raw)
|
|
if err != nil {
|
|
return valuer.UUID{}, errors.Wrapf(err, errors.TypeInvalidInput, spantypes.ErrCodeMappingInvalidInput, "mapper id is not a valid uuid")
|
|
}
|
|
return id, nil
|
|
}
|