Skip to content

Commit 244a62d

Browse files
authored
Merge pull request #84 from quickwit-oss/ddelemeny/split-datasource-process-response
Extract datasource query processing
2 parents 5e7992d + 5a98913 commit 244a62d

File tree

5 files changed

+104
-96
lines changed

5 files changed

+104
-96
lines changed

src/LogContext/components/LogContextUI.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import { Button } from "@grafana/ui";
88
import { useQueryBuilder } from '@/QueryBuilder/lucene';
99
import { LogContextQueryBuilderSidebar } from "./LogContextQueryBuilderSidebar";
1010
import { DatasourceContext } from "components/QueryEditor/ElasticsearchQueryContext";
11-
import { QuickwitDataSource } from "datasource";
12-
import { useDatasourceFields } from "datasource.utils";
11+
import { QuickwitDataSource } from "@/datasource";
12+
import { useDatasourceFields } from "@/datasource/utils";
1313

1414
const logContextUiStyle = css`
1515
display: flex;

src/components/QueryEditor/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import { QueryTypeSelector } from './QueryTypeSelector';
2222

2323
import { getHook } from 'utils/context';
2424
import { LuceneQueryEditor } from '@/components/LuceneQueryEditor';
25-
import { useDatasourceFields } from 'datasource.utils';
25+
import { useDatasourceFields } from '@/datasource/utils';
2626

2727
export type ElasticQueryEditorProps = QueryEditorProps<ElasticDatasource, ElasticsearchQuery, QuickwitOptions>;
2828

src/datasource.ts renamed to src/datasource/index.ts

Lines changed: 6 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import {
77
AdHocVariableFilter,
88
CoreApp,
99
DataFrame,
10-
DataLink,
1110
DataQueryRequest,
1211
DataQueryResponse,
1312
DataSourceApi,
@@ -30,12 +29,11 @@ import {
3029
SupplementaryQueryType,
3130
TimeRange,
3231
} from '@grafana/data';
33-
import { BucketAggregation, DataLinkConfig, ElasticsearchQuery, TermsQuery, FieldCapabilitiesResponse } from './types';
32+
import { BucketAggregation, DataLinkConfig, ElasticsearchQuery, TermsQuery, FieldCapabilitiesResponse } from '@/types';
3433
import {
3534
DataSourceWithBackend,
3635
getTemplateSrv,
37-
TemplateSrv,
38-
getDataSourceSrv } from '@grafana/runtime';
36+
TemplateSrv } from '@grafana/runtime';
3937
import { QuickwitOptions } from 'quickwit';
4038
import { getDataQuery } from 'QueryBuilder/elastic';
4139
import { colors } from '@grafana/ui';
@@ -49,7 +47,8 @@ import ElasticsearchLanguageProvider from 'LanguageProvider';
4947
import { ReactNode } from 'react';
5048
import { fieldTypeMap } from 'utils';
5149
import { addAddHocFilter } from 'modifyQuery';
52-
import { LogContextProvider, LogRowContextOptions } from './LogContext/LogContextProvider';
50+
import { LogContextProvider, LogRowContextOptions } from '@/LogContext/LogContextProvider';
51+
import { getQueryResponseProcessor } from 'datasource/processResponse';
5352

5453
export const REF_ID_STARTER_LOG_VOLUME = 'log-volume-';
5554

@@ -94,16 +93,8 @@ export class QuickwitDataSource
9493
}
9594

9695
query(request: DataQueryRequest<ElasticsearchQuery>): Observable<DataQueryResponse> {
97-
return super.query(request)
98-
.pipe(map((response) => {
99-
response.data.forEach((dataFrame) => {
100-
const metrics = request.targets[0]!.metrics
101-
if (metrics && metrics[0].type === 'logs'){
102-
enhanceDataFrameWithDataLinks(dataFrame, this.dataLinks, this.logMessageField);
103-
}
104-
});
105-
return response;
106-
}));
96+
const queryProcessor = getQueryResponseProcessor(this, request)
97+
return super.query(request) .pipe(map(queryProcessor.processResponse));
10798
}
10899

109100
/**
@@ -740,80 +731,3 @@ function luceneEscape(value: string) {
740731

741732
return value.replace(/([\!\*\+\-\=<>\s\&\|\(\)\[\]\{\}\^\~\?\:\\/"])/g, '\\$1');
742733
}
743-
744-
export function enhanceDataFrameWithDataLinks(dataFrame: DataFrame, dataLinks: DataLinkConfig[], logMessageField: string | undefined) {
745-
// Ignore log volume dataframe, no need to add links or a displayed message field.
746-
if (!dataFrame.refId || dataFrame.refId.startsWith('log-volume')) {
747-
return;
748-
}
749-
if (logMessageField) {
750-
const messageFields = logMessageField.split(',');
751-
let field_idx_list = [];
752-
for (const messageField of messageFields) {
753-
const field_idx = dataFrame.fields.findIndex((field) => field.name === messageField);
754-
if (field_idx !== -1) {
755-
field_idx_list.push(field_idx);
756-
}
757-
}
758-
const displayedMessages = Array(dataFrame.length);
759-
for (let idx = 0; idx < dataFrame.length; idx++) {
760-
let displayedMessage = "";
761-
// If we have only one field, we assume the field name is obvious for the user and we don't need to show it.
762-
if (field_idx_list.length === 1) {
763-
displayedMessage = `${dataFrame.fields[field_idx_list[0]].values[idx]}`;
764-
} else {
765-
for (const field_idx of field_idx_list) {
766-
displayedMessage += ` ${dataFrame.fields[field_idx].name}=${dataFrame.fields[field_idx].values[idx]}`;
767-
}
768-
}
769-
displayedMessages[idx] = displayedMessage.trim();
770-
}
771-
772-
const newField = {
773-
name: 'message',
774-
type: FieldType.string,
775-
config: {},
776-
values: displayedMessages,
777-
}
778-
const [timestamp, ...rest] = dataFrame.fields;
779-
dataFrame.fields = [timestamp, newField, ...rest];
780-
}
781-
782-
if (!dataLinks.length) {
783-
return;
784-
}
785-
786-
for (const field of dataFrame.fields) {
787-
const linksToApply = dataLinks.filter((dataLink) => dataLink.field === field.name);
788-
789-
if (linksToApply.length === 0) {
790-
continue;
791-
}
792-
793-
field.config = field.config || {};
794-
field.config.links = [...(field.config.links || [], linksToApply.map(generateDataLink))];
795-
}
796-
}
797-
798-
function generateDataLink(linkConfig: DataLinkConfig): DataLink {
799-
const dataSourceSrv = getDataSourceSrv();
800-
801-
if (linkConfig.datasourceUid) {
802-
const dsSettings = dataSourceSrv.getInstanceSettings(linkConfig.datasourceUid);
803-
804-
return {
805-
title: linkConfig.urlDisplayLabel || '',
806-
url: '',
807-
internal: {
808-
query: { query: linkConfig.url },
809-
datasourceUid: linkConfig.datasourceUid,
810-
datasourceName: dsSettings?.name ?? 'Data source not found',
811-
},
812-
};
813-
} else {
814-
return {
815-
title: linkConfig.urlDisplayLabel || '',
816-
url: linkConfig.url,
817-
};
818-
}
819-
}

src/datasource/processResponse.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { DataFrame, DataLink, DataQueryRequest, DataQueryResponse, FieldType } from "@grafana/data";
2+
import { getDataSourceSrv } from "@grafana/runtime";
3+
import { QuickwitDataSource } from 'datasource';
4+
import { DataLinkConfig, ElasticsearchQuery } from "../types";
5+
6+
export function getQueryResponseProcessor(datasource: QuickwitDataSource, request: DataQueryRequest<ElasticsearchQuery>) {
7+
return {
8+
processResponse: (response: DataQueryResponse) => {
9+
response.data.forEach((dataFrame) => {
10+
const metrics = request.targets[0].metrics;
11+
if (metrics && metrics[0].type === 'logs') {
12+
processLogsDataFrame(datasource, dataFrame);
13+
}
14+
});
15+
return response;
16+
}
17+
};
18+
}
19+
function getCustomFieldName(fieldname: string) { return `$qw_${fieldname}`; }
20+
export function processLogsDataFrame(datasource: QuickwitDataSource, dataFrame: DataFrame) {
21+
// Ignore log volume dataframe, no need to add links or a displayed message field.
22+
if (!dataFrame.refId || dataFrame.refId.startsWith('log-volume')) {
23+
return;
24+
}
25+
if (datasource.logMessageField) {
26+
const messageFields = datasource.logMessageField.split(',');
27+
let field_idx_list = [];
28+
for (const messageField of messageFields) {
29+
const field_idx = dataFrame.fields.findIndex((field) => field.name === messageField);
30+
if (field_idx !== -1) {
31+
field_idx_list.push(field_idx);
32+
}
33+
}
34+
const displayedMessages = Array(dataFrame.length);
35+
for (let idx = 0; idx < dataFrame.length; idx++) {
36+
let displayedMessage = "";
37+
// If we have only one field, we assume the field name is obvious for the user and we don't need to show it.
38+
if (field_idx_list.length === 1) {
39+
displayedMessage = `${dataFrame.fields[field_idx_list[0]].values[idx]}`;
40+
} else {
41+
for (const field_idx of field_idx_list) {
42+
displayedMessage += ` ${dataFrame.fields[field_idx].name}=${dataFrame.fields[field_idx].values[idx]}`;
43+
}
44+
}
45+
displayedMessages[idx] = displayedMessage.trim();
46+
}
47+
48+
const newField = {
49+
name: getCustomFieldName('message'),
50+
type: FieldType.string,
51+
config: {},
52+
values: displayedMessages,
53+
};
54+
const [timestamp, ...rest] = dataFrame.fields;
55+
dataFrame.fields = [timestamp, newField, ...rest];
56+
}
57+
58+
if (!datasource.dataLinks.length) {
59+
return;
60+
}
61+
62+
for (const field of dataFrame.fields) {
63+
const linksToApply = datasource.dataLinks.filter((dataLink) => dataLink.field === field.name);
64+
65+
if (linksToApply.length === 0) {
66+
continue;
67+
}
68+
69+
field.config = field.config || {};
70+
field.config.links = [...(field.config.links || [], linksToApply.map(generateDataLink))];
71+
}
72+
}
73+
function generateDataLink(linkConfig: DataLinkConfig): DataLink {
74+
const dataSourceSrv = getDataSourceSrv();
75+
76+
if (linkConfig.datasourceUid) {
77+
const dsSettings = dataSourceSrv.getInstanceSettings(linkConfig.datasourceUid);
78+
79+
return {
80+
title: linkConfig.urlDisplayLabel || '',
81+
url: '',
82+
internal: {
83+
query: { query: linkConfig.url },
84+
datasourceUid: linkConfig.datasourceUid,
85+
datasourceName: dsSettings?.name ?? 'Data source not found',
86+
},
87+
};
88+
} else {
89+
return {
90+
title: linkConfig.urlDisplayLabel || '',
91+
url: linkConfig.url,
92+
};
93+
}
94+
}

src/datasource.utils.ts renamed to src/datasource/utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { QuickwitDataSource } from "datasource";
1+
import { QuickwitDataSource } from "@/datasource";
22
import { useState, useEffect, useCallback } from "react";
33
import{ MetricFindValue } from '@grafana/data';
44

0 commit comments

Comments
 (0)