mirror of
https://github.com/grafana/grafana.git
synced 2025-08-06 20:59:35 +08:00
Logs panel: Add meta field to show total hits; add total hits to ElasticSearch plugin response (#104117)
* feat: Show total amount of hits in Elastic Search query * Add test with multiple series.
This commit is contained in:
@ -344,11 +344,19 @@ func processHits(dec *json.Decoder, sr *SearchResponse) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if tok == "hits" {
|
switch tok {
|
||||||
|
case "hits":
|
||||||
if err := streamHitsArray(dec, sr); err != nil {
|
if err := streamHitsArray(dec, sr); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
case "total":
|
||||||
|
var total *SearchResponseHitsTotal
|
||||||
|
err := dec.Decode(&total)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sr.Hits.Total = total
|
||||||
|
default:
|
||||||
// ignore these fields as they are not used in the current implementation
|
// ignore these fields as they are not used in the current implementation
|
||||||
err := skipUnknownField(dec)
|
err := skipUnknownField(dec)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -44,9 +44,15 @@ func (r *SearchRequest) MarshalJSON() ([]byte, error) {
|
|||||||
return json.Marshal(root)
|
return json.Marshal(root)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SearchResponseHitsTotal struct {
|
||||||
|
Value int `json:"value"`
|
||||||
|
Relation string `json:"relation"`
|
||||||
|
}
|
||||||
|
|
||||||
// SearchResponseHits represents search response hits
|
// SearchResponseHits represents search response hits
|
||||||
type SearchResponseHits struct {
|
type SearchResponseHits struct {
|
||||||
Hits []map[string]interface{}
|
Hits []map[string]interface{}
|
||||||
|
Total *SearchResponseHitsTotal `json:"total"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// SearchResponse represents a search response
|
// SearchResponse represents a search response
|
||||||
|
@ -208,7 +208,12 @@ func processLogsResponse(res *es.SearchResponse, target *Query, configuredFields
|
|||||||
frames := data.Frames{}
|
frames := data.Frames{}
|
||||||
frame := data.NewFrame("", fields...)
|
frame := data.NewFrame("", fields...)
|
||||||
setPreferredVisType(frame, data.VisTypeLogs)
|
setPreferredVisType(frame, data.VisTypeLogs)
|
||||||
setLogsCustomMeta(frame, searchWords, stringToIntWithDefaultValue(target.Metrics[0].Settings.Get("limit").MustString(), defaultSize))
|
|
||||||
|
var total int
|
||||||
|
if res.Hits.Total != nil {
|
||||||
|
total = res.Hits.Total.Value
|
||||||
|
}
|
||||||
|
setLogsCustomMeta(frame, searchWords, stringToIntWithDefaultValue(target.Metrics[0].Settings.Get("limit").MustString(), defaultSize), total)
|
||||||
frames = append(frames, frame)
|
frames = append(frames, frame)
|
||||||
queryRes.Frames = frames
|
queryRes.Frames = frames
|
||||||
|
|
||||||
@ -1192,7 +1197,7 @@ func setPreferredVisType(frame *data.Frame, visType data.VisType) {
|
|||||||
frame.Meta.PreferredVisualization = visType
|
frame.Meta.PreferredVisualization = visType
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int) {
|
func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int, total int) {
|
||||||
i := 0
|
i := 0
|
||||||
searchWordsList := make([]string, len(searchWords))
|
searchWordsList := make([]string, len(searchWords))
|
||||||
for searchWord := range searchWords {
|
for searchWord := range searchWords {
|
||||||
@ -1212,6 +1217,7 @@ func setLogsCustomMeta(frame *data.Frame, searchWords map[string]bool, limit int
|
|||||||
frame.Meta.Custom = map[string]interface{}{
|
frame.Meta.Custom = map[string]interface{}{
|
||||||
"searchWords": searchWordsList,
|
"searchWords": searchWordsList,
|
||||||
"limit": limit,
|
"limit": limit,
|
||||||
|
"total": total,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ func TestProcessLogsResponse(t *testing.T) {
|
|||||||
{
|
{
|
||||||
"aggregations": {},
|
"aggregations": {},
|
||||||
"hits": {
|
"hits": {
|
||||||
|
"total": { "value": 2 },
|
||||||
"hits": [
|
"hits": [
|
||||||
{
|
{
|
||||||
"_id": "fdsfs",
|
"_id": "fdsfs",
|
||||||
@ -107,7 +108,7 @@ func TestProcessLogsResponse(t *testing.T) {
|
|||||||
logsFrame := frames[0]
|
logsFrame := frames[0]
|
||||||
|
|
||||||
meta := logsFrame.Meta
|
meta := logsFrame.Meta
|
||||||
require.Equal(t, map[string]any{"searchWords": []string{"hello", "message"}, "limit": 500}, meta.Custom)
|
require.Equal(t, map[string]any{"searchWords": []string{"hello", "message"}, "limit": 500, "total": 2}, meta.Custom)
|
||||||
require.Equal(t, data.VisTypeLogs, string(meta.PreferredVisualization))
|
require.Equal(t, data.VisTypeLogs, string(meta.PreferredVisualization))
|
||||||
|
|
||||||
logsFieldMap := make(map[string]*data.Field)
|
logsFieldMap := make(map[string]*data.Field)
|
||||||
@ -431,6 +432,7 @@ func TestProcessLogsResponse(t *testing.T) {
|
|||||||
require.Equal(t, map[string]any{
|
require.Equal(t, map[string]any{
|
||||||
"searchWords": []string{"hello", "message"},
|
"searchWords": []string{"hello", "message"},
|
||||||
"limit": 500,
|
"limit": 500,
|
||||||
|
"total": 109,
|
||||||
}, customMeta)
|
}, customMeta)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -703,7 +705,7 @@ func TestProcessRawDocumentResponse(t *testing.T) {
|
|||||||
"responses": [
|
"responses": [
|
||||||
{
|
{
|
||||||
"hits": {
|
"hits": {
|
||||||
"total": 100,
|
"total": { "value": 100 },
|
||||||
"hits": [
|
"hits": [
|
||||||
{
|
{
|
||||||
"_id": "1",
|
"_id": "1",
|
||||||
@ -3239,7 +3241,7 @@ func TestParseResponse(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"hits": {
|
"hits": {
|
||||||
"total": 2,
|
"total": { "value": 2 },
|
||||||
"hits": [
|
"hits": [
|
||||||
{
|
{
|
||||||
"_id": "5",
|
"_id": "5",
|
||||||
|
@ -10,7 +10,8 @@
|
|||||||
// "searchWords": [
|
// "searchWords": [
|
||||||
// "hello",
|
// "hello",
|
||||||
// "message"
|
// "message"
|
||||||
// ]
|
// ],
|
||||||
|
// "total": 81
|
||||||
// },
|
// },
|
||||||
// "preferredVisualisationType": "logs"
|
// "preferredVisualisationType": "logs"
|
||||||
// }
|
// }
|
||||||
@ -45,7 +46,8 @@
|
|||||||
"searchWords": [
|
"searchWords": [
|
||||||
"hello",
|
"hello",
|
||||||
"message"
|
"message"
|
||||||
]
|
],
|
||||||
|
"total": 81
|
||||||
},
|
},
|
||||||
"preferredVisualisationType": "logs"
|
"preferredVisualisationType": "logs"
|
||||||
},
|
},
|
||||||
|
@ -34,6 +34,7 @@ import {
|
|||||||
filterLogLevels,
|
filterLogLevels,
|
||||||
getSeriesProperties,
|
getSeriesProperties,
|
||||||
LIMIT_LABEL,
|
LIMIT_LABEL,
|
||||||
|
TOTAL_LABEL,
|
||||||
logRowToSingleRowDataFrame,
|
logRowToSingleRowDataFrame,
|
||||||
logSeriesToLogsModel,
|
logSeriesToLogsModel,
|
||||||
queryLogsSample,
|
queryLogsSample,
|
||||||
@ -492,6 +493,52 @@ describe('dataFrameToLogsModel', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('given one series with total as custom meta property should return correct total', () => {
|
||||||
|
const series: DataFrame[] = [
|
||||||
|
createDataFrame({
|
||||||
|
fields: [],
|
||||||
|
meta: {
|
||||||
|
custom: {
|
||||||
|
total: 9999,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const logsModel = dataFrameToLogsModel(series, 1);
|
||||||
|
expect(logsModel.meta![0]).toMatchObject({
|
||||||
|
label: TOTAL_LABEL,
|
||||||
|
value: 9999,
|
||||||
|
kind: LogsMetaKind.Number,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('given multiple series with total as custom meta property should return correct total', () => {
|
||||||
|
const series: DataFrame[] = [
|
||||||
|
createDataFrame({
|
||||||
|
fields: [],
|
||||||
|
meta: {
|
||||||
|
custom: {
|
||||||
|
total: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
createDataFrame({
|
||||||
|
fields: [],
|
||||||
|
meta: {
|
||||||
|
custom: {
|
||||||
|
total: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
const logsModel = dataFrameToLogsModel(series, 1);
|
||||||
|
expect(logsModel.meta![0]).toMatchObject({
|
||||||
|
label: TOTAL_LABEL,
|
||||||
|
value: 9,
|
||||||
|
kind: LogsMetaKind.Number,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should return the expected meta when the line limit is reached', () => {
|
it('should return the expected meta when the line limit is reached', () => {
|
||||||
const series: DataFrame[] = getTestDataFrame();
|
const series: DataFrame[] = getTestDataFrame();
|
||||||
series[0].meta = {
|
series[0].meta = {
|
||||||
|
@ -51,6 +51,7 @@ import { createLogRowsMap, getLogLevel, getLogLevelFromKey, sortInAscendingOrder
|
|||||||
|
|
||||||
export const LIMIT_LABEL = 'Line limit';
|
export const LIMIT_LABEL = 'Line limit';
|
||||||
export const COMMON_LABELS = 'Common labels';
|
export const COMMON_LABELS = 'Common labels';
|
||||||
|
export const TOTAL_LABEL = 'Total lines';
|
||||||
|
|
||||||
export const LogLevelColor = {
|
export const LogLevelColor = {
|
||||||
[LogLevel.critical]: colors[7],
|
[LogLevel.critical]: colors[7],
|
||||||
@ -492,6 +493,15 @@ export function logSeriesToLogsModel(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const totalValue = logSeries.reduce((acc, series) => (acc += series.meta?.custom?.total), 0);
|
||||||
|
if (totalValue > 0) {
|
||||||
|
meta.push({
|
||||||
|
label: TOTAL_LABEL,
|
||||||
|
value: totalValue,
|
||||||
|
kind: LogsMetaKind.Number,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let totalBytes = 0;
|
let totalBytes = 0;
|
||||||
const queriesVisited: { [refId: string]: boolean } = {};
|
const queriesVisited: { [refId: string]: boolean } = {};
|
||||||
// To add just 1 error message
|
// To add just 1 error message
|
||||||
|
Reference in New Issue
Block a user