From 5e2f08de316dab68a25e251bf7b6eb80c01774c0 Mon Sep 17 00:00:00 2001 From: Tobias Skarhed <1438972+tskarhed@users.noreply.github.com> Date: Thu, 13 Jun 2024 13:41:14 +0200 Subject: [PATCH 01/13] New Select: Initial scaffolding (#89114) * Initial scaffolding * Extend props from Input * Rename to Combobox * Use search icon * Remove use of SelectableValue * Remove unused import * Memoize --- packages/grafana-ui/package.json | 1 + .../Combobox/Combobox.internal.story.tsx | 47 ++++++++++++++ .../src/components/Combobox/Combobox.tsx | 64 +++++++++++++++++++ yarn.lock | 27 +++++++- 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 packages/grafana-ui/src/components/Combobox/Combobox.internal.story.tsx create mode 100644 packages/grafana-ui/src/components/Combobox/Combobox.tsx diff --git a/packages/grafana-ui/package.json b/packages/grafana-ui/package.json index f497b997252..78a915e534b 100644 --- a/packages/grafana-ui/package.json +++ b/packages/grafana-ui/package.json @@ -66,6 +66,7 @@ "classnames": "2.5.1", "d3": "7.9.0", "date-fns": "3.6.0", + "downshift": "^9.0.6", "hoist-non-react-statics": "3.3.2", "i18next": "^23.0.0", "i18next-browser-languagedetector": "^7.0.2", diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.internal.story.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.internal.story.tsx new file mode 100644 index 00000000000..e56a989a526 --- /dev/null +++ b/packages/grafana-ui/src/components/Combobox/Combobox.internal.story.tsx @@ -0,0 +1,47 @@ +import { action } from '@storybook/addon-actions'; +import { Meta, StoryFn } from '@storybook/react'; +import React, { useState } from 'react'; + +import { Combobox } from './Combobox'; + +const meta: Meta = { + title: 'Forms/Combobox', + component: Combobox, + args: { + loading: undefined, + invalid: undefined, + placeholder: 'Select an option...', + options: [ + { label: 'Apple', value: 'apple' }, + { label: 'Banana', value: 'banana' }, + { label: 'Carrot', value: 'carrot' }, + { label: 'Dill', value: 'dill' }, + { label: 'Eggplant', value: 'eggplant' }, + { label: 'Fennel', value: 'fennel' }, + { label: 'Grape', value: 'grape' }, + { label: 'Honeydew', value: 'honeydew' }, + { label: 'Iceberg Lettuce', value: 'iceberg-lettuce' }, + { label: 'Jackfruit', value: 'jackfruit' }, + { label: '1', value: 1 }, + { label: '2', value: 2 }, + { label: '3', value: 3 }, + ], + value: 'banana', + }, +}; + +export const Basic: StoryFn = (args) => { + const [value, setValue] = useState(args.value); + return ( + { + setValue(val.value); + action('onChange')(val); + }} + /> + ); +}; + +export default meta; diff --git a/packages/grafana-ui/src/components/Combobox/Combobox.tsx b/packages/grafana-ui/src/components/Combobox/Combobox.tsx new file mode 100644 index 00000000000..ff83f3edc27 --- /dev/null +++ b/packages/grafana-ui/src/components/Combobox/Combobox.tsx @@ -0,0 +1,64 @@ +import { useCombobox } from 'downshift'; +import React, { useMemo, useState } from 'react'; + +import { Icon } from '../Icon/Icon'; +import { Input, Props as InputProps } from '../Input/Input'; + +type Value = string | number; +type Option = { + label: string; + value: Value; +}; + +interface ComboboxProps + extends Omit { + onChange: (val: Option) => void; + value: Value; + options: Option[]; +} + +function itemToString(item: Option | null) { + return item?.label || ''; +} + +function itemFilter(inputValue: string) { + const lowerCasedInputValue = inputValue.toLowerCase(); + + return (item: Option) => { + return ( + !inputValue || + item?.label?.toLowerCase().includes(lowerCasedInputValue) || + item?.value?.toString().toLowerCase().includes(lowerCasedInputValue) + ); + }; +} + +export const Combobox = ({ options, onChange, value, ...restProps }: ComboboxProps) => { + const [items, setItems] = useState(options); + const selectedItem = useMemo(() => options.find((option) => option.value === value) || null, [options, value]); + + const { getInputProps, getMenuProps, getItemProps, isOpen } = useCombobox({ + items, + itemToString, + selectedItem, + onInputValueChange: ({ inputValue }) => { + setItems(options.filter(itemFilter(inputValue))); + }, + onSelectedItemChange: ({ selectedItem }) => onChange(selectedItem), + }); + return ( +
+ } {...restProps} {...getInputProps()} /> +
    + {isOpen && + items.map((item, index) => { + return ( +
  • + {item.label} +
  • + ); + })} +
