mirror of
https://github.com/grafana/grafana.git
synced 2026-02-03 20:49:50 -05:00
Elasticsearch: Decoupling from core (#115900)
* Complete decoupling of backend - Replace usage of featuremgmt - Copy simplejson - Add standalone logic * Complete frontend decoupling - Fix imports - Copy store and reducer logic * Add required files for full decoupling * Regen cue * Prettier * Remove unneeded script * Jest fix * Add jest config * Lint * Lit * Prune suppresions
This commit is contained in:
parent
7143324229
commit
c1a46fdcb5
64 changed files with 1378 additions and 128 deletions
|
|
@ -121,6 +121,8 @@ linters:
|
||||||
- '**/pkg/tsdb/zipkin/**/*'
|
- '**/pkg/tsdb/zipkin/**/*'
|
||||||
- '**/pkg/tsdb/jaeger/*'
|
- '**/pkg/tsdb/jaeger/*'
|
||||||
- '**/pkg/tsdb/jaeger/**/*'
|
- '**/pkg/tsdb/jaeger/**/*'
|
||||||
|
- '**/pkg/tsdb/elasticsearch/*'
|
||||||
|
- '**/pkg/tsdb/elasticsearch/**/*'
|
||||||
deny:
|
deny:
|
||||||
- pkg: github.com/grafana/grafana/pkg/api
|
- pkg: github.com/grafana/grafana/pkg/api
|
||||||
desc: Core plugins are not allowed to depend on Grafana core packages
|
desc: Core plugins are not allowed to depend on Grafana core packages
|
||||||
|
|
|
||||||
|
|
@ -3743,46 +3743,21 @@
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/DateHistogramSettingsEditor.tsx": {
|
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/SettingsEditor/TermsSettingsEditor.tsx": {
|
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": {
|
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/aggregations.ts": {
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
"@typescript-eslint/consistent-type-assertions": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/BucketAggregationsEditor/state/reducer.ts": {
|
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": {
|
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/MetricEditor.tsx": {
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
"@typescript-eslint/consistent-type-assertions": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/SettingsEditor/SettingField.tsx": {
|
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
|
||||||
"count": 2
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": {
|
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/aggregations.ts": {
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
"@typescript-eslint/consistent-type-assertions": {
|
||||||
"count": 1
|
"count": 1
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"public/app/plugins/datasource/elasticsearch/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts": {
|
|
||||||
"@typescript-eslint/consistent-type-assertions": {
|
|
||||||
"count": 1
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": {
|
"public/app/plugins/datasource/elasticsearch/configuration/DataLinks.tsx": {
|
||||||
"no-restricted-syntax": {
|
"no-restricted-syntax": {
|
||||||
"count": 1
|
"count": 1
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,7 @@ module.exports = {
|
||||||
// Decoupled plugins run their own tests so ignoring them here.
|
// Decoupled plugins run their own tests so ignoring them here.
|
||||||
'<rootDir>/public/app/plugins/datasource/azuremonitor',
|
'<rootDir>/public/app/plugins/datasource/azuremonitor',
|
||||||
'<rootDir>/public/app/plugins/datasource/cloud-monitoring',
|
'<rootDir>/public/app/plugins/datasource/cloud-monitoring',
|
||||||
|
'<rootDir>/public/app/plugins/datasource/elasticsearch',
|
||||||
'<rootDir>/public/app/plugins/datasource/grafana-postgresql-datasource',
|
'<rootDir>/public/app/plugins/datasource/grafana-postgresql-datasource',
|
||||||
'<rootDir>/public/app/plugins/datasource/grafana-pyroscope-datasource',
|
'<rootDir>/public/app/plugins/datasource/grafana-pyroscope-datasource',
|
||||||
'<rootDir>/public/app/plugins/datasource/grafana-testdata-datasource',
|
'<rootDir>/public/app/plugins/datasource/grafana-testdata-datasource',
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import * as common from '@grafana/schema';
|
import * as common from '@grafana/schema';
|
||||||
|
|
||||||
export const pluginVersion = "12.4.0-pre";
|
export const pluginVersion = "%VERSION%";
|
||||||
|
|
||||||
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);
|
export type BucketAggregation = (DateHistogram | Histogram | Terms | Filters | GeoHashGrid | Nested);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -639,7 +639,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"grafanaDependency": "",
|
"grafanaDependency": "\u003e=11.6.0",
|
||||||
"grafanaVersion": "*",
|
"grafanaVersion": "*",
|
||||||
"plugins": [],
|
"plugins": [],
|
||||||
"extensions": {
|
"extensions": {
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ package elasticsearch
|
||||||
import (
|
import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// addDateHistogramAgg adds a date histogram aggregation to the aggregation builder
|
// addDateHistogramAgg adds a date histogram aggregation to the aggregation builder
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import (
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/tracing"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Used in logging to mark a stage
|
// Used in logging to mark a stage
|
||||||
|
|
@ -160,7 +159,7 @@ func (c *baseClientImpl) ExecuteMultisearch(r *MultiSearchRequest) (*MultiSearch
|
||||||
resSpan.End()
|
resSpan.End()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
improvedParsingEnabled := isFeatureEnabled(c.ctx, featuremgmt.FlagElasticsearchImprovedParsing)
|
improvedParsingEnabled := isFeatureEnabled(c.ctx, "elasticsearchImprovedParsing")
|
||||||
msr, err := c.parser.parseMultiSearchResponse(res.Body, improvedParsingEnabled)
|
msr, err := c.parser.parseMultiSearchResponse(res.Body, improvedParsingEnabled)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ import (
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClient_ExecuteMultisearch(t *testing.T) {
|
func TestClient_ExecuteMultisearch(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSearchRequest(t *testing.T) {
|
func TestSearchRequest(t *testing.T) {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// processQuery processes a single query and adds it to the multi-search request builder
|
// processQuery processes a single query and adds it to the multi-search request builder
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ package elasticsearch
|
||||||
import (
|
import (
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// setFloatPath converts a string value at the specified path to float64
|
// setFloatPath converts a string value at the specified path to float64
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// metricsResponseProcessor handles processing of metrics query responses
|
// metricsResponseProcessor handles processing of metrics query responses
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Query represents the time series query model of the datasource
|
// Query represents the time series query model of the datasource
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseQuery(tsdbQuery []backend.DataQuery, logger log.Logger) ([]*Query, error) {
|
func parseQuery(tsdbQuery []backend.DataQuery, logger log.Logger) ([]*Query, error) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AggregationParser parses raw Elasticsearch DSL aggregations
|
// AggregationParser parses raw Elasticsearch DSL aggregations
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,9 @@ import (
|
||||||
"go.opentelemetry.io/otel/codes"
|
"go.opentelemetry.io/otel/codes"
|
||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
||||||
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/instrumentation"
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/instrumentation"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
|
||||||
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
es "github.com/grafana/grafana/pkg/tsdb/elasticsearch/client"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/elasticsearch/simplejson"
|
||||||
)
|
)
|
||||||
|
|
||||||
// flatten flattens multi-level objects to single level objects. It uses dot notation to join keys.
|
// flatten flattens multi-level objects to single level objects. It uses dot notation to join keys.
|
||||||
|
|
|
||||||
582
pkg/tsdb/elasticsearch/simplejson/simplejson.go
Normal file
582
pkg/tsdb/elasticsearch/simplejson/simplejson.go
Normal file
|
|
@ -0,0 +1,582 @@
|
||||||
|
// Package simplejson provides a wrapper for arbitrary JSON objects that adds methods to access properties.
|
||||||
|
// Use of this package in place of types and the standard library's encoding/json package is strongly discouraged.
|
||||||
|
//
|
||||||
|
// Don't lint for stale code, since it's a copied library and we might as well keep the whole thing.
|
||||||
|
// nolint:unused
|
||||||
|
package simplejson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql/driver"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
)
|
||||||
|
|
||||||
|
// returns the current implementation version
|
||||||
|
func Version() string {
|
||||||
|
return "0.5.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Json struct {
|
||||||
|
data any
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) FromDB(data []byte) error {
|
||||||
|
j.data = make(map[string]any)
|
||||||
|
|
||||||
|
dec := json.NewDecoder(bytes.NewBuffer(data))
|
||||||
|
dec.UseNumber()
|
||||||
|
return dec.Decode(&j.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) ToDB() ([]byte, error) {
|
||||||
|
if j == nil || j.data == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return j.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) Scan(val any) error {
|
||||||
|
switch v := val.(type) {
|
||||||
|
case []byte:
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal(v, &j)
|
||||||
|
case string:
|
||||||
|
if len(v) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return json.Unmarshal([]byte(v), &j)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported type: %T", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (j *Json) Value() (driver.Value, error) {
|
||||||
|
return j.ToDB()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto creates a copy by serializing JSON
|
||||||
|
func (j *Json) DeepCopyInto(out *Json) {
|
||||||
|
b, err := j.Encode()
|
||||||
|
if err == nil {
|
||||||
|
_ = out.UnmarshalJSON(b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy will make a deep copy of the JSON object
|
||||||
|
func (j *Json) DeepCopy() *Json {
|
||||||
|
if j == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Json)
|
||||||
|
j.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJson returns a pointer to a new `Json` object
|
||||||
|
// after unmarshaling `body` bytes
|
||||||
|
func NewJson(body []byte) (*Json, error) {
|
||||||
|
j := new(Json)
|
||||||
|
err := j.UnmarshalJSON(body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return j, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustJson returns a pointer to a new `Json` object, panicking if `body` cannot be parsed.
|
||||||
|
func MustJson(body []byte) *Json {
|
||||||
|
j, err := NewJson(body)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(fmt.Sprintf("could not unmarshal JSON: %q", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a pointer to a new, empty `Json` object
|
||||||
|
func New() *Json {
|
||||||
|
return &Json{
|
||||||
|
data: make(map[string]any),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromAny returns a pointer to a new `Json` object with provided data.
|
||||||
|
func NewFromAny(data any) *Json {
|
||||||
|
return &Json{data: data}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interface returns the underlying data
|
||||||
|
func (j *Json) Interface() any {
|
||||||
|
return j.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode returns its marshaled data as `[]byte`
|
||||||
|
func (j *Json) Encode() ([]byte, error) {
|
||||||
|
return j.MarshalJSON()
|
||||||
|
}
|
||||||
|
|
||||||
|
// EncodePretty returns its marshaled data as `[]byte` with indentation
|
||||||
|
func (j *Json) EncodePretty() ([]byte, error) {
|
||||||
|
return json.MarshalIndent(&j.data, "", " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Implements the json.Marshaler interface.
|
||||||
|
func (j *Json) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(&j.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set modifies `Json` map by `key` and `value`
|
||||||
|
// Useful for changing single key/value in a `Json` object easily.
|
||||||
|
func (j *Json) Set(key string, val any) {
|
||||||
|
m, err := j.Map()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
m[key] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPath modifies `Json`, recursively checking/creating map keys for the supplied path,
|
||||||
|
// and then finally writing in the value
|
||||||
|
func (j *Json) SetPath(branch []string, val any) {
|
||||||
|
if len(branch) == 0 {
|
||||||
|
j.data = val
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// in order to insert our branch, we need map[string]any
|
||||||
|
if _, ok := (j.data).(map[string]any); !ok {
|
||||||
|
// have to replace with something suitable
|
||||||
|
j.data = make(map[string]any)
|
||||||
|
}
|
||||||
|
curr := j.data.(map[string]any)
|
||||||
|
|
||||||
|
for i := 0; i < len(branch)-1; i++ {
|
||||||
|
b := branch[i]
|
||||||
|
// key exists?
|
||||||
|
if _, ok := curr[b]; !ok {
|
||||||
|
n := make(map[string]any)
|
||||||
|
curr[b] = n
|
||||||
|
curr = n
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// make sure the value is the right sort of thing
|
||||||
|
if _, ok := curr[b].(map[string]any); !ok {
|
||||||
|
// have to replace with something suitable
|
||||||
|
n := make(map[string]any)
|
||||||
|
curr[b] = n
|
||||||
|
}
|
||||||
|
|
||||||
|
curr = curr[b].(map[string]any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// add remaining k/v
|
||||||
|
curr[branch[len(branch)-1]] = val
|
||||||
|
}
|
||||||
|
|
||||||
|
// Del modifies `Json` map by deleting `key` if it is present.
|
||||||
|
func (j *Json) Del(key string) {
|
||||||
|
m, err := j.Map()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(m, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get returns a pointer to a new `Json` object
|
||||||
|
// for `key` in its `map` representation
|
||||||
|
//
|
||||||
|
// useful for chaining operations (to traverse a nested JSON):
|
||||||
|
//
|
||||||
|
// js.Get("top_level").Get("dict").Get("value").Int()
|
||||||
|
func (j *Json) Get(key string) *Json {
|
||||||
|
m, err := j.Map()
|
||||||
|
if err == nil {
|
||||||
|
if val, ok := m[key]; ok {
|
||||||
|
return &Json{val}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Json{nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPath searches for the item as specified by the branch
|
||||||
|
// without the need to deep dive using Get()'s.
|
||||||
|
//
|
||||||
|
// js.GetPath("top_level", "dict")
|
||||||
|
func (j *Json) GetPath(branch ...string) *Json {
|
||||||
|
jin := j
|
||||||
|
for _, p := range branch {
|
||||||
|
jin = jin.Get(p)
|
||||||
|
}
|
||||||
|
return jin
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndex returns a pointer to a new `Json` object
|
||||||
|
// for `index` in its `array` representation
|
||||||
|
//
|
||||||
|
// this is the analog to Get when accessing elements of
|
||||||
|
// a json array instead of a json object:
|
||||||
|
//
|
||||||
|
// js.Get("top_level").Get("array").GetIndex(1).Get("key").Int()
|
||||||
|
func (j *Json) GetIndex(index int) *Json {
|
||||||
|
a, err := j.Array()
|
||||||
|
if err == nil {
|
||||||
|
if len(a) > index {
|
||||||
|
return &Json{a[index]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &Json{nil}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckGetIndex returns a pointer to a new `Json` object
|
||||||
|
// for `index` in its `array` representation, and a `bool`
|
||||||
|
// indicating success or failure
|
||||||
|
//
|
||||||
|
// useful for chained operations when success is important:
|
||||||
|
//
|
||||||
|
// if data, ok := js.Get("top_level").CheckGetIndex(0); ok {
|
||||||
|
// log.Println(data)
|
||||||
|
// }
|
||||||
|
func (j *Json) CheckGetIndex(index int) (*Json, bool) {
|
||||||
|
a, err := j.Array()
|
||||||
|
if err == nil {
|
||||||
|
if len(a) > index {
|
||||||
|
return &Json{a[index]}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetIndex modifies `Json` array by `index` and `value`
|
||||||
|
// for `index` in its `array` representation
|
||||||
|
func (j *Json) SetIndex(index int, val any) {
|
||||||
|
a, err := j.Array()
|
||||||
|
if err == nil {
|
||||||
|
if len(a) > index {
|
||||||
|
a[index] = val
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckGet returns a pointer to a new `Json` object and
|
||||||
|
// a `bool` identifying success or failure
|
||||||
|
//
|
||||||
|
// useful for chained operations when success is important:
|
||||||
|
//
|
||||||
|
// if data, ok := js.Get("top_level").CheckGet("inner"); ok {
|
||||||
|
// log.Println(data)
|
||||||
|
// }
|
||||||
|
func (j *Json) CheckGet(key string) (*Json, bool) {
|
||||||
|
m, err := j.Map()
|
||||||
|
if err == nil {
|
||||||
|
if val, ok := m[key]; ok {
|
||||||
|
return &Json{val}, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Map type asserts to `map`
|
||||||
|
func (j *Json) Map() (map[string]any, error) {
|
||||||
|
if m, ok := (j.data).(map[string]any); ok {
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("type assertion to map[string]any failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array type asserts to an `array`
|
||||||
|
func (j *Json) Array() ([]any, error) {
|
||||||
|
if a, ok := (j.data).([]any); ok {
|
||||||
|
return a, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("type assertion to []any failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool type asserts to `bool`
|
||||||
|
func (j *Json) Bool() (bool, error) {
|
||||||
|
if s, ok := (j.data).(bool); ok {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
return false, errors.New("type assertion to bool failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// String type asserts to `string`
|
||||||
|
func (j *Json) String() (string, error) {
|
||||||
|
if s, ok := (j.data).(string); ok {
|
||||||
|
return s, nil
|
||||||
|
}
|
||||||
|
return "", errors.New("type assertion to string failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes type asserts to `[]byte`
|
||||||
|
func (j *Json) Bytes() ([]byte, error) {
|
||||||
|
if s, ok := (j.data).(string); ok {
|
||||||
|
return []byte(s), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("type assertion to []byte failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StringArray type asserts to an `array` of `string`
|
||||||
|
func (j *Json) StringArray() ([]string, error) {
|
||||||
|
arr, err := j.Array()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retArr := make([]string, 0, len(arr))
|
||||||
|
for _, a := range arr {
|
||||||
|
if a == nil {
|
||||||
|
retArr = append(retArr, "")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
s, ok := a.(string)
|
||||||
|
if !ok {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
retArr = append(retArr, s)
|
||||||
|
}
|
||||||
|
return retArr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustArray guarantees the return of a `[]any` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you want to iterate over array values in a succinct manner:
|
||||||
|
//
|
||||||
|
// for i, v := range js.Get("results").MustArray() {
|
||||||
|
// fmt.Println(i, v)
|
||||||
|
// }
|
||||||
|
func (j *Json) MustArray(args ...[]any) []any {
|
||||||
|
var def []any
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustArray() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := j.Array()
|
||||||
|
if err == nil {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustMap guarantees the return of a `map[string]any` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you want to iterate over map values in a succinct manner:
|
||||||
|
//
|
||||||
|
// for k, v := range js.Get("dictionary").MustMap() {
|
||||||
|
// fmt.Println(k, v)
|
||||||
|
// }
|
||||||
|
func (j *Json) MustMap(args ...map[string]any) map[string]any {
|
||||||
|
var def map[string]any
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustMap() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := j.Map()
|
||||||
|
if err == nil {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustString guarantees the return of a `string` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want a `string` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustString(), js.Get("optional_param").MustString("my_default"))
|
||||||
|
func (j *Json) MustString(args ...string) string {
|
||||||
|
var def string
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustString() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := j.String()
|
||||||
|
if err == nil {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustStringArray guarantees the return of a `[]string` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you want to iterate over array values in a succinct manner:
|
||||||
|
//
|
||||||
|
// for i, s := range js.Get("results").MustStringArray() {
|
||||||
|
// fmt.Println(i, s)
|
||||||
|
// }
|
||||||
|
func (j *Json) MustStringArray(args ...[]string) []string {
|
||||||
|
var def []string
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustStringArray() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
a, err := j.StringArray()
|
||||||
|
if err == nil {
|
||||||
|
return a
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustInt guarantees the return of an `int` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want an `int` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustInt(), js.Get("optional_param").MustInt(5150))
|
||||||
|
func (j *Json) MustInt(args ...int) int {
|
||||||
|
var def int
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustInt() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := j.Int()
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustFloat64 guarantees the return of a `float64` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want a `float64` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustFloat64(), js.Get("optional_param").MustFloat64(5.150))
|
||||||
|
func (j *Json) MustFloat64(args ...float64) float64 {
|
||||||
|
var def float64
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustFloat64() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := j.Float64()
|
||||||
|
if err == nil {
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustBool guarantees the return of a `bool` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want a `bool` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustBool(), js.Get("optional_param").MustBool(true))
|
||||||
|
func (j *Json) MustBool(args ...bool) bool {
|
||||||
|
var def bool
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustBool() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := j.Bool()
|
||||||
|
if err == nil {
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustInt64 guarantees the return of an `int64` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want an `int64` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustInt64(), js.Get("optional_param").MustInt64(5150))
|
||||||
|
func (j *Json) MustInt64(args ...int64) int64 {
|
||||||
|
var def int64
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustInt64() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := j.Int64()
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MustUInt64 guarantees the return of an `uint64` (with optional default)
|
||||||
|
//
|
||||||
|
// useful when you explicitly want an `uint64` in a single value return context:
|
||||||
|
//
|
||||||
|
// myFunc(js.Get("param1").MustUint64(), js.Get("optional_param").MustUint64(5150))
|
||||||
|
func (j *Json) MustUint64(args ...uint64) uint64 {
|
||||||
|
var def uint64
|
||||||
|
|
||||||
|
switch len(args) {
|
||||||
|
case 0:
|
||||||
|
case 1:
|
||||||
|
def = args[0]
|
||||||
|
default:
|
||||||
|
log.Panicf("MustUint64() received too many arguments %d", len(args))
|
||||||
|
}
|
||||||
|
|
||||||
|
i, err := j.Uint64()
|
||||||
|
if err == nil {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
return def
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalYAML implements yaml.Marshaller.
|
||||||
|
func (j *Json) MarshalYAML() (any, error) {
|
||||||
|
return j.data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalYAML implements yaml.Unmarshaller.
|
||||||
|
func (j *Json) UnmarshalYAML(unmarshal func(any) error) error {
|
||||||
|
var data any
|
||||||
|
if err := unmarshal(&data); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
j.data = data
|
||||||
|
return nil
|
||||||
|
}
|
||||||
90
pkg/tsdb/elasticsearch/simplejson/simplejson_go11.go
Normal file
90
pkg/tsdb/elasticsearch/simplejson/simplejson_go11.go
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
package simplejson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implements the json.Unmarshaler interface.
|
||||||
|
func (j *Json) UnmarshalJSON(p []byte) error {
|
||||||
|
dec := json.NewDecoder(bytes.NewBuffer(p))
|
||||||
|
dec.UseNumber()
|
||||||
|
return dec.Decode(&j.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFromReader returns a *Json by decoding from an io.Reader
|
||||||
|
func NewFromReader(r io.Reader) (*Json, error) {
|
||||||
|
j := new(Json)
|
||||||
|
dec := json.NewDecoder(r)
|
||||||
|
dec.UseNumber()
|
||||||
|
err := dec.Decode(&j.data)
|
||||||
|
return j, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Float64 coerces into a float64
|
||||||
|
func (j *Json) Float64() (float64, error) {
|
||||||
|
switch n := j.data.(type) {
|
||||||
|
case json.Number:
|
||||||
|
return n.Float64()
|
||||||
|
case float32, float64:
|
||||||
|
return reflect.ValueOf(j.data).Float(), nil
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
return float64(reflect.ValueOf(j.data).Int()), nil
|
||||||
|
case uint, uint8, uint16, uint32, uint64:
|
||||||
|
return float64(reflect.ValueOf(j.data).Uint()), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("invalid value type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int coerces into an int
|
||||||
|
func (j *Json) Int() (int, error) {
|
||||||
|
switch n := j.data.(type) {
|
||||||
|
case json.Number:
|
||||||
|
i, err := n.Int64()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return int(i), nil
|
||||||
|
case float32, float64:
|
||||||
|
return int(reflect.ValueOf(j.data).Float()), nil
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
return int(reflect.ValueOf(j.data).Int()), nil
|
||||||
|
case uint, uint8, uint16, uint32, uint64:
|
||||||
|
return int(reflect.ValueOf(j.data).Uint()), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("invalid value type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int64 coerces into an int64
|
||||||
|
func (j *Json) Int64() (int64, error) {
|
||||||
|
switch n := j.data.(type) {
|
||||||
|
case json.Number:
|
||||||
|
return n.Int64()
|
||||||
|
case float32, float64:
|
||||||
|
return int64(reflect.ValueOf(j.data).Float()), nil
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
return reflect.ValueOf(j.data).Int(), nil
|
||||||
|
case uint, uint8, uint16, uint32, uint64:
|
||||||
|
return int64(reflect.ValueOf(j.data).Uint()), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("invalid value type")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint64 coerces into an uint64
|
||||||
|
func (j *Json) Uint64() (uint64, error) {
|
||||||
|
switch n := j.data.(type) {
|
||||||
|
case json.Number:
|
||||||
|
return strconv.ParseUint(n.String(), 10, 64)
|
||||||
|
case float32, float64:
|
||||||
|
return uint64(reflect.ValueOf(j.data).Float()), nil
|
||||||
|
case int, int8, int16, int32, int64:
|
||||||
|
return uint64(reflect.ValueOf(j.data).Int()), nil
|
||||||
|
case uint, uint8, uint16, uint32, uint64:
|
||||||
|
return reflect.ValueOf(j.data).Uint(), nil
|
||||||
|
}
|
||||||
|
return 0, errors.New("invalid value type")
|
||||||
|
}
|
||||||
274
pkg/tsdb/elasticsearch/simplejson/simplejson_test.go
Normal file
274
pkg/tsdb/elasticsearch/simplejson/simplejson_test.go
Normal file
|
|
@ -0,0 +1,274 @@
|
||||||
|
package simplejson
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSimplejson(t *testing.T) {
|
||||||
|
var ok bool
|
||||||
|
var err error
|
||||||
|
|
||||||
|
js, err := NewJson([]byte(`{
|
||||||
|
"test": {
|
||||||
|
"string_array": ["asdf", "ghjk", "zxcv"],
|
||||||
|
"string_array_null": ["abc", null, "efg"],
|
||||||
|
"array": [1, "2", 3],
|
||||||
|
"arraywithsubs": [{"subkeyone": 1},
|
||||||
|
{"subkeytwo": 2, "subkeythree": 3}],
|
||||||
|
"int": 10,
|
||||||
|
"float": 5.150,
|
||||||
|
"string": "simplejson",
|
||||||
|
"bool": true,
|
||||||
|
"sub_obj": {"a": 1}
|
||||||
|
}
|
||||||
|
}`))
|
||||||
|
|
||||||
|
assert.NotEqual(t, nil, js)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
_, ok = js.CheckGet("test")
|
||||||
|
assert.Equal(t, true, ok)
|
||||||
|
|
||||||
|
_, ok = js.CheckGet("missing_key")
|
||||||
|
assert.Equal(t, false, ok)
|
||||||
|
|
||||||
|
aws := js.Get("test").Get("arraywithsubs")
|
||||||
|
assert.NotEqual(t, nil, aws)
|
||||||
|
var awsval int
|
||||||
|
awsval, _ = aws.GetIndex(0).Get("subkeyone").Int()
|
||||||
|
assert.Equal(t, 1, awsval)
|
||||||
|
awsval, _ = aws.GetIndex(1).Get("subkeytwo").Int()
|
||||||
|
assert.Equal(t, 2, awsval)
|
||||||
|
awsval, _ = aws.GetIndex(1).Get("subkeythree").Int()
|
||||||
|
assert.Equal(t, 3, awsval)
|
||||||
|
|
||||||
|
arr := js.Get("test").Get("array")
|
||||||
|
assert.NotEqual(t, nil, arr)
|
||||||
|
val, ok := arr.CheckGetIndex(0)
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
valInt, _ := val.Int()
|
||||||
|
assert.Equal(t, valInt, 1)
|
||||||
|
val, ok = arr.CheckGetIndex(1)
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
valStr, _ := val.String()
|
||||||
|
assert.Equal(t, valStr, "2")
|
||||||
|
val, ok = arr.CheckGetIndex(2)
|
||||||
|
assert.Equal(t, ok, true)
|
||||||
|
valInt, _ = val.Int()
|
||||||
|
assert.Equal(t, valInt, 3)
|
||||||
|
_, ok = arr.CheckGetIndex(3)
|
||||||
|
assert.Equal(t, ok, false)
|
||||||
|
|
||||||
|
i, _ := js.Get("test").Get("int").Int()
|
||||||
|
assert.Equal(t, 10, i)
|
||||||
|
|
||||||
|
f, _ := js.Get("test").Get("float").Float64()
|
||||||
|
assert.Equal(t, 5.150, f)
|
||||||
|
|
||||||
|
s, _ := js.Get("test").Get("string").String()
|
||||||
|
assert.Equal(t, "simplejson", s)
|
||||||
|
|
||||||
|
b, _ := js.Get("test").Get("bool").Bool()
|
||||||
|
assert.Equal(t, true, b)
|
||||||
|
|
||||||
|
mi := js.Get("test").Get("int").MustInt()
|
||||||
|
assert.Equal(t, 10, mi)
|
||||||
|
|
||||||
|
mi2 := js.Get("test").Get("missing_int").MustInt(5150)
|
||||||
|
assert.Equal(t, 5150, mi2)
|
||||||
|
|
||||||
|
ms := js.Get("test").Get("string").MustString()
|
||||||
|
assert.Equal(t, "simplejson", ms)
|
||||||
|
|
||||||
|
ms2 := js.Get("test").Get("missing_string").MustString("fyea")
|
||||||
|
assert.Equal(t, "fyea", ms2)
|
||||||
|
|
||||||
|
ma2 := js.Get("test").Get("missing_array").MustArray([]any{"1", 2, "3"})
|
||||||
|
assert.Equal(t, ma2, []any{"1", 2, "3"})
|
||||||
|
|
||||||
|
msa := js.Get("test").Get("string_array").MustStringArray()
|
||||||
|
assert.Equal(t, msa[0], "asdf")
|
||||||
|
assert.Equal(t, msa[1], "ghjk")
|
||||||
|
assert.Equal(t, msa[2], "zxcv")
|
||||||
|
|
||||||
|
msa2 := js.Get("test").Get("string_array").MustStringArray([]string{"1", "2", "3"})
|
||||||
|
assert.Equal(t, msa2[0], "asdf")
|
||||||
|
assert.Equal(t, msa2[1], "ghjk")
|
||||||
|
assert.Equal(t, msa2[2], "zxcv")
|
||||||
|
|
||||||
|
msa3 := js.Get("test").Get("missing_array").MustStringArray([]string{"1", "2", "3"})
|
||||||
|
assert.Equal(t, msa3, []string{"1", "2", "3"})
|
||||||
|
|
||||||
|
mm2 := js.Get("test").Get("missing_map").MustMap(map[string]any{"found": false})
|
||||||
|
assert.Equal(t, mm2, map[string]any{"found": false})
|
||||||
|
|
||||||
|
strs, err := js.Get("test").Get("string_array").StringArray()
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
assert.Equal(t, strs[0], "asdf")
|
||||||
|
assert.Equal(t, strs[1], "ghjk")
|
||||||
|
assert.Equal(t, strs[2], "zxcv")
|
||||||
|
|
||||||
|
strs2, err := js.Get("test").Get("string_array_null").StringArray()
|
||||||
|
assert.Equal(t, err, nil)
|
||||||
|
assert.Equal(t, strs2[0], "abc")
|
||||||
|
assert.Equal(t, strs2[1], "")
|
||||||
|
assert.Equal(t, strs2[2], "efg")
|
||||||
|
|
||||||
|
gp, _ := js.GetPath("test", "string").String()
|
||||||
|
assert.Equal(t, "simplejson", gp)
|
||||||
|
|
||||||
|
gp2, _ := js.GetPath("test", "int").Int()
|
||||||
|
assert.Equal(t, 10, gp2)
|
||||||
|
|
||||||
|
assert.Equal(t, js.Get("test").Get("bool").MustBool(), true)
|
||||||
|
|
||||||
|
js.Set("float2", 300.0)
|
||||||
|
assert.Equal(t, js.Get("float2").MustFloat64(), 300.0)
|
||||||
|
|
||||||
|
js.Set("test2", "setTest")
|
||||||
|
assert.Equal(t, "setTest", js.Get("test2").MustString())
|
||||||
|
|
||||||
|
js.Del("test2")
|
||||||
|
assert.NotEqual(t, "setTest", js.Get("test2").MustString())
|
||||||
|
|
||||||
|
js.Get("test").Get("sub_obj").Set("a", 2)
|
||||||
|
assert.Equal(t, 2, js.Get("test").Get("sub_obj").Get("a").MustInt())
|
||||||
|
|
||||||
|
js.GetPath("test", "sub_obj").Set("a", 3)
|
||||||
|
assert.Equal(t, 3, js.GetPath("test", "sub_obj", "a").MustInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStdlibInterfaces(t *testing.T) {
|
||||||
|
val := new(struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Params *Json `json:"params"`
|
||||||
|
})
|
||||||
|
val2 := new(struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Params *Json `json:"params"`
|
||||||
|
})
|
||||||
|
|
||||||
|
raw := `{"name":"myobject","params":{"string":"simplejson"}}`
|
||||||
|
|
||||||
|
assert.Equal(t, nil, json.Unmarshal([]byte(raw), val))
|
||||||
|
|
||||||
|
assert.Equal(t, "myobject", val.Name)
|
||||||
|
assert.NotEqual(t, nil, val.Params.data)
|
||||||
|
s, _ := val.Params.Get("string").String()
|
||||||
|
assert.Equal(t, "simplejson", s)
|
||||||
|
|
||||||
|
p, err := json.Marshal(val)
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, nil, json.Unmarshal(p, val2))
|
||||||
|
assert.Equal(t, val, val2) // stable
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSet(t *testing.T) {
|
||||||
|
js, err := NewJson([]byte(`{}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
js.Set("baz", "bing")
|
||||||
|
|
||||||
|
s, err := js.GetPath("baz").String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "bing", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReplace(t *testing.T) {
|
||||||
|
js, err := NewJson([]byte(`{}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
err = js.UnmarshalJSON([]byte(`{"baz":"bing"}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
s, err := js.GetPath("baz").String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "bing", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPath(t *testing.T) {
|
||||||
|
js, err := NewJson([]byte(`{}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
js.SetPath([]string{"foo", "bar"}, "baz")
|
||||||
|
|
||||||
|
s, err := js.GetPath("foo", "bar").String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "baz", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPathNoPath(t *testing.T) {
|
||||||
|
js, err := NewJson([]byte(`{"some":"data","some_number":1.0,"some_bool":false}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
f := js.GetPath("some_number").MustFloat64(99.0)
|
||||||
|
assert.Equal(t, f, 1.0)
|
||||||
|
|
||||||
|
js.SetPath([]string{}, map[string]any{"foo": "bar"})
|
||||||
|
|
||||||
|
s, err := js.GetPath("foo").String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "bar", s)
|
||||||
|
|
||||||
|
f = js.GetPath("some_number").MustFloat64(99.0)
|
||||||
|
assert.Equal(t, f, 99.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathWillAugmentExisting(t *testing.T) {
|
||||||
|
js, err := NewJson([]byte(`{"this":{"a":"aa","b":"bb","c":"cc"}}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
js.SetPath([]string{"this", "d"}, "dd")
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
path []string
|
||||||
|
outcome string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
path: []string{"this", "a"},
|
||||||
|
outcome: "aa",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"this", "b"},
|
||||||
|
outcome: "bb",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"this", "c"},
|
||||||
|
outcome: "cc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: []string{"this", "d"},
|
||||||
|
outcome: "dd",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range cases {
|
||||||
|
s, err := js.GetPath(tc.path...).String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, tc.outcome, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPathWillOverwriteExisting(t *testing.T) {
|
||||||
|
// notice how "a" is 0.1 - but then we'll try to set at path a, foo
|
||||||
|
js, err := NewJson([]byte(`{"this":{"a":0.1,"b":"bb","c":"cc"}}`))
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
|
||||||
|
js.SetPath([]string{"this", "a", "foo"}, "bar")
|
||||||
|
|
||||||
|
s, err := js.GetPath("this", "a", "foo").String()
|
||||||
|
assert.Equal(t, nil, err)
|
||||||
|
assert.Equal(t, "bar", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMustJson(t *testing.T) {
|
||||||
|
js := MustJson([]byte(`{"foo": "bar"}`))
|
||||||
|
assert.Equal(t, js.Get("foo").MustString(), "bar")
|
||||||
|
|
||||||
|
assert.PanicsWithValue(t, "could not unmarshal JSON: \"unexpected EOF\"", func() {
|
||||||
|
MustJson([]byte(`{`))
|
||||||
|
})
|
||||||
|
}
|
||||||
48
pkg/tsdb/elasticsearch/standalone/datasource.go
Normal file
48
pkg/tsdb/elasticsearch/standalone/datasource.go
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/httpclient"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/instancemgmt"
|
||||||
|
elasticsearch "github.com/grafana/grafana/pkg/tsdb/elasticsearch"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ backend.QueryDataHandler = (*Datasource)(nil)
|
||||||
|
_ backend.CheckHealthHandler = (*Datasource)(nil)
|
||||||
|
_ backend.CallResourceHandler = (*Datasource)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewDatasource(context.Context, backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
|
return &Datasource{
|
||||||
|
Service: elasticsearch.ProvideService(httpclient.NewProvider()),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Datasource struct {
|
||||||
|
Service *elasticsearch.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func contextualMiddlewares(ctx context.Context) context.Context {
|
||||||
|
cfg := backend.GrafanaConfigFromContext(ctx)
|
||||||
|
responseLimitMiddleware := httpclient.ResponseLimitMiddleware(cfg.ResponseLimit())
|
||||||
|
ctx = httpclient.WithContextualMiddleware(ctx, responseLimitMiddleware)
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datasource) QueryData(ctx context.Context, req *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
|
||||||
|
ctx = contextualMiddlewares(ctx)
|
||||||
|
return d.Service.QueryData(ctx, req)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datasource) CallResource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
|
||||||
|
ctx = contextualMiddlewares(ctx)
|
||||||
|
return d.Service.CallResource(ctx, req, sender)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Datasource) CheckHealth(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
|
||||||
|
ctx = contextualMiddlewares(ctx)
|
||||||
|
return d.Service.CheckHealth(ctx, req)
|
||||||
|
}
|
||||||
23
pkg/tsdb/elasticsearch/standalone/main.go
Normal file
23
pkg/tsdb/elasticsearch/standalone/main.go
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/datasource"
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// Start listening to requests sent from Grafana. This call is blocking so
|
||||||
|
// it won't finish until Grafana shuts down the process or the plugin choose
|
||||||
|
// to exit by itself using os.Exit. Manage automatically manages life cycle
|
||||||
|
// of datasource instances. It accepts datasource instance factory as first
|
||||||
|
// argument. This factory will be automatically called on incoming request
|
||||||
|
// from Grafana to create different instances of SampleDatasource (per datasource
|
||||||
|
// ID). When datasource configuration changed Dispose method will be called and
|
||||||
|
// new datasource instance created using NewSampleDatasource factory.
|
||||||
|
if err := datasource.Manage("elasticsearch", NewDatasource, datasource.ManageOpts{}); err != nil {
|
||||||
|
log.DefaultLogger.Error(err.Error())
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,6 @@ const cloudwatchPlugin = async () =>
|
||||||
await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module');
|
await import(/* webpackChunkName: "cloudwatchPlugin" */ 'app/plugins/datasource/cloudwatch/module');
|
||||||
const dashboardDSPlugin = async () =>
|
const dashboardDSPlugin = async () =>
|
||||||
await import(/* webpackChunkName "dashboardDSPlugin" */ 'app/plugins/datasource/dashboard/module');
|
await import(/* webpackChunkName "dashboardDSPlugin" */ 'app/plugins/datasource/dashboard/module');
|
||||||
const elasticsearchPlugin = async () =>
|
|
||||||
await import(/* webpackChunkName: "elasticsearchPlugin" */ 'app/plugins/datasource/elasticsearch/module');
|
|
||||||
const grafanaPlugin = async () =>
|
const grafanaPlugin = async () =>
|
||||||
await import(/* webpackChunkName: "grafanaPlugin" */ 'app/plugins/datasource/grafana/module');
|
await import(/* webpackChunkName: "grafanaPlugin" */ 'app/plugins/datasource/grafana/module');
|
||||||
const influxdbPlugin = async () =>
|
const influxdbPlugin = async () =>
|
||||||
|
|
@ -75,7 +73,6 @@ const builtInPlugins: Record<string, System.Module | (() => Promise<System.Modul
|
||||||
// datasources
|
// datasources
|
||||||
'core:plugin/cloudwatch': cloudwatchPlugin,
|
'core:plugin/cloudwatch': cloudwatchPlugin,
|
||||||
'core:plugin/dashboard': dashboardDSPlugin,
|
'core:plugin/dashboard': dashboardDSPlugin,
|
||||||
'core:plugin/elasticsearch': elasticsearchPlugin,
|
|
||||||
'core:plugin/grafana': grafanaPlugin,
|
'core:plugin/grafana': grafanaPlugin,
|
||||||
'core:plugin/influxdb': influxdbPlugin,
|
'core:plugin/influxdb': influxdbPlugin,
|
||||||
'core:plugin/mixed': mixedPlugin,
|
'core:plugin/mixed': mixedPlugin,
|
||||||
|
|
|
||||||
0
public/app/plugins/datasource/elasticsearch/CHANGELOG.md
Normal file
0
public/app/plugins/datasource/elasticsearch/CHANGELOG.md
Normal file
|
|
@ -1,8 +1,7 @@
|
||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import { select } from 'react-select-event';
|
import { select } from 'react-select-event';
|
||||||
|
|
||||||
import { DateHistogram } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { DateHistogram } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
|
|
||||||
import { DateHistogramSettingsEditor } from './DateHistogramSettingsEditor';
|
import { DateHistogramSettingsEditor } from './DateHistogramSettingsEditor';
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import { GroupBase, OptionsOrGroups } from 'react-select';
|
||||||
|
|
||||||
import { InternalTimeZones, SelectableValue } from '@grafana/data';
|
import { InternalTimeZones, SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui';
|
import { InlineField, Input, Select, TimeZonePicker } from '@grafana/ui';
|
||||||
import { DateHistogram } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { calendarIntervals } from '../../../../QueryBuilder';
|
import { calendarIntervals } from '../../../../QueryBuilder';
|
||||||
|
import { DateHistogram } from '../../../../dataquery.gen';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour';
|
import { useCreatableSelectPersistedBehaviour } from '../../../hooks/useCreatableSelectPersistedBehaviour';
|
||||||
import { changeBucketAggregationSetting } from '../state/actions';
|
import { changeBucketAggregationSetting } from '../state/actions';
|
||||||
|
|
@ -37,11 +37,11 @@ const hasValue =
|
||||||
const isValidNewOption = (
|
const isValidNewOption = (
|
||||||
inputValue: string,
|
inputValue: string,
|
||||||
_: SelectableValue<string> | null,
|
_: SelectableValue<string> | null,
|
||||||
options: OptionsOrGroups<unknown, GroupBase<unknown>>
|
options: OptionsOrGroups<SelectableValue<string>, GroupBase<SelectableValue<string>>>
|
||||||
) => {
|
) => {
|
||||||
// TODO: would be extremely nice here to allow only template variables and values that are
|
// TODO: would be extremely nice here to allow only template variables and values that are
|
||||||
// valid date histogram's Interval options
|
// valid date histogram's Interval options
|
||||||
const valueExists = (options as Array<SelectableValue<string>>).some(hasValue(inputValue));
|
const valueExists = options.some(hasValue(inputValue));
|
||||||
// we also don't want users to create "empty" values
|
// we also don't want users to create "empty" values
|
||||||
return !valueExists && inputValue.trim().length > 0;
|
return !valueExists && inputValue.trim().length > 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,8 @@ import { uniqueId } from 'lodash';
|
||||||
import { useEffect, useRef } from 'react';
|
import { useEffect, useRef } from 'react';
|
||||||
|
|
||||||
import { InlineField, Input, QueryField } from '@grafana/ui';
|
import { InlineField, Input, QueryField } from '@grafana/ui';
|
||||||
import { Filters } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { Filters } from '../../../../../dataquery.gen';
|
||||||
import { useDispatch, useStatelessReducer } from '../../../../../hooks/useStatelessReducer';
|
import { useDispatch, useStatelessReducer } from '../../../../../hooks/useStatelessReducer';
|
||||||
import { AddRemove } from '../../../../AddRemove';
|
import { AddRemove } from '../../../../AddRemove';
|
||||||
import { changeBucketAggregationSetting } from '../../state/actions';
|
import { changeBucketAggregationSetting } from '../../state/actions';
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { Filter } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { Filter } from '../../../../../../dataquery.gen';
|
||||||
|
|
||||||
export const addFilter = createAction('@bucketAggregations/filter/add');
|
export const addFilter = createAction('@bucketAggregations/filter/add');
|
||||||
export const removeFilter = createAction<number>('@bucketAggregations/filter/remove');
|
export const removeFilter = createAction<number>('@bucketAggregations/filter/remove');
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import { reducerTester } from 'test/core/redux/reducerTester';
|
import { Filter } from '../../../../../../dataquery.gen';
|
||||||
|
import { reducerTester } from '../../../../../reducerTester';
|
||||||
import { Filter } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { addFilter, changeFilter, removeFilter } from './actions';
|
import { addFilter, changeFilter, removeFilter } from './actions';
|
||||||
import { reducer } from './reducer';
|
import { reducer } from './reducer';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
|
||||||
import { Filter } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { Filter } from '../../../../../../dataquery.gen';
|
||||||
|
|
||||||
import { defaultFilter } from '../utils';
|
import { defaultFilter } from '../utils';
|
||||||
|
|
||||||
import { addFilter, changeFilter, removeFilter } from './actions';
|
import { addFilter, changeFilter, removeFilter } from './actions';
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,3 @@
|
||||||
import { Filter } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { Filter } from '../../../../../dataquery.gen';
|
||||||
|
|
||||||
export const defaultFilter = (): Filter => ({ label: '', query: '*' });
|
export const defaultFilter = (): Filter => ({ label: '', query: '*' });
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,7 @@
|
||||||
import { fireEvent, screen } from '@testing-library/react';
|
import { fireEvent, screen } from '@testing-library/react';
|
||||||
import selectEvent from 'react-select-event';
|
import selectEvent from 'react-select-event';
|
||||||
|
|
||||||
import {
|
import { Average, Derivative, ElasticsearchDataQuery, Terms, TopMetrics } from '../../../../dataquery.gen';
|
||||||
Average,
|
|
||||||
Derivative,
|
|
||||||
ElasticsearchDataQuery,
|
|
||||||
Terms,
|
|
||||||
TopMetrics,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { renderWithESProvider } from '../../../../test-helpers/render';
|
import { renderWithESProvider } from '../../../../test-helpers/render';
|
||||||
import { describeMetric } from '../../../../utils';
|
import { describeMetric } from '../../../../utils';
|
||||||
|
|
|
||||||
|
|
@ -2,15 +2,9 @@ import { uniqueId } from 'lodash';
|
||||||
import { useRef } from 'react';
|
import { useRef } from 'react';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { InlineField, Select, Input } from '@grafana/ui';
|
import { InlineField, Input, Select } from '@grafana/ui';
|
||||||
import {
|
|
||||||
Terms,
|
|
||||||
ExtendedStats,
|
|
||||||
ExtendedStatMetaType,
|
|
||||||
Percentiles,
|
|
||||||
MetricAggregation,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { ExtendedStats, MetricAggregation, Percentiles, Terms } from '../../../../dataquery.gen';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { describeMetric } from '../../../../utils';
|
import { describeMetric } from '../../../../utils';
|
||||||
import { useQuery } from '../../ElasticsearchQueryContext';
|
import { useQuery } from '../../ElasticsearchQueryContext';
|
||||||
|
|
@ -105,7 +99,7 @@ function createOrderByOptionsForExtendedStats(metric: ExtendedStats): Selectable
|
||||||
if (!metric.meta) {
|
if (!metric.meta) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const metaKeys = Object.keys(metric.meta) as ExtendedStatMetaType[];
|
const metaKeys = Object.keys(metric.meta);
|
||||||
return metaKeys
|
return metaKeys
|
||||||
.filter((key) => metric.meta?.[key])
|
.filter((key) => metric.meta?.[key])
|
||||||
.map((key) => {
|
.map((key) => {
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { uniqueId } from 'lodash';
|
||||||
import { ComponentProps, useRef } from 'react';
|
import { ComponentProps, useRef } from 'react';
|
||||||
|
|
||||||
import { InlineField, Input } from '@grafana/ui';
|
import { InlineField, Input } from '@grafana/ui';
|
||||||
import { BucketAggregation } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { BucketAggregation } from '../../../../dataquery.gen';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { SettingsEditorContainer } from '../../SettingsEditorContainer';
|
import { SettingsEditorContainer } from '../../SettingsEditorContainer';
|
||||||
import { changeBucketAggregationSetting } from '../state/actions';
|
import { changeBucketAggregationSetting } from '../state/actions';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { BucketAggregation } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { BucketAggregation } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { defaultGeoHashPrecisionString } from '../../../../queryDef';
|
import { defaultGeoHashPrecisionString } from '../../../../queryDef';
|
||||||
import { describeMetric, convertOrderByToMetricId } from '../../../../utils';
|
import { convertOrderByToMetricId, describeMetric } from '../../../../utils';
|
||||||
import { useQuery } from '../../ElasticsearchQueryContext';
|
import { useQuery } from '../../ElasticsearchQueryContext';
|
||||||
import { bucketAggregationConfig, orderByOptions, orderOptions } from '../utils';
|
import { bucketAggregationConfig, orderByOptions, orderOptions } from '../utils';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,6 @@
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import {
|
import { BucketAggregation, BucketAggregationType, BucketAggregationWithField } from '../../../../dataquery.gen';
|
||||||
BucketAggregation,
|
|
||||||
BucketAggregationType,
|
|
||||||
BucketAggregationWithField,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
export const addBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/add');
|
export const addBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/add');
|
||||||
export const removeBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/remove');
|
export const removeBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/remove');
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,4 @@
|
||||||
import {
|
import { BucketAggregation, DateHistogram, ElasticsearchDataQuery } from '../../../../dataquery.gen';
|
||||||
BucketAggregation,
|
|
||||||
DateHistogram,
|
|
||||||
ElasticsearchDataQuery,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { defaultBucketAgg } from '../../../../queryDef';
|
import { defaultBucketAgg } from '../../../../queryDef';
|
||||||
import { reducerTester } from '../../../reducerTester';
|
import { reducerTester } from '../../../reducerTester';
|
||||||
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Action } from '@reduxjs/toolkit';
|
import { Action } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { BucketAggregation, ElasticsearchDataQuery, Terms } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { BucketAggregation, ElasticsearchDataQuery, Terms } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { defaultBucketAgg } from '../../../../queryDef';
|
import { defaultBucketAgg } from '../../../../queryDef';
|
||||||
import { removeEmpty } from '../../../../utils';
|
import { removeEmpty } from '../../../../utils';
|
||||||
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
|
||||||
|
|
@ -47,11 +46,12 @@ export const createReducer =
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
TODO: The previous version of the query editor was keeping some of the old bucket aggregation's configurations
|
TODO: The previous version of the query editor was keeping some of the old bucket aggregation's configurations
|
||||||
in the new selected one (such as field or some settings).
|
in the new selected one (such as field or some settings).
|
||||||
It the future would be nice to have the same behavior but it's hard without a proper definition,
|
It the future would be nice to have the same behavior but it's hard without a proper definition,
|
||||||
as Elasticsearch will error sometimes if some settings are not compatible.
|
as Elasticsearch will error sometimes if some settings are not compatible.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
return {
|
return {
|
||||||
id: bucketAgg.id,
|
id: bucketAgg.id,
|
||||||
type: action.payload.newType,
|
type: action.payload.newType,
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,10 @@ import { css } from '@emotion/css';
|
||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import { Fragment, useEffect } from 'react';
|
import { Fragment, useEffect } from 'react';
|
||||||
|
|
||||||
import { Input, InlineLabel } from '@grafana/ui';
|
import { InlineLabel, Input } from '@grafana/ui';
|
||||||
import { BucketScript, MetricAggregation } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { useStatelessReducer, useDispatch } from '../../../../../hooks/useStatelessReducer';
|
import { BucketScript, MetricAggregation } from '../../../../../dataquery.gen';
|
||||||
|
import { useDispatch, useStatelessReducer } from '../../../../../hooks/useStatelessReducer';
|
||||||
import { AddRemove } from '../../../../AddRemove';
|
import { AddRemove } from '../../../../AddRemove';
|
||||||
import { MetricPicker } from '../../../../MetricPicker';
|
import { MetricPicker } from '../../../../MetricPicker';
|
||||||
import { changeMetricAttribute } from '../../state/actions';
|
import { changeMetricAttribute } from '../../state/actions';
|
||||||
|
|
@ -13,9 +13,9 @@ import { SettingField } from '../SettingField';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
addPipelineVariable,
|
addPipelineVariable,
|
||||||
|
changePipelineVariableMetric,
|
||||||
removePipelineVariable,
|
removePipelineVariable,
|
||||||
renamePipelineVariable,
|
renamePipelineVariable,
|
||||||
changePipelineVariableMetric,
|
|
||||||
} from './state/actions';
|
} from './state/actions';
|
||||||
import { reducer } from './state/reducer';
|
import { reducer } from './state/reducer';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { PipelineVariable } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { PipelineVariable } from '../../../../../../dataquery.gen';
|
||||||
|
|
||||||
import { reducerTester } from '../../../../../reducerTester';
|
import { reducerTester } from '../../../../../reducerTester';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Action } from '@reduxjs/toolkit';
|
import { Action } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { PipelineVariable } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { PipelineVariable } from '../../../../../../dataquery.gen';
|
||||||
|
|
||||||
import { defaultPipelineVariable, generatePipelineVariableName } from '../utils';
|
import { defaultPipelineVariable, generatePipelineVariableName } from '../utils';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { PipelineVariable } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { PipelineVariable } from '../../../../../dataquery.gen';
|
||||||
|
|
||||||
export const defaultPipelineVariable = (name: string): PipelineVariable => ({ name, pipelineAgg: '' });
|
export const defaultPipelineVariable = (name: string): PipelineVariable => ({ name, pipelineAgg: '' });
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,11 +2,8 @@ import { uniqueId } from 'lodash';
|
||||||
import { ComponentProps, useState } from 'react';
|
import { ComponentProps, useState } from 'react';
|
||||||
|
|
||||||
import { InlineField, Input, TextArea } from '@grafana/ui';
|
import { InlineField, Input, TextArea } from '@grafana/ui';
|
||||||
import {
|
|
||||||
MetricAggregationWithSettings,
|
|
||||||
MetricAggregationWithInlineScript,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { MetricAggregationWithInlineScript, MetricAggregationWithSettings } from '../../../../dataquery.gen';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { getScriptValue } from '../../../../utils';
|
import { getScriptValue } from '../../../../utils';
|
||||||
import { SettingKeyOf } from '../../../types';
|
import { SettingKeyOf } from '../../../types';
|
||||||
|
|
@ -33,9 +30,11 @@ export function SettingField<T extends MetricAggregationWithSettings, K extends
|
||||||
const [id] = useState(uniqueId(`es-field-id-`));
|
const [id] = useState(uniqueId(`es-field-id-`));
|
||||||
const settings = metric.settings;
|
const settings = metric.settings;
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
let defaultValue = settings?.[settingName as keyof typeof settings] || '';
|
let defaultValue = settings?.[settingName as keyof typeof settings] || '';
|
||||||
|
|
||||||
if (settingName === 'script') {
|
if (settingName === 'script') {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
defaultValue = getScriptValue(metric as MetricAggregationWithInlineScript);
|
defaultValue = getScriptValue(metric as MetricAggregationWithInlineScript);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@ import { css } from '@emotion/css';
|
||||||
|
|
||||||
import { SelectableValue } from '@grafana/data';
|
import { SelectableValue } from '@grafana/data';
|
||||||
import { AsyncMultiSelect, InlineField, SegmentAsync, Select } from '@grafana/ui';
|
import { AsyncMultiSelect, InlineField, SegmentAsync, Select } from '@grafana/ui';
|
||||||
import { TopMetrics } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { TopMetrics } from '../../../../dataquery.gen';
|
||||||
import { useFields } from '../../../../hooks/useFields';
|
import { useFields } from '../../../../hooks/useFields';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { orderOptions } from '../../BucketAggregationsEditor/utils';
|
import { orderOptions } from '../../BucketAggregationsEditor/utils';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { fireEvent, render, screen } from '@testing-library/react';
|
import { fireEvent, render, screen } from '@testing-library/react';
|
||||||
|
|
||||||
import { getDefaultTimeRange } from '@grafana/data';
|
import { getDefaultTimeRange } from '@grafana/data';
|
||||||
import { ElasticsearchDataQuery } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { ElasticsearchDataQuery } from '../../../../dataquery.gen';
|
||||||
import { ElasticDatasource } from '../../../../datasource';
|
import { ElasticDatasource } from '../../../../datasource';
|
||||||
import { ElasticsearchProvider } from '../../ElasticsearchQueryContext';
|
import { ElasticsearchProvider } from '../../ElasticsearchQueryContext';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import { uniqueId } from 'lodash';
|
import { uniqueId } from 'lodash';
|
||||||
import { ComponentProps, useId, useRef, useState } from 'react';
|
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
import { ComponentProps, useId, useRef, useState } from 'react';
|
||||||
|
|
||||||
import { InlineField, Input, InlineSwitch, Select } from '@grafana/ui';
|
import { InlineField, InlineSwitch, Input, Select } from '@grafana/ui';
|
||||||
import { MetricAggregation, ExtendedStat } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
|
import { ExtendedStat, MetricAggregation } from '../../../../dataquery.gen';
|
||||||
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
import { useDispatch } from '../../../../hooks/useStatelessReducer';
|
||||||
import { extendedStats } from '../../../../queryDef';
|
import { extendedStats } from '../../../../queryDef';
|
||||||
import { SettingsEditorContainer } from '../../SettingsEditorContainer';
|
import { SettingsEditorContainer } from '../../SettingsEditorContainer';
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { MetricAggregation } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { MetricAggregation } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { extendedStats } from '../../../../queryDef';
|
import { extendedStats } from '../../../../queryDef';
|
||||||
|
|
||||||
const hasValue = (value: string) => (object: { value: string }) => object.value === value;
|
const hasValue = (value: string) => (object: { value: string }) => object.value === value;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { createAction } from '@reduxjs/toolkit';
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { MetricAggregation, MetricAggregationWithSettings } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { MetricAggregation, MetricAggregationWithSettings } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { MetricAggregationWithMeta } from '../../../../types';
|
import { MetricAggregationWithMeta } from '../../../../types';
|
||||||
|
|
||||||
export const addMetric = createAction<MetricAggregation['id']>('@metrics/add');
|
export const addMetric = createAction<MetricAggregation['id']>('@metrics/add');
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,4 @@
|
||||||
import {
|
import { Derivative, ElasticsearchDataQuery, ExtendedStats, MetricAggregation } from '../../../../dataquery.gen';
|
||||||
MetricAggregation,
|
|
||||||
ElasticsearchDataQuery,
|
|
||||||
Derivative,
|
|
||||||
ExtendedStats,
|
|
||||||
} from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
|
||||||
|
|
||||||
import { defaultMetricAgg } from '../../../../queryDef';
|
import { defaultMetricAgg } from '../../../../queryDef';
|
||||||
import { reducerTester } from '../../../reducerTester';
|
import { reducerTester } from '../../../reducerTester';
|
||||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import { Action } from '@reduxjs/toolkit';
|
import { Action } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
import { ElasticsearchDataQuery, MetricAggregation } from 'app/plugins/datasource/elasticsearch/dataquery.gen';
|
import { ElasticsearchDataQuery, MetricAggregation } from '../../../../dataquery.gen';
|
||||||
|
|
||||||
import { defaultMetricAgg, queryTypeToMetricType } from '../../../../queryDef';
|
import { defaultMetricAgg, queryTypeToMetricType } from '../../../../queryDef';
|
||||||
import { removeEmpty } from '../../../../utils';
|
import { removeEmpty } from '../../../../utils';
|
||||||
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
import { changeEditorTypeAndResetQuery, initQuery } from '../../state';
|
||||||
|
|
@ -57,6 +56,7 @@ export const reducer = (
|
||||||
It the future would be nice to have the same behavior but it's hard without a proper definition,
|
It the future would be nice to have the same behavior but it's hard without a proper definition,
|
||||||
as Elasticsearch will error sometimes if some settings are not compatible.
|
as Elasticsearch will error sometimes if some settings are not compatible.
|
||||||
*/
|
*/
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
return {
|
return {
|
||||||
id: metric.id,
|
id: metric.id,
|
||||||
type: action.payload.type,
|
type: action.payload.type,
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { AnyAction } from '@reduxjs/toolkit';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { Action } from 'redux';
|
import { Action } from 'redux';
|
||||||
|
|
||||||
import { StoreState } from 'app/types/store';
|
import { StoreState } from '../types/store';
|
||||||
|
|
||||||
type GrafanaReducer<S = StoreState, A extends Action = AnyAction> = (state: S, action: A) => S;
|
type GrafanaReducer<S = StoreState, A extends Action = AnyAction> = (state: S, action: A) => S;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
import '@grafana/plugin-configs/jest/jest-setup';
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
import defaultConfig from '@grafana/plugin-configs/jest/jest.config.js';
|
||||||
|
|
||||||
|
export default defaultConfig;
|
||||||
62
public/app/plugins/datasource/elasticsearch/package.json
Normal file
62
public/app/plugins/datasource/elasticsearch/package.json
Normal file
|
|
@ -0,0 +1,62 @@
|
||||||
|
{
|
||||||
|
"name": "@grafana-plugins/elasticsearch",
|
||||||
|
"description": "Grafana data source for Elasticsearch",
|
||||||
|
"private": true,
|
||||||
|
"version": "12.4.0-pre",
|
||||||
|
"dependencies": {
|
||||||
|
"@emotion/css": "11.13.5",
|
||||||
|
"@grafana/aws-sdk": "0.8.3",
|
||||||
|
"@grafana/data": "12.4.0-pre",
|
||||||
|
"@grafana/plugin-ui": "^0.11.1",
|
||||||
|
"@grafana/runtime": "12.4.0-pre",
|
||||||
|
"@grafana/schema": "12.4.0-pre",
|
||||||
|
"@grafana/ui": "12.4.0-pre",
|
||||||
|
"@reduxjs/toolkit": "2.10.1",
|
||||||
|
"lodash": "4.17.21",
|
||||||
|
"lucene": "^2.1.1",
|
||||||
|
"react": "18.3.1",
|
||||||
|
"react-dom": "18.3.1",
|
||||||
|
"react-redux": "9.2.0",
|
||||||
|
"react-select": "5.10.2",
|
||||||
|
"react-use": "17.6.0",
|
||||||
|
"redux": "5.0.1",
|
||||||
|
"redux-thunk": "3.1.0",
|
||||||
|
"rxjs": "7.8.2",
|
||||||
|
"semver": "7.7.3",
|
||||||
|
"tslib": "2.8.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@grafana/e2e-selectors": "12.4.0-pre",
|
||||||
|
"@grafana/plugin-configs": "12.4.0-pre",
|
||||||
|
"@testing-library/dom": "10.4.1",
|
||||||
|
"@testing-library/jest-dom": "6.6.4",
|
||||||
|
"@testing-library/react": "16.3.0",
|
||||||
|
"@testing-library/user-event": "14.6.1",
|
||||||
|
"@types/jest": "29.5.14",
|
||||||
|
"@types/lodash": "4.17.20",
|
||||||
|
"@types/lucene": "^2",
|
||||||
|
"@types/node": "24.10.1",
|
||||||
|
"@types/react": "18.3.18",
|
||||||
|
"@types/react-dom": "18.3.5",
|
||||||
|
"@types/semver": "7.7.1",
|
||||||
|
"jest": "29.7.0",
|
||||||
|
"react-select-event": "5.5.1",
|
||||||
|
"ts-node": "10.9.2",
|
||||||
|
"typescript": "5.9.2",
|
||||||
|
"webpack": "5.101.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@grafana/runtime": "*"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "webpack -c ./webpack.config.ts --env production",
|
||||||
|
"build:commit": "webpack -c ./webpack.config.ts --env production --env commit=$(git rev-parse --short HEAD)",
|
||||||
|
"dev": "webpack -w -c ./webpack.config.ts --env development",
|
||||||
|
"test": "jest --watch --onlyChanged",
|
||||||
|
"test:ci": "jest --maxWorkers 4"
|
||||||
|
},
|
||||||
|
"packageManager": "yarn@4.11.0"
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
"type": "datasource",
|
"type": "datasource",
|
||||||
"name": "Elasticsearch",
|
"name": "Elasticsearch",
|
||||||
"id": "elasticsearch",
|
"id": "elasticsearch",
|
||||||
|
"executable": "gpx_elasticsearch",
|
||||||
"category": "logging",
|
"category": "logging",
|
||||||
"info": {
|
"info": {
|
||||||
"description": "Open source logging & analytics database",
|
"description": "Open source logging & analytics database",
|
||||||
|
|
@ -27,7 +28,8 @@
|
||||||
"name": "Documentation",
|
"name": "Documentation",
|
||||||
"url": "https://grafana.com/docs/grafana/latest/datasources/elasticsearch/"
|
"url": "https://grafana.com/docs/grafana/latest/datasources/elasticsearch/"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"version": "%VERSION%"
|
||||||
},
|
},
|
||||||
"alerting": true,
|
"alerting": true,
|
||||||
"annotations": true,
|
"annotations": true,
|
||||||
|
|
@ -36,5 +38,9 @@
|
||||||
"backend": true,
|
"backend": true,
|
||||||
"queryOptions": {
|
"queryOptions": {
|
||||||
"minInterval": true
|
"minInterval": true
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"grafanaDependency": ">=11.6.0",
|
||||||
|
"plugins": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
public/app/plugins/datasource/elasticsearch/project.json
Normal file
9
public/app/plugins/datasource/elasticsearch/project.json
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"$schema": "../../../../../node_modules/nx/schemas/project-schema.json",
|
||||||
|
"projectType": "library",
|
||||||
|
"tags": ["scope:plugin", "type:datasource"],
|
||||||
|
"targets": {
|
||||||
|
"build": {},
|
||||||
|
"dev": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { createAction } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
import { StoreState } from '../../types/store';
|
||||||
|
|
||||||
|
export type CleanUpAction = (state: StoreState) => void;
|
||||||
|
|
||||||
|
export interface CleanUpPayload {
|
||||||
|
cleanupAction: CleanUpAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const cleanUpAction = createAction<CleanUpPayload>('core/cleanUpState');
|
||||||
21
public/app/plugins/datasource/elasticsearch/reducers/root.ts
Normal file
21
public/app/plugins/datasource/elasticsearch/reducers/root.ts
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
import { ReducersMapObject } from '@reduxjs/toolkit';
|
||||||
|
import { Action as AnyAction, combineReducers } from 'redux';
|
||||||
|
|
||||||
|
const addedReducers = {
|
||||||
|
defaultReducer: (state = {}) => state,
|
||||||
|
templating: (state = { lastKey: 'key' }) => state,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addReducer = (newReducers: ReducersMapObject) => {
|
||||||
|
Object.assign(addedReducers, newReducers);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createRootReducer = () => {
|
||||||
|
const appReducer = combineReducers({
|
||||||
|
...addedReducers,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (state: Parameters<typeof appReducer>[0], action: AnyAction) => {
|
||||||
|
return appReducer(state, action);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
import { createListenerMiddleware, configureStore as reduxConfigureStore } from '@reduxjs/toolkit';
|
||||||
|
import { setupListeners } from '@reduxjs/toolkit/query';
|
||||||
|
import { Middleware } from 'redux';
|
||||||
|
|
||||||
|
import { addReducer, createRootReducer } from '../reducers/root';
|
||||||
|
import { StoreState } from '../types/store';
|
||||||
|
|
||||||
|
import { setStore } from './store';
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function addRootReducer(reducers: any) {
|
||||||
|
// this is ok now because we add reducers before configureStore is called
|
||||||
|
// in the future if we want to add reducers during runtime
|
||||||
|
// we'll have to solve this in a more dynamic way
|
||||||
|
addReducer(reducers);
|
||||||
|
}
|
||||||
|
|
||||||
|
const listenerMiddleware = createListenerMiddleware();
|
||||||
|
const extraMiddleware: Middleware[] = [];
|
||||||
|
|
||||||
|
export function addExtraMiddleware(middleware: Middleware) {
|
||||||
|
extraMiddleware.push(middleware);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function configureStore(initialState?: Partial<StoreState>) {
|
||||||
|
const store = reduxConfigureStore({
|
||||||
|
reducer: createRootReducer(),
|
||||||
|
middleware: (getDefaultMiddleware) =>
|
||||||
|
getDefaultMiddleware({ thunk: true, serializableCheck: false, immutableCheck: false }).concat(
|
||||||
|
listenerMiddleware.middleware,
|
||||||
|
...extraMiddleware
|
||||||
|
),
|
||||||
|
devTools: process.env.NODE_ENV !== 'production',
|
||||||
|
preloadedState: {
|
||||||
|
...initialState,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// this enables "refetchOnFocus" and "refetchOnReconnect" for RTK Query
|
||||||
|
setupListeners(store.dispatch);
|
||||||
|
|
||||||
|
setStore(store);
|
||||||
|
return store;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RootState = ReturnType<ReturnType<typeof configureStore>['getState']>;
|
||||||
|
export type AppDispatch = ReturnType<typeof configureStore>['dispatch'];
|
||||||
26
public/app/plugins/datasource/elasticsearch/store/store.ts
Normal file
26
public/app/plugins/datasource/elasticsearch/store/store.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { Store } from 'redux';
|
||||||
|
|
||||||
|
import { StoreState } from '../types/store';
|
||||||
|
|
||||||
|
export let store: Store<StoreState>;
|
||||||
|
|
||||||
|
export function setStore(newStore: Store<StoreState>) {
|
||||||
|
store = newStore;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getState(): StoreState {
|
||||||
|
if (!store || !store.getState) {
|
||||||
|
return { defaultReducer: () => ({}), templating: { lastKey: 'key' } }; // used by tests
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.getState();
|
||||||
|
}
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
export function dispatch(action: any) {
|
||||||
|
if (!store || !store.getState) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return store.dispatch(action);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"jsx": "react-jsx",
|
||||||
|
"types": ["node", "jest", "@testing-library/jest-dom"]
|
||||||
|
},
|
||||||
|
"extends": "@grafana/plugin-configs/tsconfig.json",
|
||||||
|
"include": ["."]
|
||||||
|
}
|
||||||
46
public/app/plugins/datasource/elasticsearch/types/store.ts
Normal file
46
public/app/plugins/datasource/elasticsearch/types/store.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
/* eslint-disable no-restricted-imports */
|
||||||
|
import {
|
||||||
|
Action,
|
||||||
|
addListener as addListenerUntyped,
|
||||||
|
AsyncThunk,
|
||||||
|
AsyncThunkOptions,
|
||||||
|
AsyncThunkPayloadCreator,
|
||||||
|
createAsyncThunk as createAsyncThunkUntyped,
|
||||||
|
PayloadAction,
|
||||||
|
TypedAddListener,
|
||||||
|
} from '@reduxjs/toolkit';
|
||||||
|
import {
|
||||||
|
TypedUseSelectorHook,
|
||||||
|
useDispatch as useDispatchUntyped,
|
||||||
|
useSelector as useSelectorUntyped,
|
||||||
|
} from 'react-redux';
|
||||||
|
import { ThunkDispatch as GenericThunkDispatch, ThunkAction } from 'redux-thunk';
|
||||||
|
|
||||||
|
import type { createRootReducer } from '../reducers/root';
|
||||||
|
import { AppDispatch, RootState } from '../store/configureStore';
|
||||||
|
import { dispatch as storeDispatch } from '../store/store';
|
||||||
|
|
||||||
|
export type StoreState = ReturnType<ReturnType<typeof createRootReducer>>;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Utility type to get strongly types thunks
|
||||||
|
*/
|
||||||
|
export type ThunkResult<R> = ThunkAction<R, StoreState, undefined, PayloadAction<unknown>>;
|
||||||
|
|
||||||
|
export type ThunkDispatch = GenericThunkDispatch<StoreState, undefined, Action>;
|
||||||
|
|
||||||
|
// Typed useDispatch & useSelector hooks
|
||||||
|
export const useDispatch: () => AppDispatch = useDispatchUntyped;
|
||||||
|
export const useSelector: TypedUseSelectorHook<RootState> = useSelectorUntyped;
|
||||||
|
|
||||||
|
type DefaultThunkApiConfig = { dispatch: AppDispatch; state: StoreState };
|
||||||
|
export const createAsyncThunk = <Returned, ThunkArg = void, ThunkApiConfig extends {} = DefaultThunkApiConfig>(
|
||||||
|
typePrefix: string,
|
||||||
|
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, ThunkApiConfig>,
|
||||||
|
options?: AsyncThunkOptions<ThunkArg, ThunkApiConfig>
|
||||||
|
): AsyncThunk<Returned, ThunkArg, ThunkApiConfig> =>
|
||||||
|
createAsyncThunkUntyped<Returned, ThunkArg, ThunkApiConfig>(typePrefix, payloadCreator, options);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
|
export const addListener = addListenerUntyped as TypedAddListener<RootState, AppDispatch>;
|
||||||
|
export const dispatch: AppDispatch = storeDispatch;
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import type { Configuration } from 'webpack';
|
||||||
|
|
||||||
|
import grafanaConfig, { type Env } from '@grafana/plugin-configs/webpack.config.ts';
|
||||||
|
|
||||||
|
const config = async (env: Env): Promise<Configuration> => {
|
||||||
|
return await grafanaConfig(env);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
||||||
47
yarn.lock
47
yarn.lock
|
|
@ -2610,6 +2610,53 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"@grafana-plugins/elasticsearch@workspace:public/app/plugins/datasource/elasticsearch":
|
||||||
|
version: 0.0.0-use.local
|
||||||
|
resolution: "@grafana-plugins/elasticsearch@workspace:public/app/plugins/datasource/elasticsearch"
|
||||||
|
dependencies:
|
||||||
|
"@emotion/css": "npm:11.13.5"
|
||||||
|
"@grafana/aws-sdk": "npm:0.8.3"
|
||||||
|
"@grafana/data": "npm:12.4.0-pre"
|
||||||
|
"@grafana/e2e-selectors": "npm:12.4.0-pre"
|
||||||
|
"@grafana/plugin-configs": "npm:12.4.0-pre"
|
||||||
|
"@grafana/plugin-ui": "npm:^0.11.1"
|
||||||
|
"@grafana/runtime": "npm:12.4.0-pre"
|
||||||
|
"@grafana/schema": "npm:12.4.0-pre"
|
||||||
|
"@grafana/ui": "npm:12.4.0-pre"
|
||||||
|
"@reduxjs/toolkit": "npm:2.10.1"
|
||||||
|
"@testing-library/dom": "npm:10.4.1"
|
||||||
|
"@testing-library/jest-dom": "npm:6.6.4"
|
||||||
|
"@testing-library/react": "npm:16.3.0"
|
||||||
|
"@testing-library/user-event": "npm:14.6.1"
|
||||||
|
"@types/jest": "npm:29.5.14"
|
||||||
|
"@types/lodash": "npm:4.17.20"
|
||||||
|
"@types/lucene": "npm:^2"
|
||||||
|
"@types/node": "npm:24.10.1"
|
||||||
|
"@types/react": "npm:18.3.18"
|
||||||
|
"@types/react-dom": "npm:18.3.5"
|
||||||
|
"@types/semver": "npm:7.7.1"
|
||||||
|
jest: "npm:29.7.0"
|
||||||
|
lodash: "npm:4.17.21"
|
||||||
|
lucene: "npm:^2.1.1"
|
||||||
|
react: "npm:18.3.1"
|
||||||
|
react-dom: "npm:18.3.1"
|
||||||
|
react-redux: "npm:9.2.0"
|
||||||
|
react-select: "npm:5.10.2"
|
||||||
|
react-select-event: "npm:5.5.1"
|
||||||
|
react-use: "npm:17.6.0"
|
||||||
|
redux: "npm:5.0.1"
|
||||||
|
redux-thunk: "npm:3.1.0"
|
||||||
|
rxjs: "npm:7.8.2"
|
||||||
|
semver: "npm:7.7.3"
|
||||||
|
ts-node: "npm:10.9.2"
|
||||||
|
tslib: "npm:2.8.1"
|
||||||
|
typescript: "npm:5.9.2"
|
||||||
|
webpack: "npm:5.101.0"
|
||||||
|
peerDependencies:
|
||||||
|
"@grafana/runtime": "*"
|
||||||
|
languageName: unknown
|
||||||
|
linkType: soft
|
||||||
|
|
||||||
"@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor":
|
"@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor":
|
||||||
version: 0.0.0-use.local
|
version: 0.0.0-use.local
|
||||||
resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor"
|
resolution: "@grafana-plugins/grafana-azure-monitor-datasource@workspace:public/app/plugins/datasource/azuremonitor"
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue