mirror of
https://github.com/grafana/grafana.git
synced 2024-11-26 02:40:26 -06:00
now with dashboard history
This commit is contained in:
parent
7345ece8ef
commit
999cd506c0
@ -19,6 +19,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore/session"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
@ -55,19 +56,25 @@ type dashboardSqlAccess struct {
|
||||
namespacer request.NamespaceMapper
|
||||
dashStore dashboards.Store
|
||||
provisioning provisioning.ProvisioningService
|
||||
versions dashver.Service
|
||||
|
||||
// Typically one... the server wrapper
|
||||
subscribers []chan *resource.WrittenEvent
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
func NewDashboardAccess(sql db.DB, namespacer request.NamespaceMapper, dashStore dashboards.Store, provisioning provisioning.ProvisioningService) DashboardAccess {
|
||||
func NewDashboardAccess(sql db.DB,
|
||||
namespacer request.NamespaceMapper,
|
||||
dashStore dashboards.Store,
|
||||
provisioning provisioning.ProvisioningService,
|
||||
versions dashver.Service) DashboardAccess {
|
||||
return &dashboardSqlAccess{
|
||||
sql: sql,
|
||||
sess: sql.GetSqlxSession(),
|
||||
namespacer: namespacer,
|
||||
dashStore: dashStore,
|
||||
provisioning: provisioning,
|
||||
versions: versions,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
)
|
||||
|
||||
@ -241,7 +244,7 @@ func (a *dashboardSqlAccess) SupportsSignedURLs() bool {
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) PutBlob(context.Context, *resource.PutBlobRequest) (*resource.PutBlobResponse, error) {
|
||||
return nil, fmt.Errorf("not implemented yet")
|
||||
return nil, fmt.Errorf("put blob not implemented yet")
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) GetBlob(ctx context.Context, key *resource.ResourceKey, info *utils.BlobInfo, mustProxy bool) (*resource.GetBlobResponse, error) {
|
||||
@ -262,3 +265,51 @@ func (a *dashboardSqlAccess) GetBlob(ctx context.Context, key *resource.Resource
|
||||
rsp.Value, err = json.Marshal(dash.Spec)
|
||||
return rsp, err
|
||||
}
|
||||
|
||||
func (a *dashboardSqlAccess) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) {
|
||||
ns, err := request.ParseNamespace(req.Key.Namespace)
|
||||
if err == nil {
|
||||
err = isDashboardKey(req.Key, true)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
versions, err := a.versions.List(ctx, &dashver.ListDashboardVersionsQuery{
|
||||
OrgID: ns.OrgID,
|
||||
DashboardUID: req.Key.Name,
|
||||
Limit: 100,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
rsp := &resource.HistoryResponse{}
|
||||
for _, version := range versions {
|
||||
partial := &metav1.PartialObjectMetadata{}
|
||||
meta, err := utils.MetaAccessor(partial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta.SetName(version.DashboardUID)
|
||||
meta.SetCreationTimestamp(metav1.NewTime(version.Created)) // ???
|
||||
meta.SetUpdatedTimestampMillis(version.Created.UnixMilli())
|
||||
meta.SetMessage(version.Message)
|
||||
meta.SetResourceVersionInt64(version.Created.UnixMilli())
|
||||
|
||||
bytes, err := json.Marshal(partial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rsp.Items = append(rsp.Items, &resource.ResourceMeta{
|
||||
ResourceVersion: version.Created.UnixMilli(),
|
||||
PartialObjectMeta: bytes,
|
||||
})
|
||||
}
|
||||
return rsp, err
|
||||
|
||||
}
|
||||
|
||||
// Used for efficient provisioning
|
||||
func (a *dashboardSqlAccess) Origin(context.Context, *resource.OriginRequest) (*resource.OriginResponse, error) {
|
||||
return nil, fmt.Errorf("not yet (origin)")
|
||||
}
|
||||
|
@ -23,6 +23,7 @@ type DashboardQuery struct {
|
||||
type DashboardAccess interface {
|
||||
resource.AppendingStore
|
||||
resource.BlobStore
|
||||
resource.ResourceSearchServer
|
||||
|
||||
GetDashboard(ctx context.Context, orgId int64, uid string) (*dashboardsV0.Dashboard, int64, error)
|
||||
|
||||
|
@ -17,11 +17,14 @@ type dashboardStorage struct {
|
||||
resource common.ResourceInfo
|
||||
access access.DashboardAccess
|
||||
tableConverter rest.TableConvertor
|
||||
|
||||
server resource.ResourceServer
|
||||
}
|
||||
|
||||
func (s *dashboardStorage) newStore(scheme *runtime.Scheme, defaultOptsGetter generic.RESTOptionsGetter) (rest.Storage, error) {
|
||||
server, err := resource.NewResourceServer(resource.ResourceServerOptions{
|
||||
Store: s.access,
|
||||
Search: s.access,
|
||||
Blob: s.access,
|
||||
// WriteAccess: resource.WriteAccessHooks{
|
||||
// Folder: func(ctx context.Context, user identity.Requester, uid string) bool {
|
||||
@ -32,6 +35,7 @@ func (s *dashboardStorage) newStore(scheme *runtime.Scheme, defaultOptsGetter ge
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s.server = server
|
||||
|
||||
resourceInfo := s.resource
|
||||
defaultOpts, err := defaultOptsGetter.GetRESTOptions(resourceInfo.GroupResource())
|
||||
|
@ -39,7 +39,6 @@ var _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil)
|
||||
type DashboardsAPIBuilder struct {
|
||||
dashboardService dashboards.DashboardService
|
||||
|
||||
dashboardVersionService dashver.Service
|
||||
accessControl accesscontrol.AccessControl
|
||||
store *dashboardStorage
|
||||
|
||||
@ -66,12 +65,11 @@ func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
|
||||
log: log.New("grafana-apiserver.dashboards"),
|
||||
|
||||
dashboardService: dashboardService,
|
||||
dashboardVersionService: dashboardVersionService,
|
||||
accessControl: accessControl,
|
||||
|
||||
store: &dashboardStorage{
|
||||
resource: dashboard.DashboardResourceInfo,
|
||||
access: access.NewDashboardAccess(sql, namespacer, dashStore, provisioning),
|
||||
access: access.NewDashboardAccess(sql, namespacer, dashStore, provisioning, dashboardVersionService),
|
||||
tableConverter: gapiutil.NewTableConverter(
|
||||
dashboard.DashboardResourceInfo.GroupResource(),
|
||||
[]metav1.TableColumnDefinition{
|
||||
@ -114,6 +112,8 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
|
||||
&v0alpha1.DashboardWithAccessInfo{},
|
||||
&v0alpha1.DashboardVersionList{},
|
||||
&v0alpha1.VersionsQueryOptions{},
|
||||
&metav1.PartialObjectMetadata{},
|
||||
&metav1.PartialObjectMetadataList{},
|
||||
)
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
|
||||
builder: b,
|
||||
}
|
||||
storage[dash.StoragePath("versions")] = &VersionsREST{
|
||||
builder: b,
|
||||
search: b.store.server, // resource.NewLocalResourceSearchClient(b.store.server),
|
||||
}
|
||||
|
||||
// // Dual writes if a RESTOptionsGetter is provided
|
||||
|
@ -2,7 +2,7 @@ package dashboard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -11,21 +11,21 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
|
||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||
)
|
||||
|
||||
type VersionsREST struct {
|
||||
builder *DashboardsAPIBuilder
|
||||
search resource.ResourceSearchServer // should be a client!
|
||||
}
|
||||
|
||||
var _ = rest.Connecter(&VersionsREST{})
|
||||
var _ = rest.StorageMetadata(&VersionsREST{})
|
||||
|
||||
func (r *VersionsREST) New() runtime.Object {
|
||||
return &dashboard.DashboardVersionList{}
|
||||
return &metav1.PartialObjectMetadataList{}
|
||||
}
|
||||
|
||||
func (r *VersionsREST) Destroy() {
|
||||
@ -40,7 +40,7 @@ func (r *VersionsREST) ProducesMIMETypes(verb string) []string {
|
||||
}
|
||||
|
||||
func (r *VersionsREST) ProducesObject(verb string) interface{} {
|
||||
return &dashboard.DashboardVersionList{}
|
||||
return &metav1.PartialObjectMetadataList{}
|
||||
}
|
||||
|
||||
func (r *VersionsREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||
@ -52,66 +52,74 @@ func (r *VersionsREST) Connect(ctx context.Context, uid string, opts runtime.Obj
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key := &resource.ResourceKey{
|
||||
Namespace: info.Value,
|
||||
Group: dashboard.GROUP,
|
||||
Resource: dashboard.DashboardResourceInfo.GroupResource().Resource,
|
||||
Name: uid,
|
||||
}
|
||||
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
path := req.URL.Path
|
||||
idx := strings.LastIndex(path, "/versions/")
|
||||
if idx > 0 {
|
||||
key := path[strings.LastIndex(path, "/")+1:]
|
||||
version, err := strconv.Atoi(key)
|
||||
vkey := path[strings.LastIndex(path, "/")+1:]
|
||||
version, err := strconv.ParseInt(vkey, 10, 64)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
dto, err := r.builder.dashboardVersionService.Get(ctx, &dashver.GetDashboardVersionQuery{
|
||||
DashboardUID: uid,
|
||||
OrgID: info.OrgID,
|
||||
Version: version,
|
||||
dashbytes, err := r.search.Read(ctx, &resource.ReadRequest{
|
||||
Key: key,
|
||||
ResourceVersion: version,
|
||||
})
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
|
||||
data, _ := dto.Data.Map()
|
||||
|
||||
// Convert the version to a regular dashboard
|
||||
dash := &dashboard.Dashboard{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: uid,
|
||||
CreationTimestamp: metav1.NewTime(dto.Created),
|
||||
},
|
||||
Spec: common.Unstructured{Object: data},
|
||||
dash := &dashboard.Dashboard{}
|
||||
json.Unmarshal(dashbytes.Value, dash)
|
||||
meta, err := utils.MetaAccessor(dash)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
meta.SetResourceVersionInt64(dashbytes.ResourceVersion)
|
||||
responder.Object(100, dash)
|
||||
return
|
||||
}
|
||||
|
||||
// Or list versions
|
||||
rsp, err := r.builder.dashboardVersionService.List(ctx, &dashver.ListDashboardVersionsQuery{
|
||||
DashboardUID: uid,
|
||||
OrgID: info.OrgID,
|
||||
rsp, err := r.search.History(ctx, &resource.HistoryRequest{
|
||||
NextPageToken: "", // TODO!
|
||||
Limit: 100,
|
||||
Key: key,
|
||||
})
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
versions := &dashboard.DashboardVersionList{}
|
||||
for _, v := range rsp {
|
||||
info := dashboard.DashboardVersionInfo{
|
||||
Version: v.Version,
|
||||
Created: v.Created.UnixMilli(),
|
||||
Message: v.Message,
|
||||
|
||||
list := &metav1.PartialObjectMetadataList{
|
||||
ListMeta: metav1.ListMeta{
|
||||
Continue: rsp.NextPageToken,
|
||||
},
|
||||
}
|
||||
if v.ParentVersion != v.Version {
|
||||
info.ParentVersion = v.ParentVersion
|
||||
if rsp.ResourceVersion > 0 {
|
||||
list.ResourceVersion = strconv.FormatInt(rsp.ResourceVersion, 10)
|
||||
}
|
||||
if v.CreatedBy > 0 {
|
||||
info.CreatedBy = fmt.Sprintf("%d", v.CreatedBy)
|
||||
|
||||
for _, v := range rsp.Items {
|
||||
partial := metav1.PartialObjectMetadata{}
|
||||
err = json.Unmarshal(v.PartialObjectMeta, &partial)
|
||||
if err != nil {
|
||||
responder.Error(err)
|
||||
return
|
||||
}
|
||||
versions.Items = append(versions.Items, info)
|
||||
list.Items = append(list.Items, partial)
|
||||
}
|
||||
responder.Object(http.StatusOK, versions)
|
||||
responder.Object(http.StatusOK, list)
|
||||
}), nil
|
||||
}
|
||||
|
@ -25,6 +25,22 @@ func NewLocalResourceStoreClient(server ResourceStoreServer) ResourceStoreClient
|
||||
return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
|
||||
}
|
||||
|
||||
func NewLocalResourceSearchClient(server ResourceStoreServer) ResourceSearchClient {
|
||||
channel := &inprocgrpc.Channel{}
|
||||
|
||||
auth := &grpcUtils.Authenticator{}
|
||||
|
||||
channel.RegisterService(
|
||||
grpchan.InterceptServer(
|
||||
&ResourceStore_ServiceDesc,
|
||||
grpcAuth.UnaryServerInterceptor(auth.Authenticate),
|
||||
grpcAuth.StreamServerInterceptor(auth.Authenticate),
|
||||
),
|
||||
server,
|
||||
)
|
||||
return NewResourceSearchClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
|
||||
}
|
||||
|
||||
func NewResourceStoreClientGRPC(channel *grpc.ClientConn) ResourceStoreClient {
|
||||
return NewResourceStoreClient(grpchan.InterceptClientConn(channel, grpcUtils.UnaryClientInterceptor, grpcUtils.StreamClientInterceptor))
|
||||
}
|
||||
|
@ -140,7 +140,7 @@ func NewResourceServer(opts ResourceServerOptions) (ResourceServer, error) {
|
||||
opts.Blob = &noopService{}
|
||||
}
|
||||
if opts.Diagnostics == nil {
|
||||
opts.Search = &noopService{}
|
||||
opts.Diagnostics = &noopService{}
|
||||
}
|
||||
if opts.Now == nil {
|
||||
opts.Now = func() int64 {
|
||||
|
@ -23,7 +23,7 @@ import (
|
||||
|
||||
// Package-level errors.
|
||||
var (
|
||||
ErrNotImplementedYet = errors.New("not implemented yet")
|
||||
ErrNotImplementedYet = errors.New("not implemented yet (sqlnext)")
|
||||
)
|
||||
|
||||
func ProvideSQLResourceServer(db db.EntityDBInterface, tracer tracing.Tracer) (resource.ResourceServer, error) {
|
||||
|
Loading…
Reference in New Issue
Block a user