Search: Allowed actions (#52529)

This commit is contained in:
Artur Wierzbicki 2022-07-23 04:42:43 +04:00 committed by GitHub
parent ce04a01ae9
commit bcfa4fabf6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1163 additions and 19 deletions

View File

@ -0,0 +1,257 @@
package searchV2
import (
"context"
"encoding/json"
"errors"
"sort"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
)
func (s *StandardSearchService) addAllowedActionsField(ctx context.Context, orgId int64, user *models.SignedInUser, response *backend.DataResponse) error {
references, err := getEntityReferences(response)
if err != nil {
return err
}
allAllowedActions, err := s.createAllowedActions(ctx, orgId, user, references)
if err != nil {
return err
}
if len(response.Frames) == 0 {
return errors.New("empty response")
}
frame := response.Frames[0]
allowedActionsField := data.NewFieldFromFieldType(data.FieldTypeJSON, len(allAllowedActions))
allowedActionsField.Name = "allowed_actions"
frame.Fields = append(frame.Fields, allowedActionsField)
for i, actions := range allAllowedActions {
js, _ := json.Marshal(actions)
jsb := json.RawMessage(js)
allowedActionsField.Set(i, jsb)
}
return nil
}
type allowedActions struct {
EntityKind entityKind `json:"kind"`
UID string `json:"uid"`
Actions []string `json:"actions"`
}
func (s *StandardSearchService) createAllowedActions(ctx context.Context, orgId int64, user *models.SignedInUser, references []entityReferences) ([][]allowedActions, error) {
uidsPerKind := make(map[entityKind][]string)
for _, refs := range references {
if _, ok := uidsPerKind[refs.entityKind]; !ok {
uidsPerKind[refs.entityKind] = []string{}
}
uidsPerKind[refs.entityKind] = append(uidsPerKind[refs.entityKind], refs.uid)
if len(refs.dsUids) > 0 {
if _, ok := uidsPerKind[entityKindDatasource]; !ok {
uidsPerKind[entityKindDatasource] = []string{}
}
uidsPerKind[entityKindDatasource] = append(uidsPerKind[entityKindDatasource], refs.dsUids...)
}
}
allowedActionsByUid := make(map[entityKind]map[string][]string)
for entKind, uids := range uidsPerKind {
if entKind == entityKindPanel {
emptyAllowedActions := make(map[string][]string)
for _, uid := range uids {
emptyAllowedActions[uid] = []string{}
}
allowedActionsByUid[entityKindPanel] = emptyAllowedActions
}
var prefix string
switch entKind {
case entityKindFolder:
prefix = dashboards.ScopeFoldersPrefix
case entityKindDatasource:
prefix = datasources.ScopePrefix
case entityKindDashboard:
prefix = dashboards.ScopeDashboardsPrefix
default:
continue
}
allowedActionsByUid[entKind] = s.getAllowedActionsByUid(ctx, user, orgId, prefix, uids)
}
dsActionsByUid, ok := allowedActionsByUid[entityKindDatasource]
if !ok {
dsActionsByUid = make(map[string][]string)
}
var out [][]allowedActions
for _, ref := range references {
var actions []allowedActions
selfActions := make([]string, 0)
if selfKindActions, ok := allowedActionsByUid[ref.entityKind]; ok {
if self, ok := selfKindActions[ref.uid]; ok && len(self) > 0 {
selfActions = self
}
}
actions = append(actions, allowedActions{
EntityKind: ref.entityKind,
UID: ref.uid,
Actions: selfActions,
})
for _, dsUid := range ref.dsUids {
dsActions := make([]string, 0)
if dsAct, ok := dsActionsByUid[dsUid]; ok {
dsActions = dsAct
}
actions = append(actions, allowedActions{
EntityKind: entityKindDatasource,
UID: dsUid,
Actions: dsActions,
})
}
out = append(out, actions)
}
return out, nil
}
func (s *StandardSearchService) getAllowedActionsByUid(ctx context.Context, user *models.SignedInUser,
orgID int64, prefix string, resourceIDs []string) map[string][]string {
if s.ac.IsDisabled() {
return map[string][]string{}
}
if user.Permissions == nil {
return map[string][]string{}
}
permissions, ok := user.Permissions[orgID]
if !ok {
return map[string][]string{}
}
uidsAsMap := make(map[string]bool)
for _, uid := range resourceIDs {
uidsAsMap[uid] = true
}
out := make(map[string][]string)
resp := accesscontrol.GetResourcesMetadata(ctx, permissions, prefix, uidsAsMap)
for uid, meta := range resp {
var actions []string
for action := range meta {
actions = append(actions, action)
}
sort.Strings(actions)
out[uid] = actions
}
return out
}
type entityReferences struct {
entityKind entityKind
uid string
dsUids []string
}
func getEntityReferences(resp *backend.DataResponse) ([]entityReferences, error) {
if resp == nil {
return nil, errors.New("nil response")
}
if resp.Error != nil {
return nil, resp.Error
}
if len(resp.Frames) == 0 {
return nil, errors.New("empty response")
}
frame := resp.Frames[0]
kindField, idx := frame.FieldByName("kind")
if idx == -1 {
return nil, errors.New("no kind field")
}
dsUidField, idx := frame.FieldByName("ds_uid")
if idx == -1 {
return nil, errors.New("no ds_uid field")
}
uidField, idx := frame.FieldByName("uid")
if idx == -1 {
return nil, errors.New("no dash_uid field")
}
if dsUidField.Len() != uidField.Len() {
return nil, errors.New("mismatched lengths")
}
var out []entityReferences
for i := 0; i < dsUidField.Len(); i++ {
kind, ok := kindField.At(i).(string)
if !ok || kind == "" {
return nil, errors.New("invalid value in kind field")
}
uid, ok := uidField.At(i).(string)
if !ok || uid == "" {
return nil, errors.New("invalid value in uid field")
}
if entityKind(kind) != entityKindDashboard {
out = append(out, entityReferences{
entityKind: entityKind(kind),
uid: uid,
})
continue
}
uidField, ok := uidField.At(i).(string)
if !ok || uidField == "" {
return nil, errors.New("invalid value in dash_uid field")
}
rawDsUids, ok := dsUidField.At(i).(*json.RawMessage)
if !ok {
return nil, errors.New("invalid value in ds_uid field")
}
var uids []string
if rawDsUids != nil {
jsonValue, err := rawDsUids.MarshalJSON()
if err != nil {
return nil, err
}
err = json.Unmarshal(jsonValue, &uids)
if err != nil {
return nil, err
}
}
out = append(out, entityReferences{entityKind: entityKindDashboard, uid: uid, dsUids: uids})
}
return out, nil
}

View File

@ -0,0 +1,119 @@
package searchV2
import (
"context"
_ "embed"
"fmt"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/grafana/grafana-plugin-sdk-go/data"
"github.com/grafana/grafana-plugin-sdk-go/experimental"
"github.com/grafana/grafana/pkg/models"
ac "github.com/grafana/grafana/pkg/services/accesscontrol"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/stretchr/testify/require"
)
var (
//go:embed testdata/search_response_frame.json
exampleListFrameJSON string
orgId = int64(1)
permissionsWithScopeAll = map[string][]string{
datasources.ActionIDRead: {datasources.ScopeAll},
datasources.ActionDelete: {datasources.ScopeAll},
ac.ActionDatasourcesExplore: {datasources.ScopeAll},
datasources.ActionQuery: {datasources.ScopeAll},
datasources.ActionRead: {datasources.ScopeAll},
datasources.ActionWrite: {datasources.ScopeAll},
datasources.ActionPermissionsRead: {datasources.ScopeAll},
datasources.ActionPermissionsWrite: {datasources.ScopeAll},
dashboards.ActionFoldersCreate: {dashboards.ScopeFoldersAll},
dashboards.ActionFoldersRead: {dashboards.ScopeFoldersAll},
dashboards.ActionFoldersWrite: {dashboards.ScopeFoldersAll},
dashboards.ActionFoldersDelete: {dashboards.ScopeFoldersAll},
dashboards.ActionFoldersPermissionsRead: {dashboards.ScopeFoldersAll},
dashboards.ActionFoldersPermissionsWrite: {dashboards.ScopeFoldersAll},
dashboards.ActionDashboardsCreate: {dashboards.ScopeDashboardsAll},
dashboards.ActionDashboardsRead: {dashboards.ScopeDashboardsAll},
dashboards.ActionDashboardsWrite: {dashboards.ScopeDashboardsAll},
dashboards.ActionDashboardsDelete: {dashboards.ScopeDashboardsAll},
dashboards.ActionDashboardsPermissionsRead: {dashboards.ScopeDashboardsAll},
dashboards.ActionDashboardsPermissionsWrite: {dashboards.ScopeDashboardsAll},
}
permissionsWithUidScopes = map[string][]string{
datasources.ActionIDRead: {},
datasources.ActionDelete: {},
ac.ActionDatasourcesExplore: {},
datasources.ActionQuery: {},
datasources.ActionRead: {
datasources.ScopeProvider.GetResourceScopeUID("datasource-2"),
datasources.ScopeProvider.GetResourceScopeUID("datasource-3"),
},
datasources.ActionWrite: {},
datasources.ActionPermissionsRead: {},
datasources.ActionPermissionsWrite: {},
dashboards.ActionFoldersCreate: {},
dashboards.ActionFoldersRead: {
dashboards.ScopeFoldersProvider.GetResourceScopeUID("ujaM1h6nz"),
},
dashboards.ActionFoldersWrite: {},
dashboards.ActionFoldersDelete: {},
dashboards.ActionFoldersPermissionsRead: {},
dashboards.ActionFoldersPermissionsWrite: {},
dashboards.ActionDashboardsCreate: {},
dashboards.ActionDashboardsRead: {},
dashboards.ActionDashboardsWrite: {
dashboards.ScopeDashboardsProvider.GetResourceScopeUID("7MeksYbmk"),
},
dashboards.ActionDashboardsDelete: {},
dashboards.ActionDashboardsPermissionsRead: {},
dashboards.ActionDashboardsPermissionsWrite: {},
}
)
func service(t *testing.T) *StandardSearchService {
service, ok := ProvideService(nil, nil, nil, accesscontrolmock.New()).(*StandardSearchService)
require.True(t, ok)
return service
}
func TestAllowedActionsForPermissionsWithScopeAll(t *testing.T) {
tests := []struct {
name string
permissions map[string][]string
}{
{
name: "scope_all",
permissions: permissionsWithScopeAll,
},
{
name: "scope_uids",
permissions: permissionsWithUidScopes,
},
}
for _, tt := range tests {
frame := &data.Frame{}
err := frame.UnmarshalJSON([]byte(exampleListFrameJSON))
require.NoError(t, err)
err = service(t).addAllowedActionsField(context.Background(), orgId, &models.SignedInUser{
Permissions: map[int64]map[string][]string{
orgId: tt.permissions,
},
}, &backend.DataResponse{
Frames: []*data.Frame{frame},
})
require.NoError(t, err)
experimental.CheckGoldenJSONFrame(t, "testdata", fmt.Sprintf("allowed_actions_%s.golden", tt.name), frame, true)
}
}

View File

@ -18,9 +18,10 @@ type PermissionFilter struct {
type entityKind string
const (
entityKindPanel entityKind = "panel"
entityKindDashboard entityKind = "dashboard"
entityKindFolder entityKind = "folder"
entityKindPanel entityKind = "panel"
entityKindDashboard entityKind = "dashboard"
entityKindFolder entityKind = "folder"
entityKindDatasource entityKind = "datasource"
)
func (r entityKind) IsValid() bool {

View File

@ -175,5 +175,13 @@ func (s *StandardSearchService) DoDashboardQuery(ctx context.Context, user *back
return rsp
}
return doSearchQuery(ctx, s.logger, index, filter, q, s.extender.GetQueryExtender(q), s.cfg.AppSubURL)
response := doSearchQuery(ctx, s.logger, index, filter, q, s.extender.GetQueryExtender(q), s.cfg.AppSubURL)
if q.WithAllowedActions {
if err := s.addAllowedActionsField(ctx, orgID, signedInUser, response); err != nil {
s.logger.Error("error when adding the allowedActions field", "err", err)
}
}
return response
}

View File

@ -0,0 +1,321 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "search-results",
// "custom": {
// "count": 106,
// "locationInfo": {
// "yboVMzb7z": {
// "kind": "folder",
// "name": "gdev dashboards",
// "url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
// }
// },
// "sortBy": "name_sort"
// }
// }
// Name: Query results
// Dimensions: 9 Fields by 4 Rows
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions |
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage |
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders.permissions:read","folders.permissions:write","folders:create","folders:delete","folders:read","folders:write"]}] |
// | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards.permissions:read","dashboards.permissions:write","dashboards:create","dashboards:delete","dashboards:read","dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]}] |
// | | | | | | "gdev", | "datasource-1" | | |
// | | | | | | "alerting" | ] | | |
// | | | | | | ] | | | |
// | dashboard | vmie2cmWz | Bar Gauge Demo | | /d/vmie2cmWz/bar-gauge-demo | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"vmie2cmWz","actions":["dashboards.permissions:read","dashboards.permissions:write","dashboards:create","dashboards:delete","dashboards:read","dashboards:write"]},{"kind":"datasource","uid":"datasource-2","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]},{"kind":"datasource","uid":"datasource-3","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]},{"kind":"datasource","uid":"datasource-4","actions":["datasources.id:read","datasources.permissions:read","datasources.permissions:write","datasources:delete","datasources:explore","datasources:query","datasources:read","datasources:write"]}] |
// | | | | | | "gdev", | "datasource-2", | | |
// | | | | | | "demo" | "datasource-3", | | |
// | | | | | | ] | "datasource-4" | | |
// | | | | | | | ] | | |
// | dashboard | xMsQdBfWz | Bar Gauge Demo Unfilled | | /d/xMsQdBfWz/bar-gauge-demo-unfilled | [ | [] | yboVMzb7z | [{"kind":"dashboard","uid":"xMsQdBfWz","actions":["dashboards.permissions:read","dashboards.permissions:write","dashboards:create","dashboards:delete","dashboards:read","dashboards:write"]}] |
// | | | | | | "gdev", | | | |
// | | | | | | "demo" | | | |
// | | | | | | ] | | | |
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"frames": [
{
"schema": {
"name": "Query results",
"refId": "Search",
"meta": {
"type": "search-results",
"custom": {
"count": 106,
"locationInfo": {
"yboVMzb7z": {
"kind": "folder",
"name": "gdev dashboards",
"url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
}
},
"sortBy": "name_sort"
}
},
"fields": [
{
"name": "kind",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "uid",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "name",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "panel_type",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "url",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"links": [
{
"title": "link",
"url": "${__value.text}"
}
]
}
},
{
"name": "tags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "ds_uid",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "location",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "allowed_actions",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
}
]
},
"data": {
"values": [
[
"folder",
"dashboard",
"dashboard",
"dashboard"
],
[
"ujaM1h6nz",
"7MeksYbmk",
"vmie2cmWz",
"xMsQdBfWz"
],
[
"abc2",
"Alerting with TestData",
"Bar Gauge Demo",
"Bar Gauge Demo Unfilled"
],
[
"",
"",
"",
""
],
[
"/dashboards/f/ujaM1h6nz/abc2",
"/d/7MeksYbmk/alerting-with-testdata",
"/d/vmie2cmWz/bar-gauge-demo",
"/d/xMsQdBfWz/bar-gauge-demo-unfilled"
],
[
null,
[
"gdev",
"alerting"
],
[
"gdev",
"demo"
],
[
"gdev",
"demo"
]
],
[
null,
[
"datasource-1"
],
[
"datasource-2",
"datasource-3",
"datasource-4"
],
[]
],
[
"",
"yboVMzb7z",
"yboVMzb7z",
"yboVMzb7z"
],
[
[
{
"kind": "folder",
"uid": "ujaM1h6nz",
"actions": [
"folders.permissions:read",
"folders.permissions:write",
"folders:create",
"folders:delete",
"folders:read",
"folders:write"
]
}
],
[
{
"kind": "dashboard",
"uid": "7MeksYbmk",
"actions": [
"dashboards.permissions:read",
"dashboards.permissions:write",
"dashboards:create",
"dashboards:delete",
"dashboards:read",
"dashboards:write"
]
},
{
"kind": "datasource",
"uid": "datasource-1",
"actions": [
"datasources.id:read",
"datasources.permissions:read",
"datasources.permissions:write",
"datasources:delete",
"datasources:explore",
"datasources:query",
"datasources:read",
"datasources:write"
]
}
],
[
{
"kind": "dashboard",
"uid": "vmie2cmWz",
"actions": [
"dashboards.permissions:read",
"dashboards.permissions:write",
"dashboards:create",
"dashboards:delete",
"dashboards:read",
"dashboards:write"
]
},
{
"kind": "datasource",
"uid": "datasource-2",
"actions": [
"datasources.id:read",
"datasources.permissions:read",
"datasources.permissions:write",
"datasources:delete",
"datasources:explore",
"datasources:query",
"datasources:read",
"datasources:write"
]
},
{
"kind": "datasource",
"uid": "datasource-3",
"actions": [
"datasources.id:read",
"datasources.permissions:read",
"datasources.permissions:write",
"datasources:delete",
"datasources:explore",
"datasources:query",
"datasources:read",
"datasources:write"
]
},
{
"kind": "datasource",
"uid": "datasource-4",
"actions": [
"datasources.id:read",
"datasources.permissions:read",
"datasources.permissions:write",
"datasources:delete",
"datasources:explore",
"datasources:query",
"datasources:read",
"datasources:write"
]
}
],
[
{
"kind": "dashboard",
"uid": "xMsQdBfWz",
"actions": [
"dashboards.permissions:read",
"dashboards.permissions:write",
"dashboards:create",
"dashboards:delete",
"dashboards:read",
"dashboards:write"
]
}
]
]
]
}
}
]
}

