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 }