From 29cf60988b89ab6731c51b965875078d680ac847 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 12 Oct 2023 08:29:06 -0700 Subject: [PATCH] Playlist: Use a different go struct for sql service vs k8s (#76393) --- .../kinds/core/playlist/schema-reference.md | 117 ----------- kinds/playlist/playlist_kind.cue | 47 ----- packages/grafana-schema/src/index.gen.ts | 9 - .../src/raw/playlist/x/playlist_types.gen.ts | 58 ------ pkg/apis/playlist/v0alpha1/conversions.go | 47 +++++ .../playlist/v0alpha1/conversions_test.go | 64 ++++++ pkg/apis/playlist/v0alpha1/legacy_storage.go | 42 +--- pkg/apis/playlist/v0alpha1/openapi.go | 115 ----------- pkg/apis/playlist/v0alpha1/register.go | 5 - pkg/apis/playlist/v0alpha1/types.go | 51 ++++- pkg/apis/playlist/v0alpha1/types_test.go | 42 ++++ .../v0alpha1/zz_generated.deepcopy.go | 32 ++- .../playlist/v0alpha1/zz_generated.openapi.go | 188 ++++++++++++++++++ pkg/kinds/playlist/playlist_gen.go | 39 ---- pkg/kinds/playlist/playlist_kind_gen.go | 79 -------- pkg/kinds/playlist/playlist_metadata_gen.go | 42 ---- pkg/kinds/playlist/playlist_spec_gen.go | 57 ------ pkg/kinds/playlist/playlist_status_gen.go | 74 ------- pkg/kindsysreport/codegen/report.json | 27 +-- pkg/registry/corekind/base_gen.go | 14 -- pkg/services/playlist/model.go | 53 +++-- .../playlist/playlistimpl/playlist.go | 22 +- pkg/services/store/kind/playlist/summary.go | 68 ------- .../store/kind/playlist/summary_test.go | 40 ---- pkg/services/store/kind/registry.go | 5 - pkg/services/store/kind/registry_test.go | 10 +- public/app/features/playlist/api.ts | 14 +- 27 files changed, 497 insertions(+), 864 deletions(-) delete mode 100644 docs/sources/developers/kinds/core/playlist/schema-reference.md delete mode 100644 kinds/playlist/playlist_kind.cue delete mode 100644 packages/grafana-schema/src/raw/playlist/x/playlist_types.gen.ts create mode 100644 pkg/apis/playlist/v0alpha1/conversions.go create mode 100644 pkg/apis/playlist/v0alpha1/conversions_test.go delete mode 100644 pkg/apis/playlist/v0alpha1/openapi.go create mode 100644 pkg/apis/playlist/v0alpha1/types_test.go create mode 100644 pkg/apis/playlist/v0alpha1/zz_generated.openapi.go delete mode 100644 pkg/kinds/playlist/playlist_gen.go delete mode 100644 pkg/kinds/playlist/playlist_kind_gen.go delete mode 100644 pkg/kinds/playlist/playlist_metadata_gen.go delete mode 100644 pkg/kinds/playlist/playlist_spec_gen.go delete mode 100644 pkg/kinds/playlist/playlist_status_gen.go delete mode 100644 pkg/services/store/kind/playlist/summary.go delete mode 100644 pkg/services/store/kind/playlist/summary_test.go diff --git a/docs/sources/developers/kinds/core/playlist/schema-reference.md b/docs/sources/developers/kinds/core/playlist/schema-reference.md deleted file mode 100644 index 6864981b00c..00000000000 --- a/docs/sources/developers/kinds/core/playlist/schema-reference.md +++ /dev/null @@ -1,117 +0,0 @@ ---- -keywords: - - grafana - - schema -labels: - products: - - cloud - - enterprise - - oss -title: Playlist kind ---- -> Both documentation generation and kinds schemas are in active development and subject to change without prior notice. - -## Playlist - -#### Maturity: [merged](../../../maturity/#merged) -#### Version: 0.0 - -A playlist is a series of dashboards that is automatically rotated in the browser, on a configurable interval. - -| Property | Type | Required | Default | Description | -|------------|---------------------|----------|---------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `metadata` | [object](#metadata) | **Yes** | | metadata contains embedded CommonMetadata and can be extended with custom string fields
TODO: use CommonMetadata instead of redefining here; currently needs to be defined here
without external reference as using the CommonMetadata reference breaks thema codegen. | -| `spec` | [object](#spec) | **Yes** | | | -| `status` | [object](#status) | **Yes** | | | - -### Metadata - -metadata contains embedded CommonMetadata and can be extended with custom string fields -TODO: use CommonMetadata instead of redefining here; currently needs to be defined here -without external reference as using the CommonMetadata reference breaks thema codegen. - -It extends [_kubeObjectMetadata](#_kubeobjectmetadata). - -| Property | Type | Required | Default | Description | -|---------------------|------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------| -| `createdBy` | string | **Yes** | | | -| `creationTimestamp` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | -| `extraFields` | [object](#extrafields) | **Yes** | | extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata | -| `finalizers` | string[] | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | -| `labels` | map[string]string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | -| `resourceVersion` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | -| `uid` | string | **Yes** | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | -| `updateTimestamp` | string | **Yes** | | | -| `updatedBy` | string | **Yes** | | | -| `deletionTimestamp` | string | No | | *(Inherited from [_kubeObjectMetadata](#_kubeobjectmetadata))* | - -### _kubeObjectMetadata - -_kubeObjectMetadata is metadata found in a kubernetes object's metadata field. -It is not exhaustive and only includes fields which may be relevant to a kind's implementation, -As it is also intended to be generic enough to function with any API Server. - -| Property | Type | Required | Default | Description | -|---------------------|-------------------|----------|---------|-------------| -| `creationTimestamp` | string | **Yes** | | | -| `finalizers` | string[] | **Yes** | | | -| `labels` | map[string]string | **Yes** | | | -| `resourceVersion` | string | **Yes** | | | -| `uid` | string | **Yes** | | | -| `deletionTimestamp` | string | No | | | - -### ExtraFields - -extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata - -| Property | Type | Required | Default | Description | -|----------|------|----------|---------|-------------| - -### Spec - -| Property | Type | Required | Default | Description | -|------------|---------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `interval` | string | **Yes** | `5m` | Interval sets the time between switching views in a playlist.
FIXME: Is this based on a standardized format or what options are available? Can datemath be used? | -| `name` | string | **Yes** | | Name of the playlist. | -| `uid` | string | **Yes** | | Unique playlist identifier. Generated on creation, either by the
creator of the playlist of by the application. | -| `items` | [PlaylistItem](#playlistitem)[] | No | | The ordered list of items that the playlist will iterate over.
FIXME! This should not be optional, but changing it makes the godegen awkward | - -### PlaylistItem - -| Property | Type | Required | Default | Description | -|----------|--------|----------|---------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `type` | string | **Yes** | | Type of the item.
Possible values are: `dashboard_by_uid`, `dashboard_by_id`, `dashboard_by_tag`. | -| `value` | string | **Yes** | | Value depends on type and describes the playlist item.

- dashboard_by_id: The value is an internal numerical identifier set by Grafana. This
is not portable as the numerical identifier is non-deterministic between different instances.
Will be replaced by dashboard_by_uid in the future. (deprecated)
- dashboard_by_tag: The value is a tag which is set on any number of dashboards. All
dashboards behind the tag will be added to the playlist.
- dashboard_by_uid: The value is the dashboard UID | -| `title` | string | No | | Title is an unused property -- it will be removed in the future | - -### Status - -| Property | Type | Required | Default | Description | -|--------------------|------------------------------------------------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `additionalFields` | [object](#additionalfields) | No | | additionalFields is reserved for future use | -| `operatorStates` | map[string][status.#OperatorState](#status.#operatorstate) | No | | operatorStates is a map of operator ID to operator state evaluations.
Any operator which consumes this kind SHOULD add its state evaluation information to this field. | - -### AdditionalFields - -additionalFields is reserved for future use - -| Property | Type | Required | Default | Description | -|----------|------|----------|---------|-------------| - -### Status.#OperatorState - -| Property | Type | Required | Default | Description | -|--------------------|--------------------|----------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `lastEvaluation` | string | **Yes** | | lastEvaluation is the ResourceVersion last evaluated | -| `state` | string | **Yes** | | state describes the state of the lastEvaluation.
It is limited to three possible states for machine evaluation.
Possible values are: `success`, `in_progress`, `failed`. | -| `descriptiveState` | string | No | | descriptiveState is an optional more descriptive state field which has no requirements on format | -| `details` | [object](#details) | No | | details contains any extra information that is operator-specific | - -### Details - -details contains any extra information that is operator-specific - -| Property | Type | Required | Default | Description | -|----------|------|----------|---------|-------------| - - diff --git a/kinds/playlist/playlist_kind.cue b/kinds/playlist/playlist_kind.cue deleted file mode 100644 index a87e1c01a1e..00000000000 --- a/kinds/playlist/playlist_kind.cue +++ /dev/null @@ -1,47 +0,0 @@ -package kind - -name: "Playlist" -maturity: "merged" -description: "A playlist is a series of dashboards that is automatically rotated in the browser, on a configurable interval." - -lineage: schemas: [{ - version: [0, 0] - schema: { - spec: { - // Unique playlist identifier. Generated on creation, either by the - // creator of the playlist of by the application. - uid: string - - // Name of the playlist. - name: string - - // Interval sets the time between switching views in a playlist. - // FIXME: Is this based on a standardized format or what options are available? Can datemath be used? - interval: string | *"5m" - - // The ordered list of items that the playlist will iterate over. - // FIXME! This should not be optional, but changing it makes the godegen awkward - items?: [...#PlaylistItem] - } @cuetsy(kind="interface") - - /////////////////////////////////////// - // Definitions (referenced above) are declared below - - #PlaylistItem: { - // Type of the item. - type: "dashboard_by_uid" | "dashboard_by_id" | "dashboard_by_tag" - // Value depends on type and describes the playlist item. - // - // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - // is not portable as the numerical identifier is non-deterministic between different instances. - // Will be replaced by dashboard_by_uid in the future. (deprecated) - // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - // dashboards behind the tag will be added to the playlist. - // - dashboard_by_uid: The value is the dashboard UID - value: string - - // Title is an unused property -- it will be removed in the future - title?: string - } @cuetsy(kind="interface") - } -}] diff --git a/packages/grafana-schema/src/index.gen.ts b/packages/grafana-schema/src/index.gen.ts index 7d1d3d05068..6576c1ce63f 100644 --- a/packages/grafana-schema/src/index.gen.ts +++ b/packages/grafana-schema/src/index.gen.ts @@ -124,15 +124,6 @@ export type { // TODO generate code such that tsc enforces type compatibility between raw and veneer decls export type { LibraryPanel } from './veneer/librarypanel.types'; -// Raw generated types from Playlist kind. -export type { - Playlist, - PlaylistItem -} from './raw/playlist/x/playlist_types.gen'; - -// Raw generated enums and default consts from playlist kind. -export { defaultPlaylist } from './raw/playlist/x/playlist_types.gen'; - // Raw generated types from Preferences kind. export type { Preferences, diff --git a/packages/grafana-schema/src/raw/playlist/x/playlist_types.gen.ts b/packages/grafana-schema/src/raw/playlist/x/playlist_types.gen.ts deleted file mode 100644 index 4f98abe19bf..00000000000 --- a/packages/grafana-schema/src/raw/playlist/x/playlist_types.gen.ts +++ /dev/null @@ -1,58 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// TSResourceJenny -// LatestMajorsOrXJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -export interface PlaylistItem { - /** - * Title is an unused property -- it will be removed in the future - */ - title?: string; - /** - * Type of the item. - */ - type: ('dashboard_by_uid' | 'dashboard_by_id' | 'dashboard_by_tag'); - /** - * Value depends on type and describes the playlist item. - * - * - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - * is not portable as the numerical identifier is non-deterministic between different instances. - * Will be replaced by dashboard_by_uid in the future. (deprecated) - * - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - * dashboards behind the tag will be added to the playlist. - * - dashboard_by_uid: The value is the dashboard UID - */ - value: string; -} - -export interface Playlist { - /** - * Interval sets the time between switching views in a playlist. - * FIXME: Is this based on a standardized format or what options are available? Can datemath be used? - */ - interval: string; - /** - * The ordered list of items that the playlist will iterate over. - * FIXME! This should not be optional, but changing it makes the godegen awkward - */ - items?: Array; - /** - * Name of the playlist. - */ - name: string; - /** - * Unique playlist identifier. Generated on creation, either by the - * creator of the playlist of by the application. - */ - uid: string; -} - -export const defaultPlaylist: Partial = { - interval: '5m', - items: [], -}; diff --git a/pkg/apis/playlist/v0alpha1/conversions.go b/pkg/apis/playlist/v0alpha1/conversions.go new file mode 100644 index 00000000000..fc393ffcae3 --- /dev/null +++ b/pkg/apis/playlist/v0alpha1/conversions.go @@ -0,0 +1,47 @@ +package v0alpha1 + +import ( + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/grafana/grafana/pkg/services/playlist" +) + +type namespaceMapper = func(orgId int64) string + +func orgNamespaceMapper(orgId int64) string { + if orgId == 1 { + return "default" + } + return fmt.Sprintf("org-%d", orgId) +} + +func convertToK8sResource(v *playlist.PlaylistDTO, namespacer namespaceMapper) *Playlist { + spec := Spec{ + Title: v.Name, + Interval: v.Interval, + } + for _, item := range v.Items { + spec.Items = append(spec.Items, Item{ + Type: ItemType(item.Type), + Value: item.Value, + }) + } + return &Playlist{ + TypeMeta: metav1.TypeMeta{ + Kind: "Playlist", + APIVersion: APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: v.Uid, + UID: types.UID(v.Uid), + ResourceVersion: fmt.Sprintf("%d", v.UpdatedAt), + CreationTimestamp: metav1.NewTime(time.UnixMilli(v.CreatedAt)), + Namespace: namespacer(v.OrgID), + }, + Spec: spec, + } +} diff --git a/pkg/apis/playlist/v0alpha1/conversions_test.go b/pkg/apis/playlist/v0alpha1/conversions_test.go new file mode 100644 index 00000000000..48f63dde95a --- /dev/null +++ b/pkg/apis/playlist/v0alpha1/conversions_test.go @@ -0,0 +1,64 @@ +package v0alpha1 + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/grafana/grafana/pkg/services/playlist" +) + +func TestPlaylistConversion(t *testing.T) { + src := &playlist.PlaylistDTO{ + OrgID: 3, + Uid: "abc", // becomes k8s name + Name: "MyPlaylists", // becomes title + Interval: "10s", + CreatedAt: 12345, + UpdatedAt: 54321, + Items: []playlist.PlaylistItemDTO{ + {Type: "dashboard_by_uid", Value: "UID0"}, + {Type: "dashboard_by_tag", Value: "tagA"}, + {Type: "dashboard_by_id", Value: "123"}, // deprecated + }, + } + dst := convertToK8sResource(src, orgNamespaceMapper) + + require.Equal(t, "abc", src.Uid) + require.Equal(t, "abc", dst.Name) + require.Equal(t, src.Name, dst.Spec.Title) + + out, err := json.MarshalIndent(dst, "", " ") + require.NoError(t, err) + //fmt.Printf("%s", string(out)) + require.JSONEq(t, `{ + "kind": "Playlist", + "apiVersion": "playlist.x.grafana.com/v0alpha1", + "metadata": { + "name": "abc", + "namespace": "org-3", + "uid": "abc", + "resourceVersion": "54321", + "creationTimestamp": "1970-01-01T00:00:12Z" + }, + "spec": { + "title": "MyPlaylists", + "interval": "10s", + "items": [ + { + "type": "dashboard_by_uid", + "value": "UID0" + }, + { + "type": "dashboard_by_tag", + "value": "tagA" + }, + { + "type": "dashboard_by_id", + "value": "123" + } + ] + } + }`, string(out)) +} diff --git a/pkg/apis/playlist/v0alpha1/legacy_storage.go b/pkg/apis/playlist/v0alpha1/legacy_storage.go index 1880499ce4f..d476b6a228c 100644 --- a/pkg/apis/playlist/v0alpha1/legacy_storage.go +++ b/pkg/apis/playlist/v0alpha1/legacy_storage.go @@ -9,7 +9,6 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/registry/rest" - playlistkind "github.com/grafana/grafana/pkg/kinds/playlist" grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/playlist" ) @@ -81,21 +80,14 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO }, } for _, v := range res { - p := Playlist{ - TypeMeta: metav1.TypeMeta{ - Kind: "Playlist", - APIVersion: APIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: v.UID, - }, - Spec: playlistkind.Spec{ - Name: v.Name, - Uid: v.UID, - Interval: v.Interval, - }, + p, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{ + UID: v.UID, + OrgId: orgId, // required + }) + if err != nil { + return nil, err } - list.Items = append(list.Items, p) + list.Items = append(list.Items, *convertToK8sResource(p, orgNamespaceMapper)) } if len(list.Items) == limit { list.Continue = "" // TODO? @@ -109,30 +101,16 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge orgId = 1 // TODO: default org ID 1 for now } - p, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{ + dto, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{ UID: name, OrgId: orgId, }) if err != nil { return nil, err } - if p == nil { + if dto == nil { return nil, fmt.Errorf("not found?") } - return &Playlist{ - TypeMeta: metav1.TypeMeta{ - Kind: "Playlist", - APIVersion: APIVersion, - }, - ObjectMeta: metav1.ObjectMeta{ - Name: p.Uid, - }, - Spec: playlistkind.Spec{ - Name: p.Name, - Uid: p.Uid, - Interval: p.Interval, - Items: p.Items, - }, - }, nil + return convertToK8sResource(dto, orgNamespaceMapper), nil } diff --git a/pkg/apis/playlist/v0alpha1/openapi.go b/pkg/apis/playlist/v0alpha1/openapi.go deleted file mode 100644 index 6b7123d06fa..00000000000 --- a/pkg/apis/playlist/v0alpha1/openapi.go +++ /dev/null @@ -1,115 +0,0 @@ -package v0alpha1 - -import ( - common "k8s.io/kube-openapi/pkg/common" - spec "k8s.io/kube-openapi/pkg/validation/spec" -) - -// NOTE: this must match the golang fully qualifid name! -const kindKey = "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Playlist" - -func getOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { - return map[string]common.OpenAPIDefinition{ - kindKey: schema_pkg_Playlist(ref), - kindKey + "List": schema_pkg_PlaylistList(ref), - } -} - -func schema_pkg_Playlist(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "Playlist", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), - }, - }, - // TODO! add a real spec here - // "spec": { - // SchemaProps: spec.SchemaProps{ - // Default: map[string]interface{}{}, - // Ref: ref("github.com/grafana/google-sheets-datasource/pkg/apis/googlesheets/v1.DatasourceSpec"), - // }, - // }, - // "status": { - // SchemaProps: spec.SchemaProps{ - // Default: map[string]interface{}{}, - // Ref: ref("github.com/grafana/google-sheets-datasource/pkg/apis/googlesheets/v1.DatasourceStatus"), - // }, - // }, - }, - }, - }, - Dependencies: []string{ - "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta", - }, - } -} - -func schema_pkg_PlaylistList(ref common.ReferenceCallback) common.OpenAPIDefinition { - return common.OpenAPIDefinition{ - Schema: spec.Schema{ - SchemaProps: spec.SchemaProps{ - Description: "PlaylistList", - Type: []string{"object"}, - Properties: map[string]spec.Schema{ - "kind": { - SchemaProps: spec.SchemaProps{ - Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", - Type: []string{"string"}, - Format: "", - }, - }, - "apiVersion": { - SchemaProps: spec.SchemaProps{ - Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", - Type: []string{"string"}, - Format: "", - }, - }, - "metadata": { - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), - }, - }, - "items": { - SchemaProps: spec.SchemaProps{ - Type: []string{"array"}, - Items: &spec.SchemaOrArray{ - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: map[string]interface{}{}, - Ref: ref(kindKey), - }, - }, - }, - }, - }, - }, - Required: []string{"items"}, - }, - }, - Dependencies: []string{ - kindKey, - "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, - } -} diff --git a/pkg/apis/playlist/v0alpha1/register.go b/pkg/apis/playlist/v0alpha1/register.go index 1c0e59edd4b..9a7335f5251 100644 --- a/pkg/apis/playlist/v0alpha1/register.go +++ b/pkg/apis/playlist/v0alpha1/register.go @@ -15,11 +15,6 @@ import ( "github.com/grafana/grafana/pkg/services/playlist" ) -// GroupName is the group name for this API. -const GroupName = "playlist.x.grafana.com" -const VersionID = "v0alpha1" // -const APIVersion = GroupName + "/" + VersionID - var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil) // This is used just so wire has something unique to return diff --git a/pkg/apis/playlist/v0alpha1/types.go b/pkg/apis/playlist/v0alpha1/types.go index 2e94bd84f44..7a8a147fb2f 100644 --- a/pkg/apis/playlist/v0alpha1/types.go +++ b/pkg/apis/playlist/v0alpha1/types.go @@ -2,10 +2,13 @@ package v0alpha1 import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - - "github.com/grafana/grafana/pkg/kinds/playlist" ) +// GroupName is the group name for this API. +const GroupName = "playlist.x.grafana.com" +const VersionID = "v0alpha1" // +const APIVersion = GroupName + "/" + VersionID + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object type Playlist struct { metav1.TypeMeta `json:",inline"` @@ -14,7 +17,7 @@ type Playlist struct { // +optional metav1.ObjectMeta `json:"metadata,omitempty"` - Spec playlist.Spec `json:"spec,omitempty"` + Spec Spec `json:"spec,omitempty"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -23,5 +26,45 @@ type PlaylistList struct { // +optional metav1.ListMeta `json:"metadata,omitempty"` - Items []Playlist `json:"playlists,omitempty"` + Items []Playlist `json:"items,omitempty"` } + +// Spec defines model for Spec. +type Spec struct { + // Name of the playlist. + Title string `json:"title"` + + // Interval sets the time between switching views in a playlist. + Interval string `json:"interval"` + + // The ordered list of items that the playlist will iterate over. + Items []Item `json:"items,omitempty"` +} + +// Defines values for ItemType. +const ( + ItemTypeDashboardByTag ItemType = "dashboard_by_tag" + ItemTypeDashboardByUid ItemType = "dashboard_by_uid" + + // deprecated -- should use UID + ItemTypeDashboardById ItemType = "dashboard_by_id" +) + +// Item defines model for Item. +type Item struct { + // Type of the item. + Type ItemType `json:"type"` + + // Value depends on type and describes the playlist item. + // + // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + // is not portable as the numerical identifier is non-deterministic between different instances. + // Will be replaced by dashboard_by_uid in the future. (deprecated) + // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + // dashboards behind the tag will be added to the playlist. + // - dashboard_by_uid: The value is the dashboard UID + Value string `json:"value"` +} + +// Type of the item. +type ItemType string diff --git a/pkg/apis/playlist/v0alpha1/types_test.go b/pkg/apis/playlist/v0alpha1/types_test.go new file mode 100644 index 00000000000..83033359872 --- /dev/null +++ b/pkg/apis/playlist/v0alpha1/types_test.go @@ -0,0 +1,42 @@ +package v0alpha1 + +import ( + "encoding/json" + "testing" + "time" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestPlaylistClone(t *testing.T) { + src := Playlist{ + TypeMeta: metav1.TypeMeta{ + Kind: "Playlist", + APIVersion: APIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "TheUID", + ResourceVersion: "12345", + CreationTimestamp: metav1.NewTime(time.Now()), + Annotations: map[string]string{ + "grafana.com/updatedTime": time.Now().Format(time.RFC3339), + }, + }, + Spec: Spec{ + Title: "A title", + Interval: "20s", + Items: []Item{ + {Type: ItemTypeDashboardByTag, Value: "graph-ng"}, + }, + }, + } + copy := src.DeepCopyObject() + + json0, err := json.Marshal(src) + require.NoError(t, err) + json1, err := json.Marshal(copy) + require.NoError(t, err) + + require.JSONEq(t, string(json0), string(json1)) +} diff --git a/pkg/apis/playlist/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/playlist/v0alpha1/zz_generated.deepcopy.go index 66da954054f..74637ae2fb4 100644 --- a/pkg/apis/playlist/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/playlist/v0alpha1/zz_generated.deepcopy.go @@ -1,6 +1,8 @@ //go:build !ignore_autogenerated // +build !ignore_autogenerated +// SPDX-License-Identifier: AGPL-3.0-only + // Code generated by deepcopy-gen. DO NOT EDIT. package v0alpha1 @@ -9,11 +11,28 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Item) DeepCopyInto(out *Item) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Item. +func (in *Item) DeepCopy() *Item { + if in == nil { + return nil + } + out := new(Item) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Playlist) DeepCopyInto(out *Playlist) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) return } @@ -69,17 +88,22 @@ func (in *PlaylistList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *legacyStorage) DeepCopyInto(out *legacyStorage) { +func (in *Spec) DeepCopyInto(out *Spec) { *out = *in + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Item, len(*in)) + copy(*out, *in) + } return } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Storage. -func (in *legacyStorage) DeepCopy() *legacyStorage { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Spec. +func (in *Spec) DeepCopy() *Spec { if in == nil { return nil } - out := new(legacyStorage) + out := new(Spec) in.DeepCopyInto(out) return out } diff --git a/pkg/apis/playlist/v0alpha1/zz_generated.openapi.go b/pkg/apis/playlist/v0alpha1/zz_generated.openapi.go new file mode 100644 index 00000000000..4be50c3a8f7 --- /dev/null +++ b/pkg/apis/playlist/v0alpha1/zz_generated.openapi.go @@ -0,0 +1,188 @@ +//go:build !ignore_autogenerated +// +build !ignore_autogenerated + +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by openapi-gen. DO NOT EDIT. + +// This file was autogenerated by openapi-gen. Do not edit it manually! + +package v0alpha1 + +import ( + common "k8s.io/kube-openapi/pkg/common" + spec "k8s.io/kube-openapi/pkg/validation/spec" +) + +func getOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { + return map[string]common.OpenAPIDefinition{ + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Item": schema_pkg_apis_playlist_v0alpha1_Item(ref), + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Playlist": schema_pkg_apis_playlist_v0alpha1_Playlist(ref), + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.PlaylistList": schema_pkg_apis_playlist_v0alpha1_PlaylistList(ref), + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Spec": schema_pkg_apis_playlist_v0alpha1_Spec(ref), + } +} + +func schema_pkg_apis_playlist_v0alpha1_Item(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Item defines model for Item.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "type": { + SchemaProps: spec.SchemaProps{ + Description: "Type of the item.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "value": { + SchemaProps: spec.SchemaProps{ + Description: "Value depends on type and describes the playlist item.\n\n - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This\n is not portable as the numerical identifier is non-deterministic between different instances.\n Will be replaced by dashboard_by_uid in the future. (deprecated)\n - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All\n dashboards behind the tag will be added to the playlist.\n - dashboard_by_uid: The value is the dashboard UID", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"type", "value"}, + }, + }, + } +} + +func schema_pkg_apis_playlist_v0alpha1_Playlist(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Description: "Standard object's metadata More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata", + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"), + }, + }, + "spec": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Spec"), + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Spec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_playlist_v0alpha1_PlaylistList(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "kind": { + SchemaProps: spec.SchemaProps{ + Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds", + Type: []string{"string"}, + Format: "", + }, + }, + "apiVersion": { + SchemaProps: spec.SchemaProps{ + Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources", + Type: []string{"string"}, + Format: "", + }, + }, + "metadata": { + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"), + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Playlist"), + }, + }, + }, + }, + }, + }, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Playlist", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_playlist_v0alpha1_Spec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Spec defines model for Spec.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "title": { + SchemaProps: spec.SchemaProps{ + Description: "Name of the playlist.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "interval": { + SchemaProps: spec.SchemaProps{ + Description: "Interval sets the time between switching views in a playlist.", + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "items": { + SchemaProps: spec.SchemaProps{ + Description: "The ordered list of items that the playlist will iterate over.", + Type: []string{"array"}, + Items: &spec.SchemaOrArray{ + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: map[string]interface{}{}, + Ref: ref("github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Item"), + }, + }, + }, + }, + }, + }, + Required: []string{"title", "interval"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Item"}, + } +} diff --git a/pkg/kinds/playlist/playlist_gen.go b/pkg/kinds/playlist/playlist_gen.go deleted file mode 100644 index 289e3cb5785..00000000000 --- a/pkg/kinds/playlist/playlist_gen.go +++ /dev/null @@ -1,39 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// GoTypesJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package playlist - -import ( - "github.com/grafana/grafana/pkg/kinds" -) - -// Resource is the kubernetes style representation of Playlist. (TODO be better) -type K8sResource = kinds.GrafanaResource[Spec, Status] - -// NewResource creates a new instance of the resource with a given name (UID) -func NewK8sResource(name string, s *Spec) K8sResource { - return K8sResource{ - Kind: "Playlist", - APIVersion: "v0-0-alpha", - Metadata: kinds.GrafanaResourceMetadata{ - Name: name, - Annotations: make(map[string]string), - Labels: make(map[string]string), - }, - Spec: s, - } -} - -// Resource is the wire representation of Playlist. -// It currently will soon be merged into the k8s flavor (TODO be better) -type Resource struct { - Metadata Metadata `json:"metadata"` - Spec Spec `json:"spec"` - Status Status `json:"status"` -} diff --git a/pkg/kinds/playlist/playlist_kind_gen.go b/pkg/kinds/playlist/playlist_kind_gen.go deleted file mode 100644 index d2f2a04cf4b..00000000000 --- a/pkg/kinds/playlist/playlist_kind_gen.go +++ /dev/null @@ -1,79 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// CoreKindJenny -// -// Run 'make gen-cue' from repository root to regenerate. - -package playlist - -import ( - "github.com/grafana/kindsys" - "github.com/grafana/thema" - "github.com/grafana/thema/vmux" - - "github.com/grafana/grafana/pkg/cuectx" -) - -// rootrel is the relative path from the grafana repository root to the -// directory containing the .cue files in which this kind is defined. Necessary -// for runtime errors related to the definition and/or lineage to provide -// a real path to the correct .cue file. -const rootrel string = "kinds/playlist" - -// TODO standard generated docs -type Kind struct { - kindsys.Core - lin thema.ConvergentLineage[*Resource] - jcodec vmux.Codec - valmux vmux.ValueMux[*Resource] -} - -// type guard - ensure generated Kind type satisfies the kindsys.Core interface -var _ kindsys.Core = &Kind{} - -// TODO standard generated docs -func NewKind(rt *thema.Runtime, opts ...thema.BindOption) (*Kind, error) { - def, err := cuectx.LoadCoreKindDef(rootrel, rt.Context(), nil) - if err != nil { - return nil, err - } - - k := &Kind{} - k.Core, err = kindsys.BindCore(rt, def, opts...) - if err != nil { - return nil, err - } - // Get the thema.Schema that the meta says is in the current version (which - // codegen ensures is always the latest) - cursch := thema.SchemaP(k.Core.Lineage(), def.Properties.CurrentVersion) - tsch, err := thema.BindType(cursch, &Resource{}) - if err != nil { - // Should be unreachable, modulo bugs in the Thema->Go code generator - return nil, err - } - - k.jcodec = vmux.NewJSONCodec("playlist.json") - k.lin = tsch.ConvergentLineage() - k.valmux = vmux.NewValueMux(k.lin.TypedSchema(), k.jcodec) - return k, nil -} - -// ConvergentLineage returns the same [thema.Lineage] as Lineage, but bound (see [thema.BindType]) -// to the the Playlist [Resource] type generated from the current schema, v0.0. -func (k *Kind) ConvergentLineage() thema.ConvergentLineage[*Resource] { - return k.lin -} - -// JSONValueMux is a version multiplexer that maps a []byte containing JSON data -// at any schematized dashboard version to an instance of Playlist [Resource]. -// -// Validation and translation errors emitted from this func will identify the -// input bytes as "dashboard.json". -// -// This is a thin wrapper around Thema's [vmux.ValueMux]. -func (k *Kind) JSONValueMux(b []byte) (*Resource, thema.TranslationLacunas, error) { - return k.valmux(b) -} diff --git a/pkg/kinds/playlist/playlist_metadata_gen.go b/pkg/kinds/playlist/playlist_metadata_gen.go deleted file mode 100644 index f97a4b6eb01..00000000000 --- a/pkg/kinds/playlist/playlist_metadata_gen.go +++ /dev/null @@ -1,42 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// GoResourceTypes -// -// Run 'make gen-cue' from repository root to regenerate. - -package playlist - -import ( - "time" -) - -// Metadata defines model for Metadata. -type Metadata struct { - CreatedBy string `json:"createdBy"` - CreationTimestamp time.Time `json:"creationTimestamp"` - DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` - - // extraFields is reserved for any fields that are pulled from the API server metadata but do not have concrete fields in the CUE metadata - ExtraFields map[string]any `json:"extraFields"` - Finalizers []string `json:"finalizers"` - Labels map[string]string `json:"labels"` - ResourceVersion string `json:"resourceVersion"` - Uid string `json:"uid"` - UpdateTimestamp time.Time `json:"updateTimestamp"` - UpdatedBy string `json:"updatedBy"` -} - -// _kubeObjectMetadata is metadata found in a kubernetes object's metadata field. -// It is not exhaustive and only includes fields which may be relevant to a kind's implementation, -// As it is also intended to be generic enough to function with any API Server. -type KubeObjectMetadata struct { - CreationTimestamp time.Time `json:"creationTimestamp"` - DeletionTimestamp *time.Time `json:"deletionTimestamp,omitempty"` - Finalizers []string `json:"finalizers"` - Labels map[string]string `json:"labels"` - ResourceVersion string `json:"resourceVersion"` - Uid string `json:"uid"` -} diff --git a/pkg/kinds/playlist/playlist_spec_gen.go b/pkg/kinds/playlist/playlist_spec_gen.go deleted file mode 100644 index 211fb577b7f..00000000000 --- a/pkg/kinds/playlist/playlist_spec_gen.go +++ /dev/null @@ -1,57 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// GoResourceTypes -// -// Run 'make gen-cue' from repository root to regenerate. - -package playlist - -// Defines values for ItemType. -const ( - ItemTypeDashboardById ItemType = "dashboard_by_id" - ItemTypeDashboardByTag ItemType = "dashboard_by_tag" - ItemTypeDashboardByUid ItemType = "dashboard_by_uid" -) - -// Item defines model for Item. -type Item struct { - // Title is an unused property -- it will be removed in the future - Title *string `json:"title,omitempty"` - - // Type of the item. - Type ItemType `json:"type"` - - // Value depends on type and describes the playlist item. - // - // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This - // is not portable as the numerical identifier is non-deterministic between different instances. - // Will be replaced by dashboard_by_uid in the future. (deprecated) - // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All - // dashboards behind the tag will be added to the playlist. - // - dashboard_by_uid: The value is the dashboard UID - Value string `json:"value"` -} - -// Type of the item. -type ItemType string - -// Spec defines model for Spec. -type Spec struct { - // Interval sets the time between switching views in a playlist. - // FIXME: Is this based on a standardized format or what options are available? Can datemath be used? - Interval string `json:"interval"` - - // The ordered list of items that the playlist will iterate over. - // FIXME! This should not be optional, but changing it makes the godegen awkward - Items []Item `json:"items,omitempty"` - - // Name of the playlist. - Name string `json:"name"` - - // Unique playlist identifier. Generated on creation, either by the - // creator of the playlist of by the application. - Uid string `json:"uid"` -} diff --git a/pkg/kinds/playlist/playlist_status_gen.go b/pkg/kinds/playlist/playlist_status_gen.go deleted file mode 100644 index 33644fe4a87..00000000000 --- a/pkg/kinds/playlist/playlist_status_gen.go +++ /dev/null @@ -1,74 +0,0 @@ -// Code generated - EDITING IS FUTILE. DO NOT EDIT. -// -// Generated by: -// kinds/gen.go -// Using jennies: -// GoResourceTypes -// -// Run 'make gen-cue' from repository root to regenerate. - -package playlist - -// Defines values for OperatorStateState. -const ( - OperatorStateStateFailed OperatorStateState = "failed" - OperatorStateStateInProgress OperatorStateState = "in_progress" - OperatorStateStateSuccess OperatorStateState = "success" -) - -// Defines values for StatusOperatorStateState. -const ( - StatusOperatorStateStateFailed StatusOperatorStateState = "failed" - StatusOperatorStateStateInProgress StatusOperatorStateState = "in_progress" - StatusOperatorStateStateSuccess StatusOperatorStateState = "success" -) - -// OperatorState defines model for OperatorState. -type OperatorState struct { - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - - // details contains any extra information that is operator-specific - Details map[string]any `json:"details,omitempty"` - - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State OperatorStateState `json:"state"` -} - -// OperatorStateState state describes the state of the lastEvaluation. -// It is limited to three possible states for machine evaluation. -type OperatorStateState string - -// Status defines model for Status. -type Status struct { - // additionalFields is reserved for future use - AdditionalFields map[string]any `json:"additionalFields,omitempty"` - - // operatorStates is a map of operator ID to operator state evaluations. - // Any operator which consumes this kind SHOULD add its state evaluation information to this field. - OperatorStates map[string]StatusOperatorState `json:"operatorStates,omitempty"` -} - -// StatusOperatorState defines model for status.#OperatorState. -type StatusOperatorState struct { - // descriptiveState is an optional more descriptive state field which has no requirements on format - DescriptiveState *string `json:"descriptiveState,omitempty"` - - // details contains any extra information that is operator-specific - Details map[string]any `json:"details,omitempty"` - - // lastEvaluation is the ResourceVersion last evaluated - LastEvaluation string `json:"lastEvaluation"` - - // state describes the state of the lastEvaluation. - // It is limited to three possible states for machine evaluation. - State StatusOperatorStateState `json:"state"` -} - -// StatusOperatorStateState state describes the state of the lastEvaluation. -// It is limited to three possible states for machine evaluation. -type StatusOperatorStateState string diff --git a/pkg/kindsysreport/codegen/report.json b/pkg/kindsysreport/codegen/report.json index 2aee0908d05..dca8c8bd03f 100644 --- a/pkg/kindsysreport/codegen/report.json +++ b/pkg/kindsysreport/codegen/report.json @@ -1322,31 +1322,26 @@ }, "playlist": { "category": "core", - "codeowners": [ - "grafana/grafana-as-code", - "grafana/grafana-frontend-platform", - "grafana/plugins-platform-frontend" - ], + "codeowners": [], "crd": { "dummySchema": false, - "group": "playlist.core.grafana.com", - "scope": "Namespaced" + "group": "", + "scope": "" }, "currentVersion": [ 0, 0 ], - "description": "A playlist is a series of dashboards that is automatically rotated in the browser, on a configurable interval.", "grafanaMaturityCount": 0, "lineageIsGroup": false, "links": { - "docs": "https://grafana.com/docs/grafana/next/developers/kinds/core/playlist/schema-reference", - "go": "https://github.com/grafana/grafana/tree/main/pkg/kinds/playlist", - "schema": "https://github.com/grafana/grafana/tree/main/kinds/playlist/playlist_kind.cue", - "ts": "https://github.com/grafana/grafana/tree/main/packages/grafana-schema/src/raw/playlist/x/playlist_types.gen.ts" + "docs": "n/a", + "go": "n/a", + "schema": "n/a", + "ts": "n/a" }, "machineName": "playlist", - "maturity": "merged", + "maturity": "planned", "name": "Playlist", "pluralMachineName": "playlists", "pluralName": "Playlists" @@ -2277,7 +2272,6 @@ "folder", "googlecloudmonitoringdataquery", "heatmappanelcfg", - "playlist", "preferences", "publicdashboard", "role", @@ -2286,7 +2280,7 @@ "timeseriespanelcfg", "trendpanelcfg" ], - "count": 14 + "count": 13 }, "planned": { "name": "planned", @@ -2319,6 +2313,7 @@ "mysqldataquery", "mysqldatasourcecfg", "parcadatasourcecfg", + "playlist", "postgresqldataquery", "postgresqldatasourcecfg", "prometheusdatasourcecfg", @@ -2335,7 +2330,7 @@ "zipkindataquery", "zipkindatasourcecfg" ], - "count": 43 + "count": 44 }, "stable": { "name": "stable", diff --git a/pkg/registry/corekind/base_gen.go b/pkg/registry/corekind/base_gen.go index f969ff245ea..5f72862b04a 100644 --- a/pkg/registry/corekind/base_gen.go +++ b/pkg/registry/corekind/base_gen.go @@ -16,7 +16,6 @@ import ( "github.com/grafana/grafana/pkg/kinds/dashboard" "github.com/grafana/grafana/pkg/kinds/folder" "github.com/grafana/grafana/pkg/kinds/librarypanel" - "github.com/grafana/grafana/pkg/kinds/playlist" "github.com/grafana/grafana/pkg/kinds/preferences" "github.com/grafana/grafana/pkg/kinds/publicdashboard" "github.com/grafana/grafana/pkg/kinds/role" @@ -47,7 +46,6 @@ type Base struct { dashboard *dashboard.Kind folder *folder.Kind librarypanel *librarypanel.Kind - playlist *playlist.Kind preferences *preferences.Kind publicdashboard *publicdashboard.Kind role *role.Kind @@ -61,7 +59,6 @@ var ( _ kindsys.Core = &dashboard.Kind{} _ kindsys.Core = &folder.Kind{} _ kindsys.Core = &librarypanel.Kind{} - _ kindsys.Core = &playlist.Kind{} _ kindsys.Core = &preferences.Kind{} _ kindsys.Core = &publicdashboard.Kind{} _ kindsys.Core = &role.Kind{} @@ -89,11 +86,6 @@ func (b *Base) LibraryPanel() *librarypanel.Kind { return b.librarypanel } -// Playlist returns the [kindsys.Interface] implementation for the playlist kind. -func (b *Base) Playlist() *playlist.Kind { - return b.playlist -} - // Preferences returns the [kindsys.Interface] implementation for the preferences kind. func (b *Base) Preferences() *preferences.Kind { return b.preferences @@ -147,12 +139,6 @@ func doNewBase(rt *thema.Runtime) *Base { } reg.all = append(reg.all, reg.librarypanel) - reg.playlist, err = playlist.NewKind(rt) - if err != nil { - panic(fmt.Sprintf("error while initializing the playlist Kind: %s", err)) - } - reg.all = append(reg.all, reg.playlist) - reg.preferences, err = preferences.NewKind(rt) if err != nil { panic(fmt.Sprintf("error while initializing the preferences Kind: %s", err)) diff --git a/pkg/services/playlist/model.go b/pkg/services/playlist/model.go index f0a0b4b4ae7..178bd7c83f8 100644 --- a/pkg/services/playlist/model.go +++ b/pkg/services/playlist/model.go @@ -2,8 +2,6 @@ package playlist import ( "errors" - - "github.com/grafana/grafana/pkg/kinds/playlist" ) // Typed errors @@ -27,9 +25,47 @@ type Playlist struct { UpdatedAt int64 `json:"-" db:"updated_at"` } -type PlaylistDTO = playlist.Spec -type PlaylistItemDTO = playlist.Item -type PlaylistItemType = playlist.ItemType +type PlaylistDTO struct { + // Unique playlist identifier. Generated on creation, either by the + // creator of the playlist of by the application. + Uid string `json:"uid"` + + // Name of the playlist. + Name string `json:"name"` + + // Interval sets the time between switching views in a playlist. + Interval string `json:"interval"` + + // The ordered list of items that the playlist will iterate over. + Items []PlaylistItemDTO `json:"items,omitempty"` + + // Returned for k8s + CreatedAt int64 `json:"-"` + + // Returned for k8s + UpdatedAt int64 `json:"-"` + + // Returned for k8s + OrgID int64 `json:"-"` +} + +type PlaylistItemDTO struct { + // Title is an unused property -- it will be removed in the future + Title *string `json:"title,omitempty"` + + // Type of the item. + Type string `json:"type"` + + // Value depends on type and describes the playlist item. + // + // - dashboard_by_id: The value is an internal numerical identifier set by Grafana. This + // is not portable as the numerical identifier is non-deterministic between different instances. + // Will be replaced by dashboard_by_uid in the future. (deprecated) + // - dashboard_by_tag: The value is a tag which is set on any number of dashboards. All + // dashboards behind the tag will be added to the playlist. + // - dashboard_by_uid: The value is the dashboard UID + Value string `json:"value"` +} type PlaylistItem struct { Id int64 `db:"id"` @@ -88,10 +124,3 @@ type GetPlaylistItemsByUidQuery struct { PlaylistUID string OrgId int64 } - -func PlaylistToResource(p PlaylistDTO) playlist.K8sResource { - copy := p - r := playlist.NewK8sResource(p.Uid, ©) - copy.Uid = "" // remove it from the payload - return r -} diff --git a/pkg/services/playlist/playlistimpl/playlist.go b/pkg/services/playlist/playlistimpl/playlist.go index 3111d701e58..ee6a000c627 100644 --- a/pkg/services/playlist/playlistimpl/playlist.go +++ b/pkg/services/playlist/playlistimpl/playlist.go @@ -4,9 +4,7 @@ import ( "context" "github.com/grafana/grafana/pkg/infra/db" - "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/playlist" - "github.com/grafana/grafana/pkg/services/store/entity" ) type Service struct { @@ -15,11 +13,10 @@ type Service struct { var _ playlist.Service = &Service{} -func ProvideService(db db.DB, toggles featuremgmt.FeatureToggles, objserver entity.EntityStoreServer) playlist.Service { - sqlstore := &sqlStore{ +func ProvideService(db db.DB) playlist.Service { + return &Service{store: &sqlStore{ db: db, - } - return &Service{store: sqlstore} + }} } func (s *Service) Create(ctx context.Context, cmd *playlist.CreatePlaylistCommand) (*playlist.Playlist, error) { @@ -48,7 +45,7 @@ func (s *Service) Get(ctx context.Context, q *playlist.GetPlaylistByUidQuery) (* } items := make([]playlist.PlaylistItemDTO, len(rawItems)) for i := 0; i < len(rawItems); i++ { - items[i].Type = playlist.PlaylistItemType(rawItems[i].Type) + items[i].Type = rawItems[i].Type items[i].Value = rawItems[i].Value // Add the unused title to the result @@ -58,10 +55,13 @@ func (s *Service) Get(ctx context.Context, q *playlist.GetPlaylistByUidQuery) (* } } return &playlist.PlaylistDTO{ - Uid: v.UID, - Name: v.Name, - Interval: v.Interval, - Items: items, + Uid: v.UID, + Name: v.Name, + Interval: v.Interval, + Items: items, + CreatedAt: v.CreatedAt, + UpdatedAt: v.UpdatedAt, + OrgID: v.OrgId, }, nil } diff --git a/pkg/services/store/kind/playlist/summary.go b/pkg/services/store/kind/playlist/summary.go deleted file mode 100644 index 8511ae3e511..00000000000 --- a/pkg/services/store/kind/playlist/summary.go +++ /dev/null @@ -1,68 +0,0 @@ -package playlist - -import ( - "context" - "encoding/json" - "fmt" - - "github.com/grafana/grafana/pkg/kinds/playlist" - "github.com/grafana/grafana/pkg/services/store/entity" -) - -func GetEntityKindInfo() entity.EntityKindInfo { - return entity.EntityKindInfo{ - ID: entity.StandardKindPlaylist, - Name: "Playlist", - Description: "Cycle though a collection of dashboards automatically", - } -} - -func GetEntitySummaryBuilder() entity.EntitySummaryBuilder { - return summaryBuilder -} - -func summaryBuilder(ctx context.Context, uid string, body []byte) (*entity.EntitySummary, []byte, error) { - obj := &playlist.Spec{} - err := json.Unmarshal(body, obj) - if err != nil { - return nil, nil, err // unable to read object - } - - // TODO: fix model so this is not possible - if obj.Items == nil { - temp := make([]playlist.Item, 0) - obj.Items = temp - } - - obj.Uid = uid // make sure they are consistent - summary := &entity.EntitySummary{ - UID: uid, - Name: obj.Name, - Description: fmt.Sprintf("%d items, refreshed every %s", len(obj.Items), obj.Interval), - } - - for _, item := range obj.Items { - switch item.Type { - case playlist.ItemTypeDashboardByUid: - summary.References = append(summary.References, &entity.EntityExternalReference{ - Family: entity.StandardKindDashboard, - Identifier: item.Value, - }) - - case playlist.ItemTypeDashboardByTag: - if summary.Labels == nil { - summary.Labels = make(map[string]string, 0) - } - summary.Labels[item.Value] = "" - - case playlist.ItemTypeDashboardById: - // obviously insufficient long term... but good to have an example :) - summary.Error = &entity.EntityErrorInfo{ - Message: "Playlist uses deprecated internal id system", - } - } - } - - out, err := json.MarshalIndent(obj, "", " ") - return summary, out, err -} diff --git a/pkg/services/store/kind/playlist/summary_test.go b/pkg/services/store/kind/playlist/summary_test.go deleted file mode 100644 index 7a9186cbc37..00000000000 --- a/pkg/services/store/kind/playlist/summary_test.go +++ /dev/null @@ -1,40 +0,0 @@ -package playlist - -import ( - "context" - "encoding/json" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/grafana/grafana/pkg/kinds/playlist" -) - -func TestPlaylistSummary(t *testing.T) { - builder := GetEntitySummaryBuilder() - - // Do not parse invalid input - _, _, err := builder(context.Background(), "abc", []byte("{invalid json")) - require.Error(t, err) - - playlist := playlist.Spec{ - Interval: "30s", - Name: "test", - Items: []playlist.Item{ - {Type: playlist.ItemTypeDashboardByUid, Value: "D1"}, - {Type: playlist.ItemTypeDashboardByTag, Value: "tagA"}, - {Type: playlist.ItemTypeDashboardByUid, Value: "D3"}, - }, - } - out, err := json.Marshal(playlist) - require.NoError(t, err) - require.NotNil(t, out) - - // Do not parse invalid input - summary, body, err := builder(context.Background(), "abc", out) - require.NoError(t, err) - require.Equal(t, "test", summary.Name) - require.Equal(t, 2, len(summary.References)) - require.Equal(t, map[string]string{"tagA": ""}, summary.Labels) - require.True(t, json.Valid(body)) -} diff --git a/pkg/services/store/kind/registry.go b/pkg/services/store/kind/registry.go index a43d7c7a592..d6c9f6d27a5 100644 --- a/pkg/services/store/kind/registry.go +++ b/pkg/services/store/kind/registry.go @@ -12,7 +12,6 @@ import ( "github.com/grafana/grafana/pkg/services/store/kind/folder" "github.com/grafana/grafana/pkg/services/store/kind/geojson" "github.com/grafana/grafana/pkg/services/store/kind/jsonobj" - "github.com/grafana/grafana/pkg/services/store/kind/playlist" "github.com/grafana/grafana/pkg/services/store/kind/png" "github.com/grafana/grafana/pkg/services/store/kind/preferences" "github.com/grafana/grafana/pkg/services/store/kind/snapshot" @@ -30,10 +29,6 @@ type KindRegistry interface { func NewKindRegistry() KindRegistry { kinds := make(map[string]*kindValues) - kinds[entity.StandardKindPlaylist] = &kindValues{ - info: playlist.GetEntityKindInfo(), - builder: playlist.GetEntitySummaryBuilder(), - } kinds[entity.StandardKindDashboard] = &kindValues{ info: dashboard.GetEntityKindInfo(), builder: dashboard.GetEntitySummaryBuilder(), diff --git a/pkg/services/store/kind/registry_test.go b/pkg/services/store/kind/registry_test.go index 9443edc1263..a9e501ed5f8 100644 --- a/pkg/services/store/kind/registry_test.go +++ b/pkg/services/store/kind/registry_test.go @@ -5,7 +5,6 @@ import ( "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/services/store/entity" "github.com/grafana/grafana/pkg/services/store/kind/dummy" ) @@ -24,21 +23,14 @@ func TestKindRegistry(t *testing.T) { "frame", "geojson", "jsonobj", - "playlist", "png", "preferences", "snapshot", "test", }, ids) - // Check playlist exists - info, err := registry.GetInfo(entity.StandardKindPlaylist) - require.NoError(t, err) - require.Equal(t, "Playlist", info.Name) - require.False(t, info.IsRaw) - // Check that we registered a test item - info, err = registry.GetInfo("test") + info, err := registry.GetInfo("test") require.NoError(t, err) require.Equal(t, "test", info.Name) require.True(t, info.IsRaw) diff --git a/public/app/features/playlist/api.ts b/public/app/features/playlist/api.ts index f5987ad5bad..a24152ada11 100644 --- a/public/app/features/playlist/api.ts +++ b/public/app/features/playlist/api.ts @@ -46,7 +46,7 @@ interface K8sPlaylist { name: string; }; spec: { - name: string; + title: string; interval: string; items: PlaylistItem[]; }; @@ -58,10 +58,7 @@ class K8sAPI implements PlaylistAPI { async getAllPlaylist(): Promise { const result = await getBackendSrv().get(this.url); - console.log('getAllPlaylist', result); - const v = result.playlists.map(k8sResourceAsPlaylist); - console.log('after', v); - return v; + return result.playlists.map(k8sResourceAsPlaylist); } async getPlaylist(uid: string): Promise { @@ -118,9 +115,12 @@ class K8sAPI implements PlaylistAPI { // the main difference is that k8s uses metdata.name as the uid // to avoid future confusion, the display name is now called "title" function k8sResourceAsPlaylist(r: K8sPlaylist): Playlist { + const { spec, metadata } = r; return { - ...r.spec, - uid: r.metadata.name, // replace the uid from the k8s name + uid: metadata.name, // use the k8s name as uid + name: spec.title, + interval: spec.interval, + items: spec.items, }; }