View File

@ -0,0 +1,265 @@
// 🌟 This was machine generated. Do not edit. 🌟
//
// Frame[0] {
// "type": "search-results",
// "custom": {
// "count": 106,
// "locationInfo": {
// "yboVMzb7z": {
// "kind": "folder",
// "name": "gdev dashboards",
// "url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
// }
// },
// "sortBy": "name_sort"
// }
// }
// Name: Query results
// Dimensions: 9 Fields by 4 Rows
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
// | Name: kind | Name: uid | Name: name | Name: panel_type | Name: url | Name: tags | Name: ds_uid | Name: location | Name: allowed_actions |
// | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: | Labels: |
// | Type: []string | Type: []string | Type: []string | Type: []string | Type: []string | Type: []*json.RawMessage | Type: []*json.RawMessage | Type: []string | Type: []json.RawMessage |
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
// | folder | ujaM1h6nz | abc2 | | /dashboards/f/ujaM1h6nz/abc2 | null | null | | [{"kind":"folder","uid":"ujaM1h6nz","actions":["folders:read"]}] |
// | dashboard | 7MeksYbmk | Alerting with TestData | | /d/7MeksYbmk/alerting-with-testdata | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"7MeksYbmk","actions":["dashboards:write"]},{"kind":"datasource","uid":"datasource-1","actions":[]}] |
// | | | | | | "gdev", | "datasource-1" | | |
// | | | | | | "alerting" | ] | | |
// | | | | | | ] | | | |
// | dashboard | vmie2cmWz | Bar Gauge Demo | | /d/vmie2cmWz/bar-gauge-demo | [ | [ | yboVMzb7z | [{"kind":"dashboard","uid":"vmie2cmWz","actions":[]},{"kind":"datasource","uid":"datasource-2","actions":["datasources:read"]},{"kind":"datasource","uid":"datasource-3","actions":["datasources:read"]},{"kind":"datasource","uid":"datasource-4","actions":[]}] |
// | | | | | | "gdev", | "datasource-2", | | |
// | | | | | | "demo" | "datasource-3", | | |
// | | | | | | ] | "datasource-4" | | |
// | | | | | | | ] | | |
// | dashboard | xMsQdBfWz | Bar Gauge Demo Unfilled | | /d/xMsQdBfWz/bar-gauge-demo-unfilled | [ | [] | yboVMzb7z | [{"kind":"dashboard","uid":"xMsQdBfWz","actions":[]}] |
// | | | | | | "gdev", | | | |
// | | | | | | "demo" | | | |
// | | | | | | ] | | | |
// +----------------+----------------+-------------------------+------------------+--------------------------------------+--------------------------+---------------------------+----------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
//
//
// 🌟 This was machine generated. Do not edit. 🌟
{
"frames": [
{
"schema": {
"name": "Query results",
"refId": "Search",
"meta": {
"type": "search-results",
"custom": {
"count": 106,
"locationInfo": {
"yboVMzb7z": {
"kind": "folder",
"name": "gdev dashboards",
"url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
}
},
"sortBy": "name_sort"
}
},
"fields": [
{
"name": "kind",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "uid",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "name",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "panel_type",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "url",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"links": [
{
"title": "link",
"url": "${__value.text}"
}
]
}
},
{
"name": "tags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "ds_uid",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "location",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "allowed_actions",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage"
}
}
]
},
"data": {
"values": [
[
"folder",
"dashboard",
"dashboard",
"dashboard"
],
[
"ujaM1h6nz",
"7MeksYbmk",
"vmie2cmWz",
"xMsQdBfWz"
],
[
"abc2",
"Alerting with TestData",
"Bar Gauge Demo",
"Bar Gauge Demo Unfilled"
],
[
"",
"",
"",
""
],
[
"/dashboards/f/ujaM1h6nz/abc2",
"/d/7MeksYbmk/alerting-with-testdata",
"/d/vmie2cmWz/bar-gauge-demo",
"/d/xMsQdBfWz/bar-gauge-demo-unfilled"
],
[
null,
[
"gdev",
"alerting"
],
[
"gdev",
"demo"
],
[
"gdev",
"demo"
]
],
[
null,
[
"datasource-1"
],
[
"datasource-2",
"datasource-3",
"datasource-4"
],
[]
],
[
"",
"yboVMzb7z",
"yboVMzb7z",
"yboVMzb7z"
],
[
[
{
"kind": "folder",
"uid": "ujaM1h6nz",
"actions": [
"folders:read"
]
}
],
[
{
"kind": "dashboard",
"uid": "7MeksYbmk",
"actions": [
"dashboards:write"
]
},
{
"kind": "datasource",
"uid": "datasource-1",
"actions": []
}
],
[
{
"kind": "dashboard",
"uid": "vmie2cmWz",
"actions": []
},
{
"kind": "datasource",
"uid": "datasource-2",
"actions": [
"datasources:read"
]
},
{
"kind": "datasource",
"uid": "datasource-3",
"actions": [
"datasources:read"
]
},
{
"kind": "datasource",
"uid": "datasource-4",
"actions": []
}
],
[
{
"kind": "dashboard",
"uid": "xMsQdBfWz",
"actions": []
}
]
]
]
}
}
]
}

