Stars: include query history (#111979)

This commit is contained in:
Ryan McKinley
2025-10-06 21:08:10 +03:00
committed by GitHub
parent 19fc24d35e
commit 22b88988a4
43 changed files with 825 additions and 334 deletions
+20
View File
@@ -168,6 +168,16 @@ func (s *QueryHistoryService) starHandler(c *contextmodel.ReqContext) response.R
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
}
if s.k8sClients != nil {
if err := s.k8sClients.AddStar(c, queryUID); err != nil {
return response.Error(http.StatusInternalServerError, "Failed to star query in query history", err)
}
return response.JSON(http.StatusOK, QueryHistoryResponse{
Result: QueryHistoryDTO{
UID: queryUID,
Starred: true,
}})
}
query, err := s.StarQueryInQueryHistory(c.Req.Context(), c.SignedInUser, queryUID)
if err != nil {
@@ -192,6 +202,16 @@ func (s *QueryHistoryService) unstarHandler(c *contextmodel.ReqContext) response
if len(queryUID) > 0 && !util.IsValidShortUID(queryUID) {
return response.Error(http.StatusNotFound, "Query in query history not found", nil)
}
if s.k8sClients != nil {
if err := s.k8sClients.RemoveStar(c, queryUID); err != nil {
return response.Error(http.StatusInternalServerError, "Failed to star query in query history", err)
}
return response.JSON(http.StatusOK, QueryHistoryResponse{
Result: QueryHistoryDTO{
UID: queryUID,
Starred: true,
}})
}
query, err := s.UnstarQueryInQueryHistory(c.Req.Context(), c.SignedInUser, queryUID)
if err != nil {
+103
View File
@@ -0,0 +1,103 @@
package queryhistory
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/kubernetes"
authlib "github.com/grafana/authlib/types"
preferencesV1 "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/apiserver"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
)
type k8sClients struct {
namespacer authlib.NamespaceFormatter
configProvider apiserver.DirectRestConfigProvider
}
// GetStars implements K8sClients.
func (k *k8sClients) GetStars(c *contextmodel.ReqContext) ([]string, error) {
dyn, err := dynamic.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return nil, err
}
client := dyn.Resource(preferencesV1.StarsResourceInfo.GroupVersionResource()).Namespace(k.namespacer(c.OrgID))
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
obj, _ := client.Get(ctx, "user-"+user.GetIdentifier(), v1.GetOptions{})
if obj != nil {
resources, ok, _ := unstructured.NestedSlice(obj.Object, "spec", "resource")
if ok && resources != nil {
for _, r := range resources {
tmp, ok := r.(map[string]any)
if ok {
g, _, _ := unstructured.NestedString(tmp, "group")
k, _, _ := unstructured.NestedString(tmp, "kind")
if k == "Query" && g == "history.grafana.app" {
names, _, _ := unstructured.NestedStringSlice(tmp, "names")
return names, nil
}
}
}
}
}
return []string{}, nil
}
// AddStar implements K8sClients.
func (k *k8sClients) AddStar(c *contextmodel.ReqContext, uid string) error {
dyn, err := kubernetes.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return err
}
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return err
}
ns := k.namespacer(c.OrgID)
client := dyn.RESTClient()
rsp := client.Put().AbsPath(
"apis", preferencesV1.APIGroup, preferencesV1.APIVersion, "namespaces", ns,
"stars", "user-"+user.GetIdentifier(),
"update", "history.grafana.app", "Query", uid,
).Do(ctx)
return rsp.Error()
}
// RemoveStar implements K8sClients.
func (k *k8sClients) RemoveStar(c *contextmodel.ReqContext, uid string) error {
dyn, err := kubernetes.NewForConfig(k.configProvider.GetDirectRestConfig(c))
if err != nil {
return err
}
ctx := c.Req.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
return err
}
ns := k.namespacer(c.OrgID)
client := dyn.RESTClient()
rsp := client.Delete().AbsPath(
"apis", preferencesV1.APIGroup, preferencesV1.APIVersion, "namespaces", ns,
"stars", "user-"+user.GetIdentifier(),
"update", "history.grafana.app", "Query", uid,
).Do(ctx)
return rsp.Error()
}
+17 -1
View File
@@ -8,11 +8,20 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/log"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.RouteRegister, accessControl ac.AccessControl) *QueryHistoryService {
func ProvideService(cfg *setting.Cfg,
sqlStore db.DB,
routeRegister routing.RouteRegister,
accessControl ac.AccessControl,
features featuremgmt.FeatureToggles,
configProvider apiserver.DirectRestConfigProvider,
) *QueryHistoryService {
s := &QueryHistoryService{
store: sqlStore,
Cfg: cfg,
@@ -24,6 +33,12 @@ func ProvideService(cfg *setting.Cfg, sqlStore db.DB, routeRegister routing.Rout
// Register routes only when query history is enabled
if s.Cfg.QueryHistoryEnabled {
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesStars) {
s.k8sClients = &k8sClients{
namespacer: request.GetNamespaceMapper(s.Cfg),
configProvider: configProvider,
}
}
s.registerAPIEndpoints()
}
@@ -48,6 +63,7 @@ type QueryHistoryService struct {
log log.Logger
now func() time.Time
accessControl ac.AccessControl
k8sClients *k8sClients
}
func (s QueryHistoryService) CreateQueryInQueryHistory(ctx context.Context, user *user.SignedInUser, cmd CreateQueryInQueryHistoryCommand) (QueryHistoryDTO, error) {