Chore: Reduce Elasticsearch data source plugin strict errors (#37399)

This commit is contained in:
Giordano Ricci 2021-08-05 16:03:38 +01:00 committed by GitHub
parent e0010860bd
commit 2f21bf5cfd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
33 changed files with 528 additions and 834 deletions

View File

@ -7,7 +7,6 @@ import { segmentStyles } from '../styles';
import { BucketAggregation, BucketAggregationType, isBucketAggregationWithField } from './aggregations';
import { SettingsEditor } from './SettingsEditor';
import { changeBucketAggregationField, changeBucketAggregationType } from './state/actions';
import { BucketAggregationAction } from './state/types';
import { bucketAggregationConfig } from './utils';
const bucketAggOptions: Array<SelectableValue<BucketAggregationType>> = Object.entries(bucketAggregationConfig).map(
@ -27,7 +26,7 @@ interface QueryMetricEditorProps {
}
export const BucketAggregationEditor = ({ value }: QueryMetricEditorProps) => {
const dispatch = useDispatch<BucketAggregationAction>();
const dispatch = useDispatch();
const getFields = useFields(value.type);
return (
@ -36,7 +35,7 @@ export const BucketAggregationEditor = ({ value }: QueryMetricEditorProps) => {
<Segment
className={segmentStyles}
options={bucketAggOptions}
onChange={(e) => dispatch(changeBucketAggregationType(value.id, e.value!))}
onChange={(e) => dispatch(changeBucketAggregationType({ id: value.id, newType: e.value! }))}
value={toOption(value)}
/>
@ -44,7 +43,7 @@ export const BucketAggregationEditor = ({ value }: QueryMetricEditorProps) => {
<SegmentAsync
className={segmentStyles}
loadOptions={getFields}
onChange={(e) => dispatch(changeBucketAggregationField(value.id, e.value))}
onChange={(e) => dispatch(changeBucketAggregationField({ id: value.id, newField: e.value }))}
placeholder="Select Field"
value={value.field}
/>

View File

@ -46,7 +46,8 @@ interface Props {
export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
const dispatch = useDispatch();
const handleIntervalChange = (v: string) => dispatch(changeBucketAggregationSetting(bucketAgg, 'interval', v));
const handleIntervalChange = (newValue: string) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue }));
return (
<>
@ -66,7 +67,11 @@ export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Min Doc Count" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'min_doc_count', e.target.value!))}
onBlur={(e) =>
dispatch(
changeBucketAggregationSetting({ bucketAgg, settingName: 'min_doc_count', newValue: e.target.value })
)
}
defaultValue={
bucketAgg.settings?.min_doc_count || bucketAggregationConfig.date_histogram.defaultSettings?.min_doc_count
}
@ -75,7 +80,9 @@ export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Trim Edges" {...inlineFieldProps} tooltip="Trim the edges on the timeseries datapoints">
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'trimEdges', e.target.value!))}
onBlur={(e) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'trimEdges', newValue: e.target.value }))
}
defaultValue={
bucketAgg.settings?.trimEdges || bucketAggregationConfig.date_histogram.defaultSettings?.trimEdges
}
@ -88,7 +95,9 @@ export const DateHistogramSettingsEditor = ({ bucketAgg }: Props) => {
tooltip="Change the start value of each bucket by the specified positive (+) or negative offset (-) duration, such as 1h for an hour, or 1d for a day"
>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'offset', e.target.value!))}
onBlur={(e) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'offset', newValue: e.target.value }))
}
defaultValue={bucketAgg.settings?.offset || bucketAggregationConfig.date_histogram.defaultSettings?.offset}
/>
</InlineField>

View File

@ -5,7 +5,6 @@ import { AddRemove } from '../../../../AddRemove';
import { useDispatch, useStatelessReducer } from '../../../../../hooks/useStatelessReducer';
import { Filters } from '../../aggregations';
import { changeBucketAggregationSetting } from '../../state/actions';
import { BucketAggregationAction } from '../../state/types';
import { addFilter, changeFilter, removeFilter } from './state/actions';
import { reducer as filtersReducer } from './state/reducer';
@ -14,10 +13,10 @@ interface Props {
}
export const FiltersSettingsEditor = ({ bucketAgg }: Props) => {
const upperStateDispatch = useDispatch<BucketAggregationAction<Filters>>();
const upperStateDispatch = useDispatch();
const dispatch = useStatelessReducer(
(newState) => upperStateDispatch(changeBucketAggregationSetting(bucketAgg, 'filters', newState)),
(newValue) => upperStateDispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'filters', newValue })),
bucketAgg.settings?.filters,
filtersReducer
);
@ -55,7 +54,7 @@ export const FiltersSettingsEditor = ({ bucketAgg }: Props) => {
placeholder="Lucene Query"
portalOrigin="elasticsearch"
onBlur={() => {}}
onChange={(query) => dispatch(changeFilter(index, { ...filter, query }))}
onChange={(query) => dispatch(changeFilter({ index, filter: { ...filter, query } }))}
query={filter.query}
/>
</InlineField>
@ -63,7 +62,7 @@ export const FiltersSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Label" labelWidth={10}>
<Input
placeholder="Label"
onBlur={(e) => dispatch(changeFilter(index, { ...filter, label: e.target.value }))}
onBlur={(e) => dispatch(changeFilter({ index, filter: { ...filter, label: e.target.value } }))}
defaultValue={filter.label}
/>
</InlineField>

View File

@ -1,16 +1,6 @@
import { createAction } from '@reduxjs/toolkit';
import { Filter } from '../../../aggregations';
import { FilterAction, ADD_FILTER, REMOVE_FILTER, CHANGE_FILTER } from './types';
export const addFilter = (): FilterAction => ({
type: ADD_FILTER,
});
export const removeFilter = (index: number): FilterAction => ({
type: REMOVE_FILTER,
payload: { index },
});
export const changeFilter = (index: number, filter: Filter): FilterAction => ({
type: CHANGE_FILTER,
payload: { index, filter },
});
export const addFilter = createAction('@bucketAggregations/filter/add');
export const removeFilter = createAction<number>('@bucketAggregations/filter/remove');
export const changeFilter = createAction<{ index: number; filter: Filter }>('@bucketAggregations/filter/change');

View File

@ -46,7 +46,7 @@ describe('Filters Bucket Aggregation Settings Reducer', () => {
reducerTester<Filter[]>()
.givenReducer(reducer, [firstFilter, secondFilter])
.whenActionIsDispatched(changeFilter(1, expectedSecondFilter))
.whenActionIsDispatched(changeFilter({ index: 1, filter: expectedSecondFilter }))
.thenStateShouldEqual([firstFilter, expectedSecondFilter]);
});
});

View File

