From 84b885716b1270311760e0f791a64ae3b591ea17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Tue, 19 Dec 2023 17:54:10 +0100 Subject: [PATCH] feat: select resources by name in query builder --- pkg/plugin/datasource.go | 81 +++++++++++++++++++++++++++++++++- src/components/QueryEditor.tsx | 48 ++++++++++++++------ src/datasource.ts | 10 ++++- src/types.ts | 2 +- 4 files changed, 124 insertions(+), 17 deletions(-) diff --git a/pkg/plugin/datasource.go b/pkg/plugin/datasource.go index 9dd387d..cdb88ca 100644 --- a/pkg/plugin/datasource.go +++ b/pkg/plugin/datasource.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/build" "github.com/prometheus/client_golang/prometheus" "math" + "net/http" "strconv" "time" @@ -61,6 +62,7 @@ type QueryModel struct { // interfaces - only those which are required for a particular task. var ( _ backend.QueryDataHandler = (*Datasource)(nil) + _ backend.CallResourceHandler = (*Datasource)(nil) _ backend.CheckHealthHandler = (*Datasource)(nil) _ instancemgmt.InstanceDisposer = (*Datasource)(nil) ) @@ -96,9 +98,11 @@ func NewDatasource(_ context.Context, settings backend.DataSourceInstanceSetting clientOpts..., ) - return &Datasource{ + d := &Datasource{ client: client, - }, nil + } + + return d, nil } // Datasource is an example datasource which can respond to data queries, reports @@ -402,3 +406,76 @@ func metricTypeToLoadBalancerMetricType(metricsType MetricsType) hcloud.LoadBala return hcloud.LoadBalancerMetricOpenConnections } } + +func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error { + ctxLogger := logger.FromContext(ctx) + + var returnData any + var err error + + switch req.Path { + case "/servers": + returnData, err = d.getServers(ctx) + case "/load-balancers": + returnData, err = d.getLoadBalancers(ctx) + } + + if err != nil { + ctxLogger.Warn("failed to respond to resource call", "path", req.Path, "error", err) + return sender.Send(&backend.CallResourceResponse{ + Status: http.StatusInternalServerError, + }) + } + + body, err := json.Marshal(returnData) + if err != nil { + ctxLogger.Warn("failed to encode json body in resource call", "path", req.Path, "error", err) + return sender.Send(&backend.CallResourceResponse{ + Status: http.StatusInternalServerError, + }) + } + + return sender.Send(&backend.CallResourceResponse{ + Status: http.StatusOK, + Body: body, + }) +} + +type SelectableValue struct { + Value int64 `json:"value"` + Label string `json:"label"` +} + +func (d *Datasource) getServers(ctx context.Context) ([]SelectableValue, error) { + servers, err := d.client.Server.All(ctx) + if err != nil { + return nil, err + } + + selectableValues := make([]SelectableValue, 0, len(servers)) + for _, server := range servers { + selectableValues = append(selectableValues, SelectableValue{ + Value: server.ID, + Label: server.Name, + }) + } + + return selectableValues, nil +} + +func (d *Datasource) getLoadBalancers(ctx context.Context) ([]SelectableValue, error) { + loadBalancers, err := d.client.LoadBalancer.All(ctx) + if err != nil { + return nil, err + } + + selectableValues := make([]SelectableValue, 0, len(loadBalancers)) + for _, loadBalancer := range loadBalancers { + selectableValues = append(selectableValues, SelectableValue{ + Value: loadBalancer.ID, + Label: loadBalancer.Name, + }) + } + + return selectableValues, nil +} diff --git a/src/components/QueryEditor.tsx b/src/components/QueryEditor.tsx index 8cf2bde..52c6b74 100644 --- a/src/components/QueryEditor.tsx +++ b/src/components/QueryEditor.tsx @@ -1,12 +1,12 @@ -import React from 'react'; -import { InlineField, Select } from '@grafana/ui'; +import React, { useState } from 'react'; +import { AsyncMultiSelect, InlineField, InlineFieldRow, Select } from '@grafana/ui'; import { QueryEditorProps, SelectableValue } from '@grafana/data'; import { DataSource } from '../datasource'; import { DataSourceOptions, LoadBalancerMetricsTypes, Query, ServerMetricsTypes } from '../types'; type Props = QueryEditorProps; -export function QueryEditor({ query, onChange, onRunQuery }: Props) { +export function QueryEditor({ query, onChange, onRunQuery, datasource }: Props) { const onResourceTypeChange = (event: SelectableValue) => { const resourceType = event.value!; let metricsType = query.metricsType; @@ -25,7 +25,8 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) { } } } - onChange({ ...query, resourceType, metricsType }); + onChange({ ...query, resourceType, metricsType, resourceIDs: [] }); + setFormResourceIDs([]); onRunQuery(); }; @@ -39,12 +40,19 @@ export function QueryEditor({ query, onChange, onRunQuery }: Props) { onRunQuery(); }; + const [formResourceIDs, setFormResourceIDs] = useState>>([]); + const onResourceNameOrIDsChange = (newValues: Array>) => { + onChange({ ...query, resourceIDs: newValues.map((value) => value.value!) }); + onRunQuery(); + setFormResourceIDs(newValues); + }; + const availableMetricTypes = query.resourceType === 'server' ? ServerMetricsTypes : LoadBalancerMetricsTypes; - const { queryType, resourceType, metricsType } = query; + const { queryType, resourceType, metricsType, resourceIDs } = query; return ( -
+ - - + + + )} -
+ ); } + +const loadResources = (datasource: DataSource, resourceType: Query['resourceType']) => async (_: string) => { + switch (resourceType) { + case 'server': { + return datasource.getServers(); + } + case 'load-balancer': { + return datasource.getLoadBalancers(); + } + } +}; diff --git a/src/datasource.ts b/src/datasource.ts index aa7e72b..3e11b6b 100644 --- a/src/datasource.ts +++ b/src/datasource.ts @@ -1,4 +1,4 @@ -import { DataSourceInstanceSettings, CoreApp } from '@grafana/data'; +import { DataSourceInstanceSettings, CoreApp, SelectableValue } from '@grafana/data'; import { DataSourceWithBackend } from '@grafana/runtime'; import { Query, DataSourceOptions, DEFAULT_QUERY } from './types'; @@ -14,4 +14,12 @@ export class DataSource extends DataSourceWithBackend getDefaultQuery(_: CoreApp): Partial { return DEFAULT_QUERY; } + + async getServers(): Promise>> { + return this.getResource('/servers'); + } + + async getLoadBalancers(): Promise>> { + return this.getResource('/load-balancers'); + } } diff --git a/src/types.ts b/src/types.ts index 6e5b14e..3eeae2d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -14,7 +14,7 @@ export interface Query extends DataQuery { resourceType: 'server' | 'load-balancer'; metricsType: (typeof ServerMetricsTypes)[number] | (typeof LoadBalancerMetricsTypes)[number]; - resourceNameOrIDs: string[]; + resourceIDs: number[]; } export const DEFAULT_QUERY: Partial = {