Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[app] Extend Placeholders for Dashboards #441

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deploy/helm/hub/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform
type: application
home: https://kobs.io
icon: https://kobs.io/assets/images/logo.svg
version: 0.20.0
version: 0.20.1
appVersion: v0.10.0
2 changes: 1 addition & 1 deletion deploy/helm/satellite/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ description: Kubernetes Observability Platform
type: application
home: https://kobs.io
icon: https://kobs.io/assets/images/logo.svg
version: 0.20.0
version: 0.20.1
appVersion: v0.10.0
4 changes: 4 additions & 0 deletions deploy/helm/satellite/crds/kobs.io_dashboards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ spec:
placeholders:
items:
properties:
default:
type: string
description:
type: string
name:
type: string
type:
type: string
required:
- name
type: object
Expand Down
4 changes: 4 additions & 0 deletions deploy/kustomize/crds/kobs.io_dashboards.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,14 @@ spec:
placeholders:
items:
properties:
default:
type: string
description:
type: string
name:
type: string
type:
type: string
required:
- name
type: object
Expand Down
46 changes: 45 additions & 1 deletion docs/resources/dashboards.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Dashboards are defined via the [Dashboard Custom Resource Definition](https://gi
| description | string | Provide a descriptions for the dashboard with additional details. | No |
| hideToolbar | boolean | If this is `true` the toolbar will be hidden in the dashboard. | No |
| placeholders | [[]Placeholder](#placeholder) | A list of placeholders, which can be directly set by the user. | No |
| variables | [[]Variable](#Variable) | A list of variables, where the values are loaded by the specified plugin. | No |
| variables | [[]Variable](#variable) | A list of variables, where the values are loaded by the specified plugin. | No |
| rows | [[]Row](#row) | A list of rows for the dashboard. | Yes |

### Placeholder
Expand All @@ -18,6 +18,8 @@ Dashboards are defined via the [Dashboard Custom Resource Definition](https://gi
| ----- | ---- | ----------- | -------- |
| name | string | The name for the placeholder, which can be used in the dashboard via `{% .<placeholder-name> %}`. | Yes |
| description | string | An optional description, to provide more information how the placeholder is used. | No |
| default | string | A default value for the placeholder, when it is not provided in a dashboard reference. | No |
| type | string | The type of the placeholder value. This could be `string`, `number` or `object`. The default value is `string`. | No |

### Variable

Expand Down Expand Up @@ -235,3 +237,45 @@ spec:
```

![Dashboard - Resource Usage](assets/dashboards-resource-usage.png)

The following example shows how complex types for placeholders can be used. In the example the `grafana-dashboards` dashboard requires a list of dashboards via the `dashboards` placeholder:

```yaml
---
apiVersion: kobs.io/v1
kind: Application
metadata:
name: kobs
namespace: kobs
spec:
dashboards:
- namespace: kobs
name: test
title: Grafana Dashboards
placeholders:
dashboards: |
- "vErzsZIVk"
- "Tf1skG8Mz"
- "iyJszGUMk"

---
apiVersion: kobs.io/v1
kind: Dashboard
metadata:
name: grafana-dashboards
namespace: kobs
spec:
placeholders:
- name: dashboards
type: object
rows:
- size: -1
panels:
- title: Grafana Dashboards
plugin:
name: grafana
type: grafana
options:
type: dashboards
dashboards: '{% .dashboards %}'
```
6 changes: 3 additions & 3 deletions pkg/hub/api/dashboards/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func (router *Router) getDashboardsFromReferences(w http.ResponseWriter, r *http
Title: reference.Title,
Description: reference.Description,
HideToolbar: reference.Inline.HideToolbar,
Variables: addPlaceholdersAsVariables(reference.Inline.Variables, reference.Placeholders),
Variables: addPlaceholdersAsVariables(nil, reference.Inline.Variables, reference.Placeholders),
Rows: reference.Inline.Rows,
})
} else {
Expand All @@ -56,7 +56,7 @@ func (router *Router) getDashboardsFromReferences(w http.ResponseWriter, r *http
}

dashboard.Title = reference.Title
dashboard.Variables = addPlaceholdersAsVariables(dashboard.Variables, reference.Placeholders)
dashboard.Variables = addPlaceholdersAsVariables(dashboard.Placeholders, dashboard.Variables, reference.Placeholders)
dashboards = append(dashboards, dashboard)
}
}
Expand Down Expand Up @@ -84,7 +84,7 @@ func (router *Router) getDashboard(w http.ResponseWriter, r *http.Request) {
return
}

dashboard.Variables = addPlaceholdersAsVariables(dashboard.Variables, placeholders)
dashboard.Variables = addPlaceholdersAsVariables(dashboard.Placeholders, dashboard.Variables, placeholders)
render.JSON(w, r, dashboard)
}

Expand Down
4 changes: 2 additions & 2 deletions pkg/hub/api/dashboards/dashboards_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,12 +77,12 @@ func TestGetDashboard(t *testing.T) {
name: "get dashboards",
url: "/dashboard?id=/satellite/satellite2/cluster/cluster1/namespace/namespace1/name/name1&key1=value1",
expectedStatusCode: http.StatusOK,
expectedBody: "{\"variables\":[{\"name\":\"key1\",\"label\":\"key1\",\"hide\":true,\"plugin\":{\"type\":\"app\",\"name\":\"static\",\"options\":[\"value1\"]}}],\"rows\":null}\n",
expectedBody: "{\"placeholders\":[{\"name\":\"key1\"}],\"variables\":[{\"name\":\"key1\",\"label\":\"key1\",\"hide\":true,\"plugin\":{\"type\":\"app\",\"name\":\"placeholder\",\"options\":{\"type\":\"string\",\"value\":\"value1\"}}}],\"rows\":null}\n",
},
} {
t.Run(tt.name, func(t *testing.T) {
mockStoreClient := &store.MockClient{}
mockStoreClient.On("GetDashboardByID", mock.Anything, "/satellite/satellite2/cluster/cluster1/namespace/namespace1/name/name1").Return(&dashboardv1.DashboardSpec{}, nil)
mockStoreClient.On("GetDashboardByID", mock.Anything, "/satellite/satellite2/cluster/cluster1/namespace/namespace1/name/name1").Return(&dashboardv1.DashboardSpec{Placeholders: []dashboardv1.Placeholder{{Name: "key1"}}}, nil)
mockStoreClient.On("GetDashboardByID", mock.Anything, "/satellite/satellite1/cluster/cluster1/namespace/namespace1/name/name1").Return(nil, fmt.Errorf("could not get dashboard"))

router := Router{chi.NewRouter(), mockStoreClient}
Expand Down
45 changes: 34 additions & 11 deletions pkg/hub/api/dashboards/variables.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package dashboards

import (
"encoding/json"
"fmt"

dashboardv1 "github.com/kobsio/kobs/pkg/kube/apis/dashboard/v1"
Expand All @@ -9,20 +10,42 @@ import (
)

// addPlaceholdersAsVariables adds the placeholders from a reference to the list of variables of the dashboard
func addPlaceholdersAsVariables(variables []dashboardv1.Variable, placeholders map[string]string) []dashboardv1.Variable {
func addPlaceholdersAsVariables(placeholders []dashboardv1.Placeholder, variables []dashboardv1.Variable, placeholderValues map[string]string) []dashboardv1.Variable {
var newVariables []dashboardv1.Variable

for k, v := range placeholders {
newVariables = append(newVariables, dashboardv1.Variable{
Name: k,
Label: k,
Hide: true,
Plugin: dashboardv1.Plugin{
Name: "static",
Type: "app",
Options: &apiextensionsv1.JSON{Raw: []byte(fmt.Sprintf(`["%s"]`, v))},
},
for _, placeholder := range placeholders {
fmt.Println(placeholder.Default, placeholder.Type)

placeholderValue := placeholder.Default
if val, ok := placeholderValues[placeholder.Name]; ok {
placeholderValue = val
}

placeholderType := placeholder.Type
if placeholderType == "" {
placeholderType = "string"
}

options, err := json.Marshal(struct {
Type string `json:"type"`
Value string `json:"value"`
}{
placeholderType,
placeholderValue,
})

if err == nil {
newVariables = append(newVariables, dashboardv1.Variable{
Name: placeholder.Name,
Label: placeholder.Name,
Hide: true,
Plugin: dashboardv1.Plugin{
Name: "placeholder",
Type: "app",
Options: &apiextensionsv1.JSON{Raw: options},
},
})
}
}

newVariables = append(newVariables, variables...)
Expand Down
42 changes: 35 additions & 7 deletions pkg/hub/api/dashboards/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@ func TestAddPlaceholdersAsVariables(t *testing.T) {
Label: "placeholder1",
Hide: true,
Plugin: dashboardv1.Plugin{
Name: "static",
Name: "placeholder",
Type: "app",
Options: &apiextensionsv1.JSON{Raw: []byte(`["value1"]`)},
Options: &apiextensionsv1.JSON{Raw: []byte(`{"type":"string","value":"value1"}`)},
},
},
}, addPlaceholdersAsVariables(nil, map[string]string{"placeholder1": "value1"}))
}, addPlaceholdersAsVariables([]dashboardv1.Placeholder{
{
Name: "placeholder1",
},
}, nil, map[string]string{"placeholder1": "value1"}))
})

t.Run("placeholders are nil", func(t *testing.T) {
Expand All @@ -37,7 +41,7 @@ func TestAddPlaceholdersAsVariables(t *testing.T) {
Options: &apiextensionsv1.JSON{Raw: []byte(`["value1"]`)},
},
},
}, addPlaceholdersAsVariables([]dashboardv1.Variable{
}, addPlaceholdersAsVariables(nil, []dashboardv1.Variable{
{
Name: "variable1",
Label: "Variable 1",
Expand All @@ -58,9 +62,9 @@ func TestAddPlaceholdersAsVariables(t *testing.T) {
Label: "placeholder1",
Hide: true,
Plugin: dashboardv1.Plugin{
Name: "static",
Name: "placeholder",
Type: "app",
Options: &apiextensionsv1.JSON{Raw: []byte(`["value1"]`)},
Options: &apiextensionsv1.JSON{Raw: []byte(`{"type":"string","value":"value1"}`)},
},
},
{
Expand All @@ -73,7 +77,11 @@ func TestAddPlaceholdersAsVariables(t *testing.T) {
Options: &apiextensionsv1.JSON{Raw: []byte(`["value1"]`)},
},
},
}, addPlaceholdersAsVariables([]dashboardv1.Variable{
}, addPlaceholdersAsVariables([]dashboardv1.Placeholder{
{
Name: "placeholder1",
},
}, []dashboardv1.Variable{
{
Name: "variable1",
Label: "Variable 1",
Expand All @@ -86,4 +94,24 @@ func TestAddPlaceholdersAsVariables(t *testing.T) {
},
}, map[string]string{"placeholder1": "value1"}))
})

t.Run("placeholder values are nil", func(t *testing.T) {
require.Equal(t, []dashboardv1.Variable{
{
Name: "placeholder1",
Label: "placeholder1",
Hide: true,
Plugin: dashboardv1.Plugin{
Name: "placeholder",
Type: "app",
Options: &apiextensionsv1.JSON{Raw: []byte(`{"type":"string","value":"default1"}`)},
},
},
}, addPlaceholdersAsVariables([]dashboardv1.Placeholder{
{
Name: "placeholder1",
Default: "default1",
},
}, nil, nil))
})
}
2 changes: 2 additions & 0 deletions pkg/kube/apis/dashboard/v1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type DashboardSpec struct {
type Placeholder struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Default string `json:"default,omitempty"`
Type string `json:"type,omitempty"`
}

type Variable struct {
Expand Down
7 changes: 7 additions & 0 deletions plugins/app/src/components/dashboards/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@ const Dashboard: React.FunctionComponent<IDashboardProps> = ({
tmpVariables[i].value = tmpVariables[i].value || tmpVariables[i].plugin.options[0];
}
}

if (tmpVariables[i].plugin.name === 'placeholder') {
if (tmpVariables[i].plugin.options) {
tmpVariables[i].values = [tmpVariables[i].plugin.options.value || ''];
tmpVariables[i].value = tmpVariables[i].plugin.options.value || '';
}
}
} else {
tmpVariables[i] = await getVariableViaPlugin(tmpVariables[i], tmpVariables, times);
}
Expand Down
18 changes: 16 additions & 2 deletions plugins/app/src/components/dashboards/utils/interpolate.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { JSONPath } from 'jsonpath-plus';
import yaml from 'js-yaml';

import { ITimes } from '@kobsio/shared';
import { IVariableValues } from '../../../crds/dashboard';

// IVariables is a map of variable names with the current value. This interface should only be used by the interpolate
// function, to convert a given array of variables to the format, which is required by the function.
interface IVariables {
[key: string]: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
[key: string]: any;
}

// interpolate is used to replace the variables in a given string with the current value for this variable. Before we
Expand All @@ -24,7 +26,19 @@ export const interpolate = (
const vars: IVariables = {};

for (const variable of variables) {
vars[variable.name] = variable.value;
if (variable.plugin.type === 'app' && variable.plugin.name === 'placeholder') {
if (variable.plugin.options && variable.plugin.options.type && variable.plugin.options.type === 'number') {
str = str.replaceAll(`"{% .${variable.name} %}"`, `{% .${variable.name} %}`);
vars[variable.name] = parseFloat(variable.value);
} else if (variable.plugin.options && variable.plugin.options.type && variable.plugin.options.type === 'object') {
str = str.replaceAll(`"{% .${variable.name} %}"`, `{% .${variable.name} %}`);
vars[variable.name] = JSON.stringify(yaml.load(variable.value));
} else {
vars[variable.name] = variable.value;
}
} else {
vars[variable.name] = variable.value;
}
}

vars['__timeStart'] = `${times.timeStart}`;
Expand Down
2 changes: 2 additions & 0 deletions plugins/app/src/crds/dashboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export interface IDashboard {
export interface IPlaceholder {
name: string;
description?: string;
default?: string;
type?: string;
}

export interface IVariable {
Expand Down