@ -1,15 +1,18 @@
import { Action } from 'redux';
import { Filter } from '../../../aggregations';
import { defaultFilter } from '../utils';
import { ADD_FILTER, CHANGE_FILTER, FilterAction, REMOVE_FILTER } from './types';
import { addFilter, changeFilter, removeFilter } from './actions';
export const reducer = (state: Filter[] = [], action: FilterAction) => {
switch (action.type) {
case ADD_FILTER:
export const reducer = (state: Filter[] = [], action: Action) => {
if (addFilter.match(action)) {
return [...state, defaultFilter()];
case REMOVE_FILTER:
return state.slice(0, action.payload.index).concat(state.slice(action.payload.index + 1));
}
case CHANGE_FILTER:
if (removeFilter.match(action)) {
return state.slice(0, action.payload).concat(state.slice(action.payload + 1));
}
if (changeFilter.match(action)) {
return state.map((filter, index) => {
if (index !== action.payload.index) {
return filter;
@ -18,4 +21,6 @@ export const reducer = (state: Filter[] = [], action: FilterAction) => {
return action.payload.filter;
});
}
return state;
};

View File

@ -1,22 +0,0 @@
import { Action } from '../../../../../../hooks/useStatelessReducer';
import { Filter } from '../../../aggregations';
export const ADD_FILTER = '@bucketAggregations/filter/add';
export const REMOVE_FILTER = '@bucketAggregations/filter/remove';
export const CHANGE_FILTER = '@bucketAggregations/filter/change';
export type AddFilterAction = Action<typeof ADD_FILTER>;
export interface RemoveFilterAction extends Action<typeof REMOVE_FILTER> {
payload: {
index: number;
};
}
export interface ChangeFilterAction extends Action<typeof CHANGE_FILTER> {
payload: {
index: number;
filter: Filter;
};
}
export type FilterAction = AddFilterAction | RemoveFilterAction | ChangeFilterAction;

View File

@ -23,7 +23,9 @@ export const TermsSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Order" {...inlineFieldProps}>
<Select
menuShouldPortal
onChange={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'order', e.value!))}
onChange={(e) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'order', newValue: e.value }))
}
options={orderOptions}
value={bucketAgg.settings?.order || bucketAggregationConfig.terms.defaultSettings?.order}
/>
@ -36,8 +38,8 @@ export const TermsSettingsEditor = ({ bucketAgg }: Props) => {
{...useCreatableSelectPersistedBehaviour({
options: sizeOptions,
value: bucketAgg.settings?.size || bucketAggregationConfig.terms.defaultSettings?.size,
onChange(value) {
dispatch(changeBucketAggregationSetting(bucketAgg, 'size', value));
onChange(newValue) {
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'size', newValue }));
},
})}
/>
@ -45,7 +47,11 @@ export const TermsSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Min Doc Count" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'min_doc_count', e.target.value!))}
onBlur={(e) =>
dispatch(
changeBucketAggregationSetting({ bucketAgg, settingName: 'min_doc_count', newValue: e.target.value })
)
}
defaultValue={
bucketAgg.settings?.min_doc_count || bucketAggregationConfig.terms.defaultSettings?.min_doc_count
}
@ -55,7 +61,9 @@ export const TermsSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Order By" {...inlineFieldProps}>
<Select
menuShouldPortal
onChange={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'orderBy', e.value!))}
onChange={(e) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'orderBy', newValue: e.value }))
}
options={orderBy}
value={bucketAgg.settings?.orderBy || bucketAggregationConfig.terms.defaultSettings?.orderBy}
/>
@ -63,7 +71,9 @@ export const TermsSettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Missing" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'missing', e.target.value!))}
onBlur={(e) =>
dispatch(changeBucketAggregationSetting({ bucketAgg, settingName: 'missing', newValue: e.target.value }))
}
defaultValue={bucketAgg.settings?.missing || bucketAggregationConfig.terms.defaultSettings?.missing}
/>
</InlineField>

View File

@ -32,7 +32,11 @@ export const SettingsEditor = ({ bucketAgg }: Props) => {
{bucketAgg.type === 'geohash_grid' && (
<InlineField label="Precision" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'precision', e.target.value!))}
onBlur={(e) =>
dispatch(
changeBucketAggregationSetting({ bucketAgg, settingName: 'precision', newValue: e.target.value })
)
}
defaultValue={
bucketAgg.settings?.precision || bucketAggregationConfig[bucketAgg.type].defaultSettings?.precision
}
@ -44,7 +48,11 @@ export const SettingsEditor = ({ bucketAgg }: Props) => {
<>
<InlineField label="Interval" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'interval', e.target.value!))}
onBlur={(e) =>
dispatch(
changeBucketAggregationSetting({ bucketAgg, settingName: 'interval', newValue: e.target.value })
)
}
defaultValue={
bucketAgg.settings?.interval || bucketAggregationConfig[bucketAgg.type].defaultSettings?.interval
}
@ -53,7 +61,11 @@ export const SettingsEditor = ({ bucketAgg }: Props) => {
<InlineField label="Min Doc Count" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeBucketAggregationSetting(bucketAgg, 'min_doc_count', e.target.value!))}
onBlur={(e) =>
dispatch(
changeBucketAggregationSetting({ bucketAgg, settingName: 'min_doc_count', newValue: e.target.value })
)
}
defaultValue={
bucketAgg.settings?.min_doc_count ||
bucketAggregationConfig[bucketAgg.type].defaultSettings?.min_doc_count

View File

@ -2,7 +2,6 @@ import React from 'react';
import { BucketAggregationEditor } from './BucketAggregationEditor';
import { useDispatch } from '../../../hooks/useStatelessReducer';
import { addBucketAggregation, removeBucketAggregation } from './state/actions';
import { BucketAggregationAction } from './state/types';
import { BucketAggregation } from './aggregations';
import { useQuery } from '../ElasticsearchQueryContext';
import { QueryEditorRow } from '../QueryEditorRow';
@ -13,7 +12,7 @@ interface Props {
}
export const BucketAggregationsEditor = ({ nextId }: Props) => {
const dispatch = useDispatch<BucketAggregationAction>();
const dispatch = useDispatch();
const { bucketAggs } = useQuery();
const totalBucketAggs = bucketAggs?.length || 0;

View File

@ -1,61 +1,18 @@
import { SettingKeyOf } from '../../../types';
import { BucketAggregation, BucketAggregationWithField } from '../aggregations';
import {
ADD_BUCKET_AGG,
BucketAggregationAction,
REMOVE_BUCKET_AGG,
CHANGE_BUCKET_AGG_TYPE,
CHANGE_BUCKET_AGG_FIELD,
CHANGE_BUCKET_AGG_SETTING,
ChangeBucketAggregationSettingAction,
} from './types';
import { createAction } from '@reduxjs/toolkit';
import { BucketAggregation, BucketAggregationType, BucketAggregationWithField } from '../aggregations';
export const addBucketAggregation = (id: string): BucketAggregationAction => ({
type: ADD_BUCKET_AGG,
payload: {
id,
},
});
export const removeBucketAggregation = (id: BucketAggregation['id']): BucketAggregationAction => ({
type: REMOVE_BUCKET_AGG,
payload: {
id,
},
});
export const changeBucketAggregationType = (
id: BucketAggregation['id'],
newType: BucketAggregation['type']
): BucketAggregationAction => ({
type: CHANGE_BUCKET_AGG_TYPE,
payload: {
id,
newType,
},
});
export const changeBucketAggregationField = (
id: BucketAggregationWithField['id'],
newField: BucketAggregationWithField['field']
): BucketAggregationAction => ({
type: CHANGE_BUCKET_AGG_FIELD,
payload: {
id,
newField,
},
});
export const changeBucketAggregationSetting = <T extends BucketAggregation, K extends SettingKeyOf<T>>(
bucketAgg: T,
settingName: K,
// This could be inferred from T, but it's causing some troubles
newValue: string | string[] | any
): ChangeBucketAggregationSettingAction<T> => ({
type: CHANGE_BUCKET_AGG_SETTING,
payload: {
bucketAgg,
settingName,
newValue,
},
});
export const addBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/add');
export const removeBucketAggregation = createAction<BucketAggregation['id']>('@bucketAggs/remove');
export const changeBucketAggregationType = createAction<{
id: BucketAggregation['id'];
newType: BucketAggregationType;
}>('@bucketAggs/change_type');
export const changeBucketAggregationField = createAction<{
id: BucketAggregation['id'];
newField: BucketAggregationWithField['field'];
}>('@bucketAggs/change_field');
export const changeBucketAggregationSetting = createAction<{
bucketAgg: BucketAggregation;
settingName: string;
newValue: any;
}>('@bucketAggs/change_setting');

View File

@ -71,7 +71,9 @@ describe('Bucket Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['bucketAggs']>()
.givenReducer(createReducer('@timestamp'), [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeBucketAggregationType(secondAggregation.id, expectedSecondAggregation.type))
.whenActionIsDispatched(
changeBucketAggregationType({ id: secondAggregation.id, newType: expectedSecondAggregation.type })
)
.thenStateShouldEqual([firstAggregation, expectedSecondAggregation]);
});
@ -92,7 +94,9 @@ describe('Bucket Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['bucketAggs']>()
.givenReducer(createReducer('@timestamp'), [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeBucketAggregationField(secondAggregation.id, expectedSecondAggregation.field))
.whenActionIsDispatched(
changeBucketAggregationField({ id: secondAggregation.id, newField: expectedSecondAggregation.field })
)
.thenStateShouldEqual([firstAggregation, expectedSecondAggregation]);
});
@ -108,13 +112,13 @@ describe('Bucket Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['bucketAggs']>()
.givenReducer(createReducer('@timestamp'), initialState)
// If the new metric aggregation is `isSingleMetric` we should remove all bucket aggregations.
.whenActionIsDispatched(changeMetricType('Some id', 'raw_data'))
.whenActionIsDispatched(changeMetricType({ id: 'Some id', type: 'raw_data' }))
.thenStatePredicateShouldEqual((newState) => newState?.length === 0)
// Switching back to another aggregation that is NOT `isSingleMetric` should bring back a bucket aggregation
.whenActionIsDispatched(changeMetricType('Some id', 'max'))
.whenActionIsDispatched(changeMetricType({ id: 'Some id', type: 'max' }))
.thenStatePredicateShouldEqual((newState) => newState?.length === 1)
// When none of the above is true state shouldn't change.
.whenActionIsDispatched(changeMetricType('Some id', 'min'))
.whenActionIsDispatched(changeMetricType({ id: 'Some id', type: 'min' }))
.thenStatePredicateShouldEqual((newState) => newState?.length === 1);
});
});
@ -139,7 +143,11 @@ describe('Bucket Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['bucketAggs']>()
.givenReducer(createReducer('@timestamp'), [firstAggregation, secondAggregation])
.whenActionIsDispatched(
changeBucketAggregationSetting(firstAggregation, 'min_doc_count', expectedSettings.min_doc_count!)
changeBucketAggregationSetting({
bucketAgg: firstAggregation,
settingName: 'min_doc_count',
newValue: expectedSettings.min_doc_count!,
})
)
.thenStateShouldEqual([{ ...firstAggregation, settings: expectedSettings }, secondAggregation]);
});

