Comments: support live comments in dashboards and annotations (#44980)
This commit is contained in:
@@ -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
|
||||
}
|
||||
Reference in New Issue
Block a user