Scopes: Move title and groups to status in ScopeDashboardBinding (#92377)

---------

Co-authored-by: Kyle Brandt <kyle@grafana.com>
Co-authored-by: Bogdan Matei <bogdan.matei@grafana.com>
This commit is contained in:
Todd Treece 2024-08-28 08:59:18 -04:00 committed by GitHub
parent 34149c86d0
commit 2bb2183b41
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 193 additions and 59 deletions

View File

@ -1,7 +1,10 @@
export interface ScopeDashboardBindingSpec {
dashboard: string;
dashboardTitle: string;
scope: string;
}
export interface ScopeDashboardBindingStatus {
dashboardTitle: string;
groups?: string[];
}
@ -11,6 +14,7 @@ export interface ScopeDashboardBinding {
name: string;
};
spec: ScopeDashboardBindingSpec;
status: ScopeDashboardBindingStatus;
}
export type ScopeFilterOperator = 'equals' | 'not-equals' | 'regex-match' | 'regex-not-match';

View File

@ -53,7 +53,6 @@ var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION,
{Name: "Created At", Type: "date"},
{Name: "Dashboard", Type: "string"},
{Name: "Scope", Type: "string"},
{Name: "Groups", Type: "array"},
},
Reader: func(obj any) ([]interface{}, error) {
m, ok := obj.(*ScopeDashboardBinding)
@ -65,7 +64,6 @@ var ScopeDashboardBindingResourceInfo = common.NewResourceInfo(GROUP, VERSION,
m.CreationTimestamp.UTC().Format(time.RFC3339),
m.Spec.Dashboard,
m.Spec.Scope,
m.Spec.Groups,
}, nil
},
},

View File

