From 3e5168212983fb6e3907f7f995ca34fb3e18c8c2 Mon Sep 17 00:00:00 2001 From: Damien de Lemeny Date: Tue, 9 Apr 2024 07:56:08 -0500 Subject: [PATCH] Make log limits configurable --- .../QueryEditor/ElasticsearchQueryContext.tsx | 29 +++++++++++--- .../MetricAggregationsEditor/state/reducer.ts | 10 +++-- src/components/QueryEditor/index.tsx | 1 + src/configuration/ConfigEditor.tsx | 12 ++++++ src/datasource/base.ts | 5 +++ src/queryDef.ts | 4 -- src/quickwit.ts | 4 ++ src/store/defaults/conf.ts | 40 +++++++++++++++++++ src/store/defaults/index.ts | 33 +++++++++++++++ src/store/index.ts | 13 ++++++ 10 files changed, 139 insertions(+), 12 deletions(-) create mode 100644 src/store/defaults/conf.ts create mode 100644 src/store/defaults/index.ts create mode 100644 src/store/index.ts diff --git a/src/components/QueryEditor/ElasticsearchQueryContext.tsx b/src/components/QueryEditor/ElasticsearchQueryContext.tsx index 51881b0..919357d 100644 --- a/src/components/QueryEditor/ElasticsearchQueryContext.tsx +++ b/src/components/QueryEditor/ElasticsearchQueryContext.tsx @@ -1,4 +1,4 @@ -import React, { createContext, PropsWithChildren, useCallback, useEffect, useState } from 'react'; +import React, { createContext, PropsWithChildren, useCallback, useEffect, useState, FunctionComponent } from 'react'; import { CoreApp, TimeRange } from '@grafana/data'; @@ -10,6 +10,9 @@ import { createReducer as createBucketAggsReducer } from './BucketAggregationsEd import { reducer as metricsReducer } from './MetricAggregationsEditor/state/reducer'; import { aliasPatternReducer, queryReducer, initQuery, initExploreQuery } from './state'; import { getHook } from '@/utils/context'; +import { Provider, useDispatch } from "react-redux"; +import { initDefaults } from '@/store/defaults'; +import { store } from "@/store" export const RangeContext = createContext(undefined); export const useRange = getHook(RangeContext); @@ -29,7 +32,17 @@ interface Props { range: TimeRange; } -export const ElasticsearchProvider = ({ +function withStore

>(Component: FunctionComponent

): FunctionComponent