View File

@ -0,0 +1,155 @@
{
"schema": {
"name": "Query results",
"refId": "Search",
"meta": {
"type": "search-results",
"custom": {
"count": 106,
"locationInfo": {
"yboVMzb7z": {
"name": "gdev dashboards",
"kind": "folder",
"url": "/dashboards/f/yboVMzb7z/gdev-dashboards"
}
},
"sortBy": "name_sort"
}
},
"fields": [
{
"name": "kind",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "uid",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "name",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "panel_type",
"type": "string",
"typeInfo": {
"frame": "string"
}
},
{
"name": "url",
"type": "string",
"typeInfo": {
"frame": "string"
},
"config": {
"links": [
{
"title": "link",
"url": "${__value.text}"
}
]
}
},
{
"name": "tags",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "ds_uid",
"type": "other",
"typeInfo": {
"frame": "json.RawMessage",
"nullable": true
}
},
{
"name": "location",
"type": "string",
"typeInfo": {
"frame": "string"
}
}
]
},
"data": {
"values": [
[
"folder",
"dashboard",
"dashboard",
"dashboard"
],
[
"ujaM1h6nz",
"7MeksYbmk",
"vmie2cmWz",
"xMsQdBfWz"
],
[
"abc2",
"Alerting with TestData",
"Bar Gauge Demo",
"Bar Gauge Demo Unfilled"
],
[
"",
"",
"",
""
],
[
"/dashboards/f/ujaM1h6nz/abc2",
"/d/7MeksYbmk/alerting-with-testdata",
"/d/vmie2cmWz/bar-gauge-demo",
"/d/xMsQdBfWz/bar-gauge-demo-unfilled"
],
[
null,
[
"gdev",
"alerting"
],
[
"gdev",
"demo"
],
[
"gdev",
"demo"
]
],
[
null,
[
"datasource-1"
],
[
"datasource-2",
"datasource-3",
"datasource-4"
],
[]
],
[
"",
"yboVMzb7z",
"yboVMzb7z",
"yboVMzb7z"
]
]
}
}

