Dashboard Schema V2: Support panel actions (#110842)

* support panel actions

* refactor

* add test; move action transformer to utils

* refactor so v2 headers and queryParams are just a simple record

* update open api

* update actions to be same shape accross all dashboard schemas and add validation on the backend

* cleanup

* update snapshot

* add tests to validation
This commit is contained in:
Haris Rozajac
2025-09-12 11:31:05 -06:00
committed by GitHub
parent ccc87a03f0
commit de01b3e209
24 changed files with 2808 additions and 25 deletions
@@ -221,6 +221,9 @@ FieldConfig: {
// The behavior when clicking on a result
links?: [...]
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...Action]
// Alternative to empty string
noValue?: string
@@ -364,6 +367,47 @@ FieldColor: {
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
DashboardLinkType: "link" | "dashboards"
ActionType: "fetch" | "infinity"
FetchOptions: {
method: HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
}
InfinityOptions: FetchOptions & {
datasourceUid: string
}
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
// Action variable type
ActionVariableType: "string"
ActionVariable: {
key: string
name: string
type: ActionVariableType
}
Action: {
type: ActionType
title: string
fetch?: FetchOptions
infinity?: InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...ActionVariable]
style?: {
backgroundColor?: string
}
}
// --- Common types ---
Kind: {
kind: string
@@ -219,6 +219,9 @@ FieldConfig: {
// The behavior when clicking on a result
links?: [...]
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...Action]
// Alternative to empty string
noValue?: string
@@ -362,6 +365,48 @@ FieldColor: {
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
DashboardLinkType: "link" | "dashboards"
ActionType: "fetch" | "infinity"
FetchOptions: {
method: HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
}
InfinityOptions: FetchOptions & {
datasourceUid: string
}
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
// Action variable type
ActionVariableType: "string"
ActionVariable: {
key: string
name: string
type: ActionVariableType
}
Action: {
type: ActionType
title: string
fetch?: FetchOptions
infinity?: InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...ActionVariable]
style?: {
backgroundColor?: string
}
}
// --- Common types ---
Kind: {
kind: string
@@ -302,6 +302,59 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
// Fetch options
#FetchOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
} @cuetsy(kind="interface")
// Infinity options
#InfinityOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
datasourceUid: string
} @cuetsy(kind="interface")
#HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH" @cuetsy(kind="type")
// Action variable type
#ActionVariableType: "string" @cuetsy(kind="type")
#ActionVariable: {
key: string
name: string
type: #ActionVariableType
} @cuetsy(kind="interface")
// Dashboard action
#Action: {
type: #ActionType
title: string
fetch?: #FetchOptions
infinity?: #InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...#ActionVariable]
style?: {
backgroundColor?: string
}
} @cuetsy(kind="interface")
// Dashboard variable type
// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
@@ -731,6 +784,9 @@ lineage: schemas: [{
// The behavior when clicking on a result
links?: [...] @grafanamaturity(NeedsExpertReview)
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...#Action] @grafanamaturity(NeedsExpertReview)
// Alternative to empty string
noValue?: string @grafanamaturity(NeedsExpertReview)
@@ -302,6 +302,59 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
// Fetch options
#FetchOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
} @cuetsy(kind="interface")
// Infinity options
#InfinityOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
datasourceUid: string
} @cuetsy(kind="interface")
#HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH" @cuetsy(kind="type")
// Action variable type
#ActionVariableType: "string" @cuetsy(kind="type")
#ActionVariable: {
key: string
name: string
type: #ActionVariableType
} @cuetsy(kind="interface")
// Dashboard action
#Action: {
type: #ActionType
title: string
fetch?: #FetchOptions
infinity?: #InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...#ActionVariable]
style?: {
backgroundColor?: string
}
} @cuetsy(kind="interface")
// Dashboard variable type
// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
@@ -731,6 +784,9 @@ lineage: schemas: [{
// The behavior when clicking on a result
links?: [...] @grafanamaturity(NeedsExpertReview)
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...#Action] @grafanamaturity(NeedsExpertReview)
// Alternative to empty string
noValue?: string @grafanamaturity(NeedsExpertReview)
@@ -225,6 +225,9 @@ FieldConfig: {
// The behavior when clicking on a result
links?: [...]
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...Action]
// Alternative to empty string
noValue?: string
@@ -368,6 +371,47 @@ FieldColor: {
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
DashboardLinkType: "link" | "dashboards"
ActionType: "fetch" | "infinity"
FetchOptions: {
method: HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
}
InfinityOptions: FetchOptions & {
datasourceUid: string
}
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
// Action variable type
ActionVariableType: "string"
ActionVariable: {
key: string
name: string
type: ActionVariableType
}
Action: {
type: ActionType
title: string
fetch?: FetchOptions
infinity?: InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...ActionVariable]
style?: {
backgroundColor?: string
}
}
// --- Common types ---
Kind: {
kind: string
@@ -392,6 +392,8 @@ type DashboardFieldConfig struct {
Color *DashboardFieldColor `json:"color,omitempty"`
// The behavior when clicking on a result
Links []interface{} `json:"links,omitempty"`
// Define interactive HTTP requests that can be triggered from data visualizations.
Actions []DashboardAction `json:"actions,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// custom is specified by the FieldConfig field
@@ -623,6 +625,95 @@ const (
DashboardFieldColorSeriesByModeLast DashboardFieldColorSeriesByMode = "last"
)
// +k8s:openapi-gen=true
type DashboardAction struct {
Type DashboardActionType `json:"type"`
Title string `json:"title"`
Fetch *DashboardFetchOptions `json:"fetch,omitempty"`
Infinity *DashboardInfinityOptions `json:"infinity,omitempty"`
Confirmation *string `json:"confirmation,omitempty"`
OneClick *bool `json:"oneClick,omitempty"`
Variables []DashboardActionVariable `json:"variables,omitempty"`
Style *DashboardV2alpha1ActionStyle `json:"style,omitempty"`
}
// NewDashboardAction creates a new DashboardAction object.
func NewDashboardAction() *DashboardAction {
return &DashboardAction{}
}
// +k8s:openapi-gen=true
type DashboardActionType string
const (
DashboardActionTypeFetch DashboardActionType = "fetch"
DashboardActionTypeInfinity DashboardActionType = "infinity"
)
// +k8s:openapi-gen=true
type DashboardFetchOptions struct {
Method DashboardHttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
Headers [][]string `json:"headers,omitempty"`
}
// NewDashboardFetchOptions creates a new DashboardFetchOptions object.
func NewDashboardFetchOptions() *DashboardFetchOptions {
return &DashboardFetchOptions{}
}
// +k8s:openapi-gen=true
type DashboardHttpRequestMethod string
const (
DashboardHttpRequestMethodGET DashboardHttpRequestMethod = "GET"
DashboardHttpRequestMethodPUT DashboardHttpRequestMethod = "PUT"
DashboardHttpRequestMethodPOST DashboardHttpRequestMethod = "POST"
DashboardHttpRequestMethodDELETE DashboardHttpRequestMethod = "DELETE"
DashboardHttpRequestMethodPATCH DashboardHttpRequestMethod = "PATCH"
)
// +k8s:openapi-gen=true
type DashboardInfinityOptions struct {
Method DashboardHttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
DatasourceUid string `json:"datasourceUid"`
Headers [][]string `json:"headers,omitempty"`
}
// NewDashboardInfinityOptions creates a new DashboardInfinityOptions object.
func NewDashboardInfinityOptions() *DashboardInfinityOptions {
return &DashboardInfinityOptions{}
}
// +k8s:openapi-gen=true
type DashboardActionVariable struct {
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
}
// NewDashboardActionVariable creates a new DashboardActionVariable object.
func NewDashboardActionVariable() *DashboardActionVariable {
return &DashboardActionVariable{
Type: DashboardActionVariableType,
}
}
// Action variable type
// +k8s:openapi-gen=true
const DashboardActionVariableType = "string"
// +k8s:openapi-gen=true
type DashboardDynamicConfigValue struct {
Id string `json:"id"`
@@ -1831,6 +1922,16 @@ func NewDashboardV2alpha1SpecialValueMapOptions() *DashboardV2alpha1SpecialValue
}
}
// +k8s:openapi-gen=true
type DashboardV2alpha1ActionStyle struct {
BackgroundColor *string `json:"backgroundColor,omitempty"`
}
// NewDashboardV2alpha1ActionStyle creates a new DashboardV2alpha1ActionStyle object.
func NewDashboardV2alpha1ActionStyle() *DashboardV2alpha1ActionStyle {
return &DashboardV2alpha1ActionStyle{}
}
// +k8s:openapi-gen=true
type DashboardRepeatOptionsDirection string
@@ -23,6 +23,9 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList {
}
}
// Custom validation for action query params and headers
validateAndTrimActionArrays(obj)
if err := cuejson.Validate(data, getCueSchema()); err != nil {
errs := field.ErrorList{}
@@ -60,6 +63,61 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList {
return nil
}
// Validates and trims action query params and headers to exactly 2 elements each
// This is because we couldn't generate with cue a go struct that would have exactly two strings in each sub-array
func validateAndTrimActionArrays(obj *Dashboard) {
for _, element := range obj.Spec.Elements {
if element.PanelKind != nil {
panelElement := element.PanelKind
if panelElement.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions != nil {
processActions(panelElement.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions)
}
}
}
}
// Helper function to process action arrays
func processActions(actions []DashboardAction) {
for _, action := range actions {
// Process FetchOptions if present
if action.Fetch != nil {
if action.Fetch.QueryParams != nil {
action.Fetch.QueryParams = trimStringArrays(action.Fetch.QueryParams)
}
if action.Fetch.Headers != nil {
action.Fetch.Headers = trimStringArrays(action.Fetch.Headers)
}
}
// Process InfinityOptions if present
if action.Infinity != nil {
if action.Infinity.QueryParams != nil {
action.Infinity.QueryParams = trimStringArrays(action.Infinity.QueryParams)
}
if action.Infinity.Headers != nil {
action.Infinity.Headers = trimStringArrays(action.Infinity.Headers)
}
}
}
}
// Helper function to trim 2D string arrays to exactly 2 elements per sub-array
func trimStringArrays(arrays [][]string) [][]string {
if arrays == nil {
return arrays
}
result := make([][]string, len(arrays))
for i, arr := range arrays {
if len(arr) > 2 {
result[i] = arr[:2]
} else {
result[i] = arr
}
}
return result
}
func formatErrorPath(path []string) string {
return strings.Join(path, ".")
}
@@ -0,0 +1,495 @@
package v2alpha1
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestTrimStringArrays(t *testing.T) {
tests := []struct {
name string
input [][]string
expected [][]string
}{
{
name: "nil input",
input: nil,
expected: nil,
},
{
name: "empty input",
input: [][]string{},
expected: [][]string{},
},
{
name: "arrays with exactly 2 elements",
input: [][]string{{"key1", "value1"}, {"key2", "value2"}},
expected: [][]string{{"key1", "value1"}, {"key2", "value2"}},
},
{
name: "arrays with less than 2 elements",
input: [][]string{{"key1"}, {}},
expected: [][]string{{"key1"}, {}},
},
{
name: "arrays with more than 2 elements",
input: [][]string{{"key1", "value1", "extra1"}, {"key2", "value2", "extra2", "extra3"}},
expected: [][]string{{"key1", "value1"}, {"key2", "value2"}},
},
{
name: "mixed arrays",
input: [][]string{{"key1"}, {"key2", "value2"}, {"key3", "value3", "extra"}},
expected: [][]string{{"key1"}, {"key2", "value2"}, {"key3", "value3"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := trimStringArrays(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestProcessActions(t *testing.T) {
tests := []struct {
name string
actions []DashboardAction
expected []DashboardAction
}{
{
name: "empty actions",
actions: []DashboardAction{},
expected: []DashboardAction{},
},
{
name: "action with fetch options having oversized arrays",
actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Fetch",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
{"header2", "value2"},
},
},
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Fetch",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
{"header2", "value2"},
},
},
},
},
},
{
name: "action with infinity options having oversized arrays",
actions: []DashboardAction{
{
Type: DashboardActionTypeInfinity,
Title: "Test Infinity",
Infinity: &DashboardInfinityOptions{
Method: DashboardHttpRequestMethodPOST,
Url: "http://example.com",
DatasourceUid: "test-uid",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
{"header2", "value2"},
},
},
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeInfinity,
Title: "Test Infinity",
Infinity: &DashboardInfinityOptions{
Method: DashboardHttpRequestMethodPOST,
Url: "http://example.com",
DatasourceUid: "test-uid",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
{"header2", "value2"},
},
},
},
},
},
{
name: "action without fetch or infinity options",
actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a copy to avoid modifying the original
actionsCopy := make([]DashboardAction, len(tt.actions))
for i, action := range tt.actions {
actionsCopy[i] = action
// Deep copy the fetch options if they exist
if action.Fetch != nil {
fetchCopy := *action.Fetch
if action.Fetch.QueryParams != nil {
fetchCopy.QueryParams = make([][]string, len(action.Fetch.QueryParams))
for j, param := range action.Fetch.QueryParams {
fetchCopy.QueryParams[j] = make([]string, len(param))
copy(fetchCopy.QueryParams[j], param)
}
}
if action.Fetch.Headers != nil {
fetchCopy.Headers = make([][]string, len(action.Fetch.Headers))
for j, header := range action.Fetch.Headers {
fetchCopy.Headers[j] = make([]string, len(header))
copy(fetchCopy.Headers[j], header)
}
}
actionsCopy[i].Fetch = &fetchCopy
}
// Deep copy the infinity options if they exist
if action.Infinity != nil {
infinityCopy := *action.Infinity
if action.Infinity.QueryParams != nil {
infinityCopy.QueryParams = make([][]string, len(action.Infinity.QueryParams))
for j, param := range action.Infinity.QueryParams {
infinityCopy.QueryParams[j] = make([]string, len(param))
copy(infinityCopy.QueryParams[j], param)
}
}
if action.Infinity.Headers != nil {
infinityCopy.Headers = make([][]string, len(action.Infinity.Headers))
for j, header := range action.Infinity.Headers {
infinityCopy.Headers[j] = make([]string, len(header))
copy(infinityCopy.Headers[j], header)
}
}
actionsCopy[i].Infinity = &infinityCopy
}
}
processActions(actionsCopy)
assert.Equal(t, tt.expected, actionsCopy)
})
}
}
func TestValidateAndTrimActionArrays(t *testing.T) {
tests := []struct {
name string
dashboard *Dashboard
expected *Dashboard
}{
{
name: "dashboard with no elements",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{},
},
},
},
{
name: "dashboard with panel having actions with oversized arrays",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "dashboard with panel having no actions",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: nil,
},
},
},
},
},
},
},
},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: nil,
},
},
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a deep copy to avoid modifying the original
dashboardCopy := deepCopyDashboard(tt.dashboard)
validateAndTrimActionArrays(dashboardCopy)
assert.Equal(t, tt.expected, dashboardCopy)
})
}
}
// Helper function to create a deep copy of a Dashboard for testing
func deepCopyDashboard(original *Dashboard) *Dashboard {
if original == nil {
return nil
}
result := &Dashboard{
TypeMeta: original.TypeMeta,
ObjectMeta: original.ObjectMeta,
Spec: DashboardSpec{
Elements: make(map[string]DashboardElement),
},
Status: original.Status,
}
for key, element := range original.Spec.Elements {
elementCopy := element
if element.PanelKind != nil {
panelCopy := *element.PanelKind
elementCopy.PanelKind = &panelCopy
if element.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions != nil {
actions := element.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions
actionsCopy := make([]DashboardAction, len(actions))
for j, action := range actions {
actionsCopy[j] = action
// Deep copy fetch options
if action.Fetch != nil {
fetchCopy := *action.Fetch
if action.Fetch.QueryParams != nil {
fetchCopy.QueryParams = make([][]string, len(action.Fetch.QueryParams))
for k, param := range action.Fetch.QueryParams {
fetchCopy.QueryParams[k] = make([]string, len(param))
copy(fetchCopy.QueryParams[k], param)
}
}
if action.Fetch.Headers != nil {
fetchCopy.Headers = make([][]string, len(action.Fetch.Headers))
for k, header := range action.Fetch.Headers {
fetchCopy.Headers[k] = make([]string, len(header))
copy(fetchCopy.Headers[k], header)
}
}
actionsCopy[j].Fetch = &fetchCopy
}
// Deep copy infinity options
if action.Infinity != nil {
infinityCopy := *action.Infinity
if action.Infinity.QueryParams != nil {
infinityCopy.QueryParams = make([][]string, len(action.Infinity.QueryParams))
for k, param := range action.Infinity.QueryParams {
infinityCopy.QueryParams[k] = make([]string, len(param))
copy(infinityCopy.QueryParams[k], param)
}
}
if action.Infinity.Headers != nil {
infinityCopy.Headers = make([][]string, len(action.Infinity.Headers))
for k, header := range action.Infinity.Headers {
infinityCopy.Headers[k] = make([]string, len(header))
copy(infinityCopy.Headers[k], header)
}
}
actionsCopy[j].Infinity = &infinityCopy
}
}
elementCopy.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions = actionsCopy
}
}
if element.LibraryPanelKind != nil {
libraryCopy := *element.LibraryPanelKind
elementCopy.LibraryPanelKind = &libraryCopy
}
result.Spec.Elements[key] = elementCopy
}
return result
}
@@ -18,6 +18,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.AnnotationPermission": schema_pkg_apis_dashboard_v2alpha1_AnnotationPermission(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.Dashboard": schema_pkg_apis_dashboard_v2alpha1_Dashboard(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAccess": schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAction": schema_pkg_apis_dashboard_v2alpha1_DashboardAction(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardActionVariable": schema_pkg_apis_dashboard_v2alpha1_DashboardActionVariable(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAdHocFilterWithLabels": schema_pkg_apis_dashboard_v2alpha1_DashboardAdHocFilterWithLabels(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAdhocVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardAdhocVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAdhocVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardAdhocVariableSpec(ref),
@@ -52,6 +54,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDatasourceVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardDatasourceVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardDynamicConfigValue": schema_pkg_apis_dashboard_v2alpha1_DashboardDynamicConfigValue(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardElementReference": schema_pkg_apis_dashboard_v2alpha1_DashboardElementReference(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFetchOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardFetchOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFieldColor": schema_pkg_apis_dashboard_v2alpha1_DashboardFieldColor(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFieldConfig": schema_pkg_apis_dashboard_v2alpha1_DashboardFieldConfig(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFieldConfigSource": schema_pkg_apis_dashboard_v2alpha1_DashboardFieldConfigSource(ref),
@@ -63,6 +66,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGridLayoutSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardGridLayoutSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGroupByVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardGroupByVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardGroupByVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardGroupByVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardInfinityOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardInfinityOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardIntervalVariableKind": schema_pkg_apis_dashboard_v2alpha1_DashboardIntervalVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardIntervalVariableSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardIntervalVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardJSONCodec": schema_pkg_apis_dashboard_v2alpha1_DashboardJSONCodec(ref),
@@ -109,6 +113,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeRangeOption": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeRangeOption(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTimeSettingsSpec": schema_pkg_apis_dashboard_v2alpha1_DashboardTimeSettingsSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardTransformationKind": schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1ActionStyle": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1ActionStyle(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RangeMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RangeMapOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1RegexMapOptions": schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1RegexMapOptions(ref),
@@ -309,6 +314,109 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardAccess(ref common.ReferenceCall
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardAction(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"fetch": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFetchOptions"),
},
},
"infinity": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardInfinityOptions"),
},
},
"confirmation": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"oneClick": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"variables": {
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/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardActionVariable"),
},
},
},
},
},
"style": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1ActionStyle"),
},
},
},
Required: []string{"type", "title"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardActionVariable", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFetchOptions", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardInfinityOptions", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardV2alpha1ActionStyle"},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardActionVariable(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"key": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"key", "name", "type"},
},
},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardAdHocFilterWithLabels(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1805,6 +1913,82 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardElementReference(ref common.Ref
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardFetchOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"method": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"body": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"queryParams": {
SchemaProps: spec.SchemaProps{
Description: "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
"headers": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
},
Required: []string{"method", "url"},
},
},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardFieldColor(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1957,6 +2141,20 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardFieldConfig(ref common.Referenc
},
},
},
"actions": {
SchemaProps: spec.SchemaProps{
Description: "Define interactive HTTP requests that can be triggered from data visualizations.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAction"),
},
},
},
},
},
"noValue": {
SchemaProps: spec.SchemaProps{
Description: "Alternative to empty string",
@@ -1983,7 +2181,7 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardFieldConfig(ref common.Referenc
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFieldColor", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardThresholdsConfig", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap"},
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardAction", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardFieldColor", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardThresholdsConfig", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap"},
}
}
@@ -2345,6 +2543,89 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardGroupByVariableSpec(ref common.
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardInfinityOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"method": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"body": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"queryParams": {
SchemaProps: spec.SchemaProps{
Description: "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
"datasourceUid": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"headers": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
},
Required: []string{"method", "url", "datasourceUid"},
},
},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardIntervalVariableKind(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -4337,6 +4618,24 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardTransformationKind(ref common.R
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1ActionStyle(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"backgroundColor": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_dashboard_v2alpha1_DashboardV2alpha1FieldConfigSourceOverrides(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1,3 +1,4 @@
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAction,Variables
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdHocFilterWithLabels,ValueLabels
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdHocFilterWithLabels,Values
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardAdhocVariableSpec,BaseFilters
@@ -9,11 +10,16 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardCustomVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardDashboardLink,Tags
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardDatasourceVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFetchOptions,Headers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFetchOptions,QueryParams
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFieldConfig,Actions
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFieldConfig,Links
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFieldConfig,Mappings
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardFieldConfigSource,Overrides
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGridLayoutSpec,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardGroupByVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardInfinityOptions,Headers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardInfinityOptions,QueryParams
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardIntervalVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardMetadata,Finalizers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardPanelSpec,Links
@@ -223,6 +223,9 @@ FieldConfig: {
// The behavior when clicking on a result
links?: [...]
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...Action]
// Alternative to empty string
noValue?: string
@@ -366,6 +369,48 @@ FieldColor: {
// Dashboard Link type. Accepted values are dashboards (to refer to another dashboard) and link (to refer to an external resource)
DashboardLinkType: "link" | "dashboards"
ActionType: "fetch" | "infinity"
FetchOptions: {
method: HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
}
InfinityOptions: FetchOptions & {
datasourceUid: string
}
HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH"
// Action variable type
ActionVariableType: "string"
ActionVariable: {
key: string
name: string
type: ActionVariableType
}
Action: {
type: ActionType
title: string
fetch?: FetchOptions
infinity?: InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...ActionVariable]
style?: {
backgroundColor?: string
}
}
// --- Common types ---
Kind: {
kind: string
@@ -386,6 +386,8 @@ type DashboardFieldConfig struct {
Color *DashboardFieldColor `json:"color,omitempty"`
// The behavior when clicking on a result
Links []interface{} `json:"links,omitempty"`
// Define interactive HTTP requests that can be triggered from data visualizations.
Actions []DashboardAction `json:"actions,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// custom is specified by the FieldConfig field
@@ -617,6 +619,95 @@ const (
DashboardFieldColorSeriesByModeLast DashboardFieldColorSeriesByMode = "last"
)
// +k8s:openapi-gen=true
type DashboardAction struct {
Type DashboardActionType `json:"type"`
Title string `json:"title"`
Fetch *DashboardFetchOptions `json:"fetch,omitempty"`
Infinity *DashboardInfinityOptions `json:"infinity,omitempty"`
Confirmation *string `json:"confirmation,omitempty"`
OneClick *bool `json:"oneClick,omitempty"`
Variables []DashboardActionVariable `json:"variables,omitempty"`
Style *DashboardV2beta1ActionStyle `json:"style,omitempty"`
}
// NewDashboardAction creates a new DashboardAction object.
func NewDashboardAction() *DashboardAction {
return &DashboardAction{}
}
// +k8s:openapi-gen=true
type DashboardActionType string
const (
DashboardActionTypeFetch DashboardActionType = "fetch"
DashboardActionTypeInfinity DashboardActionType = "infinity"
)
// +k8s:openapi-gen=true
type DashboardFetchOptions struct {
Method DashboardHttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
Headers [][]string `json:"headers,omitempty"`
}
// NewDashboardFetchOptions creates a new DashboardFetchOptions object.
func NewDashboardFetchOptions() *DashboardFetchOptions {
return &DashboardFetchOptions{}
}
// +k8s:openapi-gen=true
type DashboardHttpRequestMethod string
const (
DashboardHttpRequestMethodGET DashboardHttpRequestMethod = "GET"
DashboardHttpRequestMethodPUT DashboardHttpRequestMethod = "PUT"
DashboardHttpRequestMethodPOST DashboardHttpRequestMethod = "POST"
DashboardHttpRequestMethodDELETE DashboardHttpRequestMethod = "DELETE"
DashboardHttpRequestMethodPATCH DashboardHttpRequestMethod = "PATCH"
)
// +k8s:openapi-gen=true
type DashboardInfinityOptions struct {
Method DashboardHttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
DatasourceUid string `json:"datasourceUid"`
Headers [][]string `json:"headers,omitempty"`
}
// NewDashboardInfinityOptions creates a new DashboardInfinityOptions object.
func NewDashboardInfinityOptions() *DashboardInfinityOptions {
return &DashboardInfinityOptions{}
}
// +k8s:openapi-gen=true
type DashboardActionVariable struct {
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
}
// NewDashboardActionVariable creates a new DashboardActionVariable object.
func NewDashboardActionVariable() *DashboardActionVariable {
return &DashboardActionVariable{
Type: DashboardActionVariableType,
}
}
// Action variable type
// +k8s:openapi-gen=true
const DashboardActionVariableType = "string"
// +k8s:openapi-gen=true
type DashboardDynamicConfigValue struct {
Id string `json:"id"`
@@ -1853,6 +1944,16 @@ func NewDashboardV2beta1SpecialValueMapOptions() *DashboardV2beta1SpecialValueMa
}
}
// +k8s:openapi-gen=true
type DashboardV2beta1ActionStyle struct {
BackgroundColor *string `json:"backgroundColor,omitempty"`
}
// NewDashboardV2beta1ActionStyle creates a new DashboardV2beta1ActionStyle object.
func NewDashboardV2beta1ActionStyle() *DashboardV2beta1ActionStyle {
return &DashboardV2beta1ActionStyle{}
}
// +k8s:openapi-gen=true
type DashboardV2beta1GroupByVariableKindDatasource struct {
Name *string `json:"name,omitempty"`
@@ -23,6 +23,9 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList {
}
}
// Custom validation for action query params and headers
validateAndTrimActionArrays(obj)
if err := cuejson.Validate(data, getCueSchema()); err != nil {
errs := field.ErrorList{}
@@ -60,6 +63,61 @@ func ValidateDashboardSpec(obj *Dashboard) field.ErrorList {
return nil
}
// Validates and trims action query params and headers to exactly 2 elements each
// This is because we couldn't generate with cue a go struct that would have exactly two strings in each sub-array
func validateAndTrimActionArrays(obj *Dashboard) {
for _, element := range obj.Spec.Elements {
if element.PanelKind != nil {
panelElement := element.PanelKind
if panelElement.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions != nil {
processActions(panelElement.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions)
}
}
}
}
// Helper function to process action arrays
func processActions(actions []DashboardAction) {
for _, action := range actions {
// Process FetchOptions if present
if action.Fetch != nil {
if action.Fetch.QueryParams != nil {
action.Fetch.QueryParams = trimStringArrays(action.Fetch.QueryParams)
}
if action.Fetch.Headers != nil {
action.Fetch.Headers = trimStringArrays(action.Fetch.Headers)
}
}
// Process InfinityOptions if present
if action.Infinity != nil {
if action.Infinity.QueryParams != nil {
action.Infinity.QueryParams = trimStringArrays(action.Infinity.QueryParams)
}
if action.Infinity.Headers != nil {
action.Infinity.Headers = trimStringArrays(action.Infinity.Headers)
}
}
}
}
// Helper function to trim 2D string arrays to exactly 2 elements per sub-array
func trimStringArrays(arrays [][]string) [][]string {
if arrays == nil {
return arrays
}
result := make([][]string, len(arrays))
for i, arr := range arrays {
if len(arr) > 2 {
result[i] = arr[:2]
} else {
result[i] = arr
}
}
return result
}
func formatErrorPath(path []string) string {
return strings.Join(path, ".")
}
@@ -0,0 +1,495 @@
package v2beta1
import (
"testing"
"github.com/stretchr/testify/assert"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func TestTrimStringArrays(t *testing.T) {
tests := []struct {
name string
input [][]string
expected [][]string
}{
{
name: "nil input",
input: nil,
expected: nil,
},
{
name: "empty input",
input: [][]string{},
expected: [][]string{},
},
{
name: "arrays with exactly 2 elements",
input: [][]string{{"key1", "value1"}, {"key2", "value2"}},
expected: [][]string{{"key1", "value1"}, {"key2", "value2"}},
},
{
name: "arrays with less than 2 elements",
input: [][]string{{"key1"}, {}},
expected: [][]string{{"key1"}, {}},
},
{
name: "arrays with more than 2 elements",
input: [][]string{{"key1", "value1", "extra1"}, {"key2", "value2", "extra2", "extra3"}},
expected: [][]string{{"key1", "value1"}, {"key2", "value2"}},
},
{
name: "mixed arrays",
input: [][]string{{"key1"}, {"key2", "value2"}, {"key3", "value3", "extra"}},
expected: [][]string{{"key1"}, {"key2", "value2"}, {"key3", "value3"}},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
result := trimStringArrays(tt.input)
assert.Equal(t, tt.expected, result)
})
}
}
func TestProcessActions(t *testing.T) {
tests := []struct {
name string
actions []DashboardAction
expected []DashboardAction
}{
{
name: "empty actions",
actions: []DashboardAction{},
expected: []DashboardAction{},
},
{
name: "action with fetch options having oversized arrays",
actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Fetch",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
{"header2", "value2"},
},
},
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Fetch",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
{"header2", "value2"},
},
},
},
},
},
{
name: "action with infinity options having oversized arrays",
actions: []DashboardAction{
{
Type: DashboardActionTypeInfinity,
Title: "Test Infinity",
Infinity: &DashboardInfinityOptions{
Method: DashboardHttpRequestMethodPOST,
Url: "http://example.com",
DatasourceUid: "test-uid",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
{"header2", "value2"},
},
},
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeInfinity,
Title: "Test Infinity",
Infinity: &DashboardInfinityOptions{
Method: DashboardHttpRequestMethodPOST,
Url: "http://example.com",
DatasourceUid: "test-uid",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
{"header2", "value2"},
},
},
},
},
},
{
name: "action without fetch or infinity options",
actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
},
},
expected: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a copy to avoid modifying the original
actionsCopy := make([]DashboardAction, len(tt.actions))
for i, action := range tt.actions {
actionsCopy[i] = action
// Deep copy the fetch options if they exist
if action.Fetch != nil {
fetchCopy := *action.Fetch
if action.Fetch.QueryParams != nil {
fetchCopy.QueryParams = make([][]string, len(action.Fetch.QueryParams))
for j, param := range action.Fetch.QueryParams {
fetchCopy.QueryParams[j] = make([]string, len(param))
copy(fetchCopy.QueryParams[j], param)
}
}
if action.Fetch.Headers != nil {
fetchCopy.Headers = make([][]string, len(action.Fetch.Headers))
for j, header := range action.Fetch.Headers {
fetchCopy.Headers[j] = make([]string, len(header))
copy(fetchCopy.Headers[j], header)
}
}
actionsCopy[i].Fetch = &fetchCopy
}
// Deep copy the infinity options if they exist
if action.Infinity != nil {
infinityCopy := *action.Infinity
if action.Infinity.QueryParams != nil {
infinityCopy.QueryParams = make([][]string, len(action.Infinity.QueryParams))
for j, param := range action.Infinity.QueryParams {
infinityCopy.QueryParams[j] = make([]string, len(param))
copy(infinityCopy.QueryParams[j], param)
}
}
if action.Infinity.Headers != nil {
infinityCopy.Headers = make([][]string, len(action.Infinity.Headers))
for j, header := range action.Infinity.Headers {
infinityCopy.Headers[j] = make([]string, len(header))
copy(infinityCopy.Headers[j], header)
}
}
actionsCopy[i].Infinity = &infinityCopy
}
}
processActions(actionsCopy)
assert.Equal(t, tt.expected, actionsCopy)
})
}
}
func TestValidateAndTrimActionArrays(t *testing.T) {
tests := []struct {
name string
dashboard *Dashboard
expected *Dashboard
}{
{
name: "dashboard with no elements",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{},
},
},
},
{
name: "dashboard with panel having actions with oversized arrays",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1", "extra1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1", "extra1", "extra2"},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: []DashboardAction{
{
Type: DashboardActionTypeFetch,
Title: "Test Action",
Fetch: &DashboardFetchOptions{
Method: DashboardHttpRequestMethodGET,
Url: "http://example.com",
QueryParams: [][]string{
{"param1", "value1"},
{"param2", "value2"},
},
Headers: [][]string{
{"header1", "value1"},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
},
{
name: "dashboard with panel having no actions",
dashboard: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: nil,
},
},
},
},
},
},
},
},
},
},
expected: &Dashboard{
TypeMeta: metav1.TypeMeta{
Kind: "Dashboard",
APIVersion: "v2beta1",
},
ObjectMeta: metav1.ObjectMeta{
Name: "test-dashboard",
},
Spec: DashboardSpec{
Elements: map[string]DashboardElement{
"panel1": {
PanelKind: &DashboardPanelKind{
Spec: DashboardPanelSpec{
VizConfig: DashboardVizConfigKind{
Spec: DashboardVizConfigSpec{
FieldConfig: DashboardFieldConfigSource{
Defaults: DashboardFieldConfig{
Actions: nil,
},
},
},
},
},
},
},
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a deep copy to avoid modifying the original
dashboardCopy := deepCopyDashboard(tt.dashboard)
validateAndTrimActionArrays(dashboardCopy)
assert.Equal(t, tt.expected, dashboardCopy)
})
}
}
// Helper function to create a deep copy of a Dashboard for testing
func deepCopyDashboard(original *Dashboard) *Dashboard {
if original == nil {
return nil
}
result := &Dashboard{
TypeMeta: original.TypeMeta,
ObjectMeta: original.ObjectMeta,
Spec: DashboardSpec{
Elements: make(map[string]DashboardElement),
},
Status: original.Status,
}
for key, element := range original.Spec.Elements {
elementCopy := element
if element.PanelKind != nil {
panelCopy := *element.PanelKind
elementCopy.PanelKind = &panelCopy
if element.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions != nil {
actions := element.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions
actionsCopy := make([]DashboardAction, len(actions))
for j, action := range actions {
actionsCopy[j] = action
// Deep copy fetch options
if action.Fetch != nil {
fetchCopy := *action.Fetch
if action.Fetch.QueryParams != nil {
fetchCopy.QueryParams = make([][]string, len(action.Fetch.QueryParams))
for k, param := range action.Fetch.QueryParams {
fetchCopy.QueryParams[k] = make([]string, len(param))
copy(fetchCopy.QueryParams[k], param)
}
}
if action.Fetch.Headers != nil {
fetchCopy.Headers = make([][]string, len(action.Fetch.Headers))
for k, header := range action.Fetch.Headers {
fetchCopy.Headers[k] = make([]string, len(header))
copy(fetchCopy.Headers[k], header)
}
}
actionsCopy[j].Fetch = &fetchCopy
}
// Deep copy infinity options
if action.Infinity != nil {
infinityCopy := *action.Infinity
if action.Infinity.QueryParams != nil {
infinityCopy.QueryParams = make([][]string, len(action.Infinity.QueryParams))
for k, param := range action.Infinity.QueryParams {
infinityCopy.QueryParams[k] = make([]string, len(param))
copy(infinityCopy.QueryParams[k], param)
}
}
if action.Infinity.Headers != nil {
infinityCopy.Headers = make([][]string, len(action.Infinity.Headers))
for k, header := range action.Infinity.Headers {
infinityCopy.Headers[k] = make([]string, len(header))
copy(infinityCopy.Headers[k], header)
}
}
actionsCopy[j].Infinity = &infinityCopy
}
}
elementCopy.PanelKind.Spec.VizConfig.Spec.FieldConfig.Defaults.Actions = actionsCopy
}
}
if element.LibraryPanelKind != nil {
libraryCopy := *element.LibraryPanelKind
elementCopy.LibraryPanelKind = &libraryCopy
}
result.Spec.Elements[key] = elementCopy
}
return result
}
@@ -18,6 +18,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.AnnotationPermission": schema_pkg_apis_dashboard_v2beta1_AnnotationPermission(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.Dashboard": schema_pkg_apis_dashboard_v2beta1_Dashboard(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAccess": schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAction": schema_pkg_apis_dashboard_v2beta1_DashboardAction(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardActionVariable": schema_pkg_apis_dashboard_v2beta1_DashboardActionVariable(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAdHocFilterWithLabels": schema_pkg_apis_dashboard_v2beta1_DashboardAdHocFilterWithLabels(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAdhocVariableKind": schema_pkg_apis_dashboard_v2beta1_DashboardAdhocVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAdhocVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardAdhocVariableSpec(ref),
@@ -51,6 +53,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardDatasourceVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardDatasourceVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardDynamicConfigValue": schema_pkg_apis_dashboard_v2beta1_DashboardDynamicConfigValue(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardElementReference": schema_pkg_apis_dashboard_v2beta1_DashboardElementReference(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFetchOptions": schema_pkg_apis_dashboard_v2beta1_DashboardFetchOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFieldColor": schema_pkg_apis_dashboard_v2beta1_DashboardFieldColor(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFieldConfig": schema_pkg_apis_dashboard_v2beta1_DashboardFieldConfig(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFieldConfigSource": schema_pkg_apis_dashboard_v2beta1_DashboardFieldConfigSource(ref),
@@ -62,6 +65,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardGridLayoutSpec": schema_pkg_apis_dashboard_v2beta1_DashboardGridLayoutSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardGroupByVariableKind": schema_pkg_apis_dashboard_v2beta1_DashboardGroupByVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardGroupByVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardGroupByVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardInfinityOptions": schema_pkg_apis_dashboard_v2beta1_DashboardInfinityOptions(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardIntervalVariableKind": schema_pkg_apis_dashboard_v2beta1_DashboardIntervalVariableKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardIntervalVariableSpec": schema_pkg_apis_dashboard_v2beta1_DashboardIntervalVariableSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardJSONCodec": schema_pkg_apis_dashboard_v2beta1_DashboardJSONCodec(ref),
@@ -108,6 +112,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardTimeRangeOption": schema_pkg_apis_dashboard_v2beta1_DashboardTimeRangeOption(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardTimeSettingsSpec": schema_pkg_apis_dashboard_v2beta1_DashboardTimeSettingsSpec(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardTransformationKind": schema_pkg_apis_dashboard_v2beta1_DashboardTransformationKind(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1ActionStyle": schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1ActionStyle(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1AdhocVariableKindDatasource": schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1AdhocVariableKindDatasource(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1DataQueryKindDatasource": schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1DataQueryKindDatasource(ref),
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1FieldConfigSourceOverrides": schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1FieldConfigSourceOverrides(ref),
@@ -311,6 +316,109 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardAccess(ref common.ReferenceCallb
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardAction(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"title": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"fetch": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFetchOptions"),
},
},
"infinity": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardInfinityOptions"),
},
},
"confirmation": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"oneClick": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
"variables": {
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/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardActionVariable"),
},
},
},
},
},
"style": {
SchemaProps: spec.SchemaProps{
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1ActionStyle"),
},
},
},
Required: []string{"type", "title"},
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardActionVariable", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFetchOptions", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardInfinityOptions", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardV2beta1ActionStyle"},
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardActionVariable(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"key": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"type": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"key", "name", "type"},
},
},
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardAdHocFilterWithLabels(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1836,6 +1944,82 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardElementReference(ref common.Refe
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardFetchOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"method": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"body": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"queryParams": {
SchemaProps: spec.SchemaProps{
Description: "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
"headers": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
},
Required: []string{"method", "url"},
},
},
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardFieldColor(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1988,6 +2172,20 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardFieldConfig(ref common.Reference
},
},
},
"actions": {
SchemaProps: spec.SchemaProps{
Description: "Define interactive HTTP requests that can be triggered from data visualizations.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAction"),
},
},
},
},
},
"noValue": {
SchemaProps: spec.SchemaProps{
Description: "Alternative to empty string",
@@ -2014,7 +2212,7 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardFieldConfig(ref common.Reference
},
},
Dependencies: []string{
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFieldColor", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardThresholdsConfig", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap"},
"github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardAction", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardFieldColor", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardThresholdsConfig", "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1.DashboardValueMapOrRangeMapOrRegexMapOrSpecialValueMap"},
}
}
@@ -2389,6 +2587,89 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardGroupByVariableSpec(ref common.R
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardInfinityOptions(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"method": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"url": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"body": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
"queryParams": {
SchemaProps: spec.SchemaProps{
Description: "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
"datasourceUid": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"headers": {
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
},
},
},
},
},
},
Required: []string{"method", "url", "datasourceUid"},
},
},
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardIntervalVariableKind(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -4389,6 +4670,24 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardTransformationKind(ref common.Re
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1ActionStyle(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Type: []string{"object"},
Properties: map[string]spec.Schema{
"backgroundColor": {
SchemaProps: spec.SchemaProps{
Type: []string{"string"},
Format: "",
},
},
},
},
},
}
}
func schema_pkg_apis_dashboard_v2beta1_DashboardV2beta1AdhocVariableKindDatasource(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
@@ -1,3 +1,4 @@
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardAction,Variables
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardAdHocFilterWithLabels,ValueLabels
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardAdHocFilterWithLabels,Values
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardAdhocVariableSpec,BaseFilters
@@ -9,11 +10,16 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardCustomVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardDashboardLink,Tags
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardDatasourceVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFetchOptions,Headers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFetchOptions,QueryParams
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFieldConfig,Actions
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFieldConfig,Links
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFieldConfig,Mappings
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardFieldConfigSource,Overrides
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardGridLayoutSpec,Items
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardGroupByVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardInfinityOptions,Headers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardInfinityOptions,QueryParams
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardIntervalVariableSpec,Options
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardMetadata,Finalizers
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2beta1,DashboardPanelSpec,Links
+56
View File
@@ -298,6 +298,59 @@ lineage: schemas: [{
// - "inControlsMenu" renders the link in bottom part of the dashboard controls dropdown menu
#DashboardLinkPlacement: "inControlsMenu" @cuetsy(kind="type")
// Dashboard action type
#ActionType: "fetch" | "infinity" @cuetsy(kind="type")
// Fetch options
#FetchOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
} @cuetsy(kind="interface")
// Infinity options
#InfinityOptions: {
method: #HttpRequestMethod
url: string
body?: string
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: [...[...string]]
headers?: [...[...string]]
datasourceUid: string
} @cuetsy(kind="interface")
#HttpRequestMethod: "GET" | "PUT" | "POST" | "DELETE" | "PATCH" @cuetsy(kind="type")
// Action variable type
#ActionVariableType: "string" @cuetsy(kind="type")
#ActionVariable: {
key: string
name: string
type: #ActionVariableType
} @cuetsy(kind="interface")
// Dashboard action
#Action: {
type: #ActionType
title: string
fetch?: #FetchOptions
infinity?: #InfinityOptions
confirmation?: string
oneClick?: bool
variables?: [...#ActionVariable]
style?: {
backgroundColor?: string
}
} @cuetsy(kind="interface")
// Dashboard variable type
// `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
// `adhoc`: Key/value filters that are automatically added to all metric queries for a data source (Prometheus, Loki, InfluxDB, and Elasticsearch only).
@@ -727,6 +780,9 @@ lineage: schemas: [{
// The behavior when clicking on a result
links?: [...] @grafanamaturity(NeedsExpertReview)
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: [...#Action] @grafanamaturity(NeedsExpertReview)
// Alternative to empty string
noValue?: string @grafanamaturity(NeedsExpertReview)
+10
View File
@@ -15,6 +15,13 @@ export type {
DashboardLink,
DashboardLinkType,
DashboardLinkPlacement,
ActionType,
FetchOptions,
InfinityOptions,
HttpRequestMethod,
ActionVariableType,
ActionVariable,
Action,
VariableType,
FieldColorSeriesByMode,
FieldColor,
@@ -37,6 +44,9 @@ export {
VariableRefresh,
VariableSort,
defaultDashboardLink,
defaultFetchOptions,
defaultInfinityOptions,
defaultAction,
FieldColorModeId,
defaultGridPos,
ThresholdsMode,
@@ -363,6 +363,87 @@ export type DashboardLinkType = ('link' | 'dashboards');
*/
export type DashboardLinkPlacement = 'inControlsMenu';
/**
* Dashboard action type
*/
export type ActionType = ('fetch' | 'infinity');
/**
* Fetch options
*/
export interface FetchOptions {
body?: string;
headers?: Array<Array<string>>;
method: HttpRequestMethod;
/**
* These are 2D arrays of strings, each representing a key-value pair
* We are defining this way because we can't generate a go struct that
* that would have exactly two strings in each sub-array
*/
queryParams?: Array<Array<string>>;
url: string;
}
export const defaultFetchOptions: Partial<FetchOptions> = {
headers: [],
queryParams: [],
};
/**
* Infinity options
*/
export interface InfinityOptions {
body?: string;
datasourceUid: string;
headers?: Array<Array<string>>;
method: HttpRequestMethod;
/**
* These are 2D arrays of strings, each representing a key-value pair
* We are defining them this way because we can't generate a go struct that
* that would have exactly two strings in each sub-array
*/
queryParams?: Array<Array<string>>;
url: string;
}
export const defaultInfinityOptions: Partial<InfinityOptions> = {
headers: [],
queryParams: [],
};
export type HttpRequestMethod = ('GET' | 'PUT' | 'POST' | 'DELETE' | 'PATCH');
/**
* Action variable type
*/
export type ActionVariableType = 'string';
export interface ActionVariable {
key: string;
name: string;
type: ActionVariableType;
}
/**
* Dashboard action
*/
export interface Action {
confirmation?: string;
fetch?: FetchOptions;
infinity?: InfinityOptions;
oneClick?: boolean;
style?: {
backgroundColor?: string;
};
title: string;
type: ActionType;
variables?: Array<ActionVariable>;
}
export const defaultAction: Partial<Action> = {
variables: [],
};
/**
* Dashboard variable type
* `query`: Query-generated list of values such as metric names, server names, sensor IDs, data centers, and so on.
@@ -916,6 +997,10 @@ export const defaultMatcherConfig: Partial<MatcherConfig> = {
* Field options allow you to change how the data is displayed in your visualizations.
*/
export interface FieldConfig {
/**
* Define interactive HTTP requests that can be triggered from data visualizations.
*/
actions?: Array<Action>;
/**
* Panel color configuration
*/
@@ -1001,6 +1086,7 @@ export interface FieldConfig {
}
export const defaultFieldConfig: Partial<FieldConfig> = {
actions: [],
links: [],
mappings: [],
};
@@ -318,6 +318,8 @@ export interface FieldConfig {
color?: FieldColor;
// The behavior when clicking on a result
links?: any[];
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: Action[];
// Alternative to empty string
noValue?: string;
// custom is specified by the FieldConfig field
@@ -505,6 +507,81 @@ export type FieldColorSeriesByMode = "min" | "max" | "last";
export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min");
export interface Action {
type: ActionType;
title: string;
fetch?: FetchOptions;
infinity?: InfinityOptions;
confirmation?: string;
oneClick?: boolean;
variables?: ActionVariable[];
style?: {
backgroundColor?: string;
};
}
export const defaultAction = (): Action => ({
type: "fetch",
title: "",
});
export type ActionType = "fetch" | "infinity";
export const defaultActionType = (): ActionType => ("fetch");
export interface FetchOptions {
method: HttpRequestMethod;
url: string;
body?: string;
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: string[][];
headers?: string[][];
}
export const defaultFetchOptions = (): FetchOptions => ({
method: "GET",
url: "",
});
export type HttpRequestMethod = "GET" | "PUT" | "POST" | "DELETE" | "PATCH";
export const defaultHttpRequestMethod = (): HttpRequestMethod => ("GET");
export interface InfinityOptions {
method: HttpRequestMethod;
url: string;
body?: string;
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: string[][];
datasourceUid: string;
headers?: string[][];
}
export const defaultInfinityOptions = (): InfinityOptions => ({
method: "GET",
url: "",
datasourceUid: "",
});
export interface ActionVariable {
key: string;
name: string;
type: "string";
}
export const defaultActionVariable = (): ActionVariable => ({
key: "",
name: "",
type: ActionVariableType,
});
// Action variable type
export const ActionVariableType = "string";
export interface DynamicConfigValue {
id: string;
value?: any;
@@ -317,6 +317,8 @@ export interface FieldConfig {
color?: FieldColor;
// The behavior when clicking on a result
links?: any[];
// Define interactive HTTP requests that can be triggered from data visualizations.
actions?: Action[];
// Alternative to empty string
noValue?: string;
// custom is specified by the FieldConfig field
@@ -504,6 +506,81 @@ export type FieldColorSeriesByMode = "min" | "max" | "last";
export const defaultFieldColorSeriesByMode = (): FieldColorSeriesByMode => ("min");
export interface Action {
type: ActionType;
title: string;
fetch?: FetchOptions;
infinity?: InfinityOptions;
confirmation?: string;
oneClick?: boolean;
variables?: ActionVariable[];
style?: {
backgroundColor?: string;
};
}
export const defaultAction = (): Action => ({
type: "fetch",
title: "",
});
export type ActionType = "fetch" | "infinity";
export const defaultActionType = (): ActionType => ("fetch");
export interface FetchOptions {
method: HttpRequestMethod;
url: string;
body?: string;
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: string[][];
headers?: string[][];
}
export const defaultFetchOptions = (): FetchOptions => ({
method: "GET",
url: "",
});
export type HttpRequestMethod = "GET" | "PUT" | "POST" | "DELETE" | "PATCH";
export const defaultHttpRequestMethod = (): HttpRequestMethod => ("GET");
export interface InfinityOptions {
method: HttpRequestMethod;
url: string;
body?: string;
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
queryParams?: string[][];
datasourceUid: string;
headers?: string[][];
}
export const defaultInfinityOptions = (): InfinityOptions => ({
method: "GET",
url: "",
datasourceUid: "",
});
export interface ActionVariable {
key: string;
name: string;
type: "string";
}
export const defaultActionVariable = (): ActionVariable => ({
key: "",
name: "",
type: ActionVariableType,
});
// Action variable type
export const ActionVariableType = "string";
export interface DynamicConfigValue {
id: string;
value?: any;
+97
View File
@@ -427,6 +427,8 @@ type FieldConfig struct {
Color *FieldColor `json:"color,omitempty"`
// The behavior when clicking on a result
Links []any `json:"links,omitempty"`
// Define interactive HTTP requests that can be triggered from data visualizations.
Actions []Action `json:"actions,omitempty"`
// Alternative to empty string
NoValue *string `json:"noValue,omitempty"`
// custom is specified by the FieldConfig field
@@ -654,6 +656,92 @@ const (
FieldColorSeriesByModeLast FieldColorSeriesByMode = "last"
)
// Dashboard action
type Action struct {
Type ActionType `json:"type"`
Title string `json:"title"`
Fetch *FetchOptions `json:"fetch,omitempty"`
Infinity *InfinityOptions `json:"infinity,omitempty"`
Confirmation *string `json:"confirmation,omitempty"`
OneClick *bool `json:"oneClick,omitempty"`
Variables []ActionVariable `json:"variables,omitempty"`
Style *DashboardActionStyle `json:"style,omitempty"`
}
// NewAction creates a new Action object.
func NewAction() *Action {
return &Action{}
}
// Dashboard action type
type ActionType string
const (
ActionTypeFetch ActionType = "fetch"
ActionTypeInfinity ActionType = "infinity"
)
// Fetch options
type FetchOptions struct {
Method HttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
Headers [][]string `json:"headers,omitempty"`
}
// NewFetchOptions creates a new FetchOptions object.
func NewFetchOptions() *FetchOptions {
return &FetchOptions{}
}
type HttpRequestMethod string
const (
HttpRequestMethodGET HttpRequestMethod = "GET"
HttpRequestMethodPUT HttpRequestMethod = "PUT"
HttpRequestMethodPOST HttpRequestMethod = "POST"
HttpRequestMethodDELETE HttpRequestMethod = "DELETE"
HttpRequestMethodPATCH HttpRequestMethod = "PATCH"
)
// Infinity options
type InfinityOptions struct {
Method HttpRequestMethod `json:"method"`
Url string `json:"url"`
Body *string `json:"body,omitempty"`
// These are 2D arrays of strings, each representing a key-value pair
// We are defining them this way because we can't generate a go struct that
// that would have exactly two strings in each sub-array
QueryParams [][]string `json:"queryParams,omitempty"`
Headers [][]string `json:"headers,omitempty"`
DatasourceUid string `json:"datasourceUid"`
}
// NewInfinityOptions creates a new InfinityOptions object.
func NewInfinityOptions() *InfinityOptions {
return &InfinityOptions{}
}
type ActionVariable struct {
Key string `json:"key"`
Name string `json:"name"`
Type string `json:"type"`
}
// NewActionVariable creates a new ActionVariable object.
func NewActionVariable() *ActionVariable {
return &ActionVariable{
Type: ActionVariableType,
}
}
// Action variable type
const ActionVariableType = "string"
type DynamicConfigValue struct {
Id string `json:"id"`
Value any `json:"value,omitempty"`
@@ -1042,6 +1130,15 @@ func NewDashboardSpecialValueMapOptions() *DashboardSpecialValueMapOptions {
}
}
type DashboardActionStyle struct {
BackgroundColor *string `json:"backgroundColor,omitempty"`
}
// NewDashboardActionStyle creates a new DashboardActionStyle object.
func NewDashboardActionStyle() *DashboardActionStyle {
return &DashboardActionStyle{}
}
type PanelRepeatDirection string
const (
@@ -1060,6 +1060,71 @@
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAction": {
"type": "object",
"required": [
"type",
"title"
],
"properties": {
"confirmation": {
"type": "string"
},
"fetch": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFetchOptions"
},
"infinity": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardInfinityOptions"
},
"oneClick": {
"type": "boolean"
},
"style": {
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1ActionStyle"
},
"title": {
"type": "string",
"default": ""
},
"type": {
"type": "string",
"default": ""
},
"variables": {
"type": "array",
"items": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariable"
}
]
}
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardActionVariable": {
"type": "object",
"required": [
"key",
"name",
"type"
],
"properties": {
"key": {
"type": "string",
"default": ""
},
"name": {
"type": "string",
"default": ""
},
"type": {
"type": "string",
"default": ""
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAdHocFilterWithLabels": {
"description": "Define the AdHocFilterWithLabels type",
"type": "object",
@@ -2062,6 +2127,47 @@
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFetchOptions": {
"type": "object",
"required": [
"method",
"url"
],
"properties": {
"body": {
"type": "string"
},
"headers": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
}
},
"method": {
"type": "string",
"default": ""
},
"queryParams": {
"description": "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
}
},
"url": {
"type": "string",
"default": ""
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardFieldColor": {
"description": "Map a field to a color.",
"type": "object",
@@ -2088,6 +2194,18 @@
"description": "The data model used in Grafana, namely the data frame, is a columnar-oriented table structure that unifies both time series and table query results. Each column within this structure is called a field. A field can represent a single time series or table column. Field options allow you to change how the data is displayed in your visualizations.",
"type": "object",
"properties": {
"actions": {
"description": "Define interactive HTTP requests that can be triggered from data visualizations.",
"type": "array",
"items": {
"default": {},
"allOf": [
{
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardAction"
}
]
}
},
"color": {
"description": "Panel color configuration",
"allOf": [
@@ -2427,6 +2545,52 @@
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardInfinityOptions": {
"type": "object",
"required": [
"method",
"url",
"datasourceUid"
],
"properties": {
"body": {
"type": "string"
},
"datasourceUid": {
"type": "string",
"default": ""
},
"headers": {
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
}
},
"method": {
"type": "string",
"default": ""
},
"queryParams": {
"description": "These are 2D arrays of strings, each representing a key-value pair We are defining them this way because we can't generate a go struct that that would have exactly two strings in each sub-array",
"type": "array",
"items": {
"type": "array",
"items": {
"type": "string",
"default": ""
}
}
},
"url": {
"type": "string",
"default": ""
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardIntervalVariableKind": {
"description": "Interval variable kind",
"type": "object",
@@ -3735,6 +3899,14 @@
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1ActionStyle": {
"type": "object",
"properties": {
"backgroundColor": {
"type": "string"
}
}
},
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardV2alpha1FieldConfigSourceOverrides": {
"type": "object",
"required": [
@@ -21,33 +21,33 @@ describe('transformToV2TypesUtils', () => {
expect(transformCursorSynctoEnum(undefined)).toBe(defaultDashboardCursorSync());
});
});
});
describe('transformVariableRefreshToEnum', () => {
it('should return the correct enum value for variable refresh', () => {
expect(transformVariableRefreshToEnum(0)).toBe('never');
expect(transformVariableRefreshToEnum(1)).toBe('onDashboardLoad');
expect(transformVariableRefreshToEnum(2)).toBe('onTimeRangeChanged');
expect(transformVariableRefreshToEnum(undefined)).toBe(defaultVariableRefresh());
describe('transformVariableRefreshToEnum', () => {
it('should return the correct enum value for variable refresh', () => {
expect(transformVariableRefreshToEnum(0)).toBe('never');
expect(transformVariableRefreshToEnum(1)).toBe('onDashboardLoad');
expect(transformVariableRefreshToEnum(2)).toBe('onTimeRangeChanged');
expect(transformVariableRefreshToEnum(undefined)).toBe(defaultVariableRefresh());
});
});
});
describe('transformVariableHideToEnum', () => {
it('should return the correct enum value for variable hide', () => {
expect(transformVariableHideToEnum(0)).toBe('dontHide');
expect(transformVariableHideToEnum(1)).toBe('hideLabel');
expect(transformVariableHideToEnum(2)).toBe('hideVariable');
expect(transformVariableHideToEnum(undefined)).toBe(defaultVariableHide());
describe('transformVariableHideToEnum', () => {
it('should return the correct enum value for variable hide', () => {
expect(transformVariableHideToEnum(0)).toBe('dontHide');
expect(transformVariableHideToEnum(1)).toBe('hideLabel');
expect(transformVariableHideToEnum(2)).toBe('hideVariable');
expect(transformVariableHideToEnum(undefined)).toBe(defaultVariableHide());
});
});
});
describe('transformSortVariableToEnum', () => {
it('should return the correct enum value for variable sort', () => {
expect(transformSortVariableToEnum(0)).toBe('disabled');
expect(transformSortVariableToEnum(1)).toBe('alphabeticalAsc');
expect(transformSortVariableToEnum(2)).toBe('alphabeticalDesc');
expect(transformSortVariableToEnum(3)).toBe('numericalAsc');
expect(transformSortVariableToEnum(4)).toBe('numericalDesc');
expect(transformSortVariableToEnum(undefined)).toBe(defaultVariableSort());
describe('transformSortVariableToEnum', () => {
it('should return the correct enum value for variable sort', () => {
expect(transformSortVariableToEnum(0)).toBe('disabled');
expect(transformSortVariableToEnum(1)).toBe('alphabeticalAsc');
expect(transformSortVariableToEnum(2)).toBe('alphabeticalDesc');
expect(transformSortVariableToEnum(3)).toBe('numericalAsc');
expect(transformSortVariableToEnum(4)).toBe('numericalDesc');
expect(transformSortVariableToEnum(undefined)).toBe(defaultVariableSort());
});
});
});