{ + const newComp = (props: P) => ( + + + + ) + newComp.displayName = Component.displayName + return newComp +} + +export const ElasticsearchProvider = withStore(({ children, onChange, onRunQuery, @@ -37,7 +50,13 @@ export const ElasticsearchProvider = ({ app, datasource, range, -}: PropsWithChildren) => { +}: PropsWithChildren): JSX.Element => { + + const storeDispatch = useDispatch(); + useEffect(()=>{ + storeDispatch(initDefaults(datasource.queryEditorConfig?.defaults)) + }, [storeDispatch, datasource]) + const onStateChange = useCallback( (query: ElasticsearchQuery) => { onChange(query); @@ -77,7 +96,7 @@ export const ElasticsearchProvider = ({ }, [shouldRunInit, dispatch, isUninitialized, app]); if (isUninitialized) { - return null; + return (<>); } return ( @@ -89,4 +108,4 @@ export const ElasticsearchProvider = ({ ); -}; +}); diff --git a/src/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts b/src/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts index 9bdde1d..bb4cab7 100644 --- a/src/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts +++ b/src/components/QueryEditor/MetricAggregationsEditor/state/reducer.ts @@ -1,5 +1,5 @@ import { Action } from '@reduxjs/toolkit'; -import { defaultLogsAgg, defaultMetricAgg } from '@/queryDef'; +import { defaultMetricAgg } from '@/queryDef'; import { ElasticsearchQuery, MetricAggregation } from '@/types'; import { removeEmpty } from '@/utils'; import { initExploreQuery, initQuery } from '../../state'; @@ -17,7 +17,11 @@ import { toggleMetricVisibility, } from './actions'; +import { store } from "@/store" + export const reducer = (state: ElasticsearchQuery['metrics'], action: Action): ElasticsearchQuery['metrics'] => { + const defaultsMetricAggregation = store.getState().defaults.metricAggregation; + if (addMetric.match(action)) { return [...state!, defaultMetricAgg(action.payload)]; } @@ -55,7 +59,7 @@ export const reducer = (state: ElasticsearchQuery['metrics'], action: Action): E return { id: metric.id, type: action.payload.type, - ...metricAggregationConfig[action.payload.type].defaults, + ...defaultsMetricAggregation[action.payload.type as keyof typeof defaultsMetricAggregation], } as MetricAggregation; }); } @@ -164,7 +168,7 @@ export const reducer = (state: ElasticsearchQuery['metrics'], action: Action): E if (state && state.length > 0) { return state; } - return [defaultLogsAgg('3')]; + return [{ type: 'logs', id: '3', ...defaultsMetricAggregation.logs }]; } return state; diff --git a/src/components/QueryEditor/index.tsx b/src/components/QueryEditor/index.tsx index 9ab81f1..44d9034 100644 --- a/src/components/QueryEditor/index.tsx +++ b/src/components/QueryEditor/index.tsx @@ -84,6 +84,7 @@ export const ElasticSearchQueryField = ({ value, onChange, onSubmit }: ElasticSe }; const QueryEditorForm = ({ value, onRunQuery }: Props) => { + const dispatch = useDispatch(); const nextId = useNextId(); const styles = useStyles2(getStyles); diff --git a/src/configuration/ConfigEditor.tsx b/src/configuration/ConfigEditor.tsx index 7053ee3..511902d 100644 --- a/src/configuration/ConfigEditor.tsx +++ b/src/configuration/ConfigEditor.tsx @@ -5,6 +5,7 @@ import { QuickwitOptions } from '../quickwit'; import { coerceOptions } from './utils'; import { Divider } from '../components/Divider'; import { DataLinks } from './DataLinks'; +import _ from 'lodash'; interface Props extends DataSourcePluginOptionsEditorProps {} @@ -92,6 +93,17 @@ export const QuickwitDetails = ({ value, onChange }: DetailsProps) => { /> +

+ + onChange(_.merge(value, {jsonData:{queryEditorConfig:{defaults:{'metricAggregation.logs.settings.limit':event.currentTarget.value}}}}))} + placeholder="100" + width={40} + /> + +
); diff --git a/src/datasource/base.ts b/src/datasource/base.ts index 1a2dae0..4a91cdf 100644 --- a/src/datasource/base.ts +++ b/src/datasource/base.ts @@ -37,6 +37,7 @@ import { SECOND } from 'utils/time'; import { GConstructor } from 'utils/mixins'; import { LuceneQuery } from '@/utils/lucene'; import { uidMaker } from "@/utils/uid" +import { DefaultsConfigOverrides } from 'store/defaults/conf'; export type BaseQuickwitDataSourceConstructor = GConstructor @@ -59,6 +60,9 @@ export class BaseQuickwitDataSource logMessageField?: string; logLevelField?: string; dataLinks: DataLinkConfig[]; + queryEditorConfig?: { + defaults?: DefaultsConfigOverrides + }; languageProvider: ElasticsearchLanguageProvider; @@ -73,6 +77,7 @@ export class BaseQuickwitDataSource this.logMessageField = settingsData.logMessageField || ''; this.logLevelField = settingsData.logLevelField || ''; this.dataLinks = settingsData.dataLinks || []; + this.queryEditorConfig = settingsData.queryEditorConfig || {}; this.languageProvider = new ElasticsearchLanguageProvider(this); this.annotations = {}; } diff --git a/src/queryDef.ts b/src/queryDef.ts index 780253a..9fa54bb 100644 --- a/src/queryDef.ts +++ b/src/queryDef.ts @@ -31,10 +31,6 @@ export function defaultMetricAgg(id = '1'): MetricAggregation { return { type: 'count', id }; } -export function defaultLogsAgg(id = '1'): MetricAggregation { - return { type: 'logs', id, ...metricAggregationConfig['logs'].defaults }; -} - export function defaultBucketAgg(id = '1'): DateHistogram { return { type: 'date_histogram', id, settings: { interval: 'auto' } }; } diff --git a/src/quickwit.ts b/src/quickwit.ts index b5c45d2..c088891 100644 --- a/src/quickwit.ts +++ b/src/quickwit.ts @@ -1,5 +1,6 @@ import { DataSourceJsonData } from "@grafana/data"; import { DataLinkConfig } from "./types"; +import { DefaultsConfigOverrides } from "store/defaults/conf"; export interface QuickwitOptions extends DataSourceJsonData { timeField: string; @@ -8,4 +9,7 @@ export interface QuickwitOptions extends DataSourceJsonData { logLevelField?: string; dataLinks?: DataLinkConfig[]; index: string; + queryEditorConfig?: { + defaults?: DefaultsConfigOverrides + } } diff --git a/src/store/defaults/conf.ts b/src/store/defaults/conf.ts new file mode 100644 index 0000000..33404ba --- /dev/null +++ b/src/store/defaults/conf.ts @@ -0,0 +1,40 @@ +import { + MetricAggregation, + MetricAggregationType, + Logs as SchemaLogs, + } from '@/dataquery.gen'; +import { Logs, LogsSortDirection } from "@/types"; + +export type QuickwitMetricAggregationType = Extract<'count' | 'avg' | 'sum' | 'min' | 'max' | 'percentiles' | 'raw_data' | 'logs', MetricAggregationType > + +export type MetricsDefaultSettings = Partial<{ + [T in QuickwitMetricAggregationType]: Omit|Logs, { type: T }>, 'id' | 'type'>; +}>; + + +export const defaultMetricAggregationConfig: MetricsDefaultSettings = { + percentiles: { + settings: { + percents: ['25', '50', '75', '95', '99'], + }, + }, + raw_data: { + settings: { + size: '100', + }, + }, + logs: { + settings: { + limit: '100', + sortDirection:'desc' as LogsSortDirection + }, + }, +}; + +export const defaultConfig = { + metricAggregation: defaultMetricAggregationConfig +}; + +export type DefaultsConfig = typeof defaultConfig + +export type DefaultsConfigOverrides = {[key: string]: any}; diff --git a/src/store/defaults/index.ts b/src/store/defaults/index.ts new file mode 100644 index 0000000..7fb60ad --- /dev/null +++ b/src/store/defaults/index.ts @@ -0,0 +1,33 @@ +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { defaultConfig , DefaultsConfigOverrides } from "./conf"; +import _ from "lodash"; + +export const initialState = defaultConfig + +const defaultsSlice = createSlice({ + name: "defaults", + initialState: defaultConfig, + reducers: { + initDefaults(_s, action: PayloadAction) { + // Initialize from default state, dont keep the old one + let newState = _.cloneDeep(defaultConfig); + // override values with payload + if (action.payload) { + const overrides = action.payload; + for (const key in overrides) { + // XXX : this is very not type-safe. Can do better ? + const value = overrides[key]; + newState = _.set(newState, key, value); + } + } + return newState + } + } +}) + +const {actions, reducer} = defaultsSlice +export const { + initDefaults, +} = actions + +export default reducer; diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 0000000..094e94f --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,13 @@ +import { configureStore } from "@reduxjs/toolkit"; +import defaultsReducer from "./defaults" + +export const store = configureStore({ + reducer: { + defaults: defaultsReducer, + } +}) + +// Infer the `RootState` and `AppDispatch` types from the store itself +export type RootState = ReturnType +// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState} +export type AppDispatch = typeof store.dispatch