2015-01-08 02:00:00 -06:00
|
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
2020-04-20 09:20:45 -05:00
|
|
|
|
"net/http"
|
2016-03-14 05:59:51 -05:00
|
|
|
|
"strconv"
|
|
|
|
|
|
2021-01-15 07:43:20 -06:00
|
|
|
|
"github.com/grafana/grafana/pkg/api/response"
|
2019-02-23 16:35:26 -06:00
|
|
|
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
2020-03-04 05:57:20 -06:00
|
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2022-05-17 08:51:44 -05:00
|
|
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
|
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
2015-06-05 01:15:38 -05:00
|
|
|
|
"github.com/grafana/grafana/pkg/services/search"
|
2022-05-17 08:51:44 -05:00
|
|
|
|
"github.com/grafana/grafana/pkg/util"
|
2015-01-08 02:00:00 -06:00
|
|
|
|
)
|
|
|
|
|
|
2022-07-27 08:54:37 -05:00
|
|
|
|
// swagger:route GET /search search search
|
|
|
|
|
//
|
|
|
|
|
// Responses:
|
|
|
|
|
// 200: searchResponse
|
|
|
|
|
// 401: unauthorisedError
|
|
|
|
|
// 422: unprocessableEntityError
|
|
|
|
|
// 500: internalServerError
|
2022-02-03 11:46:38 -06:00
|
|
|
|
func (hs *HTTPServer) Search(c *models.ReqContext) response.Response {
|
2015-02-07 09:12:29 -06:00
|
|
|
|
query := c.Query("query")
|
2015-06-02 03:24:20 -05:00
|
|
|
|
tags := c.QueryStrings("tag")
|
2015-02-04 04:35:59 -06:00
|
|
|
|
starred := c.Query("starred")
|
2019-04-17 06:07:50 -05:00
|
|
|
|
limit := c.QueryInt64("limit")
|
|
|
|
|
page := c.QueryInt64("page")
|
2017-05-24 11:28:13 -05:00
|
|
|
|
dashboardType := c.Query("type")
|
2020-04-20 09:20:45 -05:00
|
|
|
|
sort := c.Query("sort")
|
2020-03-04 05:57:20 -06:00
|
|
|
|
permission := models.PERMISSION_VIEW
|
2015-02-05 04:10:56 -06:00
|
|
|
|
|
2019-04-17 06:07:50 -05:00
|
|
|
|
if limit > 5000 {
|
2021-01-15 07:43:20 -06:00
|
|
|
|
return response.Error(422, "Limit is above maximum allowed (5000), use page parameter to access hits beyond limit", nil)
|
2015-02-05 04:10:56 -06:00
|
|
|
|
}
|
2015-02-04 04:35:59 -06:00
|
|
|
|
|
2018-02-08 10:11:01 -06:00
|
|
|
|
if c.Query("permission") == "Edit" {
|
2020-03-04 05:57:20 -06:00
|
|
|
|
permission = models.PERMISSION_EDIT
|
2018-02-08 10:11:01 -06:00
|
|
|
|
}
|
|
|
|
|
|
2018-03-22 06:37:35 -05:00
|
|
|
|
dbIDs := make([]int64, 0)
|
2016-03-05 05:26:21 -06:00
|
|
|
|
for _, id := range c.QueryStrings("dashboardIds") {
|
2018-03-22 06:37:35 -05:00
|
|
|
|
dashboardID, err := strconv.ParseInt(id, 10, 64)
|
2016-03-05 05:26:21 -06:00
|
|
|
|
if err == nil {
|
2018-03-22 06:37:35 -05:00
|
|
|
|
dbIDs = append(dbIDs, dashboardID)
|
2016-03-05 05:26:21 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-07-26 21:28:14 -05:00
|
|
|
|
dbUIDs := c.QueryStrings("dashboardUIDs")
|
|
|
|
|
if len(dbUIDs) == 0 {
|
|
|
|
|
// To keep it for now backward compatible for grafana 9
|
|
|
|
|
dbUIDs = c.QueryStrings("dashboardUID")
|
|
|
|
|
}
|
2022-06-02 14:56:01 -05:00
|
|
|
|
|
2018-03-22 06:37:35 -05:00
|
|
|
|
folderIDs := make([]int64, 0)
|
2017-11-20 05:47:03 -06:00
|
|
|
|
for _, id := range c.QueryStrings("folderIds") {
|
2018-03-22 06:37:35 -05:00
|
|
|
|
folderID, err := strconv.ParseInt(id, 10, 64)
|
2017-11-20 05:47:03 -06:00
|
|
|
|
if err == nil {
|
2018-03-22 06:37:35 -05:00
|
|
|
|
folderIDs = append(folderIDs, folderID)
|
2017-11-20 05:47:03 -06:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-06-02 14:56:01 -05:00
|
|
|
|
if len(dbIDs) > 0 && len(dbUIDs) > 0 {
|
|
|
|
|
return response.Error(400, "search supports UIDs or IDs, not both", nil)
|
|
|
|
|
}
|
|
|
|
|
|
2015-05-13 03:45:53 -05:00
|
|
|
|
searchQuery := search.Query{
|
2022-06-02 14:56:01 -05:00
|
|
|
|
Title: query,
|
|
|
|
|
Tags: tags,
|
|
|
|
|
SignedInUser: c.SignedInUser,
|
|
|
|
|
Limit: limit,
|
|
|
|
|
Page: page,
|
|
|
|
|
IsStarred: starred == "true",
|
2022-08-11 06:28:55 -05:00
|
|
|
|
OrgId: c.OrgID,
|
2022-06-02 14:56:01 -05:00
|
|
|
|
DashboardIds: dbIDs,
|
|
|
|
|
DashboardUIDs: dbUIDs,
|
|
|
|
|
Type: dashboardType,
|
|
|
|
|
FolderIds: folderIDs,
|
|
|
|
|
Permission: permission,
|
|
|
|
|
Sort: sort,
|
2015-01-08 02:00:00 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-03 11:46:38 -06:00
|
|
|
|
err := hs.SearchService.SearchHandler(c.Req.Context(), &searchQuery)
|
2015-05-13 03:45:53 -05:00
|
|
|
|
if err != nil {
|
2021-01-15 07:43:20 -06:00
|
|
|
|
return response.Error(500, "Search failed", err)
|
2015-01-08 02:00:00 -06:00
|
|
|
|
}
|
|
|
|
|
|
2022-05-17 08:51:44 -05:00
|
|
|
|
defer c.TimeRequest(metrics.MApiDashboardSearch)
|
|
|
|
|
|
|
|
|
|
if !c.QueryBool("accesscontrol") {
|
|
|
|
|
return response.JSON(http.StatusOK, searchQuery.Result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return hs.searchHitsWithMetadata(c, searchQuery.Result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (hs *HTTPServer) searchHitsWithMetadata(c *models.ReqContext, hits models.HitList) response.Response {
|
|
|
|
|
folderUIDs := make(map[string]bool)
|
|
|
|
|
dashboardUIDs := make(map[string]bool)
|
|
|
|
|
|
|
|
|
|
for _, hit := range hits {
|
|
|
|
|
if hit.Type == models.DashHitFolder {
|
|
|
|
|
folderUIDs[hit.UID] = true
|
|
|
|
|
} else {
|
|
|
|
|
dashboardUIDs[hit.UID] = true
|
|
|
|
|
folderUIDs[hit.FolderUID] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-08-11 06:28:55 -05:00
|
|
|
|
folderMeta := hs.getMultiAccessControlMetadata(c, c.OrgID, dashboards.ScopeFoldersPrefix, folderUIDs)
|
|
|
|
|
dashboardMeta := hs.getMultiAccessControlMetadata(c, c.OrgID, dashboards.ScopeDashboardsPrefix, dashboardUIDs)
|
2022-05-17 08:51:44 -05:00
|
|
|
|
|
|
|
|
|
// search hit with access control metadata attached
|
|
|
|
|
type hitWithMeta struct {
|
|
|
|
|
*models.Hit
|
|
|
|
|
AccessControl accesscontrol.Metadata `json:"accessControl,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
hitsWithMeta := make([]hitWithMeta, 0, len(hits))
|
|
|
|
|
for _, hit := range hits {
|
|
|
|
|
var meta accesscontrol.Metadata
|
|
|
|
|
if hit.Type == models.DashHitFolder {
|
|
|
|
|
meta = folderMeta[hit.UID]
|
|
|
|
|
} else {
|
|
|
|
|
meta = accesscontrol.MergeMeta("dashboards", dashboardMeta[hit.UID], folderMeta[hit.FolderUID])
|
|
|
|
|
}
|
|
|
|
|
hitsWithMeta = append(hitsWithMeta, hitWithMeta{hit, meta})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return response.JSON(http.StatusOK, hitsWithMeta)
|
2015-01-08 02:00:00 -06:00
|
|
|
|
}
|
2020-04-20 09:20:45 -05:00
|
|
|
|
|
2022-07-27 08:54:37 -05:00
|
|
|
|
// swagger:route GET /search/sorting search listSortOptions
|
|
|
|
|
//
|
2022-09-12 02:40:35 -05:00
|
|
|
|
// List search sorting options.
|
2022-07-27 08:54:37 -05:00
|
|
|
|
//
|
|
|
|
|
// Responses:
|
|
|
|
|
// 200: listSortOptionsResponse
|
|
|
|
|
// 401: unauthorisedError
|
2021-01-15 07:43:20 -06:00
|
|
|
|
func (hs *HTTPServer) ListSortOptions(c *models.ReqContext) response.Response {
|
2020-04-20 09:20:45 -05:00
|
|
|
|
opts := hs.SearchService.SortOptions()
|
|
|
|
|
|
|
|
|
|
res := []util.DynMap{}
|
|
|
|
|
for _, o := range opts {
|
|
|
|
|
res = append(res, util.DynMap{
|
|
|
|
|
"name": o.Name,
|
|
|
|
|
"displayName": o.DisplayName,
|
|
|
|
|
"description": o.Description,
|
2021-02-11 01:49:16 -06:00
|
|
|
|
"meta": o.MetaName,
|
2020-04-20 09:20:45 -05:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-15 07:43:20 -06:00
|
|
|
|
return response.JSON(http.StatusOK, util.DynMap{
|
2020-04-20 09:20:45 -05:00
|
|
|
|
"sortOptions": res,
|
|
|
|
|
})
|
|
|
|
|
}
|
2022-07-27 08:54:37 -05:00
|
|
|
|
|
|
|
|
|
// swagger:parameters search
|
|
|
|
|
type SearchParams struct {
|
|
|
|
|
// Search Query
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
Query string `json:"query"`
|
|
|
|
|
// List of tags to search for
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
// type: array
|
|
|
|
|
// collectionFormat: multi
|
|
|
|
|
Tag []string `json:"tag"`
|
|
|
|
|
// Type to search for, dash-folder or dash-db
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
// Description:
|
|
|
|
|
// * `dash-folder` - Search for folder
|
|
|
|
|
// * `dash-db` - Seatch for dashboard
|
|
|
|
|
// Enum: dash-folder,dash-db
|
|
|
|
|
Type string `json:"type"`
|
|
|
|
|
// List of dashboard id’s to search for
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
DashboardIds []int64 `json:"dashboardIds"`
|
|
|
|
|
// List of dashboard uid’s to search for
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
DashboardUIDs []string `json:"dashboardUIDs"`
|
|
|
|
|
// List of folder id’s to search in for dashboards
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
FolderIds []int64 `json:"folderIds"`
|
|
|
|
|
// Flag indicating if only starred Dashboards should be returned
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
Starred bool `json:"starred"`
|
|
|
|
|
// Limit the number of returned results (max 5000)
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
Limit int64 `json:"limit"`
|
|
|
|
|
// Use this parameter to access hits beyond limit. Numbering starts at 1. limit param acts as page size. Only available in Grafana v6.2+.
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
Page int64 `json:"page"`
|
|
|
|
|
// Set to `Edit` to return dashboards/folders that the user can edit
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
// default:View
|
|
|
|
|
// Enum: Edit,View
|
|
|
|
|
Permission string `json:"permission"`
|
|
|
|
|
// Sort method; for listing all the possible sort methods use the search sorting endpoint.
|
|
|
|
|
// in:query
|
|
|
|
|
// required: false
|
|
|
|
|
// default: alpha-asc
|
|
|
|
|
// Enum: alpha-asc,alpha-desc
|
|
|
|
|
Sort string `json:"sort"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// swagger:response searchResponse
|
|
|
|
|
type SearchResponse struct {
|
|
|
|
|
// in: body
|
|
|
|
|
Body models.HitList `json:"body"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// swagger:response listSortOptionsResponse
|
|
|
|
|
type ListSortOptionsResponse struct {
|
|
|
|
|
// in: body
|
|
|
|
|
Body struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
DisplayName string `json:"displayName"`
|
|
|
|
|
Description string `json:"description"`
|
|
|
|
|
Meta string `json:"meta"`
|
|
|
|
|
} `json:"body"`
|
|
|
|
|
}
|