@ -56,21 +56,8 @@ type ScopeDashboardBinding struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec ScopeDashboardBindingSpec `json:"spec,omitempty"`
}
type ScopeDashboardBindingSpec struct {
Dashboard string `json:"dashboard"`
// DashboardTitle should be populated and update from the dashboard
DashboardTitle string `json:"dashboardTitle"`
// Groups is used for the grouping of dashboards that are suggested based
// on a scope. The source of truth for this information has not been
// determined yet.
Groups []string `json:"groups,omitempty"`
Scope string `json:"scope"`
Spec ScopeDashboardBindingSpec `json:"spec,omitempty"`
Status ScopeDashboardBindingStatus `json:"status,omitempty"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@ -97,8 +84,36 @@ type ScopeNode struct {
Spec ScopeNodeSpec `json:"spec,omitempty"`
}
type ScopeDashboardBindingSpec struct {
Dashboard string `json:"dashboard"`
Scope string `json:"scope"`
}
// Type of the item.
// +enum
// ScopeDashboardBindingStatus contains derived information about a ScopeDashboardBinding.
type ScopeDashboardBindingStatus struct {
// DashboardTitle should be populated and update from the dashboard
DashboardTitle string `json:"dashboardTitle"`
// Groups is used for the grouping of dashboards that are suggested based
// on a scope. The source of truth for this information has not been
// determined yet.
Groups []string `json:"groups,omitempty"`
// DashboardTitleConditions is a list of conditions that are used to determine if the dashboard title is valid.
// +optional
// +listType=map
// +listMapKey=type
DashboardTitleConditions []metav1.Condition `json:"dashboardTitleConditions,omitempty"`
// DashboardTitleConditions is a list of conditions that are used to determine if the list of groups is valid.
// +optional
// +listType=map
// +listMapKey=type
GroupsConditions []metav1.Condition `json:"groupsConditions,omitempty"`
}
type NodeType string
// Defines values for ItemType.

View File

@ -8,6 +8,7 @@
package v0alpha1
import (
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
runtime "k8s.io/apimachinery/pkg/runtime"
)
@ -108,7 +109,8 @@ func (in *ScopeDashboardBinding) DeepCopyInto(out *ScopeDashboardBinding) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
out.Spec = in.Spec
in.Status.DeepCopyInto(&out.Status)
return
}
@ -166,11 +168,6 @@ func (in *ScopeDashboardBindingList) DeepCopyObject() runtime.Object {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScopeDashboardBindingSpec) DeepCopyInto(out *ScopeDashboardBindingSpec) {
*out = *in
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
@ -184,6 +181,41 @@ func (in *ScopeDashboardBindingSpec) DeepCopy() *ScopeDashboardBindingSpec {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScopeDashboardBindingStatus) DeepCopyInto(out *ScopeDashboardBindingStatus) {
*out = *in
if in.Groups != nil {
in, out := &in.Groups, &out.Groups
*out = make([]string, len(*in))
copy(*out, *in)
}
if in.DashboardTitleConditions != nil {
in, out := &in.DashboardTitleConditions, &out.DashboardTitleConditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.GroupsConditions != nil {
in, out := &in.GroupsConditions, &out.GroupsConditions
*out = make([]v1.Condition, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ScopeDashboardBindingStatus.
func (in *ScopeDashboardBindingStatus) DeepCopy() *ScopeDashboardBindingStatus {
if in == nil {
return nil
}
out := new(ScopeDashboardBindingStatus)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ScopeFilter) DeepCopyInto(out *ScopeFilter) {
*out = *in

View File

@ -20,6 +20,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBinding": schema_pkg_apis_scope_v0alpha1_ScopeDashboardBinding(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingList": schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingList(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingSpec": schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingSpec(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingStatus": schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingStatus(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeFilter": schema_pkg_apis_scope_v0alpha1_ScopeFilter(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeList": schema_pkg_apis_scope_v0alpha1_ScopeList(ref),
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeNode": schema_pkg_apis_scope_v0alpha1_ScopeNode(ref),
@ -195,11 +196,17 @@ func schema_pkg_apis_scope_v0alpha1_ScopeDashboardBinding(ref common.ReferenceCa
Ref: ref("github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingSpec"),
},
},
"status": {
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingStatus"),
},
},
},
},
},
Dependencies: []string{
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingSpec", "github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeDashboardBindingStatus", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
}
}
@ -263,6 +270,27 @@ func schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingSpec(ref common.Referen
Format: "",
},
},
"scope": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"dashboard", "scope"},
},
},
}
}
func schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingStatus(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "Type of the item. ScopeDashboardBindingStatus contains derived information about a ScopeDashboardBinding.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"dashboardTitle": {
SchemaProps: spec.SchemaProps{
Description: "DashboardTitle should be populated and update from the dashboard",
@ -286,17 +314,56 @@ func schema_pkg_apis_scope_v0alpha1_ScopeDashboardBindingSpec(ref common.Referen
},
},
},
"scope": {
"dashboardTitleConditions": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"type",
},
"x-kubernetes-list-type": "map",
},
},
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
Description: "DashboardTitleConditions is a list of conditions that are used to determine if the dashboard title is valid.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"),
},
},
},
},
},
"groupsConditions": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-map-keys": []interface{}{
"type",
},
"x-kubernetes-list-type": "map",
},
},
SchemaProps: spec.SchemaProps{
Description: "DashboardTitleConditions is a list of conditions that are used to determine if the list of groups is valid.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.Condition"),
},
},
},
},
},
},
Required: []string{"dashboard", "dashboardTitle", "scope"},
Required: []string{"dashboardTitle"},
},
},
Dependencies: []string{
"k8s.io/apimachinery/pkg/apis/meta/v1.Condition"},
}
}
@ -484,11 +551,9 @@ func schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref common.ReferenceCallback)
},
"nodeType": {
SchemaProps: spec.SchemaProps{
Description: "Possible enum values:\n - `\"container\"`\n - `\"leaf\"`",
Default: "",
Type: []string{"string"},
Format: "",
Enum: []interface{}{"container", "leaf"},
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {

View File

@ -1,3 +1,3 @@
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,FindScopeDashboardBindingsResults,Items
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeDashboardBindingSpec,Groups
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeDashboardBindingStatus,Groups
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,ScopeNodeSpec,LinkID

View File

@ -133,11 +133,12 @@ func (b *ScopeAPIBuilder) GetAPIGroupInfo(
}
storage[scopeResourceInfo.StoragePath()] = scopeStorage
scopeDashboardStorage, err := newScopeDashboardBindingStorage(scheme, optsGetter)
scopeDashboardStorage, scopedDashboardStatusStorage, err := newScopeDashboardBindingStorage(scheme, optsGetter)
if err != nil {
return nil, err
}
storage[scopeDashboardResourceInfo.StoragePath()] = scopeDashboardStorage
storage[scopeDashboardResourceInfo.StoragePath()+"/status"] = scopedDashboardStatusStorage
scopeNodeStorage, err := newScopeNodeStorage(scheme, optsGetter)
if err != nil {

View File

@ -44,7 +44,7 @@ func newScopeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGette
return &storage{Store: store}, nil
}
func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, *grafanaregistry.StatusREST, error) {
resourceInfo := scope.ScopeDashboardBindingResourceInfo
strategy := grafanaregistry.NewStrategy(scheme, resourceInfo.GroupVersion())
@ -63,10 +63,12 @@ func newScopeDashboardBindingStorage(scheme *runtime.Scheme, optsGetter generic.
}
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
return nil, nil, err
}
return &storage{Store: store}, nil
statusStrategy := grafanaregistry.NewStatusStrategy(scheme, resourceInfo.GroupVersion())
statusREST := grafanaregistry.NewStatusREST(store, statusStrategy)
return &storage{Store: store}, statusREST, nil
}
func newScopeNodeStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {

View File

@ -248,7 +248,7 @@ func TestReceiverService_Delete(t *testing.T) {
deleteUID: baseReceiver.UID,
existing: util.Pointer(baseReceiver.Clone()),
storeSettings: map[models.AlertRuleKey][]models.NotificationSettings{
models.AlertRuleKey{OrgID: 1, UID: "rule1"}: {
{OrgID: 1, UID: "rule1"}: {
models.NotificationSettingsGen(models.NSMuts.WithReceiver(baseReceiver.Name))(),
},
},

View File

@ -81,6 +81,17 @@ func TestIntegrationScopes(t *testing.T) {
"watch"
]
},
{
"name": "scopedashboardbindings/status",
"singularName": "",
"namespaced": true,
"kind": "ScopeDashboardBinding",
"verbs": [
"get",
"patch",
"update"
]
},
{
"name": "scopenodes",
"singularName": "scopenode",

View File

@ -5,5 +5,6 @@ metadata:
spec:
scope: example
dashboard: abc
status:
dashboardTitle: "Example Dashboard ABC"
groups: ["group1", "group2"]

View File

@ -5,5 +5,6 @@ metadata:
spec:
scope: example
dashboard: xyz
status:
dashboardTitle: "Example Dashboard XYZ"
groups: ["group2", "group3"]

View File

@ -48,7 +48,7 @@ export function groupDashboards(dashboards: ScopeDashboardBinding[]): SuggestedD
return dashboards.reduce<SuggestedDashboardsFoldersMap>(
(acc, dashboard) => {
const rootNode = acc[''];
const groups = dashboard.spec.groups ?? [];
const groups = dashboard.status.groups ?? [];
groups.forEach((group) => {
if (group && !rootNode.folders[group]) {
@ -70,7 +70,7 @@ export function groupDashboards(dashboards: ScopeDashboardBinding[]): SuggestedD
if (!target[dashboard.spec.dashboard]) {
target[dashboard.spec.dashboard] = {
dashboard: dashboard.spec.dashboard,
dashboardTitle: dashboard.spec.dashboardTitle,
dashboardTitle: dashboard.status.dashboardTitle,
items: [],
};
}

View File

@ -22,7 +22,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
},
@ -39,7 +39,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder],
},
},
@ -60,12 +60,12 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
@ -77,7 +77,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders],
},
},
@ -101,7 +101,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
@ -113,7 +113,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
@ -140,17 +140,17 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
[dashboardWithRootFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithRootFolder.status.dashboardTitle,
items: [dashboardWithRootFolder, alternativeDashboardWithRootFolder],
},
[dashboardWithoutFolder.spec.dashboard]: {
dashboard: dashboardWithoutFolder.spec.dashboard,
dashboardTitle: dashboardWithoutFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithoutFolder.status.dashboardTitle,
items: [dashboardWithoutFolder],
},
},
@ -159,12 +159,12 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithOneFolder.spec.dashboard]: {
dashboard: dashboardWithOneFolder.spec.dashboard,
dashboardTitle: dashboardWithOneFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithOneFolder.status.dashboardTitle,
items: [dashboardWithOneFolder],
},
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
@ -176,7 +176,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithTwoFolders.spec.dashboard]: {
dashboard: dashboardWithTwoFolders.spec.dashboard,
dashboardTitle: dashboardWithTwoFolders.spec.dashboardTitle,
dashboardTitle: dashboardWithTwoFolders.status.dashboardTitle,
items: [dashboardWithTwoFolders, alternativeDashboardWithTwoFolders],
},
},
@ -188,7 +188,7 @@ describe('Scopes', () => {
dashboards: {
[dashboardWithRootFolderAndOtherFolder.spec.dashboard]: {
dashboard: dashboardWithRootFolderAndOtherFolder.spec.dashboard,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.spec.dashboardTitle,
dashboardTitle: dashboardWithRootFolderAndOtherFolder.status.dashboardTitle,
items: [dashboardWithRootFolderAndOtherFolder],
},
},

View File

@ -111,8 +111,10 @@ const dashboardBindingsGenerator = (
metadata: { name: `${scope}-${dashboard}` },
spec: {
dashboard,
dashboardTitle,
scope,
},
status: {
dashboardTitle,
groups,
},
},
@ -415,8 +417,10 @@ const generateScopeDashboardBinding = (dashboardTitle: string, groups?: string[]
metadata: { name: `${dashboardTitle}-name` },
spec: {
dashboard: `${dashboardId ?? dashboardTitle}-dashboard`,
dashboardTitle,
scope: `${dashboardTitle}-scope`,
},
status: {
dashboardTitle,
groups,
},
});