+
+ ); +}; diff --git a/yarn.lock b/yarn.lock index a83d5997e87..6be3c8126af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3684,6 +3684,7 @@ __metadata: csstype: "npm:3.1.3" d3: "npm:7.9.0" date-fns: "npm:3.6.0" + downshift: "npm:^9.0.6" esbuild: "npm:0.20.2" expose-loader: "npm:5.0.0" hoist-non-react-statics: "npm:3.3.2" @@ -12460,6 +12461,13 @@ __metadata: languageName: node linkType: hard +"compute-scroll-into-view@npm:^3.1.0": + version: 3.1.0 + resolution: "compute-scroll-into-view@npm:3.1.0" + checksum: 10/cc5211d49bced5ad23385da5c2eaf69b6045628581b0dcb9f4dd407bfee51bbd26d2bce426be26edf2feaf8c243706f5a7c3759827d89cc5a01a5cf7d299a5eb + languageName: node + linkType: hard + "concat-map@npm:0.0.1": version: 0.0.1 resolution: "concat-map@npm:0.0.1" @@ -14380,6 +14388,21 @@ __metadata: languageName: node linkType: hard +"downshift@npm:^9.0.6": + version: 9.0.6 + resolution: "downshift@npm:9.0.6" + dependencies: + "@babel/runtime": "npm:^7.24.5" + compute-scroll-into-view: "npm:^3.1.0" + prop-types: "npm:^15.8.1" + react-is: "npm:18.2.0" + tslib: "npm:^2.6.2" + peerDependencies: + react: ">=16.12.0" + checksum: 10/e84ceba61429694395e6c2ab7213e76d6807a87ceb52b0db08642120ac6d6affc74426772431df29741d571743143316a11e6a815280bed4bbc1113cd83849b1 + languageName: node + linkType: hard + "duplexer@npm:^0.1.1, duplexer@npm:^0.1.2": version: 0.1.2 resolution: "duplexer@npm:0.1.2" @@ -25292,7 +25315,7 @@ __metadata: languageName: node linkType: hard -"react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": +"react-is@npm:18.2.0, react-is@npm:^16.12.0 || ^17.0.0 || ^18.0.0, react-is@npm:^18.0.0, react-is@npm:^18.2.0": version: 18.2.0 resolution: "react-is@npm:18.2.0" checksum: 10/200cd65bf2e0be7ba6055f647091b725a45dd2a6abef03bf2380ce701fd5edccee40b49b9d15edab7ac08a762bf83cb4081e31ec2673a5bfb549a36ba21570df @@ -29156,7 +29179,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:2.6.3, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1": +"tslib@npm:2.6.3, tslib@npm:^2.0.0, tslib@npm:^2.0.1, tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.4.1, tslib@npm:^2.6.2": version: 2.6.3 resolution: "tslib@npm:2.6.3" checksum: 10/52109bb681f8133a2e58142f11a50e05476de4f075ca906d13b596ae5f7f12d30c482feb0bff167ae01cfc84c5803e575a307d47938999246f5a49d174fc558c From 74230937f4facf09e494f38c802b3a6b0e9e0a33 Mon Sep 17 00:00:00 2001 From: Will Browne Date: Thu, 13 Jun 2024 14:14:39 +0100 Subject: [PATCH 02/13] Plugins: Update plugin SDK Go to 0.235.0 (#89153) update plugin SDK go to 0.235.0 --- go.mod | 6 +++--- go.sum | 12 ++++++------ go.work.sum | 39 +++++++++++++++++++++++++++++++++++++++ pkg/apiserver/go.mod | 4 +++- pkg/apiserver/go.sum | 4 +++- pkg/promlib/go.mod | 4 +++- pkg/promlib/go.sum | 4 +++- 7 files changed, 60 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 46f668bfe0e..6632a8312cc 100644 --- a/go.mod +++ b/go.mod @@ -99,15 +99,15 @@ require ( github.com/grafana/grafana-azure-sdk-go/v2 v2.0.4 // @grafana/partner-datasources github.com/grafana/grafana-google-sdk-go v0.1.0 // @grafana/partner-datasources github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 // @grafana/grafana-backend-group - github.com/grafana/grafana-plugin-sdk-go v0.234.0 // @grafana/plugins-platform-backend - github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad + github.com/grafana/grafana-plugin-sdk-go v0.235.0 // @grafana/plugins-platform-backend + github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240613084659-7e9e5f534676 // @grafana/grafana-app-platform-squad github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 // @grafana/grafana-app-platform-squad // This needs to be here for other projects that import grafana/grafana // For local development grafana/grafana will always use the local files // Check go.work file for details github.com/grafana/grafana/pkg/promlib v0.0.6 // @grafana/observability-metrics github.com/grafana/otel-profiling-go v0.5.1 // @grafana/grafana-backend-group - github.com/grafana/pyroscope-go/godeltaprof v0.1.6 // @grafana/observability-traces-and-profiling + github.com/grafana/pyroscope-go/godeltaprof v0.1.7 // @grafana/observability-traces-and-profiling github.com/grafana/pyroscope/api v0.3.0 // @grafana/observability-traces-and-profiling github.com/grafana/tempo v1.5.1-0.20230524121406-1dc1bfe7085b // @grafana/observability-traces-and-profiling github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // @grafana/plugins-platform-backend diff --git a/go.sum b/go.sum index 746b73f40c4..0f04edbd7d5 100644 --- a/go.sum +++ b/go.sum @@ -2334,10 +2334,10 @@ github.com/grafana/grafana-google-sdk-go v0.1.0/go.mod h1:Vo2TKWfDVmNTELBUM+3lkr github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79 h1:r+mU5bGMzcXCRVAuOrTn54S80qbfVkvTdUJZfSfTNbs= github.com/grafana/grafana-openapi-client-go v0.0.0-20231213163343-bd475d63fb79/go.mod h1:wc6Hbh3K2TgCUSfBC/BOzabItujtHMESZeFk5ZhdxhQ= github.com/grafana/grafana-plugin-sdk-go v0.114.0/go.mod h1:D7x3ah+1d4phNXpbnOaxa/osSaZlwh9/ZUnGGzegRbk= -github.com/grafana/grafana-plugin-sdk-go v0.234.0 h1:p20XfGKB3Z/8aZ6jut+FIU/0cXw+dLkcGFnxJbyFd+k= -github.com/grafana/grafana-plugin-sdk-go v0.234.0/go.mod h1:FlXjmBESxaD6Hoi8ojWLkH007nyjtJM3XC8SpwzF/YE= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4 h1:hpyusz8c3yRFoJPlA0o34rWnsLbaOOBZleqRhFBi5Lg= -github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vrRQJuNprTWqwm6JPxHf3BoTJhvO15QMEjQ7Q/YUOnI= +github.com/grafana/grafana-plugin-sdk-go v0.235.0 h1:UnZ/iBDvCkfDgwR94opi8trAWJXv4V8Qr1ocJKRRmqA= +github.com/grafana/grafana-plugin-sdk-go v0.235.0/go.mod h1:6n9LbrjGL3xAATntYVNcIi90G9BVHRJjzHKz5FXVfWw= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240613084659-7e9e5f534676 h1:VfDueuzBY5Dhhv8t8Ejhg/XMKdbTrNVW0WLHrI5l9MI= +github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240613084659-7e9e5f534676/go.mod h1:adT8O7k6ZSzUKjAC4WS6VfWlCE4G1VavPwSXVhvScCs= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4 h1:tIbI5zgos92vwJ8lV3zwHwuxkV03GR3FGLkFW9V5LxY= github.com/grafana/grafana/pkg/apiserver v0.0.0-20240226124929-648abdbd0ea4/go.mod h1:vpYI6DHvFO595rpQGooUjcyicjt9rOevldDdW79peV0= github.com/grafana/grafana/pkg/promlib v0.0.6 h1:FuRyHMIgVVXkLuJnCflNfk3gqJflmyiI+/ZuJ9MoAfY= @@ -2348,8 +2348,8 @@ github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF github.com/grafana/otel-profiling-go v0.5.1/go.mod h1:ftN/t5A/4gQI19/8MoWurBEtC6gFw8Dns1sJZ9W4Tls= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b h1:HCbWyVL6vi7gxyO76gQksSPH203oBJ1MJ3JcG1OQlsg= github.com/grafana/prometheus-alertmanager v0.25.1-0.20240422145632-c33c6b5b6e6b/go.mod h1:01sXtHoRwI8W324IPAzuxDFOmALqYLCOhvSC2fUHWXc= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6 h1:nEdZ8louGAplSvIJi1HVp7kWvFvdiiYg3COLlTwJiFo= -github.com/grafana/pyroscope-go/godeltaprof v0.1.6/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= +github.com/grafana/pyroscope-go/godeltaprof v0.1.7 h1:C11j63y7gymiW8VugJ9ZW0pWfxTZugdSJyC48olk5KY= +github.com/grafana/pyroscope-go/godeltaprof v0.1.7/go.mod h1:Tk376Nbldo4Cha9RgiU7ik8WKFkNpfds98aUzS8omLE= github.com/grafana/pyroscope/api v0.3.0 h1:WcVKNZ8JlriJnD28wTkZray0wGo8dGkizSJXnbG7Gd8= github.com/grafana/pyroscope/api v0.3.0/go.mod h1:JggA80ToAAUACYGfwL49XoFk5aN5ecHp4pNIZhlk9Uc= github.com/grafana/regexp v0.0.0-20221122212121-6b5c0a4cb7fd/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= diff --git a/go.work.sum b/go.work.sum index 5185138d248..0c850161a5d 100644 --- a/go.work.sum +++ b/go.work.sum @@ -272,7 +272,10 @@ github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2 github.com/AndreasBriese/bbloom v0.0.0-20190306092124-e2d15f34fcf9 h1:HD8gA2tkByhMAwYaFAX9w2l7vxvBQ5NMoxDrkhqhtn4= github.com/Azure/azure-amqp-common-go/v3 v3.2.2 h1:CJpxNAGxP7UBhDusRUoaOn0uOorQyAYhQYLnNgkRhlY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal v1.1.2 h1:mLY+pNLjCUeKhgnAJWAKhEUQM+RJQo2H1fuGSw1Ky1E= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v2 v2.0.0 h1:PTFGRSlMKCQelWwxUyYVEUqseBJVemLyqWJjvMyt0do= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/managementgroups/armmanagementgroups v1.0.0 h1:pPvTJ1dY0sA35JOeFq6TsY2xj6Z85Yo23Pj4wCCvu4o= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.0.0 h1:ECsQtyERDVz3NP3kvDOTLvbQhqWp/x9EsGKtb4ogUr8= +github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.1.1 h1:7CBQ+Ei8SP2c6ydQTGCCrS35bDxgTMfoP2miAwK++OU= github.com/Azure/azure-service-bus-go v0.11.5 h1:EVMicXGNrSX+rHRCBgm/TRQ4VUZ1m3yAYM/AB2R/SOs= github.com/Azure/go-amqp v0.16.4 h1:/1oIXrq5zwXLHaoYDliJyiFjJSpJZMWGgtMX9e0/Z30= github.com/Azure/go-autorest/autorest/azure/auth v0.5.11 h1:P6bYXFoao05z5uhOQzbC3Qd8JqF3jUoocoTeIxkp2cA= @@ -289,6 +292,7 @@ github.com/DataDog/datadog-go v3.2.0+incompatible h1:qSG2N4FghB1He/r2mFrWKCaL7dX github.com/GoogleCloudPlatform/cloudsql-proxy v1.29.0 h1:YNu23BtH0PKF+fg3ykSorCp6jSTjcEtfnYLzbmcjVRA= github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/KimMachineGun/automemlimit v0.6.0 h1:p/BXkH+K40Hax+PuWWPQ478hPjsp9h1CPDhLlA3Z37E= github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/OneOfOne/xxhash v1.2.6 h1:U68crOE3y3MPttCMQGywZOLrTeF5HHJ3/vDBCJn9/bA= @@ -365,6 +369,7 @@ github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3I github.com/chzyer/logex v1.2.1 h1:XHDu3E6q+gdHgsdTPH6ImJMIp436vR6MPtH8gP05QzM= github.com/chzyer/readline v1.5.1 h1:upd/6fQk4src78LMRzh5vItIt361/o4uq553V8B5sGI= github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= +github.com/cilium/ebpf v0.11.0 h1:V8gS/bTCCjX9uUnkUFUpPsksM8n1lXBAvHcpiFk1X2Y= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible h1:C29Ae4G5GtYyYMm1aztcyj/J5ckgJm2zwdDajFbx1NY= github.com/circonus-labs/circonusllhist v0.1.3 h1:TJH+oke8D16535+jHExHj4nQvzlZrj7ug5D7I/orNUA= github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= @@ -377,8 +382,10 @@ github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr github.com/cockroachdb/datadriven v1.0.2 h1:H9MtNqVoVhvd9nCBwOyDjUEdZCREqbIdCJD93PBm/jA= github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd h1:qMd81Ts1T2OTKmB4acZcyKaMtRnY5Y44NuXGX2GFJ1w= github.com/codegangsta/inject v0.0.0-20150114235600-33e0aa1cb7c0 h1:sDMmm+q/3+BukdIpxwO365v/Rbspp2Nt5XntgQRXq8Q= +github.com/containerd/cgroups/v3 v3.0.3 h1:S5ByHZ/h9PMe5IOQoN7E+nMc2UcLEM/V48DGDJ9kip0= github.com/containerd/containerd v1.6.8 h1:h4dOFDwzHmqFEP754PgfgTeVXFnLiRc6kiqC7tplDJs= github.com/containerd/containerd v1.6.8/go.mod h1:By6p5KqPK0/7/CgO/A6t/Gz+CUYUu2zf1hUaaymVXB0= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/go-etcd v2.0.0+incompatible h1:bXhRBIXoTm9BYHS3gE0TtQuyNZyeEMux2sDi4oo5YOo= github.com/coreos/go-oidc v2.2.1+incompatible h1:mh48q/BqXqgjVHpy2ZY7WnWAbenxRjsz9N1i1YxjHAk= @@ -401,6 +408,13 @@ github.com/cznic/ql v1.2.0 h1:lcKp95ZtdF0XkWhGnVIXGF8dVD2X+ClS08tglKtf+ak= github.com/cznic/sortutil v0.0.0-20150617083342-4c7342852e65 h1:hxuZop6tSoOi0sxFzoGGYdRqNrPubyaIf9KoBG9tPiE= github.com/cznic/strutil v0.0.0-20171016134553-529a34b1c186 h1:0rkFMAbn5KBKNpJyHQ6Prb95vIKanmAe62KxsrN+sqA= github.com/cznic/zappy v0.0.0-20160723133515-2533cb5b45cc h1:YKKpTb2BrXN2GYyGaygIdis1vXbE7SSAG9axGWIMClg= +github.com/dave/astrid v0.0.0-20170323122508-8c2895878b14 h1:YI1gOOdmMk3xodBao7fehcvoZsEeOyy/cfhlpCSPgM4= +github.com/dave/brenda v1.1.0 h1:Sl1LlwXnbw7xMhq3y2x11McFu43AjDcwkllxxgZ3EZw= +github.com/dave/courtney v0.3.0 h1:8aR1os2ImdIQf3Zj4oro+lD/L4Srb5VwGefqZ/jzz7U= +github.com/dave/gopackages v0.0.0-20170318123100-46e7023ec56e h1:l99YKCdrK4Lvb/zTupt0GMPfNbncAGf8Cv/t1sYLOg0= +github.com/dave/kerr v0.0.0-20170318121727-bc25dd6abe8e h1:xURkGi4RydhyaYR6PzcyHTueQudxY4LgxN1oYEPJHa0= +github.com/dave/patsy v0.0.0-20210517141501-957256f50cba h1:1o36L4EKbZzazMk8iGC4kXpVnZ6TPxR2mZ9qVKjNNAs= +github.com/dave/rebecca v0.9.1 h1:jxVfdOxRirbXL28vXMvUvJ1in3djwkVKXCq339qhBL0= github.com/dchest/uniuri v1.2.0 h1:koIcOUdrTIivZgSLhHQvKgqdWZq5d7KdMEWF1Ud6+5g= github.com/dchest/uniuri v1.2.0/go.mod h1:fSzm4SLHzNZvWLvWJew423PhAzkpNQYq+uNLq4kxhkY= github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 h1:YLtO71vCjJRCBcrPMtQ9nqBsqpA1m5sE92cU+pd5Mcc= @@ -417,6 +431,7 @@ github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2 h1:tdlZCpZ/P9DhczC github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dhui/dktest v0.3.0 h1:kwX5a7EkLcjo7VpsPQSYJcKGbXBXdjI9FGjuUj1jn6I= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= +github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815 h1:bWDMxwH3px2JBh6AyO7hdCn/PkvCZXii8TGj7sbtEbQ= github.com/drone/drone-runtime v1.1.0 h1:IsKbwiLY6+ViNBzX0F8PERJVZZcEJm9rgxEh3uZP5IE= @@ -432,11 +447,13 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6 h1:8yY/I9 github.com/eapache/go-xerial-snappy v0.0.0-20230111030713-bf00bc1b83b6/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= +github.com/elazarl/goproxy v0.0.0-20230731152917-f99041a5c027/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM= github.com/etcd-io/bbolt v1.3.3 h1:gSJmxrs37LgTqR/oyJBWok6k6SvXEUerFTbltIhXkBM= github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 h1:DddqAaWDpywytcG8w/qoQ5sAN8X12d3Z3koB0C3Rxsc= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= +github.com/felixge/fgprof v0.9.3 h1:VvyZxILNuCiUCSXtPtYmmtGvb65nqXh2QFWc0Wpf2/g= github.com/felixge/fgprof v0.9.3/go.mod h1:RdbpDgzqYVh/T9fPELJyV7EYJuHB55UTEULNun8eiPw= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= @@ -551,6 +568,7 @@ github.com/grafana/grafana-plugin-sdk-go v0.227.1-0.20240430073540-ce4d126ae8b8/ github.com/grafana/grafana-plugin-sdk-go v0.228.0/go.mod h1:u4K9vVN6eU86loO68977eTXGypC4brUCnk4sfDzutZU= github.com/grafana/grafana-plugin-sdk-go v0.229.0/go.mod h1:6V6ikT4ryva8MrAp7Bdz5fTJx3/ztzKvpMJFfpzr4CI= github.com/grafana/grafana-plugin-sdk-go v0.231.1-0.20240523124942-62dae9836284/go.mod h1:bNgmNmub1I7Mc8dzIncgNqHC5jTgSZPPHlZ3aG8HKJQ= +github.com/grafana/grafana-plugin-sdk-go v0.234.0/go.mod h1:FlXjmBESxaD6Hoi8ojWLkH007nyjtJM3XC8SpwzF/YE= github.com/grafana/grafana/pkg/promlib v0.0.3/go.mod h1:3El4NlsfALz8QQCbEGHGFvJUG+538QLMuALRhZ3pcoo= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= github.com/grpc-ecosystem/grpc-gateway/v2 v2.18.1/go.mod h1:YvJ2f6MplWDhfxiUC3KpyTy76kYUZA4W3pTv/wdKQ9Y= @@ -563,6 +581,7 @@ github.com/hamba/avro/v2 v2.17.2/go.mod h1:Q9YK+qxAhtVrNqOhwlZTATLgLA8qxG2vtvkhK github.com/hanwen/go-fuse v1.0.0 h1:GxS9Zrn6c35/BnfiVsZVWmsG803xwE7eVRDvcf/BEVc= github.com/hanwen/go-fuse/v2 v2.1.0 h1:+32ffteETaLYClUj0a3aHjZ1hOPxxaNEHiZiujuDaek= github.com/hashicorp/consul/sdk v0.15.0 h1:2qK9nDrr4tiJKRoxPGhm6B7xJjLVIQqkjiab2M4aKjU= +github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.16.1 h1:IVQwpTGNRRIHafnTs2dQLIk4ENtneRIEEJWOVDqz99o= github.com/hashicorp/go-hclog v0.16.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= @@ -578,9 +597,11 @@ github.com/hydrogen18/memlistener v0.0.0-20200120041712-dcc25e7acd91 h1:KyZDvZ/G github.com/iancoleman/strcase v0.2.0 h1:05I4QRnGpI0m37iZQRuskXh+w77mr6Z41lwQzuHLwW0= github.com/iancoleman/strcase v0.3.0 h1:nTXanmYxhfFAMjZL34Ov6gkzEsSJZ5DbhxWjvSASxEI= github.com/ianlancetaylor/demangle v0.0.0-20230524184225-eabc099b10ab h1:BA4a7pe6ZTd9F8kXETBoijjFJ/ntaa//1wiH9BZu4zU= +github.com/ianlancetaylor/demangle v0.0.0-20240312041847-bd984b5ce465 h1:KwWnWVWCNtNq/ewIX7HIKnELmEx2nDP42yskD/pi7QE= github.com/influxdata/influxdb v1.7.6 h1:8mQ7A/V+3noMGCt/P9pD09ISaiz9XvgCk303UYA3gcs= github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab h1:HqW4xhhynfjrtEiiSGcQUd6vrK23iMam1FO8rI7mwig= github.com/invopop/yaml v0.1.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= +github.com/invopop/yaml v0.2.0/go.mod h1:2XuRLgs/ouIrW3XNzuNj7J3Nvu/Dig5MXvbCEdiBN3Q= github.com/iris-contrib/blackfriday v2.0.0+incompatible h1:o5sHQHHm0ToHUlAJSTjW9UWicjJSDDauOOQ2AHuIVp4= github.com/iris-contrib/go.uuid v2.0.0+incompatible h1:XZubAYg61/JwnJNbZilGjf3b3pB80+OQg2qf6c8BfWE= github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/+fafWORmlnuysV2EMP8MW+qe0= @@ -608,10 +629,12 @@ github.com/jackspirou/syscerts v0.0.0-20160531025014-b68f5469dff1/go.mod h1:zuHl github.com/jaegertracing/jaeger v1.41.0 h1:vVNky8dP46M2RjGaZ7qRENqylW+tBFay3h57N16Ip7M= github.com/jaegertracing/jaeger v1.41.0/go.mod h1:SIkAT75iVmA9U+mESGYuMH6UQv6V9Qy4qxo0lwfCQAc= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.1 h1:iUx3whfZWVf3jT01hQTO/Eo5sAYtB2/rqaUuOtpInww= github.com/jedib0t/go-pretty/v6 v6.2.4 h1:wdaj2KHD2W+mz8JgJ/Q6L/T5dB7kyqEFI16eLq7GEmk= github.com/jedib0t/go-pretty/v6 v6.2.4/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= github.com/jhump/gopoet v0.1.0 h1:gYjOPnzHd2nzB37xYQZxj4EIQNpBrBskRqQQ3q4ZgSg= github.com/jhump/goprotoc v0.5.0 h1:Y1UgUX+txUznfqcGdDef8ZOVlyQvnV0pKWZH08RmZuo= +github.com/jmattheis/goverter v1.4.0 h1:SrboBYMpGkj1XSgFhWwqzdP024zIa1+58YzUm+0jcBE= github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o= @@ -699,7 +722,9 @@ github.com/mitchellh/gox v0.4.0 h1:lfGJxY7ToLJQjHHwi0EX6uYBdK78egf954SQl13PQJc= github.com/mitchellh/iochan v1.0.0 h1:C+X3KsSTLFVBr/tK1eYN/vs4rJcvsiLU338UhYPJWeY= github.com/mithrandie/readline-csvq v1.2.1 h1:4cfeYeVSrqKEWi/1t7CjyhFD2yS6fm+l+oe+WyoSNlI= github.com/mithrandie/readline-csvq v1.2.1/go.mod h1:ydD9Eyp3/wn8KPSNbKmMZe4RQQauCuxi26yEo4N40dk= +github.com/mithrandie/readline-csvq v1.3.0 h1:VTJEOGouJ8j27jJCD4kBBbNTxM0OdBvE1aY1tMhlqE8= github.com/mithrandie/readline-csvq v1.3.0/go.mod h1:FKyYqDgf/G4SNov7SMFXRWO6LQLXIOeTog/NB97FZl0= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5 h1:8Q0qkMVC/MmWkpIdlvZgcv2o2jrlF6zqVOh7W5YHdMA= github.com/montanaflynn/stats v0.7.0 h1:r3y12KyNxj/Sb/iOE46ws+3mS1+MZca1wlHQFPsY/JU= github.com/mostynb/go-grpc-compression v1.1.17 h1:N9t6taOJN3mNTTi0wDf4e3lp/G/ON1TP67Pn0vTUA9I= @@ -716,6 +741,7 @@ github.com/nats-io/nkeys v0.3.0 h1:cgM5tL53EvYRU+2YLXIK0G2mJtK12Ft9oeooSZMA2G8= github.com/nats-io/nkeys v0.4.4/go.mod h1:XUkxdLPTufzlihbamfzQ7mw/VGx6ObUs+0bN5sNvt64= github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/nsf/jsondiff v0.0.0-20230430225905-43f6cf3098c1 h1:dOYG7LS/WK00RWZc8XGgcUTlTxpp3mKhdR2Q9z9HbXM= github.com/oapi-codegen/testutil v1.0.0/go.mod h1:ttCaYbHvJtHuiyeBF0tPIX+4uhEPTeizXKx28okijLw= github.com/oklog/oklog v0.3.2 h1:wVfs8F+in6nTBMkA7CbRw+zZMIB7nNM825cM1wuzoTk= github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= @@ -745,6 +771,7 @@ github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusrec github.com/open-telemetry/opentelemetry-collector-contrib/receiver/opencensusreceiver v0.74.0/go.mod h1:uiW3V9EX8A5DOoxqDLuSh++ewHr+owtonCSiqMcpy3w= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.74.0 h1:2uysjsaqkf9STFeJN/M6i/sSYEN5pZJ94Qd2/Hg1pKE= github.com/open-telemetry/opentelemetry-collector-contrib/receiver/zipkinreceiver v0.74.0/go.mod h1:qoGuayD7cAtshnKosIQHd6dobcn6/sqgUn0v/Cg2UB8= +github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNiaglX6v2DM6FI0= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e h1:4cPxUYdgaGzZIT5/j0IfqOrrXmq6bG8AwvwisMXpdrg= github.com/opentracing-contrib/go-grpc v0.0.0-20210225150812-73cb765af46e/go.mod h1:DYR5Eij8rJl8h7gblRrOZ8g0kW1umSpKqYIBTgeDtLo= github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492 h1:lM6RxxfUMrYL/f8bWEUqdXrANWtrL7Nndbm9iFN0DlU= @@ -754,6 +781,7 @@ github.com/openzipkin/zipkin-go v0.4.1 h1:kNd/ST2yLLWhaWrkgchya40TJabe8Hioj9udfP github.com/openzipkin/zipkin-go v0.4.1/go.mod h1:qY0VqDSN1pOBN94dBc6w2GJlWLiovAyg7Qt6/I9HecM= github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw= github.com/pact-foundation/pact-go v1.0.4 h1:OYkFijGHoZAYbOIb1LWXrwKQbMMRUv1oQ89blD2Mh2Q= +github.com/pbnjay/memory v0.0.0-20210728143218-7b4eea64cf58 h1:onHthvaw9LFnH4t2DcNVpwGmV9E1BkGknEliJkfwQj0= github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= @@ -770,6 +798,7 @@ github.com/phpdave11/gofpdi v1.0.13 h1:o61duiW8M9sMlkVXWlvP92sZJtGKENvW3VExs6dZu github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e h1:aoZm08cpOy4WuID//EZDgcC4zIxODThtZNPirFr42+A= github.com/pkg/profile v1.2.1 h1:F++O52m40owAmADcojzM+9gyjmMOY/T4oYJkgFDH8RE= +github.com/pkg/profile v1.7.0 h1:hnbDkaNWPCLMO9wGLdBFTIZvzDrDfBM2072E1S9gJkA= github.com/pkg/profile v1.7.0/go.mod h1:8Uer0jas47ZQMJ7VD+OHknK4YDY07LPUC6dEvqDjvNo= github.com/pkg/sftp v1.13.1 h1:I2qBYMChEhIjOgazfJmV3/mZM256btk6wkCDRmW7JYs= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= @@ -813,6 +842,7 @@ github.com/shirou/gopsutil/v3 v3.23.2/go.mod h1:gv0aQw33GLo3pG8SiWKiQrbDzbRY1K80 github.com/shirou/gopsutil/v3 v3.23.8/go.mod h1:7hmCaBn+2ZwaZOr6jmPBZDfawwMGuo1id3C6aM8EDqQ= github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ= github.com/shoenig/test v0.6.6 h1:Oe8TPH9wAbv++YPNDKJWUnI8Q4PPWCx3UbOfH+FxiMU= +github.com/shoenig/test v1.7.1 h1:UJcjSAI3aUKx52kfcfhblgyhZceouhvvs3OYdWgn+PY= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/sony/gobreaker v0.4.1 h1:oMnRNZXX5j85zso6xCPRNPtmAycat+WcoKbklScLDgQ= github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI= @@ -861,6 +891,7 @@ github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQ github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/vburenin/ifacemaker v1.2.1 h1:3Vq8B/bfBgjWTkv+jDg4dVL1KHt3k1K4lO7XRxYA2sk= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d h1:3wDi6J5APMqaHBVPuVd7RmHD2gRTfqbdcVSpCNoUWtk= github.com/vinzenz/yaml v0.0.0-20170920082545-91409cdd725d/go.mod h1:mb5taDqMnJiZNRQ3+02W2IFG+oEz1+dTuCXkp4jpkfo= github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= @@ -913,11 +944,13 @@ go.opentelemetry.io/collector/exporter v0.74.0/go.mod h1:kw5YoorpKqEpZZ/a5ODSoYF go.opentelemetry.io/collector/exporter/otlpexporter v0.74.0 h1:YKvTeYcBrJwbcXNy65fJ/xytUSMurpYn/KkJD0x+DAY= go.opentelemetry.io/collector/exporter/otlpexporter v0.74.0/go.mod h1:cRbvsnpSxzySoTSnXbOGPQZu9KHlEyKkTeE21f9Q1p4= go.opentelemetry.io/collector/featuregate v1.0.0 h1:5MGqe2v5zxaoo73BUOvUTunftX5J8RGrbFsC2Ha7N3g= +go.opentelemetry.io/collector/featuregate v1.5.0 h1:uK8qnYQKz1TMkK+FDTFsywg/EybW/gbnOUaPNUkRznM= go.opentelemetry.io/collector/receiver v0.74.0 h1:jlgBFa0iByvn8VuX27UxtqiPiZE8ejmU5lb1nSptWD8= go.opentelemetry.io/collector/receiver v0.74.0/go.mod h1:SQkyATvoZCJefNkI2jnrR63SOdrmDLYCnQqXJ7ACqn0= go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0 h1:e/X/W0z2Jtpy3Yd3CXkmEm9vSpKq/P3pKUrEVMUFBRw= go.opentelemetry.io/collector/receiver/otlpreceiver v0.74.0/go.mod h1:9X9/RYFxJIaK0JLlRZ0PpmQSSlYpY+r4KsTOj2jWj14= go.opentelemetry.io/collector/semconv v0.90.1 h1:2fkQZbefQBbIcNb9Rk1mRcWlFZgQOk7CpST1e1BK8eg= +go.opentelemetry.io/collector/semconv v0.98.0 h1:zO4L4TmlxXoYu8UgPeYElGY19BW7wPjM+quL5CzoOoY= go.opentelemetry.io/contrib v0.18.0 h1:uqBh0brileIvG6luvBjdxzoFL8lxDGuhxJWsvK3BveI= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.42.0/go.mod h1:5z+/ZWJQKXa9YT34fQNx5K8Hd1EoIhvtUygUQPqEOgQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.48.0/go.mod h1:tIKj3DbO8N9Y2xo52og3irLsPI4GW02DSMtrVgNMgxg= @@ -931,6 +964,7 @@ go.opentelemetry.io/otel/bridge/opencensus v0.37.0/go.mod h1:ddiK+1PE68l/Xk04BGT go.opentelemetry.io/otel/bridge/opentracing v1.10.0 h1:WzAVGovpC1s7KD5g4taU6BWYZP3QGSDVTlbRu9fIHw8= go.opentelemetry.io/otel/bridge/opentracing v1.10.0/go.mod h1:J7GLR/uxxqMAzZptsH0pjte3Ep4GacTCrbGBoDuHBqk= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.21.0 h1:digkEZCJWobwBqMwC0cwCq8/wkkRy/OowZg5OArWZrM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.25.0 h1:Mbi5PKN7u322woPa85d7ebZ+SOvEoPvoiBu+ryHWgfA= go.opentelemetry.io/otel/exporters/prometheus v0.37.0 h1:NQc0epfL0xItsmGgSXgfbH2C1fq2VLXkZoDFsfRNHpc= go.opentelemetry.io/otel/exporters/prometheus v0.37.0/go.mod h1:hB8qWjsStK36t50/R0V2ULFb4u95X/Q6zupXLgvjTh8= go.opentelemetry.io/otel/sdk v1.22.0/go.mod h1:iu7luyVGYovrRpe2fmj3CVKouQNdTOkxtLzPvPz1DOc= @@ -949,10 +983,14 @@ golang.org/x/arch v0.4.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/image v0.0.0-20220302094943-723b81ca9867 h1:TcHcE0vrmgzNH1v3ppjcMGbhG5+9fMuvOmUYwNEF4q4= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028 h1:4+4C/Iv2U4fMZBiMCc98MG1In4gJY5YRhtpDNeDeHWs= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/telemetry v0.0.0-20240208230135-b75ee8823808 h1:+Kc94D8UVEVxJnLXp/+FMfqQARZtWHfVrcRtcG8aT3g= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2 h1:IRJeR9r1pYWsHKTRe/IInb7lYvbBVIqOgsX/u0mbOWY= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457 h1:zf5N6UOrA487eEFacMePxjXAJctxKmyjKUsjA11Uzuk= golang.org/x/telemetry v0.0.0-20240521205824-bda55230c457/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0 h1:OE9mWmgKkjJyEmDAAtGMPjXu+YNeGvK9VTSHY6+Qihc= gonum.org/v1/plot v0.10.1 h1:dnifSs43YJuNMDzB7v8wV64O4ABBHReuAVAoBxqBqS4= google.golang.org/api v0.166.0/go.mod h1:4FcBc686KFi7QI/U51/2GKKevfZMpM17sCdibqe/bSA= @@ -984,6 +1022,7 @@ gopkg.in/src-d/go-billy.v4 v4.3.2 h1:0SQA1pRztfTFx2miS8sA97XvooFeNOmvUenF4o0EcVg gopkg.in/src-d/go-billy.v4 v4.3.2/go.mod h1:nDjArDMp+XMs1aFAESLRjfGSgfvoYN0hDfzEk0GjC98= gopkg.in/telebot.v3 v3.2.1 h1:3I4LohaAyJBiivGmkfB+CiVu7QFOWkuZ4+KHgO/G3rs= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.1.3 h1:qTakTkI6ni6LFD5sBwwsdSO+AQqbSIxOauHTTQKZ/7o= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6 h1:WN8Lymy+dCTDHgn4vhUSNIB6U+0sDiv/c9Zdr0UeAnI= k8s.io/component-base v0.0.0-20240417101527-62c04b35eff6/go.mod h1:l0ukbPS0lwFxOzSq5ZqjutzF+5IL2TLp495PswRPSZk= diff --git a/pkg/apiserver/go.mod b/pkg/apiserver/go.mod index 8c106bf0921..557d8653337 100644 --- a/pkg/apiserver/go.mod +++ b/pkg/apiserver/go.mod @@ -5,7 +5,7 @@ go 1.21.10 require ( github.com/bwmarrin/snowflake v0.3.0 github.com/gorilla/mux v1.8.1 - github.com/grafana/grafana-plugin-sdk-go v0.234.0 + github.com/grafana/grafana-plugin-sdk-go v0.235.0 github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f github.com/prometheus/client_golang v1.19.0 github.com/stretchr/testify v1.9.0 @@ -60,6 +60,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/pprof v0.0.0-20240416155748-26353dc0451f // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.7 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect diff --git a/pkg/apiserver/go.sum b/pkg/apiserver/go.sum index e5cb088403c..2f96fd2f2c0 100644 --- a/pkg/apiserver/go.sum +++ b/pkg/apiserver/go.sum @@ -117,9 +117,11 @@ github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grafana/grafana-plugin-sdk-go v0.234.0 h1:p20XfGKB3Z/8aZ6jut+FIU/0cXw+dLkcGFnxJbyFd+k= +github.com/grafana/grafana-plugin-sdk-go v0.235.0 h1:UnZ/iBDvCkfDgwR94opi8trAWJXv4V8Qr1ocJKRRmqA= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f h1:+CK3tH3XrAAqx5urmVqpgSxMrL2MlpTOnLVSU4w4IjY= github.com/grafana/grafana/pkg/apimachinery v0.0.0-20240409140820-518d3341d58f/go.mod h1:ZxIaCOlDmFupiL55aLU+Qp7O1dgwkDMBAQBK7wnEVBg= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/pyroscope-go/godeltaprof v0.1.7 h1:C11j63y7gymiW8VugJ9ZW0pWfxTZugdSJyC48olk5KY= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0 h1:UH//fgunKIs4JdUbpDl1VZCDaL56wXCB/5+wF6uHfaI= github.com/grpc-ecosystem/go-grpc-middleware v1.4.0/go.mod h1:g5qyo/la0ALbONm6Vbp88Yd8NsDy6rZz+RcrMPxvld8= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= diff --git a/pkg/promlib/go.mod b/pkg/promlib/go.mod index 247c0be48d1..736579dc90a 100644 --- a/pkg/promlib/go.mod +++ b/pkg/promlib/go.mod @@ -3,7 +3,7 @@ module github.com/grafana/grafana/pkg/promlib go 1.21.10 require ( - github.com/grafana/grafana-plugin-sdk-go v0.234.0 + github.com/grafana/grafana-plugin-sdk-go v0.235.0 github.com/json-iterator/go v1.1.12 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/prometheus/client_golang v1.19.0 @@ -52,6 +52,8 @@ require ( github.com/google/gofuzz v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/grafana/otel-profiling-go v0.5.1 // indirect + github.com/grafana/pyroscope-go/godeltaprof v0.1.7 // indirect github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db // indirect github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.1.0 // indirect diff --git a/pkg/promlib/go.sum b/pkg/promlib/go.sum index 4254a7d3099..6b21044383c 100644 --- a/pkg/promlib/go.sum +++ b/pkg/promlib/go.sum @@ -80,7 +80,9 @@ github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= -github.com/grafana/grafana-plugin-sdk-go v0.234.0 h1:p20XfGKB3Z/8aZ6jut+FIU/0cXw+dLkcGFnxJbyFd+k= +github.com/grafana/grafana-plugin-sdk-go v0.235.0 h1:UnZ/iBDvCkfDgwR94opi8trAWJXv4V8Qr1ocJKRRmqA= +github.com/grafana/otel-profiling-go v0.5.1 h1:stVPKAFZSa7eGiqbYuG25VcqYksR6iWvF3YH66t4qL8= +github.com/grafana/pyroscope-go/godeltaprof v0.1.7 h1:C11j63y7gymiW8VugJ9ZW0pWfxTZugdSJyC48olk5KY= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db h1:7aN5cccjIqCLTzedH7MZzRZt5/lsAHch6Z3L2ZGn5FA= github.com/grafana/regexp v0.0.0-20221123153739-15dc172cd2db/go.mod h1:M5qHK+eWfAv8VR/265dIuEpL3fNfeC21tXXp9itM24A= github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= From 2d0a3953c1a26fd583cbf1bbaf44262b282f58f2 Mon Sep 17 00:00:00 2001 From: Sam Jewell <2903904+samjewell@users.noreply.github.com> Date: Thu, 13 Jun 2024 14:18:21 +0100 Subject: [PATCH 03/13] Transformations: Move transformation variables to general availability (#89111) --- .../feature-toggles/index.md | 2 +- pkg/services/featuremgmt/registry.go | 3 +- pkg/services/featuremgmt/toggles_gen.csv | 2 +- pkg/services/featuremgmt/toggles_gen.json | 61 ++++++++++--------- 4 files changed, 36 insertions(+), 32 deletions(-) diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index d4bd70b5da3..9bb322b34fa 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -48,6 +48,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `dashgpt` | Enable AI powered features in dashboards | Yes | | `alertingInsights` | Show the new alerting insights landing page | Yes | | `panelMonitoring` | Enables panel monitoring through logs and measurements | Yes | +| `transformationsVariableSupport` | Allows using variables in transformations | Yes | | `kubernetesPlaylists` | Use the kubernetes API in the frontend for playlists, and route /api/playlist requests to k8s | Yes | | `recoveryThreshold` | Enables feature recovery threshold (aka hysteresis) for threshold server-side expression | Yes | | `lokiStructuredMetadata` | Enables the loki data source to request structured metadata from the Loki server | Yes | @@ -91,7 +92,6 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | `reportingRetries` | Enables rendering retries for the reporting feature | | `externalServiceAccounts` | Automatic service account and token setup for plugins | | `formatString` | Enable format string transformer | -| `transformationsVariableSupport` | Allows using variables in transformations | | `cloudWatchBatchQueries` | Runs CloudWatch metrics queries as separate batches | | `teamHttpHeaders` | Enables Team LBAC for datasources to apply team headers to the client requests | | `addFieldFromCalculationStatFunctions` | Add cumulative and window functions to the add field from calculation transformation | diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index f06d92c15ca..17485739de4 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -717,8 +717,9 @@ var ( Name: "transformationsVariableSupport", Description: "Allows using variables in transformations", FrontendOnly: true, - Stage: FeatureStagePublicPreview, + Stage: FeatureStageGeneralAvailability, Owner: grafanaDatavizSquad, + Expression: "true", // Enabled by default }, { Name: "kubernetesPlaylists", diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index 992701dd5c1..242c488b6fb 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -94,7 +94,7 @@ externalServiceAccounts,preview,@grafana/identity-access-team,false,false,false panelMonitoring,GA,@grafana/dataviz-squad,false,false,true enableNativeHTTPHistogram,experimental,@grafana/hosted-grafana-team,false,false,false formatString,preview,@grafana/dataviz-squad,false,false,true -transformationsVariableSupport,preview,@grafana/dataviz-squad,false,false,true +transformationsVariableSupport,GA,@grafana/dataviz-squad,false,false,true kubernetesPlaylists,GA,@grafana/grafana-app-platform-squad,false,true,false kubernetesSnapshots,experimental,@grafana/grafana-app-platform-squad,false,true,false kubernetesDashboards,experimental,@grafana/grafana-app-platform-squad,false,false,true diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 9103c3b0fc8..de18acfb7be 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -293,6 +293,20 @@ "hideFromDocs": true } }, + { + "metadata": { + "name": "authZGRPCServer", + "resourceVersion": "1718093439898", + "creationTimestamp": "2024-06-11T08:10:39Z" + }, + "spec": { + "description": "Enables the gRPC server for authorization", + "stage": "experimental", + "codeowner": "@grafana/identity-access-team", + "hideFromAdminPage": true, + "hideFromDocs": true + } + }, { "metadata": { "name": "autoMigrateGraphPanel", @@ -1662,6 +1676,18 @@ "codeowner": "@grafana/grafana-backend-group" } }, + { + "metadata": { + "name": "pinNavItems", + "resourceVersion": "1718017263521", + "creationTimestamp": "2024-06-10T11:01:03Z" + }, + "spec": { + "description": "Enables pinning of nav items", + "stage": "experimental", + "codeowner": "@grafana/grafana-frontend-platform" + } + }, { "metadata": { "name": "pluginProxyPreserveTrailingSlash", @@ -2193,12 +2219,15 @@ { "metadata": { "name": "transformationsVariableSupport", - "resourceVersion": "1717578796182", - "creationTimestamp": "2023-10-04T14:28:46Z" + "resourceVersion": "1718190346540", + "creationTimestamp": "2023-10-04T14:28:46Z", + "annotations": { + "grafana.app/updatedTimestamp": "2024-06-12 11:05:46.540869 +0000 UTC" + } }, "spec": { "description": "Allows using variables in transformations", - "stage": "preview", + "stage": "GA", "codeowner": "@grafana/dataviz-squad", "frontend": true } @@ -2252,32 +2281,6 @@ "stage": "experimental", "codeowner": "@grafana/hosted-grafana-team" } - }, - { - "metadata": { - "name": "pinNavItems", - "resourceVersion": "1718017263521", - "creationTimestamp": "2024-06-10T11:01:03Z" - }, - "spec": { - "description": "Enables pinning of nav items", - "stage": "experimental", - "codeowner": "@grafana/grafana-frontend-platform" - } - }, - { - "metadata": { - "name": "authZGRPCServer", - "resourceVersion": "1718093439898", - "creationTimestamp": "2024-06-11T08:10:39Z" - }, - "spec": { - "description": "Enables the gRPC server for authorization", - "stage": "experimental", - "codeowner": "@grafana/identity-access-team", - "hideFromAdminPage": true, - "hideFromDocs": true - } } ] } \ No newline at end of file From 59d83bc55ad0aee53f74a97f04e6917cfd2c83c0 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Thu, 13 Jun 2024 10:39:35 -0300 Subject: [PATCH 04/13] PublicDashboards: Fix error message when deleting (#89166) --- pkg/services/publicdashboards/api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/services/publicdashboards/api/api.go b/pkg/services/publicdashboards/api/api.go index 2aa8dea58d9..84433fa6598 100644 --- a/pkg/services/publicdashboards/api/api.go +++ b/pkg/services/publicdashboards/api/api.go @@ -297,7 +297,7 @@ func (api *Api) DeletePublicDashboard(c *contextmodel.ReqContext) response.Respo return response.Err(err) } - return response.JSON(http.StatusOK, nil) + return response.Empty(http.StatusOK) } // Copied from pkg/api/metrics.go From 07ec1a303ebf5bffb7db13d724444faa41a31368 Mon Sep 17 00:00:00 2001 From: Bogdan Matei Date: Thu, 13 Jun 2024 17:02:40 +0300 Subject: [PATCH 05/13] Scopes: Remove basic selector (#89098) --- .../Scopes/ScopesFiltersAdvancedSelector.tsx | 76 ------- .../Scopes/ScopesFiltersBasicSelector.tsx | 110 ---------- .../scene/Scopes/ScopesFiltersScene.tsx | 116 ++++++++--- .../scene/Scopes/ScopesScene.test.tsx | 188 +++++++----------- .../scene/Scopes/ScopesScene.tsx | 6 +- .../scene/Scopes/ScopesTreeLevel.tsx | 5 +- .../scene/Scopes/testUtils.tsx | 39 ++-- public/locales/en-US/grafana.json | 21 +- public/locales/pseudo-LOCALE/grafana.json | 21 +- 9 files changed, 198 insertions(+), 384 deletions(-) delete mode 100644 public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersAdvancedSelector.tsx delete mode 100644 public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersBasicSelector.tsx diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersAdvancedSelector.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersAdvancedSelector.tsx deleted file mode 100644 index 6163ecd73aa..00000000000 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersAdvancedSelector.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { SceneComponentProps } from '@grafana/scenes'; -import { Button, Drawer, Spinner, useStyles2 } from '@grafana/ui'; -import { t, Trans } from 'app/core/internationalization'; - -import { ScopesFiltersScene } from './ScopesFiltersScene'; -import { ScopesTreeLevel } from './ScopesTreeLevel'; - -export function ScopesFiltersAdvancedSelector({ model }: SceneComponentProps) { - const styles = useStyles2(getStyles); - const { nodes, loadingNodeName, dirtyScopeNames, isLoadingScopes, isAdvancedOpened } = model.useState(); - - if (!isAdvancedOpened) { - return null; - } - - return ( - { - model.closeAdvancedSelector(); - model.resetDirtyScopeNames(); - }} - > - {isLoadingScopes ? ( - - ) : ( - model.updateNode(path, isExpanded, query)} - onNodeSelectToggle={(path) => model.toggleNodeSelect(path)} - /> - )} -
- - -
-
- ); -} - -const getStyles = (theme: GrafanaTheme2) => { - return { - buttonGroup: css({ - display: 'flex', - gap: theme.spacing(1), - marginTop: theme.spacing(8), - }), - }; -}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersBasicSelector.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersBasicSelector.tsx deleted file mode 100644 index e55618410b0..00000000000 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersBasicSelector.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import { css } from '@emotion/css'; -import React from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { SceneComponentProps } from '@grafana/scenes'; -import { Icon, IconButton, Input, Spinner, Toggletip, useStyles2 } from '@grafana/ui'; -import { t, Trans } from 'app/core/internationalization'; - -import { ScopesFiltersScene } from './ScopesFiltersScene'; -import { ScopesTreeLevel } from './ScopesTreeLevel'; - -export function ScopesFiltersBasicSelector({ model }: SceneComponentProps) { - const styles = useStyles2(getStyles); - const { nodes, loadingNodeName, scopes, dirtyScopeNames, isLoadingScopes, isBasicOpened } = model.useState(); - const { isViewing } = model.scopesParent.useState(); - - const scopesTitles = scopes.map(({ spec: { title } }) => title).join(', '); - - return ( -
- - {isLoadingScopes ? ( - - ) : ( - model.updateNode(path, isExpanded, query)} - onNodeSelectToggle={(path) => model.toggleNodeSelect(path)} - /> - )} -
- } - footer={ - - } - onOpen={() => model.openBasicSelector()} - onClose={() => { - model.closeBasicSelector(); - model.updateScopes(); - }} - > - 0 && !isViewing ? ( - model.removeAllScopes()} - /> - ) : undefined - } - /> - - - ); -} - -const getStyles = (theme: GrafanaTheme2) => { - return { - container: css({ - width: '100%', - - '& > div': css({ - padding: 0, - - '& > div': css({ - padding: 0, - margin: 0, - }), - }), - }), - innerContainer: css({ - minWidth: 400, - padding: theme.spacing(0, 1), - }), - openAdvancedButton: css({ - backgroundColor: theme.colors.secondary.main, - border: 'none', - borderTop: `1px solid ${theme.colors.secondary.border}`, - display: 'block', - fontSize: theme.typography.pxToRem(12), - margin: 0, - padding: theme.spacing(1.5), - textAlign: 'right', - width: '100%', - }), - }; -}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx index 84c7ba88f83..341abd85e1e 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesFiltersScene.tsx @@ -1,8 +1,9 @@ +import { css } from '@emotion/css'; import { isEqual } from 'lodash'; import React from 'react'; import { finalize, from, Subscription } from 'rxjs'; -import { Scope } from '@grafana/data'; +import { GrafanaTheme2, Scope } from '@grafana/data'; import { SceneComponentProps, sceneGraph, @@ -12,10 +13,11 @@ import { SceneObjectUrlValues, SceneObjectWithUrlSync, } from '@grafana/scenes'; +import { Button, Drawer, IconButton, Input, Spinner, useStyles2 } from '@grafana/ui'; +import { t, Trans } from 'app/core/internationalization'; -import { ScopesFiltersAdvancedSelector } from './ScopesFiltersAdvancedSelector'; -import { ScopesFiltersBasicSelector } from './ScopesFiltersBasicSelector'; import { ScopesScene } from './ScopesScene'; +import { ScopesTreeLevel } from './ScopesTreeLevel'; import { fetchNodes, fetchScope, fetchScopes } from './api'; import { NodesMap } from './types'; @@ -25,8 +27,7 @@ export interface ScopesFiltersSceneState extends SceneObjectState { scopes: Scope[]; dirtyScopeNames: string[]; isLoadingScopes: boolean; - isBasicOpened: boolean; - isAdvancedOpened: boolean; + isOpened: boolean; } export class ScopesFiltersScene extends SceneObjectBase implements SceneObjectWithUrlSync { @@ -58,8 +59,7 @@ export class ScopesFiltersScene extends SceneObjectBase scopes: [], dirtyScopeNames: [], isLoadingScopes: false, - isBasicOpened: false, - isAdvancedOpened: false, + isOpened: false, }); this.addActivationHandler(() => { @@ -153,24 +153,14 @@ export class ScopesFiltersScene extends SceneObjectBase } } - public openBasicSelector() { + public open() { if (!this.scopesParent.state.isViewing) { - this.setState({ isBasicOpened: true, isAdvancedOpened: false }); + this.setState({ isOpened: true }); } } - public closeBasicSelector() { - this.setState({ isBasicOpened: false }); - } - - public openAdvancedSelector() { - if (!this.scopesParent.state.isViewing) { - this.setState({ isBasicOpened: false, isAdvancedOpened: true }); - } - } - - public closeAdvancedSelector() { - this.setState({ isAdvancedOpened: false }); + public close() { + this.setState({ isOpened: false }); } public getSelectedScopes(): Scope[] { @@ -196,7 +186,7 @@ export class ScopesFiltersScene extends SceneObjectBase } public enterViewMode() { - this.setState({ isBasicOpened: false, isAdvancedOpened: false }); + this.setState({ isOpened: false }); } private getScopeNames(): string[] { @@ -205,10 +195,88 @@ export class ScopesFiltersScene extends SceneObjectBase } export function ScopesFiltersSceneRenderer({ model }: SceneComponentProps) { + const styles = useStyles2(getStyles); + const { nodes, loadingNodeName, dirtyScopeNames, isLoadingScopes, isOpened, scopes } = model.useState(); + const { isViewing } = model.scopesParent.useState(); + + const scopesTitles = scopes.map(({ spec: { title } }) => title).join(', '); + return ( <> - - + 0 && !isViewing ? ( + model.removeAllScopes()} + /> + ) : undefined + } + onClick={() => model.open()} + /> + + {isOpened && ( + { + model.close(); + model.resetDirtyScopeNames(); + }} + > + {isLoadingScopes ? ( + + ) : ( + model.updateNode(path, isExpanded, query)} + onNodeSelectToggle={(path) => model.toggleNodeSelect(path)} + /> + )} +
+ + +
+
+ )} ); } + +const getStyles = (theme: GrafanaTheme2) => { + return { + buttonGroup: css({ + display: 'flex', + gap: theme.spacing(1), + marginTop: theme.spacing(8), + }), + }; +}; diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx index d88983f7a48..e52e3fa0b24 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.test.tsx @@ -13,8 +13,6 @@ import { fetchNodesSpy, fetchScopeSpy, fetchScopesSpy, - getAdvancedApply, - getAdvancedCancel, getApplicationsClustersExpand, getApplicationsClustersSelect, getApplicationsExpand, @@ -22,29 +20,28 @@ import { getApplicationsSlothPictureFactorySelect, getApplicationsSlothPictureFactoryTitle, getApplicationsSlothVoteTrackerSelect, - getBasicInnerContainer, - getBasicInput, - getBasicOpenAdvanced, + getFiltersApply, + getFiltersCancel, + getFiltersInput, getClustersExpand, getClustersSelect, getClustersSlothClusterNorthSelect, getClustersSlothClusterSouthSelect, getDashboard, getDashboardsContainer, + getDashboardsExpand, getDashboardsSearch, - getRootExpand, mocksNodes, mocksScopeDashboardBindings, mocksScopes, - queryAdvancedApply, + queryFiltersApply, queryApplicationsClustersSlothClusterNorthTitle, queryApplicationsClustersTitle, queryApplicationsSlothPictureFactoryTitle, queryApplicationsSlothVoteTrackerTitle, - queryBasicInnerContainer, queryDashboard, queryDashboardsContainer, - queryRootExpand, + queryDashboardsExpand, renderDashboard, } from './testUtils'; @@ -123,14 +120,14 @@ describe('ScopesScene', () => { describe('Tree', () => { it('Navigates through scopes nodes', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsClustersExpand()); await userEvents.click(getApplicationsExpand()); }); it('Fetches scope details on select', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); await waitFor(() => expect(fetchScopeSpy).toHaveBeenCalledTimes(1)); @@ -138,24 +135,24 @@ describe('ScopesScene', () => { it('Selects the proper scopes', async () => { await act(async () => filtersScene.updateScopes(['slothPictureFactory', 'slothVoteTracker'])); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); expect(getApplicationsSlothVoteTrackerSelect()).toBeChecked(); expect(getApplicationsSlothPictureFactorySelect()).toBeChecked(); }); it('Can select scopes from same level', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); await userEvents.click(getApplicationsClustersSelect()); - await userEvents.click(getBasicInput()); - expect(getBasicInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper'); + await userEvents.click(getFiltersApply()); + expect(getFiltersInput().value).toBe('slothVoteTracker, slothPictureFactory, Cluster Index Helper'); }); it("Can't navigate deeper than the level where scopes are selected", async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); await userEvents.click(getApplicationsClustersExpand()); @@ -163,25 +160,24 @@ describe('ScopesScene', () => { }); it('Can select a node from an upper level', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getClustersSelect()); - await userEvents.click(getBasicInput()); - expect(getBasicInput().value).toBe('Cluster Index Helper'); + await userEvents.click(getFiltersApply()); + expect(getFiltersInput().value).toBe('Cluster Index Helper'); }); it('Respects only one select per container', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getClustersExpand()); await userEvents.click(getClustersSlothClusterNorthSelect()); expect(getClustersSlothClusterSouthSelect()).toBeDisabled(); }); it('Search works', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.type(getApplicationsSearch(), 'Clusters'); await waitFor(() => expect(fetchNodesSpy).toHaveBeenCalledTimes(3)); @@ -197,43 +193,16 @@ describe('ScopesScene', () => { }); }); - describe('Basic selector', () => { + describe('Filters', () => { it('Opens', async () => { - await userEvents.click(getBasicInput()); - expect(getBasicInnerContainer()).toBeInTheDocument(); + await userEvents.click(getFiltersInput()); + expect(getFiltersApply()).toBeInTheDocument(); }); it('Fetches scope details on save', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getClustersSelect()); - await userEvents.click(getBasicInput()); - await waitFor(() => expect(fetchScopesSpy).toHaveBeenCalled()); - expect(filtersScene.getSelectedScopes()).toEqual( - mocksScopes.filter(({ metadata: { name } }) => name === 'indexHelperCluster') - ); - }); - - it('Shows selected scopes', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getClustersSelect()); - await userEvents.click(getBasicInput()); - expect(getBasicInput().value).toEqual('Cluster Index Helper'); - }); - }); - - describe('Advanced selector', () => { - it('Opens', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); - expect(queryBasicInnerContainer()).not.toBeInTheDocument(); - expect(getAdvancedApply()).toBeInTheDocument(); - }); - - it('Fetches scope details on save', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); - await userEvents.click(getClustersSelect()); - await userEvents.click(getAdvancedApply()); + await userEvents.click(getFiltersApply()); await waitFor(() => expect(fetchScopesSpy).toHaveBeenCalled()); expect(filtersScene.getSelectedScopes()).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'indexHelperCluster') @@ -241,85 +210,73 @@ describe('ScopesScene', () => { }); it("Doesn't save the scopes on close", async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); + await userEvents.click(getFiltersInput()); await userEvents.click(getClustersSelect()); - await userEvents.click(getAdvancedCancel()); + await userEvents.click(getFiltersCancel()); await waitFor(() => expect(fetchScopesSpy).not.toHaveBeenCalled()); expect(filtersScene.getSelectedScopes()).toEqual([]); }); - }); - describe('Selectors interoperability', () => { - it('Replicates the same structure from basic to advanced selector', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getApplicationsExpand()); - await userEvents.click(getBasicOpenAdvanced()); - expect(getApplicationsSlothPictureFactoryTitle()).toBeInTheDocument(); - }); - - it('Replicates the same structure from advanced to basic selector', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); - await userEvents.click(getApplicationsExpand()); - await userEvents.click(getAdvancedApply()); - await userEvents.click(getBasicInput()); - expect(getApplicationsSlothPictureFactoryTitle()).toBeInTheDocument(); + it('Shows selected scopes', async () => { + await userEvents.click(getFiltersInput()); + await userEvents.click(getClustersSelect()); + await userEvents.click(getFiltersApply()); + expect(getFiltersInput().value).toEqual('Cluster Index Helper'); }); }); describe('Dashboards list', () => { it('Toggles expanded state', async () => { - await userEvents.click(getRootExpand()); + await userEvents.click(getDashboardsExpand()); expect(getDashboardsContainer()).toBeInTheDocument(); }); it('Does not fetch dashboards list when the list is not expanded', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => expect(fetchDashboardsSpy).not.toHaveBeenCalled()); }); it('Fetches dashboards list when the list is expanded', async () => { - await userEvents.click(getRootExpand()); - await userEvents.click(getBasicInput()); + await userEvents.click(getDashboardsExpand()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => expect(fetchDashboardsSpy).toHaveBeenCalled()); }); it('Fetches dashboards list when the list is expanded after scope selection', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); - await userEvents.click(getRootExpand()); + await userEvents.click(getFiltersApply()); + await userEvents.click(getDashboardsExpand()); await waitFor(() => expect(fetchDashboardsSpy).toHaveBeenCalled()); }); it('Shows dashboards for multiple scopes', async () => { - await userEvents.click(getRootExpand()); - await userEvents.click(getBasicInput()); + await userEvents.click(getDashboardsExpand()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); expect(queryDashboard('3')).not.toBeInTheDocument(); expect(queryDashboard('4')).not.toBeInTheDocument(); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); expect(getDashboard('3')).toBeInTheDocument(); expect(getDashboard('4')).toBeInTheDocument(); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); expect(queryDashboard('1')).not.toBeInTheDocument(); expect(queryDashboard('2')).not.toBeInTheDocument(); expect(getDashboard('3')).toBeInTheDocument(); @@ -327,11 +284,11 @@ describe('ScopesScene', () => { }); it('Filters the dashboards list', async () => { - await userEvents.click(getRootExpand()); - await userEvents.click(getBasicInput()); + await userEvents.click(getDashboardsExpand()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); expect(getDashboard('1')).toBeInTheDocument(); expect(getDashboard('2')).toBeInTheDocument(); await userEvents.type(getDashboardsSearch(), '1'); @@ -346,43 +303,36 @@ describe('ScopesScene', () => { expect(scopesScene.state.isExpanded).toEqual(false); }); - it('Closes basic selector on enter', async () => { - await userEvents.click(getBasicInput()); + it('Closes filters on enter', async () => { + await userEvents.click(getFiltersInput()); await act(async () => dashboardScene.onEnterEditMode()); - expect(queryBasicInnerContainer()).not.toBeInTheDocument(); - }); - - it('Closes advanced selector on enter', async () => { - await userEvents.click(getBasicInput()); - await userEvents.click(getBasicOpenAdvanced()); - await act(async () => dashboardScene.onEnterEditMode()); - expect(queryAdvancedApply()).not.toBeInTheDocument(); + expect(queryFiltersApply()).not.toBeInTheDocument(); }); it('Closes dashboards list on enter', async () => { - await userEvents.click(getRootExpand()); + await userEvents.click(getDashboardsExpand()); await act(async () => dashboardScene.onEnterEditMode()); expect(queryDashboardsContainer()).not.toBeInTheDocument(); }); - it('Does not open basic selector when view mode is active', async () => { + it('Does not open filters when view mode is active', async () => { await act(async () => dashboardScene.onEnterEditMode()); - await userEvents.click(getBasicInput()); - expect(queryBasicInnerContainer()).not.toBeInTheDocument(); + await userEvents.click(getFiltersInput()); + expect(queryFiltersApply()).not.toBeInTheDocument(); }); it('Hides the expand button when view mode is active', async () => { await act(async () => dashboardScene.onEnterEditMode()); - expect(queryRootExpand()).not.toBeInTheDocument(); + expect(queryDashboardsExpand()).not.toBeInTheDocument(); }); }); describe('Enrichers', () => { it('Data requests', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -390,9 +340,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -402,9 +352,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { const queryRunner = sceneGraph.findObject(dashboardScene, (o) => o.state.key === 'data-query-runner')!; expect(dashboardScene.enrichDataRequest(queryRunner).scopes).toEqual( @@ -414,19 +364,19 @@ describe('ScopesScene', () => { }); it('Filters requests', async () => { - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsExpand()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'slothPictureFactory') ); }); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothVoteTrackerSelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter( @@ -435,9 +385,9 @@ describe('ScopesScene', () => { ); }); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersInput()); await userEvents.click(getApplicationsSlothPictureFactorySelect()); - await userEvents.click(getBasicInput()); + await userEvents.click(getFiltersApply()); await waitFor(() => { expect(dashboardScene.enrichFiltersRequest().scopes).toEqual( mocksScopes.filter(({ metadata: { name } }) => name === 'slothVoteTracker') diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx index e37d99eb374..62e59eb4a3a 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesScene.tsx @@ -94,10 +94,10 @@ export function ScopesSceneRenderer({ model }: SceneComponentProps) className={cx(!isExpanded && styles.iconNotExpanded)} aria-label={ isExpanded - ? t('scopes.root.collapse', 'Collapse scope filters') - : t('scopes.root.expand', 'Expand scope filters') + ? t('scopes.suggestedDashboards.toggle.collapse', 'Collapse scope filters') + : t('scopes.suggestedDashboards.toggle..expand', 'Expand scope filters') } - data-testid="scopes-root-expand" + data-testid="scopes-dashboards-expand" onClick={() => model.toggleIsExpanded()} /> )} diff --git a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx index 1749f5790f0..06566d270bc 100644 --- a/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx +++ b/public/app/features/dashboard-scene/scene/Scopes/ScopesTreeLevel.tsx @@ -10,7 +10,6 @@ import { t } from 'app/core/internationalization'; import { NodesMap } from './types'; export interface ScopesTreeLevelProps { - showQuery: boolean; nodes: NodesMap; nodePath: string[]; loadingNodeName: string | undefined; @@ -20,7 +19,6 @@ export interface ScopesTreeLevelProps { } export function ScopesTreeLevel({ - showQuery, nodes, nodePath, loadingNodeName, @@ -43,7 +41,7 @@ export function ScopesTreeLevel({ return ( <> - {showQuery && !anyChildExpanded && ( + {!anyChildExpanded && ( } className={styles.searchInput} @@ -101,7 +99,6 @@ export function ScopesTreeLevel({
{childNode.isExpanded && ( `scopes-tree-${nodeId}-search`, select: (nodeId: string) => `scopes-tree-${nodeId}-checkbox`, expand: (nodeId: string) => `scopes-tree-${nodeId}-expand`, title: (nodeId: string) => `scopes-tree-${nodeId}-title`, }, - basicSelector: { - container: 'scopes-basic-container', - innerContainer: 'scopes-basic-inner-container', - loading: 'scopes-basic-loading', - openAdvanced: 'scopes-basic-open-advanced', - input: 'scopes-basic-input', - }, - advancedSelector: { - container: 'scopes-advanced-container', - loading: 'scopes-advanced-loading', - apply: 'scopes-advanced-apply', - cancel: 'scopes-advanced-cancel', + filters: { + input: 'scopes-filters-input', + container: 'scopes-filters-container', + loading: 'scopes-filters-loading', + apply: 'scopes-filters-apply', + cancel: 'scopes-filters-cancel', }, dashboards: { + expand: 'scopes-dashboards-expand', container: 'scopes-dashboards-container', search: 'scopes-dashboards-search', loading: 'scopes-dashboards-loading', @@ -259,18 +251,13 @@ const selectors = { }, }; -export const queryRootExpand = () => screen.queryByTestId(selectors.root.expand); -export const getRootExpand = () => screen.getByTestId(selectors.root.expand); - -export const queryBasicInnerContainer = () => screen.queryByTestId(selectors.basicSelector.innerContainer); -export const getBasicInnerContainer = () => screen.getByTestId(selectors.basicSelector.innerContainer); -export const getBasicInput = () => screen.getByTestId(selectors.basicSelector.input); -export const getBasicOpenAdvanced = () => screen.getByTestId(selectors.basicSelector.openAdvanced); - -export const queryAdvancedApply = () => screen.queryByTestId(selectors.advancedSelector.apply); -export const getAdvancedApply = () => screen.getByTestId(selectors.advancedSelector.apply); -export const getAdvancedCancel = () => screen.getByTestId(selectors.advancedSelector.cancel); +export const getFiltersInput = () => screen.getByTestId(selectors.filters.input); +export const queryFiltersApply = () => screen.queryByTestId(selectors.filters.apply); +export const getFiltersApply = () => screen.getByTestId(selectors.filters.apply); +export const getFiltersCancel = () => screen.getByTestId(selectors.filters.cancel); +export const queryDashboardsExpand = () => screen.queryByTestId(selectors.dashboards.expand); +export const getDashboardsExpand = () => screen.getByTestId(selectors.dashboards.expand); export const queryDashboardsContainer = () => screen.queryByTestId(selectors.dashboards.container); export const getDashboardsContainer = () => screen.getByTestId(selectors.dashboards.container); export const getDashboardsSearch = () => screen.getByTestId(selectors.dashboards.search); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index 6d766c64cdf..b448f53a35b 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1594,23 +1594,22 @@ "dismissable-button": "Close" }, "scopes": { - "advancedSelector": { + "filters": { "apply": "Apply", "cancel": "Cancel", + "input": { + "placeholder": "Select scopes...", + "removeAll": "Remove all scopes" + }, "title": "Select scopes" }, - "basicSelector": { - "openAdvanced": "Open advanced scope selector <1>", - "placeholder": "Select scopes...", - "removeAll": "Remove all scopes" - }, - "root": { - "collapse": "Collapse scope filters", - "expand": "Expand scope filters" - }, "suggestedDashboards": { "loading": "Loading dashboards", - "search": "Filter" + "search": "Filter", + "toggle": { + "collapse": "Collapse scope filters", + "expand": "Expand scope filters" + } }, "tree": { "collapse": "Collapse", diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 71af32ec586..88f6ead1fcb 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1594,23 +1594,22 @@ "dismissable-button": "Cľőşę" }, "scopes": { - "advancedSelector": { + "filters": { "apply": "Åppľy", "cancel": "Cäʼnčęľ", + "input": { + "placeholder": "Ŝęľęčŧ şčőpęş...", + "removeAll": "Ŗęmővę äľľ şčőpęş" + }, "title": "Ŝęľęčŧ şčőpęş" }, - "basicSelector": { - "openAdvanced": "Øpęʼn äđväʼnčęđ şčőpę şęľęčŧőř <1>", - "placeholder": "Ŝęľęčŧ şčőpęş...", - "removeAll": "Ŗęmővę äľľ şčőpęş" - }, - "root": { - "collapse": "Cőľľäpşę şčőpę ƒįľŧęřş", - "expand": "Ēχpäʼnđ şčőpę ƒįľŧęřş" - }, "suggestedDashboards": { "loading": "Ŀőäđįʼnģ đäşĥþőäřđş", - "search": "Fįľŧęř" + "search": "Fįľŧęř", + "toggle": { + "collapse": "Cőľľäpşę şčőpę ƒįľŧęřş", + "expand": "Ēχpäʼnđ şčőpę ƒįľŧęřş" + } }, "tree": { "collapse": "Cőľľäpşę", From b7180c17b8da8cdedc0e9ade2b6672625ac2eec6 Mon Sep 17 00:00:00 2001 From: Juan Cabanas Date: Thu, 13 Jun 2024 11:11:26 -0300 Subject: [PATCH 06/13] ShareDrawer: Add confirm action (#89001) --- .../share-externally/ShareExternally.tsx | 73 +++++++++++++------ .../ShareDrawer/ShareDrawerConfirmAction.tsx | 59 +++++++++++++++ public/locales/en-US/grafana.json | 8 +- public/locales/pseudo-LOCALE/grafana.json | 8 +- 4 files changed, 124 insertions(+), 24 deletions(-) create mode 100644 public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawerConfirmAction.tsx diff --git a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.tsx b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.tsx index 3321d5074d2..262a5ea25f2 100644 --- a/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.tsx +++ b/public/app/features/dashboard-scene/sharing/ShareButton/share-externally/ShareExternally.tsx @@ -23,6 +23,7 @@ import { DashboardInteractions } from 'app/features/dashboard-scene/utils/intera import { AccessControlAction } from 'app/types'; import { getDashboardSceneFor } from '../../../utils/utils'; +import { ShareDrawerConfirmAction } from '../../ShareDrawer/ShareDrawerConfirmAction'; import { useShareDrawerContext } from '../../ShareDrawer/ShareDrawerContext'; import { EmailSharing } from './EmailShare/EmailSharing'; @@ -65,22 +66,62 @@ export class ShareExternally extends SceneObjectBase { } function ShareExternallyRenderer({ model }: SceneComponentProps) { - const dashboard = getDashboardSceneFor(model); - const { data: publicDashboard, isLoading } = useGetPublicDashboardQuery(dashboard.state.uid!); + const [showRevokeAccess, setShowRevokeAccess] = useState(false); + const styles = useStyles2(getStyles); + const dashboard = getDashboardSceneFor(model); + + const { data: publicDashboard, isLoading } = useGetPublicDashboardQuery(dashboard.state.uid!); + const [deletePublicDashboard, { isLoading: isDeleteLoading }] = useDeletePublicDashboardMutation(); + + const onRevokeClick = () => { + setShowRevokeAccess(true); + }; + + const onDeleteClick = async () => { + DashboardInteractions.revokePublicDashboardClicked(); + await deletePublicDashboard({ + dashboard, + uid: publicDashboard!.uid, + dashboardUid: dashboard.state.uid!, + }).unwrap(); + setShowRevokeAccess(false); + }; if (isLoading) { return ; } + if (showRevokeAccess) { + return ( + setShowRevokeAccess(false)} + description={t( + 'public-dashboard.share-externally.revoke-access-description', + 'Are you sure you want to revoke this access? The dashboard can no longer be shared.' + )} + isActionLoading={isDeleteLoading} + /> + ); + } + return (
- +
); } -function ShareExternallyBase({ publicDashboard }: { publicDashboard?: PublicDashboard }) { +function ShareExternallyBase({ + publicDashboard, + onRevokeClick, +}: { + publicDashboard?: PublicDashboard; + onRevokeClick: () => void; +}) { const options = getShareExternallyOptions(); const getShareType = useMemo(() => { if (publicDashboard && isEmailSharingEnabled()) { @@ -105,24 +146,21 @@ function ShareExternallyBase({ publicDashboard }: { publicDashboard?: PublicDash - {Config} {publicDashboard && ( <> - + )} ); } -function Actions({ publicDashboard }: { publicDashboard: PublicDashboard }) { +function Actions({ publicDashboard, onRevokeClick }: { publicDashboard: PublicDashboard; onRevokeClick: () => void }) { const { dashboard } = useShareDrawerContext(); const [update, { isLoading: isUpdateLoading }] = usePauseOrResumePublicDashboardMutation(); - const [deletePublicDashboard, { isLoading: isDeleteLoading }] = useDeletePublicDashboardMutation(); const styles = useStyles2(getStyles); - const isLoading = isUpdateLoading || isDeleteLoading; const hasWritePermissions = contextSrv.hasPermission(AccessControlAction.DashboardsPublicWrite); function onCopyURL() { @@ -142,15 +180,6 @@ function Actions({ publicDashboard }: { publicDashboard: PublicDashboard }) { }); }; - const onDeleteClick = () => { - DashboardInteractions.revokePublicDashboardClicked(); - deletePublicDashboard({ - dashboard, - uid: publicDashboard!.uid, - dashboardUid: dashboard.state.uid!, - }); - }; - return (
@@ -169,8 +198,8 @@ function Actions({ publicDashboard }: { publicDashboard: PublicDashboard }) { icon="trash-alt" variant="destructive" fill="outline" - disabled={isLoading || !hasWritePermissions} - onClick={onDeleteClick} + disabled={isUpdateLoading || !hasWritePermissions} + onClick={onRevokeClick} > Revoke access @@ -187,7 +216,7 @@ function Actions({ publicDashboard }: { publicDashboard: PublicDashboard }) { : '' } onClick={onPauseOrResumeClick} - disabled={isLoading || !hasWritePermissions} + disabled={isUpdateLoading || !hasWritePermissions} > {publicDashboard.isEnabled ? ( Pause access @@ -197,7 +226,7 @@ function Actions({ publicDashboard }: { publicDashboard: PublicDashboard }) {
- {isLoading && } + {isUpdateLoading && }
); } diff --git a/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawerConfirmAction.tsx b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawerConfirmAction.tsx new file mode 100644 index 00000000000..91aeab1bc84 --- /dev/null +++ b/public/app/features/dashboard-scene/sharing/ShareDrawer/ShareDrawerConfirmAction.tsx @@ -0,0 +1,59 @@ +import { css } from '@emotion/css'; +import React from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Spinner, Stack, Text } from '@grafana/ui'; +import { IconButton, useStyles2 } from '@grafana/ui/'; +import { ConfirmContent, ConfirmContentProps } from '@grafana/ui/src/components/ConfirmModal/ConfirmContent'; +import { t } from 'app/core/internationalization'; + +export function ShareDrawerConfirmAction({ + onConfirm, + onDismiss, + description, + confirmButtonLabel, + title, + isActionLoading, +}: { title: string; isActionLoading: boolean } & Pick< + ConfirmContentProps, + 'description' | 'onConfirm' | 'onDismiss' | 'confirmButtonLabel' +>) { + const styles = useStyles2(getStyles); + + const ConfirmBody = () => ( +
+ + + + {title} + + {isActionLoading && } + +
+ ); + + return ( + } + description={description} + confirmButtonLabel={confirmButtonLabel} + confirmButtonVariant="destructive" + dismissButtonLabel="Cancel" + dismissButtonVariant="secondary" + justifyButtons="flex-start" + onConfirm={onConfirm} + onDismiss={onDismiss} + /> + ); +} + +const getStyles = (theme: GrafanaTheme2) => ({ + bodyContainer: css({ + marginBottom: theme.spacing(2), + }), +}); diff --git a/public/locales/en-US/grafana.json b/public/locales/en-US/grafana.json index b448f53a35b..16bfdda9b48 100644 --- a/public/locales/en-US/grafana.json +++ b/public/locales/en-US/grafana.json @@ -1487,7 +1487,8 @@ "public-share-type-option-description": "Anyone with the link can access", "public-share-type-option-label": "Anyone with the link", "resume-access-button": "Resume access", - "revoke-access-button": "Revoke access" + "revoke-access-button": "Revoke access", + "revoke-access-description": "Are you sure you want to revoke this access? The dashboard can no longer be shared." }, "sharing": { "success-creation": "Dashboard is public!" @@ -1654,6 +1655,11 @@ "title": "You haven't created any service accounts yet" } }, + "share-drawer": { + "confirm-action": { + "back-arrow-button": "Back button" + } + }, "share-modal": { "dashboard": { "title": "Share" diff --git a/public/locales/pseudo-LOCALE/grafana.json b/public/locales/pseudo-LOCALE/grafana.json index 88f6ead1fcb..474ee4c3b22 100644 --- a/public/locales/pseudo-LOCALE/grafana.json +++ b/public/locales/pseudo-LOCALE/grafana.json @@ -1487,7 +1487,8 @@ "public-share-type-option-description": "Åʼnyőʼnę ŵįŧĥ ŧĥę ľįʼnĸ čäʼn äččęşş", "public-share-type-option-label": "Åʼnyőʼnę ŵįŧĥ ŧĥę ľįʼnĸ", "resume-access-button": "Ŗęşūmę äččęşş", - "revoke-access-button": "Ŗęvőĸę äččęşş" + "revoke-access-button": "Ŗęvőĸę äččęşş", + "revoke-access-description": "Åřę yőū şūřę yőū ŵäʼnŧ ŧő řęvőĸę ŧĥįş äččęşş? Ŧĥę đäşĥþőäřđ čäʼn ʼnő ľőʼnģęř þę şĥäřęđ." }, "sharing": { "success-creation": "Đäşĥþőäřđ įş pūþľįč!" @@ -1654,6 +1655,11 @@ "title": "Ÿőū ĥävęʼn'ŧ čřęäŧęđ äʼny şęřvįčę äččőūʼnŧş yęŧ" } }, + "share-drawer": { + "confirm-action": { + "back-arrow-button": "ßäčĸ þūŧŧőʼn" + } + }, "share-modal": { "dashboard": { "title": "Ŝĥäřę" From 82aa000e9d7091d0fae0c07f54f134dc25f2adcd Mon Sep 17 00:00:00 2001 From: Kyle Brandt Date: Thu, 13 Jun 2024 10:58:39 -0400 Subject: [PATCH 07/13] SSE: (Chore) Update log line with context (trace) (#89172) Improves log line to help with debugging in Server Side Expressions. In particular, the traceId, datasourceType, and datasourceUid will now be included. --- pkg/expr/converter.go | 3 ++- pkg/expr/ml.go | 2 +- pkg/expr/nodes.go | 4 ++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pkg/expr/converter.go b/pkg/expr/converter.go index 2ecd6c42b7f..4681aab8b72 100644 --- a/pkg/expr/converter.go +++ b/pkg/expr/converter.go @@ -8,6 +8,7 @@ import ( "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/grafana/pkg/expr/mathexp" + "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/tracing" "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/featuremgmt" @@ -119,7 +120,7 @@ func (c *ResultConverter) Convert(ctx context.Context, }, nil } -func getResponseFrame(resp *backend.QueryDataResponse, refID string) (data.Frames, error) { +func getResponseFrame(logger *log.ConcreteLogger, resp *backend.QueryDataResponse, refID string) (data.Frames, error) { response, ok := resp.Responses[refID] if !ok { // This indicates that the RefID of the request was not included to the response, i.e. some problem in the data source plugin diff --git a/pkg/expr/ml.go b/pkg/expr/ml.go index affdad77a95..e7bde5487f2 100644 --- a/pkg/expr/ml.go +++ b/pkg/expr/ml.go @@ -124,7 +124,7 @@ func (m *MLNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s * data = &backend.QueryDataResponse{Responses: map[string]backend.DataResponse{}} } - dataFrames, err := getResponseFrame(data, m.refID) + dataFrames, err := getResponseFrame(logger, data, m.refID) if err != nil { return mathexp.Results{}, MakeQueryError(m.refID, "ml", err) } diff --git a/pkg/expr/nodes.go b/pkg/expr/nodes.go index 2d0bbbf0d2b..80de28e9883 100644 --- a/pkg/expr/nodes.go +++ b/pkg/expr/nodes.go @@ -325,7 +325,7 @@ func executeDSNodesGrouped(ctx context.Context, now time.Time, vars mathexp.Vars } for _, dn := range nodeGroup { - dataFrames, err := getResponseFrame(resp, dn.refID) + dataFrames, err := getResponseFrame(logger, resp, dn.refID) if err != nil { vars[dn.refID] = mathexp.Results{Error: MakeQueryError(dn.refID, dn.datasource.UID, err)} instrument(err, "") @@ -395,7 +395,7 @@ func (dn *DSNode) Execute(ctx context.Context, now time.Time, _ mathexp.Vars, s return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) } - dataFrames, err := getResponseFrame(resp, dn.refID) + dataFrames, err := getResponseFrame(logger, resp, dn.refID) if err != nil { return mathexp.Results{}, MakeQueryError(dn.refID, dn.datasource.UID, err) } From 375be77f32f1ff79690002c3099b9fb509b6e936 Mon Sep 17 00:00:00 2001 From: Ivana Huckova <30407135+ivanahuckova@users.noreply.github.com> Date: Thu, 13 Jun 2024 17:05:50 +0200 Subject: [PATCH 08/13] Elasticsearch: Add developer documentation (#89050) * Elasticsearch: Add developer documentation * Update * Update * Add code comments * Update * Update public/app/plugins/datasource/elasticsearch/docs/developer_documentation.md --- .../datasource/elasticsearch/datasource.ts | 140 ++++++++++++++++-- .../docs/developer_documentation.md | 31 ++++ 2 files changed, 157 insertions(+), 14 deletions(-) create mode 100644 public/app/plugins/datasource/elasticsearch/docs/developer_documentation.md diff --git a/public/app/plugins/datasource/elasticsearch/datasource.ts b/public/app/plugins/datasource/elasticsearch/datasource.ts index b76c43b52f1..584f970fef8 100644 --- a/public/app/plugins/datasource/elasticsearch/datasource.ts +++ b/public/app/plugins/datasource/elasticsearch/datasource.ts @@ -1,4 +1,4 @@ -import { cloneDeep, find, first as _first, isNumber, isObject, isString, map as _map } from 'lodash'; +import { cloneDeep, first as _first, isNumber, isObject, isString, map as _map, find } from 'lodash'; import { from, generate, lastValueFrom, Observable, of } from 'rxjs'; import { catchError, first, map, mergeMap, skipWhile, throwIfEmpty, tap } from 'rxjs/operators'; import { SemVer } from 'semver'; @@ -121,7 +121,6 @@ export class ElasticDatasource queryBuilder: ElasticQueryBuilder; indexPattern: IndexPattern; intervalPattern?: Interval; - logMessageField?: string; logLevelField?: string; dataLinks: DataLinkConfig[]; languageProvider: LanguageProvider; @@ -140,7 +139,7 @@ export class ElasticDatasource this.name = instanceSettings.name; this.isProxyAccess = instanceSettings.access === 'proxy'; const settingsData = instanceSettings.jsonData || {}; - + // instanceSettings.database is deprecated and should be removed in the future this.index = settingsData.index ?? instanceSettings.database ?? ''; this.timeField = settingsData.timeField; this.indexPattern = new IndexPattern(this.index, settingsData.interval); @@ -150,19 +149,15 @@ export class ElasticDatasource this.queryBuilder = new ElasticQueryBuilder({ timeField: this.timeField, }); - this.logMessageField = settingsData.logMessageField || ''; this.logLevelField = settingsData.logLevelField || ''; this.dataLinks = settingsData.dataLinks || []; this.includeFrozen = settingsData.includeFrozen ?? false; + // we want to cache the database version so we don't have to ask for it every time this.databaseVersion = null; this.annotations = { QueryEditor: ElasticsearchAnnotationsQueryEditor, }; - if (this.logMessageField === '') { - this.logMessageField = undefined; - } - if (this.logLevelField === '') { this.logLevelField = undefined; } @@ -181,6 +176,11 @@ export class ElasticDatasource return this.postResource(path, data, resourceOptions); } + /** + * Implemented as part of DataSourceWithQueryImportSupport. + * Imports queries from AbstractQuery objects when switching between different data source types. + * @returns A Promise that resolves to an array of ES queries. + */ async importFromAbstractQueries(abstractQueries: AbstractQuery[]): Promise { return abstractQueries.map((abstractQuery) => this.languageProvider.importFromAbstractQuery(abstractQuery)); } @@ -235,6 +235,11 @@ export class ElasticDatasource ); } + /** + * Implemented as part of the DataSourceAPI. It allows the datasource to serve as a source of annotations for a dashboard. + * @returns A promise that resolves to an array of AnnotationEvent objects representing the annotations for the dashboard. + * @todo This is deprecated and it is recommended to use the `AnnotationSupport` feature for annotations. + */ annotationQuery(options: any): Promise { const payload = this.prepareAnnotationRequest(options); trackAnnotationQuery(options.annotation); @@ -255,6 +260,7 @@ export class ElasticDatasource ); } + // Private method used in the `annotationQuery` to prepare the payload for the Elasticsearch annotation request private prepareAnnotationRequest(options: { annotation: ElasticsearchAnnotationQuery; // Should be DashboardModel but cannot import that here from the main app. This is a temporary solution as we need to move from deprecated annotations. @@ -348,6 +354,7 @@ export class ElasticDatasource return payload; } + // Private method used in the `annotationQuery` to process Elasticsearch hits into AnnotationEvents private processHitsToAnnotationEvents(annotation: ElasticsearchAnnotationQuery, hits: ElasticsearchHits) { const timeField = annotation.timeField || '@timestamp'; const timeEndField = annotation.timeEndField || null; @@ -416,10 +423,15 @@ export class ElasticDatasource return list; } + // Replaces variables in a Lucene query string interpolateLuceneQuery(queryString: string, scopedVars?: ScopedVars) { return this.templateSrv.replace(queryString, scopedVars, 'lucene'); } + /** + * Implemented as a part of DataSourceApi. Interpolates variables and adds ad hoc filters to a list of ES queries. + * @returns An array of ES queries with interpolated variables and ad hoc filters using `applyTemplateVariables`. + */ interpolateVariablesInQueries( queries: ElasticsearchQuery[], scopedVars: ScopedVars, @@ -428,6 +440,9 @@ export class ElasticDatasource return queries.map((q) => this.applyTemplateVariables(q, scopedVars, filters)); } + /** + * @todo Remove as we have health checks in the backend + */ async testDatasource() { // we explicitly ask for uncached, "fresh" data here const dbVersion = await this.getDatabaseVersion(false); @@ -456,7 +471,8 @@ export class ElasticDatasource ); } - getQueryHeader(searchType: string, timeFrom?: DateTime, timeTo?: DateTime): string { + // Private method used in `getTerms` to get the header for the Elasticsearch query + private getQueryHeader(searchType: string, timeFrom?: DateTime, timeTo?: DateTime): string { const queryHeader = { search_type: searchType, ignore_unavailable: true, @@ -466,6 +482,11 @@ export class ElasticDatasource return JSON.stringify(queryHeader); } + /** + * Implemented as part of DataSourceApi. Converts a ES query to a simple text string. + * Used, for example, in Query history. + * @returns A text representation of the query. + */ getQueryDisplayText(query: ElasticsearchQuery) { // TODO: This might be refactored a bit. const metricAggs = query.metrics; @@ -517,6 +538,10 @@ export class ElasticDatasource return text; } + /** + * Part of `DataSourceWithLogsContextSupport`, used to retrieve log context for a log row. + * @returns A promise that resolves to an object containing the log context data as DataFrames. + */ getLogRowContext = async (row: LogRowModel, options?: LogRowContextOptions): Promise<{ data: DataFrame[] }> => { const contextRequest = this.makeLogContextDataRequest(row, options); return lastValueFrom( @@ -552,10 +577,20 @@ export class ElasticDatasource } } + /** + * Implemented for DataSourceWithSupplementaryQueriesSupport. + * It returns the supplementary types that the data source supports. + * @returns An array of supported supplementary query types. + */ getSupportedSupplementaryQueryTypes(): SupplementaryQueryType[] { return [SupplementaryQueryType.LogsVolume, SupplementaryQueryType.LogsSample]; } + /** + * Implemented for DataSourceWithSupplementaryQueriesSupport. + * It retrieves supplementary queries based on the provided options and ES query. + * @returns A supplemented ES query or undefined if unsupported. + */ getSupplementaryQuery(options: SupplementaryQueryOptions, query: ElasticsearchQuery): ElasticsearchQuery | undefined { let isQuerySuitable = false; @@ -628,6 +663,10 @@ export class ElasticDatasource } } + /** + * Private method used in the `getDataProvider` for DataSourceWithSupplementaryQueriesSupport, specifically for Logs volume queries. + * @returns An Observable of DataQueryResponse or undefined if no suitable queries are found. + */ private getLogsVolumeDataProvider( request: DataQueryRequest ): DataQueryRequest | undefined { @@ -643,6 +682,10 @@ export class ElasticDatasource return { ...logsVolumeRequest, targets }; } + /** + * Private method used in the `getDataProvider` for DataSourceWithSupplementaryQueriesSupport, specifically for Logs sample queries. + * @returns An Observable of DataQueryResponse or undefined if no suitable queries are found. + */ private getLogsSampleDataProvider( request: DataQueryRequest ): DataQueryRequest | undefined { @@ -659,6 +702,10 @@ export class ElasticDatasource return { ...logsSampleRequest, targets: elasticQueries }; } + /** + * Required by DataSourceApi. It executes queries based on the provided DataQueryRequest. + * @returns An Observable of DataQueryResponse containing the query results. + */ query(request: DataQueryRequest): Observable { const start = new Date(); return super.query(request).pipe( @@ -672,6 +719,11 @@ export class ElasticDatasource ); } + /** + * Filters out queries that are hidden. Used when running queries through backend. + * It is called from DatasourceWithBackend. + * @returns `true` if the query is not hidden. + */ filterQuery(query: ElasticsearchQuery): boolean { if (query.hide) { return false; @@ -679,13 +731,17 @@ export class ElasticDatasource return true; } - isMetadataField(fieldName: string) { + // Private method used in the `getFields` to check if a field is a metadata field. + private isMetadataField(fieldName: string) { return ELASTIC_META_FIELDS.includes(fieldName); } - // TODO: instead of being a string, this could be a custom type representing all the elastic types - // FIXME: This doesn't seem to return actual MetricFindValues, we should either change the return type - // or fix the implementation. + /** + * Get the list of the fields to display in query editor or used for example in getTagKeys. + * @todo instead of being a string, this could be a custom type representing all the elastic types + * @fixme This doesn't seem to return actual MetricFindValues, we should either change the return type + * or fix the implementation. + */ getFields(type?: string[], range?: TimeRange): Observable { const typeMap: Record = { float: 'number', @@ -767,6 +823,10 @@ export class ElasticDatasource ); } + /** + * Get values for a given field. + * Used for example in getTagValues. + */ getTerms(queryDef: TermsQuery, range = getDefaultTimeRange()): Observable { const searchType = 'query_then_fetch'; const header = this.getQueryHeader(searchType, range.from, range.to); @@ -798,6 +858,7 @@ export class ElasticDatasource ); } + // Method used to create URL that includes correct parameters based on ES data source config. getMultiSearchUrl() { const searchParams = new URLSearchParams(); @@ -812,6 +873,10 @@ export class ElasticDatasource return ('_msearch?' + searchParams.toString()).replace(/\?$/, ''); } + /** + * Implemented as part of DataSourceAPI and used for template variable queries. + * @returns A Promise that resolves to an array of results from the metric find query. + */ metricFindQuery(query: string, options?: { range: TimeRange }): Promise { const range = options?.range; const parsedQuery = JSON.parse(query); @@ -831,14 +896,26 @@ export class ElasticDatasource return Promise.resolve([]); } + /** + * Implemented as part of the DataSourceAPI. Retrieves tag keys that can be used for ad-hoc filtering. + * @returns A Promise that resolves to an array of label names represented as MetricFindValue objects. + */ getTagKeys() { return lastValueFrom(this.getFields()); } + /** + * Implemented as part of the DataSourceAPI. Retrieves tag values that can be used for ad-hoc filtering. + * @returns A Promise that resolves to an array of label values represented as MetricFindValue objects + */ getTagValues(options: DataSourceGetTagValuesOptions) { return lastValueFrom(this.getTerms({ field: options.key }, options.timeRange)); } + /** + * Implemented as part of the DataSourceAPI. + * Used by alerting to check if query contains template variables. + */ targetContainsTemplate(target: ElasticsearchQuery) { if (this.templateSrv.containsTemplate(target.query) || this.templateSrv.containsTemplate(target.alias)) { return true; @@ -875,6 +952,7 @@ export class ElasticDatasource return false; } + // Private method used in the `targetContainsTemplate` to check if an object contains template variables. private objectContainsTemplate(obj: any) { if (typeof obj === 'string') { return this.templateSrv.containsTemplate(obj); @@ -898,6 +976,11 @@ export class ElasticDatasource return false; } + /** + * Implemented for `DataSourceWithToggleableQueryFiltersSupport`. Toggles a filter on or off based on the provided filter action. + * It is used for example in Explore to toggle fields on and off trough log details. + * @returns A new ES query with the filter toggled as specified. + */ toggleQueryFilter(query: ElasticsearchQuery, filter: ToggleFilterAction): ElasticsearchQuery { let expression = query.query ?? ''; switch (filter.type) { @@ -921,11 +1004,20 @@ export class ElasticDatasource return { ...query, query: expression }; } + /** + * Implemented for `DataSourceWithToggleableQueryFiltersSupport`. Checks if a query expression contains a filter based on the provided filter options. + * @returns A boolean value indicating whether the filter exists in the query expression. + */ queryHasFilter(query: ElasticsearchQuery, options: QueryFilterOptions): boolean { let expression = query.query ?? ''; return queryHasFilter(expression, options.key, options.value); } + /** + * Implemented as part of `DataSourceWithQueryModificationSupport`. Used to modify a query based on the provided action. + * It is used, for example, in the Query Builder to apply hints such as parsers, operations, etc. + * @returns A new ES query with the specified modification applied. + */ modifyQuery(query: ElasticsearchQuery, action: QueryFixAction): ElasticsearchQuery { if (!action.options) { return query; @@ -954,10 +1046,18 @@ export class ElasticDatasource return { ...query, query: expression }; } + /** + * Implemented as part of `DataSourceWithQueryModificationSupport`. Returns a list of operation + * types that are supported by `modifyQuery()`. + */ getSupportedQueryModifications() { return ['ADD_FILTER', 'ADD_FILTER_OUT', 'ADD_STRING_FILTER', 'ADD_STRING_FILTER_OUT']; } + /** + * Adds ad hoc filters to a query expression, handling proper escaping of filter values. + * @returns The query expression with ad hoc filters and correctly escaped values. + */ addAdHocFilters(query: string, adhocFilters?: AdHocVariableFilter[]) { if (!adhocFilters) { return query; @@ -970,7 +1070,11 @@ export class ElasticDatasource return finalQuery; } - // Used when running queries through backend + /** + * Applies template variables and add hoc filters to a query. Used when running queries through backend. + * It is called from DatasourceWithBackend. + * @returns A modified ES query with template variables and ad hoc filters applied. + */ applyTemplateVariables( query: ElasticsearchQuery, scopedVars: ScopedVars, @@ -1006,6 +1110,7 @@ export class ElasticDatasource return finalQuery; } + // Private method used in the `getDatabaseVersion` to get the database version from the Elasticsearch API. private getDatabaseVersionUncached(): Promise { // we want this function to never fail const getDbVersionObservable = from(this.getResourceRequest('')); @@ -1029,6 +1134,11 @@ export class ElasticDatasource ); } + /** + * Method used to get the database version from cache or from the Elasticsearch API. + * Elasticsearch data source supports only certain versions of Elasticsearch and we + * want to check the version and notify the user if the version is not supported. + * */ async getDatabaseVersion(useCachedData = true): Promise { if (useCachedData) { const cached = this.databaseVersion; @@ -1042,6 +1152,7 @@ export class ElasticDatasource return freshDatabaseVersion; } + // private method used in the `getLogRowContext` to create a log context data request. private makeLogContextDataRequest = (row: LogRowModel, options?: LogRowContextOptions) => { const direction = options?.direction || LogRowContextQueryDirection.Backward; const logQuery: Logs = { @@ -1087,6 +1198,7 @@ export class ElasticDatasource }; } +// Function to enhance the data frame with data links configured in the data source settings. export function enhanceDataFrameWithDataLinks(dataFrame: DataFrame, dataLinks: DataLinkConfig[]) { if (!dataLinks.length) { return; diff --git a/public/app/plugins/datasource/elasticsearch/docs/developer_documentation.md b/public/app/plugins/datasource/elasticsearch/docs/developer_documentation.md new file mode 100644 index 00000000000..1739c49069e --- /dev/null +++ b/public/app/plugins/datasource/elasticsearch/docs/developer_documentation.md @@ -0,0 +1,31 @@ +# ElasticSearch data source in Grafana + +ElasticSearch is the built-in core data source and one of the oldest and most popular data sources in Grafana. When refactoring and improving, it's important to consider that many users have legacy dashboards, annotations and configs that need to remain compatible. + +## Running queries using backend + +Queries in the ElasticSearch data source are now exclusively run through the backend. This change is detailed in [this document](https://docs.google.com/document/d/1oLfVh54gReZEN9FdlJ0Wuo7Ja8XhSbjJ15FkRisPGs8/edit#heading=h.nuqzkh8bfixf). The `enableElasticSearchBackendQuerying` feature toggle, which allowed switching between frontend and backend modes, was removed in Grafana 11.1.0. In case of reported issues, please refer to the linked document. + +## Development + +When developing for ElasticSearch, use `make devenv sources=elastic`. To specify a version, use `make devenv sources=elastic elastic_version=7.17.0`. In `devenv/docker/blocks/elastic/data/data.js`, you can update data to suit your debugging and testing needs. Additionally, ElasticSearch has a couple of debugging dashboards located in `devenv/dev-dashboards/datasource-ElasticSearch`. + +## Instrumentation + +The ElasticSearch data source has improved instrumentation with logs, metrics, traces, and dashboards. When debugging issues, it is useful to review the available telemetry signals. + +## Technical debt + +Here is a list of our current technical debt. + +### Database field + +Previously, users stored ElasticSearch indices in the `database` field, which has since been deprecated. It is now stored in `jsonData` (implemented in https://github.com/grafana/grafana/pull/62808), though we continue to support both fields. Eventually, support for the `database` field will need to be removed. + +## Supported Explore and Log features + +Many Explore and Log features are implemented through `DataSourceWithXXXSupport`, making it clear which functionalities are supported. + +## Supported ES Versions and version changes + +The supported ElasticSearch version is documented at https://grafana.com/docs/grafana/latest/datasources/ElasticSearch/#supported-ElasticSearch-versions. We typically update it with major Grafana versions, following ElasticSearch [Elastic Product End of Life Dates](https://www.elastic.co/support/eol) to the last supported versions of ElasticSearch available at the time of Grafana's release. From ffcb13b7ab597ee32f0b6f32307eb96ac69686a3 Mon Sep 17 00:00:00 2001 From: "grafana-delivery-bot[bot]" <132647405+grafana-delivery-bot[bot]@users.noreply.github.com> Date: Thu, 13 Jun 2024 18:08:26 +0300 Subject: [PATCH 09/13] Changelog: Updated changelog for 10.4.4 (#89173) Co-authored-by: grafanabot --- CHANGELOG.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f49497b009..d523e856cf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -350,6 +350,23 @@ The deprecated `email` field to send a report via `/api/reports/email` endpoint - **Grafana UI:** Add code variant to Text component. [#82318](https://github.com/grafana/grafana/issues/82318), [@tskarhed](https://github.com/tskarhed) + + +# 10.4.4 (2024-06-13) + +### Bug fixes + +- **BrowseDashboards:** Prepend subpath to New Browse Dashboard actions. [#89129](https://github.com/grafana/grafana/issues/89129), [@joshhunt](https://github.com/joshhunt) +- **Alerting:** Fix rule storage to filter by group names using case-sensitive comparison. [#89061](https://github.com/grafana/grafana/issues/89061), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Alerting:** Fix editing Grafana folder via alert rule editor. [#88907](https://github.com/grafana/grafana/issues/88907), [@gillesdemey](https://github.com/gillesdemey) +- **AzureMonitor:** Fix bug detecting app insights queries. [#88786](https://github.com/grafana/grafana/issues/88786), [@aangelisc](https://github.com/aangelisc) +- **AuthN:** Fix signout redirect url. [#88749](https://github.com/grafana/grafana/issues/88749), [@kalleep](https://github.com/kalleep) +- **SSE:** Fix threshold unmarshal to avoid panic. [#88650](https://github.com/grafana/grafana/issues/88650), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **Alerting:** Fix typo in JSON response for rule export. [#88094](https://github.com/grafana/grafana/issues/88094), [@yuri-tceretian](https://github.com/yuri-tceretian) +- **CloudMonitoring:** Fix query type selection issue. [#88023](https://github.com/grafana/grafana/issues/88023), [@aangelisc](https://github.com/aangelisc) +- **Provisioning:** Add override option to role provisioning. (Enterprise) + + # 10.4.3 (2024-05-13) From 627d77c3651d251c37f7e706162cc4382f30601d Mon Sep 17 00:00:00 2001 From: Artur Wierzbicki Date: Thu, 13 Jun 2024 17:18:29 +0200 Subject: [PATCH 10/13] chore: change codeowner of `/pkg/components/imguploader` (#89167) * change owner for imguploader * Update .github/CODEOWNERS Co-authored-by: Steve Simpson --------- Co-authored-by: Steve Simpson --- .github/CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2deae5a196e..979444dc2e4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -79,7 +79,7 @@ /pkg/components/apikeygen/ @grafana/identity-access-team /pkg/components/satokengen/ @grafana/identity-access-team /pkg/components/dashdiffs/ @grafana/grafana-app-platform-squad -/pkg/components/imguploader/ @grafana/grafana-backend-group +/pkg/components/imguploader/ @grafana/alerting-backend /pkg/components/loki/ @grafana/grafana-backend-group /pkg/components/null/ @grafana/grafana-backend-group /pkg/components/simplejson/ @grafana/grafana-backend-group From 3853f905281be22b2594ed23c183c0a2228a1539 Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 13 Jun 2024 19:40:47 +0300 Subject: [PATCH 11/13] RBAC: Include action sets in dashboard and folder permission filter (#89133) take action sets into account in dashboard and folder permission filter --- .../sqlstore/permissions/dashboard.go | 161 ++++++++++-------- .../dashboard_filter_no_subquery.go | 54 +++--- .../sqlstore/permissions/dashboard_test.go | 136 ++++++++++++++- 3 files changed, 258 insertions(+), 93 deletions(-) diff --git a/pkg/services/sqlstore/permissions/dashboard.go b/pkg/services/sqlstore/permissions/dashboard.go index c89eb2a612f..1dc5fe990ff 100644 --- a/pkg/services/sqlstore/permissions/dashboard.go +++ b/pkg/services/sqlstore/permissions/dashboard.go @@ -2,6 +2,7 @@ package permissions import ( "bytes" + "context" "fmt" "slices" "strings" @@ -25,10 +26,12 @@ type clause struct { } type accessControlDashboardPermissionFilter struct { - user identity.Requester - dashboardAction string - folderAction string - features featuremgmt.FeatureToggles + user identity.Requester + dashboardAction string + dashboardActionSets []string + folderAction string + folderActionSets []string + features featuremgmt.FeatureToggles where clause // any recursive CTE queries (if supported) @@ -53,30 +56,63 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi var folderAction string var dashboardAction string + var folderActionSets []string + var dashboardActionSets []string if queryType == searchstore.TypeFolder { folderAction = dashboards.ActionFoldersRead - //folderAction = append(folderAction, dashboards.ActionFoldersRead) + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"} + } if needEdit { folderAction = dashboards.ActionDashboardsCreate + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:edit", "folders:admin"} + } } } else if queryType == searchstore.TypeDashboard { dashboardAction = dashboards.ActionDashboardsRead + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"} + dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"} + } if needEdit { dashboardAction = dashboards.ActionDashboardsWrite + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:edit", "folders:admin"} + dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"} + } } } else if queryType == searchstore.TypeAlertFolder { folderAction = accesscontrol.ActionAlertingRuleRead + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"} + } if needEdit { folderAction = accesscontrol.ActionAlertingRuleCreate + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:edit", "folders:admin"} + } } } else if queryType == searchstore.TypeAnnotation { dashboardAction = accesscontrol.ActionAnnotationsRead + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"} + dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"} + } } else { folderAction = dashboards.ActionFoldersRead dashboardAction = dashboards.ActionDashboardsRead + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:view", "folders:edit", "folders:admin"} + dashboardActionSets = []string{"dashboards:view", "dashboards:edit", "dashboards:admin"} + } if needEdit { folderAction = dashboards.ActionDashboardsCreate dashboardAction = dashboards.ActionDashboardsWrite + if features.IsEnabled(context.Background(), featuremgmt.FlagAccessActionSets) { + folderActionSets = []string{"folders:edit", "folders:admin"} + dashboardActionSets = []string{"dashboards:edit", "dashboards:admin"} + } } } @@ -84,13 +120,13 @@ func NewAccessControlDashboardPermissionFilter(user identity.Requester, permissi if features.IsEnabledGlobally(featuremgmt.FlagPermissionsFilterRemoveSubquery) { f = &accessControlDashboardPermissionFilterNoFolderSubquery{ accessControlDashboardPermissionFilter: accessControlDashboardPermissionFilter{ - user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features, - recursiveQueriesAreSupported: recursiveQueriesAreSupported, + user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets, + features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported, }, } } else { - f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, dashboardAction: dashboardAction, features: features, - recursiveQueriesAreSupported: recursiveQueriesAreSupported, + f = &accessControlDashboardPermissionFilter{user: user, folderAction: folderAction, folderActionSets: folderActionSets, dashboardAction: dashboardAction, dashboardActionSets: dashboardActionSets, + features: features, recursiveQueriesAreSupported: recursiveQueriesAreSupported, } } f.buildClauses() @@ -149,20 +185,24 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { // currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana) useSelfContainedPermissions := f.user.IsAuthenticatedBy(login.ExtendedJWTModule) - if len(f.dashboardAction) > 0 { - toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards) + if f.dashboardAction != "" { + toCheckDashboards := actionsToCheck(f.dashboardAction, f.dashboardActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards) + toCheckFolders := actionsToCheck(f.dashboardAction, f.folderActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards) - if len(toCheck) > 0 { + if len(toCheckDashboards) > 0 { if !useSelfContainedPermissions { builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'") builder.WriteString(rolesFilter) args = append(args, params...) - builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") - args = append(args, toCheck[0]) + if len(toCheckDashboards) == 1 { + builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") + args = append(args, toCheckDashboards[0]) + } else { + builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")) AND NOT dashboard.is_folder)") + args = append(args, toCheckDashboards...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix) + args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix) // Only add the IN clause if we have any dashboards to check if len(args) > 0 { @@ -179,12 +219,15 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString(rolesFilter) permSelectorArgs = append(permSelectorArgs, params...) - permSelector.WriteString(" AND action = ?") - permSelectorArgs = append(permSelectorArgs, toCheck[0]) + if len(toCheckDashboards) == 1 { + permSelector.WriteString(" AND action = ?") + permSelectorArgs = append(permSelectorArgs, toCheckDashboards[0]) + } else { + permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")") + permSelectorArgs = append(permSelectorArgs, toCheckFolders...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix) + permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix) // Only add the IN clause if we have any folders to check if len(permSelectorArgs) > 0 { @@ -229,7 +272,7 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { builder.WriteString(") AND NOT dashboard.is_folder)") // Include all the dashboards under the root if the user has the required permissions on the root (used to be the General folder) - if hasAccessToRoot(toCheck, f.user) { + if hasAccessToRoot(f.dashboardAction, f.user) { builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)") } } else { @@ -241,23 +284,27 @@ func (f *accessControlDashboardPermissionFilter) buildClauses() { permSelector.Reset() permSelectorArgs = permSelectorArgs[:0] - if len(f.folderAction) > 0 { - if len(f.dashboardAction) > 0 { + if f.folderAction != "" { + if f.dashboardAction != "" { builder.WriteString(" OR ") } - toCheck := actionsToCheck(f.folderAction, f.user.GetPermissions(), folderWildcards) + toCheck := actionsToCheck(f.folderAction, f.folderActionSets, f.user.GetPermissions(), folderWildcards) + if len(toCheck) > 0 { if !useSelfContainedPermissions { permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString(rolesFilter) permSelectorArgs = append(permSelectorArgs, params...) - permSelector.WriteString(" AND action = ?") - permSelectorArgs = append(permSelectorArgs, toCheck[0]) + if len(toCheck) == 1 { + permSelector.WriteString(" AND action = ?") + permSelectorArgs = append(permSelectorArgs, toCheck[0]) + } else { + permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ")") + permSelectorArgs = append(permSelectorArgs, toCheck...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix) + permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix) if len(permSelectorArgs) > 0 { permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "") @@ -342,7 +389,7 @@ func (f *accessControlDashboardPermissionFilter) addRecQry(queryName string, whe }) } -func actionsToCheck(action string, permissions map[string][]string, wildcards ...accesscontrol.Wildcards) []any { +func actionsToCheck(action string, actionSets []string, permissions map[string][]string, wildcards ...accesscontrol.Wildcards) []any { for _, scope := range permissions[action] { for _, w := range wildcards { if w.Contains(scope) { @@ -351,7 +398,12 @@ func actionsToCheck(action string, permissions map[string][]string, wildcards .. } } - return []any{action} + toCheck := []any{action} + for _, a := range actionSets { + toCheck = append(toCheck, a) + } + + return toCheck } func (f *accessControlDashboardPermissionFilter) nestedFoldersSelectors(permSelector string, permSelectorArgs []any, leftTable string, leftCol string, rightTableCol string, orgID int64) (string, []any) { @@ -382,46 +434,21 @@ func (f *accessControlDashboardPermissionFilter) nestedFoldersSelectors(permSele return strings.Join(wheres, ") OR "), args } -func parseStringSliceFromInterfaceSlice(slice []any) []string { - result := make([]string, 0, len(slice)) - for _, s := range slice { - result = append(result, s.(string)) - } - return result -} - -func getAllowedUIDs(actions []string, user identity.Requester, scopePrefix string) []any { - uidToActions := make(map[string]map[string]struct{}) - for _, action := range actions { - for _, uidScope := range user.GetPermissions()[action] { - if !strings.HasPrefix(uidScope, scopePrefix) { - continue - } - uid := strings.TrimPrefix(uidScope, scopePrefix) - if _, exists := uidToActions[uid]; !exists { - uidToActions[uid] = make(map[string]struct{}) - } - uidToActions[uid][action] = struct{}{} +func getAllowedUIDs(action string, user identity.Requester, scopePrefix string) []any { + var args []any + for _, uidScope := range user.GetPermissions()[action] { + if !strings.HasPrefix(uidScope, scopePrefix) { + continue } + uid := strings.TrimPrefix(uidScope, scopePrefix) + args = append(args, uid) } - // args max capacity is the length of the different uids - args := make([]any, 0, len(uidToActions)) - for uid, assignedActions := range uidToActions { - if len(assignedActions) == len(actions) { - args = append(args, uid) - } - } return args } // Checks if the user has the required permissions on the root (used to be the General folder) -func hasAccessToRoot(actionsToCheck []any, user identity.Requester) bool { +func hasAccessToRoot(actionToCheck string, user identity.Requester) bool { generalFolderScope := dashboards.ScopeFoldersProvider.GetResourceScopeUID(folder.GeneralFolderUID) - for _, action := range actionsToCheck { - if !slices.Contains(user.GetPermissions()[action.(string)], generalFolderScope) { - return false - } - } - return true + return slices.Contains(user.GetPermissions()[actionToCheck], generalFolderScope) } diff --git a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go index 524bb5b4760..5cba8a0184e 100644 --- a/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go +++ b/pkg/services/sqlstore/permissions/dashboard_filter_no_subquery.go @@ -53,20 +53,24 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() // currently it's used for the extended JWT module (when the user is authenticated via a JWT token generated by Grafana) useSelfContainedPermissions := f.user.GetAuthenticatedBy() == login.ExtendedJWTModule - if len(f.dashboardAction) > 0 { - toCheck := actionsToCheck(f.dashboardAction, f.user.GetPermissions(), dashWildcards, folderWildcards) + if f.dashboardAction != "" { + toCheckDashboards := actionsToCheck(f.dashboardAction, f.dashboardActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards) + toCheckFolders := actionsToCheck(f.dashboardAction, f.folderActionSets, f.user.GetPermissions(), dashWildcards, folderWildcards) - if len(toCheck) > 0 { + if len(toCheckDashboards) > 0 { if !useSelfContainedPermissions { builder.WriteString("(dashboard.uid IN (SELECT identifier FROM permission WHERE kind = 'dashboards' AND attribute = 'uid'") builder.WriteString(rolesFilter) args = append(args, params...) - builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") - args = append(args, toCheck[0]) + if len(toCheckDashboards) == 1 { + builder.WriteString(" AND action = ?) AND NOT dashboard.is_folder)") + args = append(args, toCheckDashboards[0]) + } else { + builder.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckDashboards)-1) + ")) AND NOT dashboard.is_folder)") + args = append(args, toCheckDashboards...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - args = getAllowedUIDs(actions, f.user, dashboards.ScopeDashboardsPrefix) + args = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeDashboardsPrefix) // Only add the IN clause if we have any dashboards to check if len(args) > 0 { @@ -83,12 +87,15 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString(rolesFilter) permSelectorArgs = append(permSelectorArgs, params...) - permSelector.WriteString(" AND action = ?") - permSelectorArgs = append(permSelectorArgs, toCheck[0]) + if len(toCheckFolders) == 1 { + permSelector.WriteString(" AND action = ?") + permSelectorArgs = append(permSelectorArgs, toCheckFolders[0]) + } else { + permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheckFolders)-1) + ")") + permSelectorArgs = append(permSelectorArgs, toCheckFolders...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix) + permSelectorArgs = getAllowedUIDs(f.dashboardAction, f.user, dashboards.ScopeFoldersPrefix) // Only add the IN clause if we have any folders to check if len(permSelectorArgs) > 0 { @@ -133,7 +140,7 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() } // Include all the dashboards under the root if the user has the required permissions on the root (used to be the General folder) - if hasAccessToRoot(toCheck, f.user) { + if hasAccessToRoot(f.dashboardAction, f.user) { builder.WriteString(" OR (dashboard.folder_id = 0 AND NOT dashboard.is_folder)") } } else { @@ -145,23 +152,26 @@ func (f *accessControlDashboardPermissionFilterNoFolderSubquery) buildClauses() permSelector.Reset() permSelectorArgs = permSelectorArgs[:0] - if len(f.folderAction) > 0 { - if len(f.dashboardAction) > 0 { + if f.folderAction != "" { + if f.dashboardAction != "" { builder.WriteString(" OR ") } - toCheck := actionsToCheck(f.folderAction, f.user.GetPermissions(), folderWildcards) + toCheck := actionsToCheck(f.folderAction, f.folderActionSets, f.user.GetPermissions(), folderWildcards) if len(toCheck) > 0 { if !useSelfContainedPermissions { permSelector.WriteString("(SELECT identifier FROM permission WHERE kind = 'folders' AND attribute = 'uid'") permSelector.WriteString(rolesFilter) permSelectorArgs = append(permSelectorArgs, params...) - permSelector.WriteString(" AND action = ?") - permSelectorArgs = append(permSelectorArgs, toCheck[0]) + if len(toCheck) == 1 { + permSelector.WriteString(" AND action = ?") + permSelectorArgs = append(permSelectorArgs, toCheck[0]) + } else { + permSelector.WriteString(" AND action IN (?" + strings.Repeat(", ?", len(toCheck)-1) + ")") + permSelectorArgs = append(permSelectorArgs, toCheck...) + } } else { - actions := parseStringSliceFromInterfaceSlice(toCheck) - - permSelectorArgs = getAllowedUIDs(actions, f.user, dashboards.ScopeFoldersPrefix) + permSelectorArgs = getAllowedUIDs(f.folderAction, f.user, dashboards.ScopeFoldersPrefix) if len(permSelectorArgs) > 0 { permSelector.WriteString("(?" + strings.Repeat(", ?", len(permSelectorArgs)-1) + "") diff --git a/pkg/services/sqlstore/permissions/dashboard_test.go b/pkg/services/sqlstore/permissions/dashboard_test.go index ec17492d485..640254d5def 100644 --- a/pkg/services/sqlstore/permissions/dashboard_test.go +++ b/pkg/services/sqlstore/permissions/dashboard_test.go @@ -416,8 +416,8 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { permissions: []accesscontrol.Permission{ {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll}, }, - features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"}, }, { desc: "Should be able to view inherited folders if nested folders are enabled", @@ -458,7 +458,7 @@ func TestIntegration_DashboardNestedPermissionFilter(t *testing.T) { }) usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.permissions)}} - for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} { + for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagAccessActionSets)...), featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} { m := features.GetEnabled(context.Background()) keys := make([]string, 0, len(m)) for k := range m { @@ -530,7 +530,7 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission {Action: dashboards.ActionDashboardsRead, Scope: dashboards.ScopeFoldersAll}, }, features: []any{featuremgmt.FlagNestedFolders}, - expectedResult: []string{"dashboard under parent folder", "dashboard under subfolder"}, + expectedResult: []string{"dashboard under the root", "dashboard under parent folder", "dashboard under subfolder"}, }, { desc: "Should be able to view inherited folders if nested folders are enabled", @@ -608,6 +608,125 @@ func TestIntegration_DashboardNestedPermissionFilter_WithSelfContainedPermission } } +func TestIntegration_DashboardNestedPermissionFilter_WithActionSets(t *testing.T) { + testCases := []struct { + desc string + queryType string + permission dashboardaccess.PermissionType + signedInUserPermissions []accesscontrol.Permission + expectedResult []string + features []any + }{ + { + desc: "Should not list any dashboards if user has no permissions", + permission: dashboardaccess.PERMISSION_VIEW, + signedInUserPermissions: nil, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: nil, + }, + { + desc: "Should not list any folders if user has no permissions", + permission: dashboardaccess.PERMISSION_VIEW, + signedInUserPermissions: nil, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: nil, + }, + { + desc: "Should be able to view folders if user has `folders:read` access to them", + queryType: searchstore.TypeFolder, + permission: dashboardaccess.PERMISSION_VIEW, + signedInUserPermissions: []accesscontrol.Permission{ + {Action: dashboards.ActionFoldersRead, Scope: dashboards.ScopeFoldersAll}, + }, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: []string{"parent", "subfolder"}, + }, + { + desc: "Should be able to view folders if user has action set access to them", + queryType: searchstore.TypeFolder, + permission: dashboardaccess.PERMISSION_VIEW, + signedInUserPermissions: []accesscontrol.Permission{ + {Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"}, + }, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: []string{"parent", "subfolder"}, + }, + { + desc: "Should be able to view only the subfolder if user has action set access to it", + queryType: searchstore.TypeFolder, + permission: dashboardaccess.PERMISSION_VIEW, + signedInUserPermissions: []accesscontrol.Permission{ + {Action: "folders:admin", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"}, + }, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: []string{"subfolder"}, + }, + { + desc: "Should be able to filter for folders that user has write access to", + queryType: searchstore.TypeFolder, + permission: dashboardaccess.PERMISSION_EDIT, + signedInUserPermissions: []accesscontrol.Permission{ + {Action: "folders:edit", Scope: "folders:uid:subfolder", Kind: "folders", Identifier: "subfolder"}, + {Action: "folders:view", Scope: "folders:uid:parent", Kind: "folders", Identifier: "parent"}, + }, + features: []any{featuremgmt.FlagNestedFolders, featuremgmt.FlagAccessActionSets}, + expectedResult: []string{"subfolder"}, + }, + } + + origNewGuardian := guardian.New + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true, CanSaveValue: true}) + t.Cleanup(func() { + guardian.New = origNewGuardian + }) + + var orgID int64 = 1 + + for _, tc := range testCases { + tc.signedInUserPermissions = append(tc.signedInUserPermissions, accesscontrol.Permission{ + Action: dashboards.ActionFoldersCreate, + }, accesscontrol.Permission{ + Action: dashboards.ActionFoldersWrite, + Scope: dashboards.ScopeFoldersAll, + }, accesscontrol.Permission{ + Action: dashboards.ActionFoldersRead, + Scope: "folders:uid:unrelated"}, accesscontrol.Permission{ + Action: dashboards.ActionDashboardsCreate, + Scope: "folders:uid:unrelated"}) + usr := &user.SignedInUser{OrgID: orgID, OrgRole: org.RoleViewer, Permissions: map[int64]map[string][]string{orgID: accesscontrol.GroupScopesByAction(tc.signedInUserPermissions)}} + + for _, features := range []featuremgmt.FeatureToggles{featuremgmt.WithFeatures(tc.features...), featuremgmt.WithFeatures(append(tc.features, featuremgmt.FlagPermissionsFilterRemoveSubquery)...)} { + m := features.GetEnabled(context.Background()) + keys := make([]string, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + + t.Run(tc.desc+" with features "+strings.Join(keys, ","), func(t *testing.T) { + db := setupNestedTest(t, usr, tc.signedInUserPermissions, orgID, features) + recursiveQueriesAreSupported, err := db.RecursiveQueriesAreSupported() + require.NoError(t, err) + filter := permissions.NewAccessControlDashboardPermissionFilter(usr, tc.permission, tc.queryType, features, recursiveQueriesAreSupported) + var result []string + err = db.WithDbSession(context.Background(), func(sess *sqlstore.DBSession) error { + q, params := filter.Where() + recQry, recQryParams := filter.With() + params = append(recQryParams, params...) + s := recQry + "\nSELECT dashboard.title FROM dashboard WHERE " + q + leftJoin := filter.LeftJoin() + if leftJoin != "" { + s = recQry + "\nSELECT dashboard.title FROM dashboard LEFT OUTER JOIN " + leftJoin + " WHERE " + q + "ORDER BY dashboard.id ASC" + } + err := sess.SQL(s, params...).Find(&result) + return err + }) + require.NoError(t, err) + assert.Equal(t, tc.expectedResult, result) + }) + } + } +} + func setupTest(t *testing.T, numFolders, numDashboards int, permissions []accesscontrol.Permission) db.DB { t.Helper() @@ -724,6 +843,15 @@ func setupNestedTest(t *testing.T, usr *user.SignedInUser, perms []accesscontrol }) require.NoError(t, err) + // create a root level dashboard + _, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{ + OrgID: orgID, + Dashboard: simplejson.NewFromAny(map[string]any{ + "title": "dashboard under the root", + }), + }) + require.NoError(t, err) + // create dashboard under parent folder _, err = dashStore.SaveDashboard(context.Background(), dashboards.SaveDashboardCommand{ OrgID: orgID, From 6a125fd59f9a0488c5400e3f818f3d14ee8529a3 Mon Sep 17 00:00:00 2001 From: Matias Chomicki Date: Thu, 13 Jun 2024 18:56:50 +0200 Subject: [PATCH 12/13] Logs panel: do not pass default handlers if context is not defined (#89174) --- .betterer.results | 3 ++ .../app/plugins/panel/logs/LogsPanel.test.tsx | 49 ++++++++++++++++++- public/app/plugins/panel/logs/LogsPanel.tsx | 8 ++- 3 files changed, 56 insertions(+), 4 deletions(-) diff --git a/.betterer.results b/.betterer.results index c5e1da0bb83..2cdb78262b7 100644 --- a/.betterer.results +++ b/.betterer.results @@ -7124,6 +7124,9 @@ exports[`better eslint`] = { [0, 0, 0, "Styles should be written using objects.", "6"], [0, 0, 0, "Styles should be written using objects.", "7"] ], + "public/app/plugins/panel/logs/LogsPanel.test.tsx:5381": [ + [0, 0, 0, "* import is invalid because \'Layout,HorizontalGroup,VerticalGroup\' from \'@grafana/ui\' is restricted from being used by a pattern. Use Stack component instead.", "0"] + ], "public/app/plugins/panel/logs/types.ts:5381": [ [0, 0, 0, "Do not re-export imported variable (\`./panelcfg.gen\`)", "0"] ], diff --git a/public/app/plugins/panel/logs/LogsPanel.test.tsx b/public/app/plugins/panel/logs/LogsPanel.test.tsx index 400423a8842..f462094b27b 100644 --- a/public/app/plugins/panel/logs/LogsPanel.test.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.test.tsx @@ -13,6 +13,7 @@ import { LogsDedupStrategy, EventBusSrv, } from '@grafana/data'; +import * as grafanaUI from '@grafana/ui'; import * as styles from 'app/features/logs/components/getLogRowStyles'; import { LogRowContextModal } from 'app/features/logs/components/log-context/LogRowContextModal'; @@ -347,7 +348,7 @@ describe('LogsPanel', () => { }), ]; - it('allow to filter for a value or filter out a value', async () => { + it('allows to filter for a value or filter out a value', async () => { const filterForMock = jest.fn(); const filterOutMock = jest.fn(); const isFilterLabelActiveMock = jest.fn(); @@ -380,6 +381,50 @@ describe('LogsPanel', () => { expect(isFilterLabelActiveMock).toHaveBeenCalledTimes(1); }); + + describe('invalid handlers', () => { + it('does not show the controls if onAddAdHocFilter is not defined', async () => { + jest.spyOn(grafanaUI, 'usePanelContext').mockReturnValue({ + eventsScope: 'global', + eventBus: new EventBusSrv(), + }); + + setup({ + data: { + series, + }, + }); + + expect(await screen.findByRole('row')).toBeInTheDocument(); + + await userEvent.click(screen.getByText('logline text')); + + expect(screen.queryByLabelText('Filter for value in query A')).not.toBeInTheDocument(); + expect(screen.queryByLabelText('Filter out value in query A')).not.toBeInTheDocument(); + }); + it('shows the controls if onAddAdHocFilter is defined', async () => { + jest.spyOn(grafanaUI, 'usePanelContext').mockReturnValue({ + eventsScope: 'global', + eventBus: new EventBusSrv(), + onAddAdHocFilter: jest.fn(), + }); + + setup({ + data: { + series, + }, + }); + + expect(await screen.findByRole('row')).toBeInTheDocument(); + + await userEvent.click(screen.getByText('logline text')); + + expect(await screen.findByText('common_app')).toBeInTheDocument(); + + expect(screen.getByLabelText('Filter for value in query A')).toBeInTheDocument(); + expect(screen.getByLabelText('Filter out value in query A')).toBeInTheDocument(); + }); + }); }); }); @@ -414,7 +459,7 @@ const setup = (propsOverrides?: {}) => { prettifyLogMessage: false, sortOrder: LogsSortOrder.Descending, dedupStrategy: LogsDedupStrategy.none, - enableLogDetails: false, + enableLogDetails: true, showLogContextToggle: false, }, title: 'Logs panel', diff --git a/public/app/plugins/panel/logs/LogsPanel.tsx b/public/app/plugins/panel/logs/LogsPanel.tsx index 0bafecd0004..d2cefdae7ce 100644 --- a/public/app/plugins/panel/logs/LogsPanel.tsx +++ b/public/app/plugins/panel/logs/LogsPanel.tsx @@ -249,7 +249,7 @@ export const LogsPanel = ({ [scrollElement] ); - const defaultOnClickFilterLabel = useCallback( + const handleOnClickFilterLabel = useCallback( (key: string, value: string) => { onAddAdHocFilter?.({ key, @@ -260,7 +260,7 @@ export const LogsPanel = ({ [onAddAdHocFilter] ); - const defaultOnClickFilterOutLabel = useCallback( + const handleOnClickFilterOutLabel = useCallback( (key: string, value: string) => { onAddAdHocFilter?.({ key, @@ -285,6 +285,10 @@ export const LogsPanel = ({
); + // Passing callbacks control the display of the filtering buttons. We want to pass it only if onAddAdHocFilter is defined. + const defaultOnClickFilterLabel = onAddAdHocFilter ? handleOnClickFilterLabel : undefined; + const defaultOnClickFilterOutLabel = onAddAdHocFilter ? handleOnClickFilterOutLabel : undefined; + return ( <> {contextRow && ( From eb535e163d738c6dadb16fde2ce0458f7635659e Mon Sep 17 00:00:00 2001 From: Ieva Date: Thu, 13 Jun 2024 20:01:12 +0300 Subject: [PATCH 13/13] Docs: Add parameter definition to swagger for RBAC debug endpoint (#89097) add parameter definition to swagger for RBAC debug endpoint --- public/api-enterprise-spec.json | 66 +++++++++++++++++++++++++++++++++ public/api-merged.json | 26 +++++++++++++ public/openapi3.json | 26 +++++++++++++ 3 files changed, 118 insertions(+) diff --git a/public/api-enterprise-spec.json b/public/api-enterprise-spec.json index 016fd4152ff..0ff6fb2de46 100644 --- a/public/api-enterprise-spec.json +++ b/public/api-enterprise-spec.json @@ -3127,6 +3127,14 @@ } } }, + "CloudMigrationRequest": { + "type": "object", + "properties": { + "authToken": { + "type": "string" + } + } + }, "CloudMigrationResponse": { "type": "object", "properties": { @@ -4630,6 +4638,29 @@ "$ref": "#/definitions/Frame" } }, + "GetAccessTokenResponseDTO": { + "type": "object", + "properties": { + "createdAt": { + "type": "string" + }, + "displayName": { + "type": "string" + }, + "expiresAt": { + "type": "string" + }, + "firstUsedAt": { + "type": "string" + }, + "id": { + "type": "string" + }, + "lastUsedAt": { + "type": "string" + } + } + }, "GetAnnotationTagsResponse": { "type": "object", "title": "GetAnnotationTagsResponse is a response struct for FindTagsResult.", @@ -6610,6 +6641,32 @@ } } }, + "SearchDTO": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "basicRole": { + "type": "string" + }, + "onlyRoles": { + "type": "boolean" + }, + "roleName": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, "SearchDeviceQueryResult": { "type": "object", "properties": { @@ -8276,6 +8333,15 @@ "$ref": "#/definitions/CreateAccessTokenResponseDTO" } }, + "cloudMigrationDeleteTokenResponse": { + "description": "" + }, + "cloudMigrationGetTokenResponse": { + "description": "", + "schema": { + "$ref": "#/definitions/GetAccessTokenResponseDTO" + } + }, "cloudMigrationListResponse": { "description": "", "schema": { diff --git a/public/api-merged.json b/public/api-merged.json index 4dab4ebc0b7..91eac6c8397 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -19350,6 +19350,32 @@ } } }, + "SearchDTO": { + "type": "object", + "properties": { + "action": { + "type": "string" + }, + "basicRole": { + "type": "string" + }, + "onlyRoles": { + "type": "boolean" + }, + "roleName": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "userId": { + "type": "string" + } + } + }, "SearchDeviceQueryResult": { "type": "object", "properties": { diff --git a/public/openapi3.json b/public/openapi3.json index 615735a2789..220b86d5f50 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -9729,6 +9729,32 @@ }, "type": "object" }, + "SearchDTO": { + "properties": { + "action": { + "type": "string" + }, + "basicRole": { + "type": "string" + }, + "onlyRoles": { + "type": "boolean" + }, + "roleName": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "teamId": { + "type": "string" + }, + "userId": { + "type": "string" + } + }, + "type": "object" + }, "SearchDeviceQueryResult": { "properties": { "devices": {