453 lines
13 KiB
Go
453 lines
13 KiB
Go
package legacysearcher
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"strconv"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/mock"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/selection"
|
|
|
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
|
dashboard "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/search/model"
|
|
"github.com/grafana/grafana/pkg/services/search/sort"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
|
unisearch "github.com/grafana/grafana/pkg/storage/unified/search"
|
|
)
|
|
|
|
func TestDashboardSearchClient_Search(t *testing.T) {
|
|
mockStore := dashboards.NewFakeDashboardStore(t)
|
|
sortSvc := sort.ProvideService()
|
|
client := NewDashboardSearchClient(mockStore, sortSvc)
|
|
ctx := context.Background()
|
|
user := &user.SignedInUser{OrgID: 2}
|
|
ctx = identity.WithRequester(ctx, user)
|
|
emptyTags, err := json.Marshal([]string{})
|
|
require.NoError(t, err)
|
|
|
|
dashboardKey := &resource.ResourceKey{
|
|
Name: "uid",
|
|
Resource: dashboard.DASHBOARD_RESOURCE,
|
|
}
|
|
|
|
t.Run("Should parse results into GRPC", func(t *testing.T) {
|
|
sorter, _ := sortSvc.GetSortOption("alpha-asc")
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
SignedInUser: user, // user from context should be used
|
|
Type: "dash-db", // should set type based off of key
|
|
Sort: sorter,
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1", Term: "term"},
|
|
{UID: "uid2", Title: "Test Dashboard2", FolderUID: "folder2"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
},
|
|
SortBy: []*resource.ResourceSearchRequest_Sort{
|
|
{
|
|
Field: resource.SEARCH_FIELD_TITLE,
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
require.NoError(t, err)
|
|
|
|
tags, err := json.Marshal([]string{"term"})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
searchFields := resource.StandardSearchFields()
|
|
require.Equal(t, &resource.ResourceSearchResponse{
|
|
TotalHits: 2,
|
|
Results: &resource.ResourceTable{
|
|
Columns: []*resource.ResourceTableColumnDefinition{
|
|
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
|
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
|
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
|
{
|
|
Name: "", // sort by should be empty if title is what we sorted by
|
|
Type: resource.ResourceTableColumnDefinition_INT64,
|
|
},
|
|
},
|
|
Rows: []*resource.ResourceTableRow{
|
|
{
|
|
Key: &resource.ResourceKey{
|
|
Name: "uid",
|
|
Group: dashboard.GROUP,
|
|
Resource: dashboard.DASHBOARD_RESOURCE,
|
|
},
|
|
Cells: [][]byte{
|
|
[]byte("Test Dashboard"),
|
|
[]byte("folder1"),
|
|
tags,
|
|
[]byte(strconv.FormatInt(0, 10)),
|
|
},
|
|
},
|
|
{
|
|
Key: &resource.ResourceKey{
|
|
Name: "uid2",
|
|
Group: dashboard.GROUP,
|
|
Resource: dashboard.DASHBOARD_RESOURCE,
|
|
},
|
|
Cells: [][]byte{
|
|
[]byte("Test Dashboard2"),
|
|
[]byte("folder2"),
|
|
emptyTags,
|
|
[]byte(strconv.FormatInt(0, 10)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Sorting should be properly parsed into legacy sorting options (asc), and results added", func(t *testing.T) {
|
|
sortOptionAsc := model.SortOption{
|
|
Name: "viewed-asc", // should add -asc to the sort field and match on that
|
|
}
|
|
sortSvc.RegisterSortOption(sortOptionAsc)
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
SignedInUser: user,
|
|
Type: "dash-db",
|
|
Sort: sortOptionAsc,
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(50)},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
},
|
|
SortBy: []*resource.ResourceSearchRequest_Sort{
|
|
{
|
|
Field: resource.SEARCH_FIELD_PREFIX + unisearch.DASHBOARD_VIEWS_TOTAL, // "fields." prefix should be removed
|
|
Desc: false,
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
searchFields := resource.StandardSearchFields()
|
|
require.Equal(t, &resource.ResourceSearchResponse{
|
|
TotalHits: 1,
|
|
Results: &resource.ResourceTable{
|
|
Columns: []*resource.ResourceTableColumnDefinition{
|
|
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
|
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
|
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
|
{
|
|
Name: "views_total",
|
|
Type: resource.ResourceTableColumnDefinition_INT64,
|
|
},
|
|
},
|
|
Rows: []*resource.ResourceTableRow{
|
|
{
|
|
Key: &resource.ResourceKey{
|
|
Name: "uid",
|
|
Group: dashboard.GROUP,
|
|
Resource: dashboard.DASHBOARD_RESOURCE,
|
|
},
|
|
Cells: [][]byte{
|
|
[]byte("Test Dashboard"),
|
|
[]byte("folder"),
|
|
emptyTags,
|
|
[]byte(strconv.FormatInt(50, 10)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Sorting should be properly parsed into legacy sorting options (desc)", func(t *testing.T) {
|
|
sortOptionAsc := model.SortOption{
|
|
Name: "errors-recently-desc", // should add -asc to the sort field and match on that
|
|
}
|
|
sortSvc.RegisterSortOption(sortOptionAsc)
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
SignedInUser: user,
|
|
Type: "dash-db",
|
|
Sort: sortOptionAsc,
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder", SortMeta: int64(2)},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
},
|
|
SortBy: []*resource.ResourceSearchRequest_Sort{
|
|
{
|
|
Field: unisearch.DASHBOARD_ERRORS_LAST_30_DAYS,
|
|
Desc: true,
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
searchFields := resource.StandardSearchFields()
|
|
require.Equal(t, &resource.ResourceSearchResponse{
|
|
TotalHits: 1,
|
|
Results: &resource.ResourceTable{
|
|
Columns: []*resource.ResourceTableColumnDefinition{
|
|
searchFields.Field(resource.SEARCH_FIELD_TITLE),
|
|
searchFields.Field(resource.SEARCH_FIELD_FOLDER),
|
|
searchFields.Field(resource.SEARCH_FIELD_TAGS),
|
|
{
|
|
Name: "errors_last_30_days",
|
|
Type: resource.ResourceTableColumnDefinition_INT64,
|
|
},
|
|
},
|
|
Rows: []*resource.ResourceTableRow{
|
|
{
|
|
Key: &resource.ResourceKey{
|
|
Name: "uid",
|
|
Group: dashboard.GROUP,
|
|
Resource: dashboard.DASHBOARD_RESOURCE,
|
|
},
|
|
Cells: [][]byte{
|
|
[]byte("Test Dashboard"),
|
|
[]byte("folder"),
|
|
emptyTags,
|
|
[]byte(strconv.FormatInt(2, 10)),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Query for tags should return facet properly", func(t *testing.T) {
|
|
mockStore.On("GetDashboardTags", mock.Anything, &dashboards.GetDashboardTagsQuery{OrgID: 2}).Return([]*dashboards.DashboardTagCloudItem{
|
|
{Term: "tag1", Count: 1},
|
|
{Term: "tag2", Count: 5},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Facet: map[string]*resource.ResourceSearchRequest_Facet{
|
|
"tags": {
|
|
Field: "tags",
|
|
},
|
|
},
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
require.Equal(t, &resource.ResourceSearchResponse{
|
|
Results: &resource.ResourceTable{},
|
|
Facet: map[string]*resource.ResourceSearchResponse_Facet{
|
|
"tags": {
|
|
Terms: []*resource.ResourceSearchResponse_TermFacet{
|
|
{
|
|
Term: "tag1",
|
|
Count: 1,
|
|
},
|
|
{
|
|
Term: "tag2",
|
|
Count: 5,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Query should be set as the title, and * should be removed", func(t *testing.T) {
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
Title: "test",
|
|
SignedInUser: user, // user from context should be used
|
|
Type: "dash-db", // should set type based off of key
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
},
|
|
Query: "*test*",
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Should read labels for the dashboard ids", func(t *testing.T) {
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
DashboardIds: []int64{1, 2},
|
|
SignedInUser: user, // user from context should be used
|
|
Type: "dash-db", // should set type based off of key
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
Labels: []*resource.Requirement{
|
|
{
|
|
Key: utils.LabelKeyDeprecatedInternalID,
|
|
Operator: "in",
|
|
Values: []string{"1", "2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Should modify fields to legacy compatible queries", func(t *testing.T) {
|
|
mockStore.On("FindDashboards", mock.Anything, &dashboards.FindPersistedDashboardsQuery{
|
|
DashboardUIDs: []string{"uid1", "uid2"},
|
|
Tags: []string{"tag1", "tag2"},
|
|
FolderUIDs: []string{"general", "folder1"},
|
|
SignedInUser: user, // user from context should be used
|
|
Type: "dash-db", // should set type based off of key
|
|
}).Return([]dashboards.DashboardSearchProjection{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
Fields: []*resource.Requirement{
|
|
{
|
|
Key: resource.SEARCH_FIELD_TAGS,
|
|
Operator: "in",
|
|
Values: []string{"tag1", "tag2"},
|
|
},
|
|
{
|
|
Key: resource.SEARCH_FIELD_NAME, // name should be used as uid
|
|
Operator: "in",
|
|
Values: []string{"uid1", "uid2"},
|
|
},
|
|
{
|
|
Key: resource.SEARCH_FIELD_FOLDER,
|
|
Operator: "in",
|
|
Values: []string{"", "folder1"}, // empty folder should be general
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Should retrieve dashboards by plugin through a different function", func(t *testing.T) {
|
|
mockStore.On("GetDashboardsByPluginID", mock.Anything, &dashboards.GetDashboardsByPluginIDQuery{
|
|
PluginID: "slo",
|
|
OrgID: 2, // retrieved from the signed in user
|
|
}).Return([]*dashboards.Dashboard{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
Fields: []*resource.Requirement{
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_ID,
|
|
Operator: "in",
|
|
Values: []string{"slo"},
|
|
},
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_KIND,
|
|
Operator: "in",
|
|
Values: []string{"plugin"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Should retrieve dashboards by provisioner name through a different function", func(t *testing.T) {
|
|
mockStore.On("GetProvisionedDashboardsByName", mock.Anything, "test").Return([]*dashboards.Dashboard{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
Fields: []*resource.Requirement{
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_KIND,
|
|
Operator: "=",
|
|
Values: []string{string(utils.ManagerKindClassicFP)}, // nolint:staticcheck
|
|
},
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_ID,
|
|
Operator: "in",
|
|
Values: []string{"test"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
|
|
t.Run("Should retrieve orphaned dashboards if provisioner not in is specified", func(t *testing.T) {
|
|
mockStore.On("GetOrphanedProvisionedDashboards", mock.Anything, []string{"test", "test2"}).Return([]*dashboards.Dashboard{
|
|
{UID: "uid", Title: "Test Dashboard", FolderUID: "folder1"},
|
|
}, nil).Once()
|
|
|
|
req := &resource.ResourceSearchRequest{
|
|
Options: &resource.ListOptions{
|
|
Key: dashboardKey,
|
|
Fields: []*resource.Requirement{
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_KIND,
|
|
Operator: "=",
|
|
Values: []string{string(utils.ManagerKindClassicFP)}, // nolint:staticcheck
|
|
},
|
|
{
|
|
Key: resource.SEARCH_FIELD_MANAGER_ID,
|
|
Operator: string(selection.NotIn),
|
|
Values: []string{"test", "test2"},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
resp, err := client.Search(ctx, req)
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
mockStore.AssertExpectations(t)
|
|
})
|
|
}
|