View File

@ -1,28 +1,27 @@
import { defaultBucketAgg } from '../../../../query_def';
import { ElasticsearchQuery } from '../../../../types';
import { ChangeMetricTypeAction, CHANGE_METRIC_TYPE } from '../../MetricAggregationsEditor/state/types';
import { metricAggregationConfig } from '../../MetricAggregationsEditor/utils';
import { BucketAggregation, Terms } from '../aggregations';
import { INIT, InitAction } from '../../state';
import {
ADD_BUCKET_AGG,
REMOVE_BUCKET_AGG,
CHANGE_BUCKET_AGG_TYPE,
CHANGE_BUCKET_AGG_FIELD,
CHANGE_BUCKET_AGG_SETTING,
BucketAggregationAction,
} from './types';
import { initQuery } from '../../state';
import { bucketAggregationConfig } from '../utils';
import { removeEmpty } from '../../../../utils';
import { Action } from '@reduxjs/toolkit';
import {
addBucketAggregation,
changeBucketAggregationField,
changeBucketAggregationSetting,
changeBucketAggregationType,
removeBucketAggregation,
} from './actions';
import { changeMetricType } from '../../MetricAggregationsEditor/state/actions';
export const createReducer = (defaultTimeField: string) => (
state: ElasticsearchQuery['bucketAggs'],
action: BucketAggregationAction | ChangeMetricTypeAction | InitAction
action: Action
): ElasticsearchQuery['bucketAggs'] => {
switch (action.type) {
case ADD_BUCKET_AGG:
if (addBucketAggregation.match(action)) {
const newAgg: Terms = {
id: action.payload.id,
id: action.payload,
type: 'terms',
settings: bucketAggregationConfig['terms'].defaultSettings,
};
@ -34,11 +33,13 @@ export const createReducer = (defaultTimeField: string) => (
}
return [...state!, newAgg];
}
case REMOVE_BUCKET_AGG:
return state!.filter((bucketAgg) => bucketAgg.id !== action.payload.id);
if (removeBucketAggregation.match(action)) {
return state!.filter((bucketAgg) => bucketAgg.id !== action.payload);
}
case CHANGE_BUCKET_AGG_TYPE:
if (changeBucketAggregationType.match(action)) {
return state!.map((bucketAgg) => {
if (bucketAgg.id !== action.payload.id) {
return bucketAgg;
@ -56,8 +57,9 @@ export const createReducer = (defaultTimeField: string) => (
settings: bucketAggregationConfig[action.payload.newType].defaultSettings,
} as BucketAggregation;
});
}
case CHANGE_BUCKET_AGG_FIELD:
if (changeBucketAggregationField.match(action)) {
return state!.map((bucketAgg) => {
if (bucketAgg.id !== action.payload.id) {
return bucketAgg;
@ -68,8 +70,9 @@ export const createReducer = (defaultTimeField: string) => (
field: action.payload.newField,
};
});
}
case CHANGE_METRIC_TYPE:
if (changeMetricType.match(action)) {
// If we are switching to a metric which requires the absence of bucket aggregations
// we remove all of them.
if (metricAggregationConfig[action.payload.type].isSingleMetric) {
@ -81,8 +84,9 @@ export const createReducer = (defaultTimeField: string) => (
return [{ ...defaultBucketAgg('2'), field: defaultTimeField }];
}
return state;
}
case CHANGE_BUCKET_AGG_SETTING:
if (changeBucketAggregationSetting.match(action)) {
return state!.map((bucketAgg) => {
if (bucketAgg.id !== action.payload.bucketAgg.id) {
return bucketAgg;
@ -100,15 +104,15 @@ export const createReducer = (defaultTimeField: string) => (
},
};
});
}
case INIT:
if (initQuery.match(action)) {
if (state?.length || 0 > 0) {
return state;
}
return [{ ...defaultBucketAgg('2'), field: defaultTimeField }];
default:
return state;
}
return state;
};

View File

@ -1,51 +0,0 @@
import { Action } from '../../../../hooks/useStatelessReducer';
import { SettingKeyOf } from '../../../types';
import { BucketAggregation, BucketAggregationWithField } from '../aggregations';
export const ADD_BUCKET_AGG = '@bucketAggs/add';
export const REMOVE_BUCKET_AGG = '@bucketAggs/remove';
export const CHANGE_BUCKET_AGG_TYPE = '@bucketAggs/change_type';
export const CHANGE_BUCKET_AGG_FIELD = '@bucketAggs/change_field';
export const CHANGE_BUCKET_AGG_SETTING = '@bucketAggs/change_setting';
export interface AddBucketAggregationAction extends Action<typeof ADD_BUCKET_AGG> {
payload: {
id: BucketAggregation['id'];
};
}
export interface RemoveBucketAggregationAction extends Action<typeof REMOVE_BUCKET_AGG> {
payload: {
id: BucketAggregation['id'];
};
}
export interface ChangeBucketAggregationTypeAction extends Action<typeof CHANGE_BUCKET_AGG_TYPE> {
payload: {
id: BucketAggregation['id'];
newType: BucketAggregation['type'];
};
}
export interface ChangeBucketAggregationFieldAction extends Action<typeof CHANGE_BUCKET_AGG_FIELD> {
payload: {
id: BucketAggregation['id'];
newField: BucketAggregationWithField['field'];
};
}
export interface ChangeBucketAggregationSettingAction<T extends BucketAggregation>
extends Action<typeof CHANGE_BUCKET_AGG_SETTING> {
payload: {
bucketAgg: T;
settingName: SettingKeyOf<T>;
newValue: unknown;
};
}
export type BucketAggregationAction<T extends BucketAggregation = BucketAggregation> =
| AddBucketAggregationAction
| RemoveBucketAggregationAction
| ChangeBucketAggregationTypeAction
| ChangeBucketAggregationFieldAction
| ChangeBucketAggregationSettingAction<T>;

View File

@ -79,7 +79,7 @@ export const orderByOptions = [
/**
* This returns the valid options for each of the enabled extended stat
*/
function createOrderByOptionsForExtendedStats(metric: ExtendedStats): OrderByOption[] {
function createOrderByOptionsForExtendedStats(metric: ExtendedStats): SelectableValue<string> {
if (!metric.meta) {
return [];
}

View File

@ -6,7 +6,6 @@ import { useDatasource, useQuery } from '../ElasticsearchQueryContext';
import { useDispatch } from '../../../hooks/useStatelessReducer';
import { getStyles } from './styles';
import { SettingsEditor } from './SettingsEditor';
import { MetricAggregationAction } from './state/types';
import { metricAggregationConfig } from './utils';
import { changeMetricField, changeMetricType } from './state/actions';
import { MetricPicker } from '../../MetricPicker';
@ -65,7 +64,7 @@ export const MetricEditor = ({ value }: Props) => {
const styles = getStyles(useTheme2(), !!value.hide);
const datasource = useDatasource();
const query = useQuery();
const dispatch = useDispatch<MetricAggregationAction>();
const dispatch = useDispatch();
const getFields = useFields(value.type);
const loadOptions = useCallback(async () => {
@ -90,7 +89,7 @@ export const MetricEditor = ({ value }: Props) => {
<Segment
className={cx(styles.color, segmentStyles)}
options={getTypeOptions(previousMetrics, datasource.esVersion, datasource.xpack)}
onChange={(e) => dispatch(changeMetricType(value.id, e.value!))}
onChange={(e) => dispatch(changeMetricType({ id: value.id, type: e.value! }))}
value={toOption(value)}
/>
@ -98,7 +97,7 @@ export const MetricEditor = ({ value }: Props) => {
<SegmentAsync
className={cx(styles.color, segmentStyles)}
loadOptions={loadOptions}
onChange={(e) => dispatch(changeMetricField(value.id, e.value!))}
onChange={(e) => dispatch(changeMetricField({ id: value.id, field: e.value! }))}
placeholder="Select Field"
value={value.field}
/>
@ -107,7 +106,7 @@ export const MetricEditor = ({ value }: Props) => {
{isPipelineAggregation(value) && !isPipelineAggregationWithMultipleBucketPaths(value) && (
<MetricPicker
className={cx(styles.color, segmentStyles)}
onChange={(e) => dispatch(changeMetricField(value.id, e.value?.id!))}
onChange={(e) => dispatch(changeMetricField({ id: value.id, field: e.value?.id! }))}
options={previousMetrics}
value={value.field}
/>

View File

@ -1,6 +1,5 @@
import React, { Fragment, useEffect } from 'react';
import { Input, InlineLabel } from '@grafana/ui';
import { MetricAggregationAction } from '../../state/types';
import { changeMetricAttribute } from '../../state/actions';
import { css } from '@emotion/css';
import { AddRemove } from '../../../../AddRemove';
@ -23,10 +22,11 @@ interface Props {
}
export const BucketScriptSettingsEditor = ({ value, previousMetrics }: Props) => {
const upperStateDispatch = useDispatch<MetricAggregationAction<BucketScript>>();
const upperStateDispatch = useDispatch();
const dispatch = useStatelessReducer(
(newState) => upperStateDispatch(changeMetricAttribute(value, 'pipelineVariables', newState)),
(newValue) =>
upperStateDispatch(changeMetricAttribute({ metric: value, attribute: 'pipelineVariables', newValue })),
value.pipelineVariables,
reducer
);
@ -74,10 +74,10 @@ export const BucketScriptSettingsEditor = ({ value, previousMetrics }: Props) =>
<Input
defaultValue={pipelineVar.name}
placeholder="Variable Name"
onBlur={(e) => dispatch(renamePipelineVariable(e.target.value, index))}
onBlur={(e) => dispatch(renamePipelineVariable({ newName: e.target.value, index }))}
/>
<MetricPicker
onChange={(e) => dispatch(changePipelineVariableMetric(e.value!.id, index))}
onChange={(e) => dispatch(changePipelineVariableMetric({ newMetric: e.value!.id, index }))}
options={previousMetrics}
value={pipelineVar.pipelineAgg}
/>

View File

@ -1,34 +1,10 @@
import {
ADD_PIPELINE_VARIABLE,
REMOVE_PIPELINE_VARIABLE,
PipelineVariablesAction,
RENAME_PIPELINE_VARIABLE,
CHANGE_PIPELINE_VARIABLE_METRIC,
} from './types';
import { createAction } from '@reduxjs/toolkit';
export const addPipelineVariable = (): PipelineVariablesAction => ({
type: ADD_PIPELINE_VARIABLE,
});
export const addPipelineVariable = createAction('@pipelineVariables/add');
export const removePipelineVariable = createAction<number>('@pipelineVariables/remove');
export const removePipelineVariable = (index: number): PipelineVariablesAction => ({
type: REMOVE_PIPELINE_VARIABLE,
payload: {
index,
},
});
export const renamePipelineVariable = createAction<{ index: number; newName: string }>('@pipelineVariables/rename');
export const renamePipelineVariable = (newName: string, index: number): PipelineVariablesAction => ({
type: RENAME_PIPELINE_VARIABLE,
payload: {
index,
newName,
},
});
export const changePipelineVariableMetric = (newMetric: string, index: number): PipelineVariablesAction => ({
type: CHANGE_PIPELINE_VARIABLE_METRIC,
payload: {
index,
newMetric,
},
});
export const changePipelineVariableMetric = createAction<{ index: number; newMetric: string }>(
'@pipelineVariables/change_metric'
);

View File

@ -72,7 +72,7 @@ describe('BucketScript Settings Reducer', () => {
reducerTester<PipelineVariable[]>()
.givenReducer(reducer, [firstVar, secondVar])
.whenActionIsDispatched(renamePipelineVariable(expectedSecondVar.name, 1))
.whenActionIsDispatched(renamePipelineVariable({ newName: expectedSecondVar.name, index: 1 }))
.thenStateShouldEqual([firstVar, expectedSecondVar]);
});
@ -94,7 +94,7 @@ describe('BucketScript Settings Reducer', () => {
reducerTester<PipelineVariable[]>()
.givenReducer(reducer, [firstVar, secondVar])
.whenActionIsDispatched(changePipelineVariableMetric(expectedSecondVar.pipelineAgg, 1))
.whenActionIsDispatched(changePipelineVariableMetric({ newMetric: expectedSecondVar.pipelineAgg, index: 1 }))
.thenStateShouldEqual([firstVar, expectedSecondVar]);
});

View File

@ -1,22 +1,23 @@
import { Action } from '@reduxjs/toolkit';
import { PipelineVariable } from '../../../aggregations';
import { defaultPipelineVariable, generatePipelineVariableName } from '../utils';
import {
PipelineVariablesAction,
REMOVE_PIPELINE_VARIABLE,
ADD_PIPELINE_VARIABLE,
RENAME_PIPELINE_VARIABLE,
CHANGE_PIPELINE_VARIABLE_METRIC,
} from './types';
addPipelineVariable,
changePipelineVariableMetric,
removePipelineVariable,
renamePipelineVariable,
} from './actions';
export const reducer = (state: PipelineVariable[] = [], action: PipelineVariablesAction) => {
switch (action.type) {
case ADD_PIPELINE_VARIABLE:
export const reducer = (state: PipelineVariable[] = [], action: Action) => {
if (addPipelineVariable.match(action)) {
return [...state, defaultPipelineVariable(generatePipelineVariableName(state))];
}
case REMOVE_PIPELINE_VARIABLE:
return state.slice(0, action.payload.index).concat(state.slice(action.payload.index + 1));
if (removePipelineVariable.match(action)) {
return state.slice(0, action.payload).concat(state.slice(action.payload + 1));
}
case RENAME_PIPELINE_VARIABLE:
if (renamePipelineVariable.match(action)) {
return state.map((pipelineVariable, index) => {
if (index !== action.payload.index) {
return pipelineVariable;
@ -27,8 +28,9 @@ export const reducer = (state: PipelineVariable[] = [], action: PipelineVariable
name: action.payload.newName,
};
});
}
case CHANGE_PIPELINE_VARIABLE_METRIC:
if (changePipelineVariableMetric.match(action)) {
return state.map((pipelineVariable, index) => {
if (index !== action.payload.index) {
return pipelineVariable;
@ -39,8 +41,7 @@ export const reducer = (state: PipelineVariable[] = [], action: PipelineVariable
pipelineAgg: action.payload.newMetric,
};
});
default:
return state;
}
return state;
};

View File

@ -1,34 +0,0 @@
import { Action } from '../../../../../../hooks/useStatelessReducer';
export const ADD_PIPELINE_VARIABLE = '@pipelineVariables/add';
export const REMOVE_PIPELINE_VARIABLE = '@pipelineVariables/remove';
export const RENAME_PIPELINE_VARIABLE = '@pipelineVariables/rename';
export const CHANGE_PIPELINE_VARIABLE_METRIC = '@pipelineVariables/change_metric';
export type AddPipelineVariableAction = Action<typeof ADD_PIPELINE_VARIABLE>;
export interface RemovePipelineVariableAction extends Action<typeof REMOVE_PIPELINE_VARIABLE> {
payload: {
index: number;
};
}
export interface RenamePipelineVariableAction extends Action<typeof RENAME_PIPELINE_VARIABLE> {
payload: {
index: number;
newName: string;
};
}
export interface ChangePipelineVariableMetricAction extends Action<typeof CHANGE_PIPELINE_VARIABLE_METRIC> {
payload: {
index: number;
newMetric: string;
};
}
export type PipelineVariablesAction =
| AddPipelineVariableAction
| RemovePipelineVariableAction
| RenamePipelineVariableAction
| ChangePipelineVariableMetricAction;

View File

@ -21,7 +21,7 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<InlineField label="Model" labelWidth={16}>
<Select
menuShouldPortal
onChange={(value) => dispatch(changeMetricSetting(metric, 'model', value.value!))}
onChange={(value) => dispatch(changeMetricSetting({ metric, settingName: 'model', newValue: value.value }))}
options={movingAvgModelOptions}
value={metric.settings?.model}
/>
@ -36,9 +36,13 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<Input
onBlur={(e) =>
dispatch(
changeMetricSetting(metric, 'settings', {
changeMetricSetting({
metric,
settingName: 'settings',
newValue: {
...metric.settings?.settings,
alpha: e.target.value,
},
})
)
}
@ -52,9 +56,13 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<Input
onBlur={(e) =>
dispatch(
changeMetricSetting(metric, 'settings', {
changeMetricSetting({
metric,
settingName: 'settings',
newValue: {
...metric.settings?.settings,
beta: e.target.value,
},
})
)
}
@ -69,9 +77,13 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<Input
onBlur={(e) =>
dispatch(
changeMetricSetting(metric, 'settings', {
changeMetricSetting({
metric,
settingName: 'settings',
newValue: {
...metric.settings?.settings,
gamma: e.target.value,
},
})
)
}
@ -82,9 +94,13 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<Input
onBlur={(e) =>
dispatch(
changeMetricSetting(metric, 'settings', {
changeMetricSetting({
metric,
settingName: 'settings',
newValue: {
...metric.settings?.settings,
period: e.target.value!,
},
})
)
}
@ -96,7 +112,11 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<InlineSwitch
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
dispatch(
changeMetricSetting(metric, 'settings', { ...metric.settings?.settings, pad: e.target.checked })
changeMetricSetting({
metric,
settingName: 'settings',
newValue: { ...metric.settings?.settings, pad: e.target.checked },
})
)
}
checked={!!metric.settings?.settings?.pad}
@ -109,7 +129,7 @@ export const MovingAverageSettingsEditor = ({ metric }: Props) => {
<InlineField label="Minimize" labelWidth={16}>
<InlineSwitch
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
dispatch(changeMetricSetting(metric, 'minimize', e.target.checked))
dispatch(changeMetricSetting({ metric, settingName: 'minimize', newValue: e.target.checked }))
}
checked={!!metric.settings?.minimize}
/>

View File

@ -2,7 +2,6 @@ import React, { ComponentProps, useState } from 'react';
import { InlineField, Input } from '@grafana/ui';
import { useDispatch } from '../../../../hooks/useStatelessReducer';
import { changeMetricSetting } from '../state/actions';
import { ChangeMetricSettingAction } from '../state/types';
import { SettingKeyOf } from '../../../types';
import { MetricAggregationWithInlineScript, MetricAggregationWithSettings } from '../aggregations';
import { uniqueId } from 'lodash';
@ -23,7 +22,7 @@ export function SettingField<T extends MetricAggregationWithSettings, K extends
placeholder,
tooltip,
}: Props<T, K>) {
const dispatch = useDispatch<ChangeMetricSettingAction<T>>();
const dispatch = useDispatch();
const [id] = useState(uniqueId(`es-field-id-`));
const settings = metric.settings;
@ -38,7 +37,7 @@ export function SettingField<T extends MetricAggregationWithSettings, K extends
<Input
id={id}
placeholder={placeholder}
onBlur={(e) => dispatch(changeMetricSetting(metric, settingName, e.target.value as any))}
onBlur={(e) => dispatch(changeMetricSetting({ metric, settingName, newValue: e.target.value }))}
defaultValue={defaultValue}
/>
</InlineField>

View File

@ -26,11 +26,11 @@ export const TopMetricsSettingsEditor: FunctionComponent<Props> = ({ metric }) =
menuShouldPortal
onChange={(e) =>
dispatch(
changeMetricSetting(
changeMetricSetting({
metric,
'metrics',
e.map((v) => v.value!)
)
settingName: 'metrics',
newValue: e.map((v) => v.value!),
})
)
}
loadOptions={getMetricsOptions}
@ -42,7 +42,7 @@ export const TopMetricsSettingsEditor: FunctionComponent<Props> = ({ metric }) =
<InlineField label="Order" labelWidth={16}>
<Select
menuShouldPortal
onChange={(e) => dispatch(changeMetricSetting(metric, 'order', e.value))}
onChange={(e) => dispatch(changeMetricSetting({ metric, settingName: 'order', newValue: e.value }))}
options={orderOptions}
value={metric.settings?.order}
/>
@ -61,7 +61,7 @@ export const TopMetricsSettingsEditor: FunctionComponent<Props> = ({ metric }) =
margin-right: 0;
`}
loadOptions={getOrderByOptions}
onChange={(e) => dispatch(changeMetricSetting(metric, 'orderBy', e.value))}
onChange={(e) => dispatch(changeMetricSetting({ metric, settingName: 'orderBy', newValue: e.value }))}
placeholder="Select Field"
value={metric.settings?.orderBy}
/>

View File

@ -78,7 +78,7 @@ export const SettingsEditor = ({ metric, previousMetrics }: Props) => {
<InlineField label="Size" {...inlineFieldProps}>
<Input
id={`ES-query-${query.refId}_metric-${metric.id}-size`}
onBlur={(e) => dispatch(changeMetricSetting(metric, 'size', e.target.value))}
onBlur={(e) => dispatch(changeMetricSetting({ metric, settingName: 'size', newValue: e.target.value }))}
defaultValue={metric.settings?.size ?? metricAggregationConfig['raw_data'].defaults.settings?.size}
/>
</InlineField>
@ -96,7 +96,7 @@ export const SettingsEditor = ({ metric, previousMetrics }: Props) => {
<ExtendedStatSetting
key={stat.value}
stat={stat}
onChange={(checked) => dispatch(changeMetricMeta(metric, stat.value, checked))}
onChange={(newValue) => dispatch(changeMetricMeta({ metric, meta: stat.value, newValue }))}
value={
metric.meta?.[stat.value] !== undefined
? !!metric.meta?.[stat.value]
@ -112,7 +112,15 @@ export const SettingsEditor = ({ metric, previousMetrics }: Props) => {
{metric.type === 'percentiles' && (
<InlineField label="Percentiles" {...inlineFieldProps}>
<Input
onBlur={(e) => dispatch(changeMetricSetting(metric, 'percents', e.target.value.split(',').filter(Boolean)))}
onBlur={(e) =>
dispatch(
changeMetricSetting({
metric,
settingName: 'percents',
newValue: e.target.value.split(',').filter(Boolean),
})
)
}
defaultValue={
metric.settings?.percents || metricAggregationConfig['percentiles'].defaults.settings?.percents
}
@ -127,7 +135,7 @@ export const SettingsEditor = ({ metric, previousMetrics }: Props) => {
<Select
menuShouldPortal
id={`ES-query-${query.refId}_metric-${metric.id}-unit`}
onChange={(e) => dispatch(changeMetricSetting(metric, 'unit', e.value))}
onChange={(e) => dispatch(changeMetricSetting({ metric, settingName: 'unit', newValue: e.value }))}
options={rateAggUnitOptions}
value={metric.settings?.unit}
/>
@ -137,7 +145,7 @@ export const SettingsEditor = ({ metric, previousMetrics }: Props) => {
<Select
menuShouldPortal
id={`ES-query-${query.refId}_metric-${metric.id}-mode`}
onChange={(e) => dispatch(changeMetricSetting(metric, 'mode', e.value))}
onChange={(e) => dispatch(changeMetricSetting({ metric, settingName: 'mode', newValue: e.value }))}
options={rateAggModeOptions}
value={metric.settings?.unit}
/>

View File

@ -1,7 +1,6 @@
import React from 'react';
import { MetricEditor } from './MetricEditor';
import { useDispatch } from '../../../hooks/useStatelessReducer';
import { MetricAggregationAction } from './state/types';
import { metricAggregationConfig } from './utils';
import { addMetric, removeMetric, toggleMetricVisibility } from './state/actions';
import { MetricAggregation } from './aggregations';
@ -14,7 +13,7 @@ interface Props {
}
export const MetricAggregationsEditor = ({ nextId }: Props) => {
const dispatch = useDispatch<MetricAggregationAction>();
const dispatch = useDispatch();
const { metrics } = useQuery();
const totalMetrics = metrics?.length || 0;

View File

@ -1,96 +1,23 @@
import { SettingKeyOf } from '../../../types';
import { createAction } from '@reduxjs/toolkit';
import { MetricAggregation, MetricAggregationWithMeta, MetricAggregationWithSettings } from '../aggregations';
import {
ADD_METRIC,
CHANGE_METRIC_FIELD,
CHANGE_METRIC_TYPE,
REMOVE_METRIC,
TOGGLE_METRIC_VISIBILITY,
CHANGE_METRIC_SETTING,
CHANGE_METRIC_META,
CHANGE_METRIC_ATTRIBUTE,
MetricAggregationAction,
ChangeMetricAttributeAction,
ChangeMetricSettingAction,
ChangeMetricMetaAction,
} from './types';
export const addMetric = (id: MetricAggregation['id']): MetricAggregationAction => ({
type: ADD_METRIC,
payload: {
id,
},
});
export const removeMetric = (id: MetricAggregation['id']): MetricAggregationAction => ({
type: REMOVE_METRIC,
payload: {
id,
},
});
export const changeMetricType = (
id: MetricAggregation['id'],
type: MetricAggregation['type']
): MetricAggregationAction => ({
type: CHANGE_METRIC_TYPE,
payload: {
id,
type,
},
});
export const changeMetricField = (id: MetricAggregation['id'], field: string): MetricAggregationAction => ({
type: CHANGE_METRIC_FIELD,
payload: {
id,
field,
},
});
export const toggleMetricVisibility = (id: MetricAggregation['id']): MetricAggregationAction => ({
type: TOGGLE_METRIC_VISIBILITY,
payload: {
id,
},
});
export const changeMetricAttribute = <T extends MetricAggregation, K extends Extract<keyof T, string>>(
metric: T,
attribute: K,
newValue: T[K]
): ChangeMetricAttributeAction<T> => ({
type: CHANGE_METRIC_ATTRIBUTE,
payload: {
metric,
attribute,
newValue,
},
});
export const changeMetricSetting = <T extends MetricAggregationWithSettings, K extends SettingKeyOf<T>>(
metric: T,
settingName: K,
// Maybe this could have been NonNullable<T['settings']>[K], but it doesn't seem to work really well
newValue: NonNullable<T['settings']>[K]
): ChangeMetricSettingAction<T> => ({
type: CHANGE_METRIC_SETTING,
payload: {
metric,
settingName,
newValue,
},
});
export const changeMetricMeta = <T extends MetricAggregationWithMeta>(
metric: T,
meta: Extract<keyof Required<T>['meta'], string>,
newValue: string | number | boolean
): ChangeMetricMetaAction<T> => ({
type: CHANGE_METRIC_META,
payload: {
metric,
meta,
newValue,
},
});
export const addMetric = createAction<MetricAggregation['id']>('@metrics/add');
export const removeMetric = createAction<MetricAggregation['id']>('@metrics/remove');
export const toggleMetricVisibility = createAction<MetricAggregation['id']>('@metrics/toggle_visibility');
export const changeMetricField = createAction<{ id: MetricAggregation['id']; field: string }>('@metrics/change_field');
export const changeMetricType = createAction<{ id: MetricAggregation['id']; type: MetricAggregation['type'] }>(
'@metrics/change_type'
);
export const changeMetricAttribute = createAction<{ metric: MetricAggregation; attribute: string; newValue: any }>(
'@metrics/change_attr'
);
export const changeMetricSetting = createAction<{
metric: MetricAggregationWithSettings;
settingName: string;
newValue: any;
}>('@metrics/change_setting');
export const changeMetricMeta = createAction<{
metric: MetricAggregationWithMeta;
meta: string;
newValue: any;
}>('@metrics/change_meta');

View File

@ -77,7 +77,7 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeMetricType(secondAggregation.id, expectedSecondAggregation.type))
.whenActionIsDispatched(changeMetricType({ id: secondAggregation.id, type: expectedSecondAggregation.type }))
.thenStateShouldEqual([firstAggregation, { ...secondAggregation, type: expectedSecondAggregation.type }]);
});
@ -99,7 +99,7 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeMetricType(secondAggregation.id, expectedAggregation.type))
.whenActionIsDispatched(changeMetricType({ id: secondAggregation.id, type: expectedAggregation.type }))
.thenStateShouldEqual([expectedAggregation]);
});
});
@ -128,10 +128,10 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
// When changing a a pipelineAggregation field we set both pipelineAgg and field
.whenActionIsDispatched(changeMetricField(secondAggregation.id, expectedSecondAggregation.field))
.whenActionIsDispatched(changeMetricField({ id: secondAggregation.id, field: expectedSecondAggregation.field }))
.thenStateShouldEqual([firstAggregation, expectedSecondAggregation])
// otherwhise only field
.whenActionIsDispatched(changeMetricField(firstAggregation.id, expectedFirstAggregation.field))
.whenActionIsDispatched(changeMetricField({ id: firstAggregation.id, field: expectedFirstAggregation.field }))
.thenStateShouldEqual([expectedFirstAggregation, expectedSecondAggregation]);
});
@ -173,7 +173,9 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeMetricSetting(firstAggregation, 'unit', expectedSettings.unit!))
.whenActionIsDispatched(
changeMetricSetting({ metric: firstAggregation, settingName: 'unit', newValue: expectedSettings.unit! })
)
.thenStateShouldEqual([{ ...firstAggregation, settings: expectedSettings }, secondAggregation]);
});
@ -196,7 +198,7 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeMetricMeta(firstAggregation, 'avg', expectedMeta.avg!))
.whenActionIsDispatched(changeMetricMeta({ metric: firstAggregation, meta: 'avg', newValue: expectedMeta.avg! }))
.thenStateShouldEqual([{ ...firstAggregation, meta: expectedMeta }, secondAggregation]);
});
@ -214,7 +216,9 @@ describe('Metric Aggregations Reducer', () => {
reducerTester<ElasticsearchQuery['metrics']>()
.givenReducer(reducer, [firstAggregation, secondAggregation])
.whenActionIsDispatched(changeMetricAttribute(firstAggregation, 'hide', expectedHide))
.whenActionIsDispatched(
changeMetricAttribute({ metric: firstAggregation, attribute: 'hide', newValue: expectedHide })
)
.thenStateShouldEqual([{ ...firstAggregation, hide: expectedHide }, secondAggregation]);
});

View File

@ -1,7 +1,8 @@
import { Action } from '@reduxjs/toolkit';
import { defaultMetricAgg } from '../../../../query_def';
import { ElasticsearchQuery } from '../../../../types';
import { removeEmpty } from '../../../../utils';
import { INIT, InitAction } from '../../state';
import { initQuery } from '../../state';
import {
isMetricAggregationWithMeta,
isMetricAggregationWithSettings,
@ -10,37 +11,32 @@ import {
} from '../aggregations';
import { getChildren, metricAggregationConfig } from '../utils';
import {
ADD_METRIC,
CHANGE_METRIC_TYPE,
REMOVE_METRIC,
TOGGLE_METRIC_VISIBILITY,
MetricAggregationAction,
CHANGE_METRIC_FIELD,
CHANGE_METRIC_SETTING,
CHANGE_METRIC_META,
CHANGE_METRIC_ATTRIBUTE,
} from './types';
addMetric,
changeMetricAttribute,
changeMetricField,
changeMetricMeta,
changeMetricSetting,
changeMetricType,
removeMetric,
toggleMetricVisibility,
} from './actions';
export const reducer = (
state: ElasticsearchQuery['metrics'],
action: MetricAggregationAction | InitAction
): ElasticsearchQuery['metrics'] => {
switch (action.type) {
case ADD_METRIC:
return [...state!, defaultMetricAgg(action.payload.id)];
export const reducer = (state: ElasticsearchQuery['metrics'], action: Action): ElasticsearchQuery['metrics'] => {
if (addMetric.match(action)) {
return [...state!, defaultMetricAgg(action.payload)];
}
case REMOVE_METRIC:
const metricToRemove = state!.find((m) => m.id === action.payload.id)!;
if (removeMetric.match(action)) {
const metricToRemove = state!.find((m) => m.id === action.payload)!;
const metricsToRemove = [metricToRemove, ...getChildren(metricToRemove, state!)];
const resultingMetrics = state!.filter(
(metric) => !metricsToRemove.some((toRemove) => toRemove.id === metric.id)
);
const resultingMetrics = state!.filter((metric) => !metricsToRemove.some((toRemove) => toRemove.id === metric.id));
if (resultingMetrics.length === 0) {
return [defaultMetricAgg('1')];
}
return resultingMetrics;
}
case CHANGE_METRIC_TYPE:
if (changeMetricType.match(action)) {
return state!
.filter((metric) =>
// When the new metric type is `isSingleMetric` we remove all other metrics from the query
@ -64,8 +60,9 @@ export const reducer = (
...metricAggregationConfig[action.payload.type].defaults,
} as MetricAggregation;
});
}
case CHANGE_METRIC_FIELD:
if (changeMetricField.match(action)) {
return state!.map((metric) => {
if (metric.id !== action.payload.id) {
return metric;
@ -82,10 +79,11 @@ export const reducer = (
return newMetric;
});
}
case TOGGLE_METRIC_VISIBILITY:
if (toggleMetricVisibility.match(action)) {
return state!.map((metric) => {
if (metric.id !== action.payload.id) {
if (metric.id !== action.payload) {
return metric;
}
@ -94,8 +92,9 @@ export const reducer = (
hide: !metric.hide,
};
});
}
case CHANGE_METRIC_SETTING:
if (changeMetricSetting.match(action)) {
return state!.map((metric) => {
if (metric.id !== action.payload.metric.id) {
return metric;
@ -119,8 +118,9 @@ export const reducer = (
// This should never happen.
return metric;
});
}
case CHANGE_METRIC_META:
if (changeMetricMeta.match(action)) {
return state!.map((metric) => {
if (metric.id !== action.payload.metric.id) {
return metric;
@ -140,8 +140,9 @@ export const reducer = (
// This should never happen.
return metric;
});
}
case CHANGE_METRIC_ATTRIBUTE:
if (changeMetricAttribute.match(action)) {
return state!.map((metric) => {
if (metric.id !== action.payload.metric.id) {
return metric;
@ -152,14 +153,14 @@ export const reducer = (
[action.payload.attribute]: action.payload.newValue,
};
});
}
case INIT:
if (initQuery.match(action)) {
if (state?.length || 0 > 0) {
return state;
}
return [defaultMetricAgg('1')];
default:
return state;
}
return state;
};

View File

@ -1,89 +0,0 @@
import { Action } from '../../../../hooks/useStatelessReducer';
import { SettingKeyOf } from '../../../types';
import {
MetricAggregation,
MetricAggregationWithMeta,
MetricAggregationWithSettings,
MetricAggregationWithField,
} from '../aggregations';
export const ADD_METRIC = '@metrics/add';
export const REMOVE_METRIC = '@metrics/remove';
export const CHANGE_METRIC_TYPE = '@metrics/change_type';
export const CHANGE_METRIC_FIELD = '@metrics/change_field';
export const CHANGE_METRIC_SETTING = '@metrics/change_setting';
export const CHANGE_METRIC_META = '@metrics/change_meta';
export const CHANGE_METRIC_ATTRIBUTE = '@metrics/change_attr';
export const TOGGLE_METRIC_VISIBILITY = '@metrics/toggle_visibility';
export interface AddMetricAction extends Action<typeof ADD_METRIC> {
payload: {
id: MetricAggregation['id'];
};
}
export interface RemoveMetricAction extends Action<typeof REMOVE_METRIC> {
payload: {
id: MetricAggregation['id'];
};
}
export interface ChangeMetricTypeAction extends Action<typeof CHANGE_METRIC_TYPE> {
payload: {
id: MetricAggregation['id'];
type: MetricAggregation['type'];
};
}
export interface ChangeMetricFieldAction extends Action<typeof CHANGE_METRIC_FIELD> {
payload: {
id: MetricAggregation['id'];
field: MetricAggregationWithField['field'];
};
}
export interface ToggleMetricVisibilityAction extends Action<typeof TOGGLE_METRIC_VISIBILITY> {
payload: {
id: MetricAggregation['id'];
};
}
export interface ChangeMetricSettingAction<T extends MetricAggregationWithSettings>
extends Action<typeof CHANGE_METRIC_SETTING> {
payload: {
metric: T;
settingName: SettingKeyOf<T>;
newValue: unknown;
};
}
export interface ChangeMetricMetaAction<T extends MetricAggregationWithMeta> extends Action<typeof CHANGE_METRIC_META> {
payload: {
metric: T;
meta: Extract<keyof Required<T>['meta'], string>;
newValue: string | number | boolean;
};
}
export interface ChangeMetricAttributeAction<
T extends MetricAggregation,
K extends Extract<keyof T, string> = Extract<keyof T, string>
> extends Action<typeof CHANGE_METRIC_ATTRIBUTE> {
payload: {
metric: T;
attribute: K;
newValue: T[K];
};
}
type CommonActions =
| AddMetricAction
| RemoveMetricAction
| ChangeMetricTypeAction
| ChangeMetricFieldAction
| ToggleMetricVisibilityAction;
export type MetricAggregationAction<T extends MetricAggregation = MetricAggregation> =
| (T extends MetricAggregationWithSettings ? ChangeMetricSettingAction<T> : never)
| (T extends MetricAggregationWithMeta ? ChangeMetricMetaAction<T> : never)
| ChangeMetricAttributeAction<T>
| CommonActions;

View File

@ -1,69 +1,36 @@
import { Action } from '../../hooks/useStatelessReducer';
import { Action, createAction } from '@reduxjs/toolkit';
import { ElasticsearchQuery } from '../../types';
export const INIT = 'init';
const CHANGE_QUERY = 'change_query';
const CHANGE_ALIAS_PATTERN = 'change_alias_pattern';
export interface InitAction extends Action<typeof INIT> {}
interface ChangeQueryAction extends Action<typeof CHANGE_QUERY> {
payload: {
query: string;
};
}
interface ChangeAliasPatternAction extends Action<typeof CHANGE_ALIAS_PATTERN> {
payload: {
aliasPattern: string;
};
}
/**
* When the `initQuery` Action is dispatched, the query gets populated with default values where values are not present.
* This means it won't override any existing value in place, but just ensure the query is in a "runnable" state.
*/
export const initQuery = (): InitAction => ({ type: INIT });
export const initQuery = createAction('init');
export const changeQuery = (query: string): ChangeQueryAction => ({
type: CHANGE_QUERY,
payload: {
query,
},
});
export const changeQuery = createAction<ElasticsearchQuery['query']>('change_query');
export const changeAliasPattern = (aliasPattern: string): ChangeAliasPatternAction => ({
type: CHANGE_ALIAS_PATTERN,
payload: {
aliasPattern,
},
});
export const changeAliasPattern = createAction<ElasticsearchQuery['alias']>('change_alias_pattern');
export const queryReducer = (prevQuery: ElasticsearchQuery['query'], action: ChangeQueryAction | InitAction) => {
switch (action.type) {
case CHANGE_QUERY:
return action.payload.query;
export const queryReducer = (prevQuery: ElasticsearchQuery['query'], action: Action) => {
if (changeQuery.match(action)) {
return action.payload;
}
case INIT:
if (initQuery.match(action)) {
return prevQuery || '';
}
default:
return prevQuery;
}
};
export const aliasPatternReducer = (
prevAliasPattern: ElasticsearchQuery['alias'],
action: ChangeAliasPatternAction | InitAction
) => {
switch (action.type) {
case CHANGE_ALIAS_PATTERN:
return action.payload.aliasPattern;
export const aliasPatternReducer = (prevAliasPattern: ElasticsearchQuery['alias'], action: Action) => {
if (changeAliasPattern.match(action)) {
return action.payload;
}
case INIT:
if (initQuery.match(action)) {
return prevAliasPattern || '';
default:
return prevAliasPattern;
}
return prevAliasPattern;
};

View File

@ -1,10 +1,7 @@
import { createContext, useCallback, useContext } from 'react';
import { Action } from '@reduxjs/toolkit';
export interface Action<T extends string = string> {
type: T;
}
export type Reducer<S, A extends Action = Action> = (state: S, action: A) => S;
export type Reducer<S, A extends Action> = (state: S, action: A) => S;
export const combineReducers = <S, A extends Action = Action>(reducers: { [P in keyof S]: Reducer<S[P], A> }) => (
state: S,

View File

@ -3,7 +3,7 @@ set -e
echo -e "Collecting code stats (typescript errors & more)"
ERROR_COUNT_LIMIT=73
ERROR_COUNT_LIMIT=61
ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --strict true | grep -oP 'Found \K(\d+)')"
if [ "$ERROR_COUNT" -gt $ERROR_COUNT_LIMIT ]; then