Skip to content

Commit

Permalink
Publicdasboards: Add annotations support (#56413)
Browse files Browse the repository at this point in the history
adds annotations support for public dashboards
  • Loading branch information
owensmallwood committed Oct 19, 2022
1 parent cc6245d commit b2408dd
Show file tree
Hide file tree
Showing 21 changed files with 874 additions and 49 deletions.
7 changes: 6 additions & 1 deletion packages/grafana-schema/src/raw/dashboard/x/dashboard.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,12 @@ export interface AnnotationQuery {
*/
rawQuery?: string;
showIn: number;
target?: Record<string, unknown>;
target?: {
limit: number;
matchAny: boolean;
tags: Array<string>;
type: string;
};
type: string;
}

Expand Down
9 changes: 8 additions & 1 deletion pkg/coremodel/dashboard/coremodel.cue
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ seqs: [
///////////////////////////////////////
// Definitions (referenced above) are declared below

#AnnotationTarget: {
limit: int64
matchAny: bool
tags: [...string]
type: string
}

// TODO docs
// FROM: AnnotationQuery in grafana-data/src/types/annotations.ts
#AnnotationQuery: {
Expand All @@ -106,7 +113,7 @@ seqs: [
// Query for annotation data.
rawQuery?: string @grafanamaturity(NeedsExpertReview)
showIn: uint8 | *0 @grafanamaturity(NeedsExpertReview)
target?: #Target @grafanamaturity(NeedsExpertReview) // TODO currently a generic in AnnotationQuery
target?: #AnnotationTarget @grafanamaturity(NeedsExpertReview)
} @cuetsy(kind="interface")

// FROM: packages/grafana-data/src/types/templateVars.ts
Expand Down
27 changes: 15 additions & 12 deletions pkg/coremodel/dashboard/dashboard_gen.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,18 +343,21 @@ type AnnotationQuery struct {
Name *string `json:"name,omitempty"`

// Query for annotation data.
RawQuery *string `json:"rawQuery,omitempty"`
ShowIn int `json:"showIn"`

// Schema for panel targets is specified by datasource
// plugins. We use a placeholder definition, which the Go
// schema loader either left open/as-is with the Base
// variant of the Model and Panel families, or filled
// with types derived from plugins in the Instance variant.
// When working directly from CUE, importers can extend this
// type directly to achieve the same effect.
Target *Target `json:"target,omitempty"`
Type string `json:"type"`
RawQuery *string `json:"rawQuery,omitempty"`
ShowIn int `json:"showIn"`
Target *AnnotationTarget `json:"target,omitempty"`
Type string `json:"type"`
}

// AnnotationTarget is the Go representation of a dashboard.AnnotationTarget.
//
// THIS TYPE IS INTENDED FOR INTERNAL USE BY THE GRAFANA BACKEND, AND IS SUBJECT TO BREAKING CHANGES.
// Equivalent Go types at stable import paths are provided in https://github.com/grafana/grok.
type AnnotationTarget struct {
Limit int64 `json:"limit"`
MatchAny bool `json:"matchAny"`
Tags []string `json:"tags"`
Type string `json:"type"`
}

// 0 for no shared crosshair or tooltip (default).
Expand Down
1 change: 1 addition & 0 deletions pkg/services/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ var (
ErrBaseTagLimitExceeded = errutil.NewBase(errutil.StatusBadRequest, "annotations.tag-limit-exceeded", errutil.WithPublicMessage("Tags length exceeds the maximum allowed."))
)

//go:generate mockery --name Repository --structname FakeAnnotationsRepo --inpackage --filename annotations_repository_mock.go
type Repository interface {
Save(ctx context.Context, item *Item) error
Update(ctx context.Context, item *Item) error
Expand Down
111 changes: 111 additions & 0 deletions pkg/services/annotations/annotations_repository_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

16 changes: 16 additions & 0 deletions pkg/services/publicdashboards/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ func (api *Api) RegisterAPIEndpoints() {
// public endpoints
api.RouteRegister.Get("/api/public/dashboards/:accessToken", routing.Wrap(api.GetPublicDashboard))
api.RouteRegister.Post("/api/public/dashboards/:accessToken/panels/:panelId/query", routing.Wrap(api.QueryPublicDashboard))
api.RouteRegister.Get("/api/public/dashboards/:accessToken/annotations", routing.Wrap(api.GetAnnotations))

// List Public Dashboards
api.RouteRegister.Get("/api/dashboards/public", middleware.ReqSignedIn, routing.Wrap(api.ListPublicDashboards))
Expand Down Expand Up @@ -187,6 +188,21 @@ func (api *Api) QueryPublicDashboard(c *models.ReqContext) response.Response {
return toJsonStreamingResponse(api.Features, resp)
}

func (api *Api) GetAnnotations(c *models.ReqContext) response.Response {
reqDTO := AnnotationsQueryDTO{
From: c.QueryInt64("from"),
To: c.QueryInt64("to"),
}

annotations, err := api.PublicDashboardService.GetAnnotations(c.Req.Context(), reqDTO, web.Params(c.Req)[":accessToken"])

if err != nil {
return api.handleError(c.Req.Context(), http.StatusInternalServerError, "error getting public dashboard annotations", err)
}

return response.JSON(http.StatusOK, annotations)
}

// util to help us unpack dashboard and publicdashboard errors or use default http code and message
// we should look to do some future refactoring of these errors as publicdashboard err is the same as a dashboarderr, just defined in a
// different package.
Expand Down
55 changes: 54 additions & 1 deletion pkg/services/publicdashboards/api/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations/annotationstest"
"github.com/grafana/grafana/pkg/services/dashboards"
dashboardStore "github.com/grafana/grafana/pkg/services/dashboards/database"
"github.com/grafana/grafana/pkg/services/datasources"
Expand Down Expand Up @@ -49,6 +50,56 @@ type JsonErrResponse struct {
Error string `json:"error"`
}

func TestAPIGetAnnotations(t *testing.T) {
testCases := []struct {
Name string
ExpectedHttpResponse int
Annotations []AnnotationEvent
ServiceError error
From string
To string
}{
{
Name: "will return success when there is no error and to and from are provided",
ExpectedHttpResponse: http.StatusOK,
Annotations: []AnnotationEvent{{Id: 1}},
ServiceError: nil,
From: "123",
To: "123",
},
{
Name: "will return 500 when service returns an error",
ExpectedHttpResponse: http.StatusInternalServerError,
Annotations: nil,
ServiceError: errors.New("an error happened"),
From: "123",
To: "123",
},
}
for _, test := range testCases {
t.Run(test.Name, func(t *testing.T) {
cfg := setting.NewCfg()
cfg.RBACEnabled = false
service := publicdashboards.NewFakePublicDashboardService(t)
service.On("GetAnnotations", mock.Anything, mock.Anything, mock.AnythingOfType("string")).
Return(test.Annotations, test.ServiceError).Once()
testServer := setupTestServer(t, cfg, featuremgmt.WithFeatures(featuremgmt.FlagPublicDashboards), service, nil, anonymousUser)

path := fmt.Sprintf("/api/public/dashboards/abc123/annotations?from=%s&to=%s", test.From, test.To)
response := callAPI(testServer, http.MethodGet, path, nil, t)

assert.Equal(t, test.ExpectedHttpResponse, response.Code)

if test.ExpectedHttpResponse == http.StatusOK {
var items []AnnotationEvent
err := json.Unmarshal(response.Body.Bytes(), &items)
assert.NoError(t, err)
assert.Equal(t, items, test.Annotations)
}
})
}
}

func TestAPIFeatureFlag(t *testing.T) {
testCases := []struct {
Name string
Expand Down Expand Up @@ -630,11 +681,13 @@ func TestIntegrationUnauthenticatedUserCanGetPubdashPanelQueryData(t *testing.T)
},
}

annotationsService := annotationstest.NewFakeAnnotationsRepo()

// create public dashboard
store := publicdashboardsStore.ProvideStore(db)
cfg := setting.NewCfg()
cfg.RBACEnabled = false
service := publicdashboardsService.ProvideService(cfg, store, qds)
service := publicdashboardsService.ProvideService(cfg, store, qds, annotationsService)
pubdash, err := service.SavePublicDashboardConfig(context.Background(), &user.SignedInUser{}, savePubDashboardCmd)
require.NoError(t, err)

Expand Down
28 changes: 28 additions & 0 deletions pkg/services/publicdashboards/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"strconv"
"time"

"github.com/grafana/grafana/pkg/coremodel/dashboard"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb/legacydata"
)
Expand Down Expand Up @@ -77,6 +78,28 @@ type PublicDashboard struct {
UpdatedAt time.Time `json:"updatedAt" xorm:"updated_at"`
}

// Alias the generated type
type DashAnnotation = dashboard.AnnotationQuery

type AnnotationsDto struct {
Annotations struct {
List []DashAnnotation `json:"list"`
}
}

type AnnotationEvent struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Tags []string `json:"tags"`
IsRegion bool `json:"isRegion"`
Text string `json:"text"`
Color string `json:"color"`
Time int64 `json:"time"`
TimeEnd int64 `json:"timeEnd"`
Source dashboard.AnnotationQuery `json:"source"`
}

func (pd PublicDashboard) TableName() string {
return "dashboard_public"
}
Expand Down Expand Up @@ -135,6 +158,11 @@ type PublicDashboardQueryDTO struct {
MaxDataPoints int64
}

type AnnotationsQueryDTO struct {
From int64
To int64
}

//
// COMMANDS
//
Expand Down
23 changes: 23 additions & 0 deletions pkg/services/publicdashboards/public_dashboard_service_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/services/publicdashboards/publicdashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
type Service interface {
AccessTokenExists(ctx context.Context, accessToken string) (bool, error)
BuildAnonymousUser(ctx context.Context, dashboard *models.Dashboard) *user.SignedInUser
GetAnnotations(ctx context.Context, reqDTO AnnotationsQueryDTO, accessToken string) ([]AnnotationEvent, error)
GetDashboard(ctx context.Context, dashboardUid string) (*models.Dashboard, error)
GetMetricRequest(ctx context.Context, dashboard *models.Dashboard, publicDashboard *PublicDashboard, panelId int64, reqDTO PublicDashboardQueryDTO) (dtos.MetricRequest, error)
GetPublicDashboard(ctx context.Context, accessToken string) (*PublicDashboard, *models.Dashboard, error)
Expand Down

0 comments on commit b2408dd

Please sign in to comment.