Comments: support live comments in dashboards and annotations (#44980)

This commit is contained in:
Alexander Emelin
2022-02-22 10:47:42 +03:00
committed by GitHub
parent 67c1a359d1
commit 28c30a34ad
32 changed files with 1254 additions and 18 deletions
@@ -0,0 +1,13 @@
package commentmodel
type EventType string
const (
EventCommentCreated EventType = "commentCreated"
)
// Event represents comment event structure.
type Event struct {
Event EventType `json:"event"`
CommentCreated *CommentDto `json:"commentCreated"`
}
@@ -0,0 +1,105 @@
package commentmodel
import (
"database/sql"
"database/sql/driver"
"encoding/json"
"fmt"
)
const (
// ObjectTypeOrg is reserved for future use for per-org comments.
ObjectTypeOrg = "org"
// ObjectTypeDashboard used for dashboard-wide comments.
ObjectTypeDashboard = "dashboard"
// ObjectTypeAnnotation used for annotation comments.
ObjectTypeAnnotation = "annotation"
)
var RegisteredObjectTypes = map[string]struct{}{
ObjectTypeOrg: {},
ObjectTypeDashboard: {},
ObjectTypeAnnotation: {},
}
type CommentGroup struct {
Id int64
OrgId int64
ObjectType string
ObjectId string
Settings Settings
Created int64
Updated int64
}
func (i CommentGroup) TableName() string {
return "comment_group"
}
type Settings struct {
}
var (
_ driver.Valuer = Settings{}
_ sql.Scanner = &Settings{}
)
func (s Settings) Value() (driver.Value, error) {
d, err := json.Marshal(s)
if err != nil {
return nil, err
}
return string(d), nil
}
func (s *Settings) Scan(value interface{}) error {
switch v := value.(type) {
case string:
return json.Unmarshal([]byte(v), &s)
case []uint8:
return json.Unmarshal(v, &s)
default:
return fmt.Errorf("type assertion on scan failed: got %T", value)
}
}
type Comment struct {
Id int64
GroupId int64
UserId int64
Content string
Created int64
Updated int64
}
type CommentUser struct {
Id int64 `json:"id"`
Name string `json:"name"`
Login string `json:"login"`
Email string `json:"email"`
AvatarUrl string `json:"avatarUrl"`
}
type CommentDto struct {
Id int64 `json:"id"`
UserId int64 `json:"userId"`
Content string `json:"content"`
Created int64 `json:"created"`
User *CommentUser `json:"user,omitempty"`
}
func (i Comment) ToDTO(user *CommentUser) *CommentDto {
return &CommentDto{
Id: i.Id,
UserId: i.UserId,
Content: i.Content,
Created: i.Created,
User: user,
}
}
func (i Comment) TableName() string {
return "comment"
}
@@ -0,0 +1,132 @@
package commentmodel
import (
"context"
"strconv"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/guardian"
)
type PermissionChecker struct {
sqlStore *sqlstore.SQLStore
features featuremgmt.FeatureToggles
}
func NewPermissionChecker(sqlStore *sqlstore.SQLStore, features featuremgmt.FeatureToggles) *PermissionChecker {
return &PermissionChecker{sqlStore: sqlStore, features: features}
}
func (c *PermissionChecker) getDashboardByUid(ctx context.Context, orgID int64, uid string) (*models.Dashboard, error) {
query := models.GetDashboardQuery{Uid: uid, OrgId: orgID}
if err := c.sqlStore.GetDashboard(ctx, &query); err != nil {
return nil, err
}
return query.Result, nil
}
func (c *PermissionChecker) getDashboardById(ctx context.Context, orgID int64, id int64) (*models.Dashboard, error) {
query := models.GetDashboardQuery{Id: id, OrgId: orgID}
if err := c.sqlStore.GetDashboard(ctx, &query); err != nil {
return nil, err
}
return query.Result, nil
}
func (c *PermissionChecker) CheckReadPermissions(ctx context.Context, orgId int64, signedInUser *models.SignedInUser, objectType string, objectID string) (bool, error) {
switch objectType {
case ObjectTypeOrg:
return false, nil
case ObjectTypeDashboard:
if !c.features.IsEnabled(featuremgmt.FlagDashboardComments) {
return false, nil
}
dash, err := c.getDashboardByUid(ctx, orgId, objectID)
if err != nil {
return false, err
}
guard := guardian.New(ctx, dash.Id, orgId, signedInUser)
if ok, err := guard.CanView(); err != nil || !ok {
return false, nil
}
case ObjectTypeAnnotation:
if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) {
return false, nil
}
repo := annotations.GetRepository()
annotationID, err := strconv.ParseInt(objectID, 10, 64)
if err != nil {
return false, nil
}
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId})
if err != nil || len(items) != 1 {
return false, nil
}
dashboardID := items[0].DashboardId
if dashboardID == 0 {
return false, nil
}
dash, err := c.getDashboardById(ctx, orgId, dashboardID)
if err != nil {
return false, err
}
guard := guardian.New(ctx, dash.Id, orgId, signedInUser)
if ok, err := guard.CanView(); err != nil || !ok {
return false, nil
}
default:
return false, nil
}
return true, nil
}
func (c *PermissionChecker) CheckWritePermissions(ctx context.Context, orgId int64, signedInUser *models.SignedInUser, objectType string, objectID string) (bool, error) {
switch objectType {
case ObjectTypeOrg:
return false, nil
case ObjectTypeDashboard:
if !c.features.IsEnabled(featuremgmt.FlagDashboardComments) {
return false, nil
}
dash, err := c.getDashboardByUid(ctx, orgId, objectID)
if err != nil {
return false, err
}
guard := guardian.New(ctx, dash.Id, orgId, signedInUser)
if ok, err := guard.CanEdit(); err != nil || !ok {
return false, nil
}
case ObjectTypeAnnotation:
if !c.features.IsEnabled(featuremgmt.FlagAnnotationComments) {
return false, nil
}
repo := annotations.GetRepository()
annotationID, err := strconv.ParseInt(objectID, 10, 64)
if err != nil {
return false, nil
}
items, err := repo.Find(&annotations.ItemQuery{AnnotationId: annotationID, OrgId: orgId})
if err != nil || len(items) != 1 {
return false, nil
}
dashboardID := items[0].DashboardId
if dashboardID == 0 {
return false, nil
}
dash, err := c.getDashboardById(ctx, orgId, dashboardID)
if err != nil {
return false, nil
}
guard := guardian.New(ctx, dash.Id, orgId, signedInUser)
if ok, err := guard.CanEdit(); err != nil || !ok {
return false, nil
}
default:
return false, nil
}
return true, nil
}