Files
grafana/pkg/registry/apis/preferences/current.go
T

133 lines
3.6 KiB
Go

package preferences
import (
"encoding/json"
"net/http"
"dario.cat/mergo"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
preferences "github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/registry/apis/preferences/legacy"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util/errhttp"
)
type calculator struct {
defaults preferences.PreferencesSpec
sql *legacy.LegacySQL
}
func newCalculator(cfg *setting.Cfg, sql *legacy.LegacySQL) *calculator {
return &calculator{
sql: sql,
defaults: preferences.PreferencesSpec{
Theme: &cfg.DefaultTheme,
Timezone: &cfg.DateFormats.DefaultTimezone,
WeekStart: &cfg.DateFormats.DefaultWeekStart,
Language: &cfg.DefaultLanguage,
},
}
}
func (s *calculator) GetAPIRoutes(defs map[string]common.OpenAPIDefinition) *builder.APIRoutes {
schema := defs["github.com/grafana/grafana/apps/preferences/pkg/apis/preferences/v1alpha1.Preference"].Schema
return &builder.APIRoutes{
Namespace: []builder.APIRouteHandler{
{
Path: "current", // calculate?
Spec: &spec3.PathProps{
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
OperationId: "currentPreferences",
Tags: []string{"Preferences"},
Description: "Get preferences for requester. This combines the user preferences with the team and global defaults",
Parameters: []*spec3.Parameter{
{
ParameterProps: spec3.ParameterProps{
Name: "namespace",
In: "path",
Required: true,
Example: "default",
Description: "workspace",
Schema: spec.StringProperty(),
},
// Allow getting theme+language from accept
},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: map[int]*spec3.Response{
200: {
ResponseProps: spec3.ResponseProps{
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &schema,
},
},
},
},
},
},
},
},
},
},
},
Handler: s.Current,
},
},
}
}
func (s *calculator) Current(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
user, err := identity.GetRequester(ctx)
if err != nil {
errhttp.Write(ctx, err, w)
return
}
ns := user.GetNamespace() // namespace not in context!
list, err := s.sql.ListPreferences(ctx, ns, user, false)
if err != nil {
errhttp.Write(ctx, err, w)
return
}
p, err := merge(s.defaults, list.Items)
if err != nil {
errhttp.Write(ctx, err, w)
return
}
p.Namespace = ns
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(p)
}
// items should be in ascending order of importance
func merge(defaults preferences.PreferencesSpec, items []preferences.Preferences) (*preferences.Preferences, error) {
p := &preferences.Preferences{
TypeMeta: preferences.PreferencesResourceInfo.TypeMeta(),
ObjectMeta: v1.ObjectMeta{
CreationTimestamp: v1.Now(),
},
Spec: defaults,
}
// Iterate in reverse order (least relevant to most relevant)
for _, v := range items {
if err := mergo.Merge(&p.Spec, &v.Spec, mergo.WithOverride); err != nil {
return nil, err
}
}
return p, nil
}