package opentsdb import ( "encoding/json" "fmt" "net/http" "net/url" "path" "sort" "strings" "github.com/grafana/grafana-plugin-sdk-go/backend" ) func (s *Service) HandleSuggestQuery(rw http.ResponseWriter, req *http.Request) { logger := logger.FromContext(req.Context()) dsInfo, err := s.getDSInfo(req.Context(), backend.PluginConfigFromContext(req.Context())) if err != nil { http.Error(rw, fmt.Sprintf("failed to get datasource info: %v", err), http.StatusInternalServerError) return } u, err := url.Parse(dsInfo.URL) if err != nil { http.Error(rw, fmt.Sprintf("failed to parse datasource URL: %v", err), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "api/suggest") u.RawQuery = req.URL.RawQuery httpReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, u.String(), nil) if err != nil { http.Error(rw, fmt.Sprintf("failed to create request: %v", err), http.StatusInternalServerError) return } res, err := dsInfo.HTTPClient.Do(httpReq) if err != nil { http.Error(rw, fmt.Sprintf("failed to execute request: %v", err), http.StatusInternalServerError) return } defer func() { if err := res.Body.Close(); err != nil { logger.Error("Failed to close response body", "error", err) } }() responseBody, err := DecodeResponseBody(res, logger) if err != nil { http.Error(rw, fmt.Sprintf("failed to decode response: %v", err), http.StatusInternalServerError) return } for name, values := range res.Header { if name == "Content-Encoding" || name == "Content-Length" { continue } for _, value := range values { rw.Header().Add(name, value) } } rw.WriteHeader(res.StatusCode) if _, err := rw.Write(responseBody); err != nil { logger.Error("Failed to write response", "error", err) return } } func (s *Service) HandleAggregatorsQuery(rw http.ResponseWriter, req *http.Request) { logger := logger.FromContext(req.Context()) dsInfo, err := s.getDSInfo(req.Context(), backend.PluginConfigFromContext(req.Context())) if err != nil { http.Error(rw, fmt.Sprintf("failed to get datasource info: %v", err), http.StatusInternalServerError) return } u, err := url.Parse(dsInfo.URL) if err != nil { http.Error(rw, fmt.Sprintf("failed to parse datasource URL: %v", err), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "api/aggregators") httpReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, u.String(), nil) if err != nil { http.Error(rw, fmt.Sprintf("failed to create request: %v", err), http.StatusInternalServerError) return } res, err := dsInfo.HTTPClient.Do(httpReq) if err != nil { http.Error(rw, fmt.Sprintf("failed to execute request: %v", err), http.StatusInternalServerError) return } defer func() { if err := res.Body.Close(); err != nil { logger.Error("Failed to close response body", "error", err) } }() responseBody, err := DecodeResponseBody(res, logger) if err != nil { http.Error(rw, fmt.Sprintf("failed to decode response: %v", err), http.StatusInternalServerError) return } var aggregators []string if err := json.Unmarshal(responseBody, &aggregators); err != nil { http.Error(rw, fmt.Sprintf("failed to unmarshal aggregators response: %v", err), http.StatusInternalServerError) return } sort.Strings(aggregators) sortedResponse, err := json.Marshal(aggregators) if err != nil { http.Error(rw, fmt.Sprintf("failed to marshal response: %v", err), http.StatusInternalServerError) return } for name, values := range res.Header { if name == "Content-Encoding" || name == "Content-Length" { continue } for _, value := range values { rw.Header().Add(name, value) } } rw.WriteHeader(res.StatusCode) if _, err := rw.Write(sortedResponse); err != nil { logger.Error("Failed to write response", "error", err) return } } func (s *Service) HandleFiltersQuery(rw http.ResponseWriter, req *http.Request) { logger := logger.FromContext(req.Context()) dsInfo, err := s.getDSInfo(req.Context(), backend.PluginConfigFromContext(req.Context())) if err != nil { http.Error(rw, fmt.Sprintf("failed to get datasource info: %v", err), http.StatusInternalServerError) return } u, err := url.Parse(dsInfo.URL) if err != nil { http.Error(rw, fmt.Sprintf("failed to parse datasource URL: %v", err), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "/api/config/filters") httpReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, u.String(), nil) if err != nil { http.Error(rw, fmt.Sprintf("failed to create request: %v", err), http.StatusInternalServerError) return } res, err := dsInfo.HTTPClient.Do(httpReq) if err != nil { http.Error(rw, fmt.Sprintf("failed to execute request: %v", err), http.StatusInternalServerError) return } defer func() { if err := res.Body.Close(); err != nil { logger.Error("Failed to close response body", "error", err) } }() responseBody, err := DecodeResponseBody(res, logger) if err != nil { http.Error(rw, fmt.Sprintf("failed to decode response: %v", err), http.StatusInternalServerError) return } var filters map[string]json.RawMessage if err := json.Unmarshal(responseBody, &filters); err != nil { http.Error(rw, fmt.Sprintf("failed to unmarshal filters response: %v", err), http.StatusInternalServerError) return } keys := make([]string, 0, len(filters)) for key := range filters { keys = append(keys, key) } sort.Strings(keys) sortedResponse, err := json.Marshal(keys) if err != nil { http.Error(rw, fmt.Sprintf("failed to marshal response: %v", err), http.StatusInternalServerError) return } for name, values := range res.Header { if name == "Content-Encoding" || name == "Content-Length" { continue } for _, value := range values { rw.Header().Add(name, value) } } rw.WriteHeader(res.StatusCode) if _, err := rw.Write(sortedResponse); err != nil { logger.Error("Failed to write response", "error", err) return } } func (s *Service) HandleLookupQuery(rw http.ResponseWriter, req *http.Request) { queryParams := req.URL.Query() typeParam := queryParams.Get("type") if typeParam == "" { http.Error(rw, "missing 'type' parameter", http.StatusBadRequest) return } switch typeParam { case "key": s.HandleKeyLookup(rw, req, queryParams) case "keyvalue": s.HandleKeyValueLookup(rw, req, queryParams) default: http.Error(rw, fmt.Sprintf("unsupported type: %s", typeParam), http.StatusBadRequest) return } } func (s *Service) HandleKeyLookup(rw http.ResponseWriter, req *http.Request, queryParams url.Values) { logger := logger.FromContext(req.Context()) dsInfo, err := s.getDSInfo(req.Context(), backend.PluginConfigFromContext(req.Context())) if err != nil { http.Error(rw, fmt.Sprintf("failed to get datasource info: %v", err), http.StatusInternalServerError) return } metric := queryParams.Get("metric") if metric == "" { http.Error(rw, "missing 'metric' parameter", http.StatusBadRequest) return } u, err := url.Parse(dsInfo.URL) if err != nil { http.Error(rw, fmt.Sprintf("failed to parse datasource URL: %v", err), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "api/search/lookup") lookupQueryParams := u.Query() lookupQueryParams.Set("m", metric) lookupQueryParams.Set("limit", "1000") u.RawQuery = lookupQueryParams.Encode() httpReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, u.String(), nil) if err != nil { http.Error(rw, fmt.Sprintf("failed to create request: %v", err), http.StatusInternalServerError) return } res, err := dsInfo.HTTPClient.Do(httpReq) if err != nil { http.Error(rw, fmt.Sprintf("failed to execute request: %v", err), http.StatusInternalServerError) return } defer func() { if err := res.Body.Close(); err != nil { logger.Error("Failed to close response body", "error", err) } }() responseBody, err := DecodeResponseBody(res, logger) if err != nil { http.Error(rw, fmt.Sprintf("failed to decode response: %v", err), http.StatusInternalServerError) return } var lookupResponse struct { Results []struct { Tags map[string]string `json:"tags"` } `json:"results"` } if err := json.Unmarshal(responseBody, &lookupResponse); err != nil { http.Error(rw, fmt.Sprintf("failed to unmarshal lookup response: %v", err), http.StatusInternalServerError) return } tagKeysMap := make(map[string]bool) for _, result := range lookupResponse.Results { for tagKey := range result.Tags { tagKeysMap[tagKey] = true } } tagKeys := make([]string, 0, len(tagKeysMap)) for tagKey := range tagKeysMap { tagKeys = append(tagKeys, tagKey) } sort.Strings(tagKeys) sortedResponse, err := json.Marshal(tagKeys) if err != nil { http.Error(rw, fmt.Sprintf("failed to marshal response: %v", err), http.StatusInternalServerError) return } for name, values := range res.Header { if name == "Content-Encoding" || name == "Content-Length" { continue } for _, value := range values { rw.Header().Add(name, value) } } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(res.StatusCode) if _, err := rw.Write(sortedResponse); err != nil { logger.Error("Failed to write response", "error", err) return } } func (s *Service) HandleKeyValueLookup(rw http.ResponseWriter, req *http.Request, queryParams url.Values) { logger := logger.FromContext(req.Context()) dsInfo, err := s.getDSInfo(req.Context(), backend.PluginConfigFromContext(req.Context())) if err != nil { http.Error(rw, fmt.Sprintf("failed to get datasource info: %v", err), http.StatusInternalServerError) return } metric := queryParams.Get("metric") if metric == "" { http.Error(rw, "missing 'metric' parameter", http.StatusBadRequest) return } keys := queryParams.Get("keys") if keys == "" { http.Error(rw, "missing 'keys' parameter", http.StatusBadRequest) return } keysArray := strings.Split(keys, ",") for i := range keysArray { keysArray[i] = strings.TrimSpace(keysArray[i]) } if len(keysArray) == 0 { http.Error(rw, "keys parameter cannot be empty", http.StatusBadRequest) return } key := keysArray[0] keysQuery := key + "=*" if len(keysArray) > 1 { keysQuery += "," + strings.Join(keysArray[1:], ",") } m := metric + "{" + keysQuery + "}" u, err := url.Parse(dsInfo.URL) if err != nil { http.Error(rw, fmt.Sprintf("failed to parse datasource URL: %v", err), http.StatusInternalServerError) return } u.Path = path.Join(u.Path, "api/search/lookup") lookupQueryParams := u.Query() lookupQueryParams.Set("m", m) lookupQueryParams.Set("limit", fmt.Sprintf("%d", dsInfo.LookupLimit)) u.RawQuery = lookupQueryParams.Encode() httpReq, err := http.NewRequestWithContext(req.Context(), http.MethodGet, u.String(), nil) if err != nil { http.Error(rw, fmt.Sprintf("failed to create request: %v", err), http.StatusInternalServerError) return } res, err := dsInfo.HTTPClient.Do(httpReq) if err != nil { http.Error(rw, fmt.Sprintf("failed to execute request: %v", err), http.StatusInternalServerError) return } defer func() { if err := res.Body.Close(); err != nil { logger.Error("Failed to close response body", "error", err) } }() responseBody, err := DecodeResponseBody(res, logger) if err != nil { http.Error(rw, fmt.Sprintf("failed to decode response: %v", err), http.StatusInternalServerError) return } var lookupResponse struct { Results []struct { Tags map[string]string `json:"tags"` } `json:"results"` } if err := json.Unmarshal(responseBody, &lookupResponse); err != nil { http.Error(rw, fmt.Sprintf("failed to unmarshal lookup response: %v", err), http.StatusInternalServerError) return } tagValuesMap := make(map[string]bool) for _, result := range lookupResponse.Results { if tagValue, exists := result.Tags[key]; exists { tagValuesMap[tagValue] = true } } tagValues := make([]string, 0, len(tagValuesMap)) for tagValue := range tagValuesMap { tagValues = append(tagValues, tagValue) } sort.Strings(tagValues) sortedResponse, err := json.Marshal(tagValues) if err != nil { http.Error(rw, fmt.Sprintf("failed to marshal response: %v", err), http.StatusInternalServerError) return } for name, values := range res.Header { if name == "Content-Encoding" || name == "Content-Length" { continue } for _, value := range values { rw.Header().Add(name, value) } } rw.Header().Set("Content-Type", "application/json") rw.WriteHeader(res.StatusCode) if _, err := rw.Write(sortedResponse); err != nil { logger.Error("Failed to write response", "error", err) return } }