View File

@ -14,21 +14,21 @@ type FacetField struct {
}
type DashboardQuery struct {
Query string `json:"query"`
Location string `json:"location,omitempty"` // parent folder ID
Sort string `json:"sort,omitempty"` // field ASC/DESC
Datasource string `json:"ds_uid,omitempty"` // "datasource" collides with the JSON value at the same leel :()
Tags []string `json:"tags,omitempty"`
Kind []string `json:"kind,omitempty"`
PanelType string `json:"panel_type,omitempty"`
UIDs []string `json:"uid,omitempty"`
Explain bool `json:"explain,omitempty"` // adds details on why document matched
Facet []FacetField `json:"facet,omitempty"`
SkipLocation bool `json:"skipLocation,omitempty"`
AccessInfo bool `json:"accessInfo,omitempty"` // adds field for access control
HasPreview string `json:"hasPreview,omitempty"` // the light|dark theme
Limit int `json:"limit,omitempty"` // explicit page size
From int `json:"from,omitempty"` // for paging
Query string `json:"query"`
Location string `json:"location,omitempty"` // parent folder ID
Sort string `json:"sort,omitempty"` // field ASC/DESC
Datasource string `json:"ds_uid,omitempty"` // "datasource" collides with the JSON value at the same leel :()
Tags []string `json:"tags,omitempty"`
Kind []string `json:"kind,omitempty"`
PanelType string `json:"panel_type,omitempty"`
UIDs []string `json:"uid,omitempty"`
Explain bool `json:"explain,omitempty"` // adds details on why document matched
WithAllowedActions bool `json:"withAllowedActions,omitempty"` // adds allowed actions per entity
Facet []FacetField `json:"facet,omitempty"`
SkipLocation bool `json:"skipLocation,omitempty"`
HasPreview string `json:"hasPreview,omitempty"` // the light|dark theme
Limit int `json:"limit,omitempty"` // explicit page size
From int `json:"from,omitempty"` // for paging
}
type SearchService interface {

View File

@ -14,6 +14,7 @@ export interface Props {
const tabs = [
{ label: 'Score', value: 'score' },
{ label: 'Fields', value: 'fields' },
{ label: 'Allowed actions', value: 'allowed_actions' },
];
export function ExplainScorePopup({ name, explain, frame, row }: Props) {
@ -51,6 +52,21 @@ export function ExplainScorePopup({ name, explain, frame, row }: Props) {
<DataHoverView data={frame} rowIndex={row} />
</div>
)}
{activeTab === tabs[2].value && (
<CodeEditor
width="100%"
height="70vh"
language="json"
showLineNumbers={false}
showMiniMap={false}
value={(() => {
const allowedActions = frame.fields.find((f) => f.name === 'allowed_actions')?.values?.get(row);
const dsUids = frame.fields.find((f) => f.name === 'ds_uid')?.values?.get(row);
return JSON.stringify({ dsUids: dsUids ?? [], allowedActions: allowedActions ?? [] }, null, 2);
})()}
readOnly={false}
/>
)}
</TabContent>
</Modal>
);

View File

@ -65,6 +65,7 @@ export const SearchView = ({
location: folderDTO?.uid, // This will scope all results to the prefix
sort: query.sort?.value,
explain: query.explain,
withAllowedActions: query.explain, // allowedActions are currently not used for anything on the UI and added only in `explain` mode
};
// Only dashboards have additional properties

View File

@ -18,6 +18,7 @@ export interface SearchQuery {
id?: number[];
facet?: FacetField[];
explain?: boolean;
withAllowedActions?: boolean;
accessInfo?: boolean;
hasPreview?: string; // theme
limit?: number;