fix current field candidates list

This commit is contained in:
Walter Rafelsberger 2024-10-11 20:15:37 +02:00
parent 26cd1a53df
commit 632b711ca1
4 changed files with 59 additions and 43 deletions

View file

@ -9,14 +9,16 @@ import { httpServiceMock } from '@kbn/core/public/mocks';
import type { FetchFieldCandidatesResponse } from '../queries/fetch_field_candidates'; import type { FetchFieldCandidatesResponse } from '../queries/fetch_field_candidates';
import { fetchFieldCandidates } from './log_rate_analysis_field_candidates_slice'; import { fetchFieldCandidates, getDefaultState } from './log_rate_analysis_field_candidates_slice';
const mockHttp = httpServiceMock.createStartContract(); const mockHttp = httpServiceMock.createStartContract();
describe('fetchFieldCandidates', () => { describe('fetchFieldCandidates', () => {
it('dispatches field candidates', async () => { it('dispatches field candidates', async () => {
const mockDispatch = jest.fn(); const mockDispatch = jest.fn();
const mockGetState = jest.fn(); const mockGetState = jest.fn().mockReturnValue({
logRateAnalysisFieldCandidates: getDefaultState(),
});
const mockResponse: FetchFieldCandidatesResponse = { const mockResponse: FetchFieldCandidatesResponse = {
isECS: false, isECS: false,
@ -60,7 +62,12 @@ describe('fetchFieldCandidates', () => {
payload: { payload: {
fieldSelectionMessage: fieldSelectionMessage:
'2 out of 5 fields were preselected for the analysis. Use the "Fields" dropdown to adjust the selection.', '2 out of 5 fields were preselected for the analysis. Use the "Fields" dropdown to adjust the selection.',
fieldFilterSkippedItems: [ initialFieldFilterSkippedItems: [
'another-keyword-field',
'another-text-field',
'yet-another-text-field',
],
currentFieldFilterSkippedItems: [
'another-keyword-field', 'another-keyword-field',
'another-text-field', 'another-text-field',
'yet-another-text-field', 'yet-another-text-field',

View file

@ -90,10 +90,14 @@ export const fetchFieldCandidates = createAsyncThunk(
...selectedKeywordFieldCandidates, ...selectedKeywordFieldCandidates,
...selectedTextFieldCandidates, ...selectedTextFieldCandidates,
]; ];
const fieldFilterSkippedItems = fieldFilterUniqueItems.filter( const initialFieldFilterSkippedItems = fieldFilterUniqueItems.filter(
(d) => !fieldFilterUniqueSelectedItems.includes(d) (d) => !fieldFilterUniqueSelectedItems.includes(d)
); );
const currentFieldFilterSkippedItems = (
thunkApi.getState() as { logRateAnalysisFieldCandidates: FieldCandidatesState }
).logRateAnalysisFieldCandidates.currentFieldFilterSkippedItems;
thunkApi.dispatch( thunkApi.dispatch(
setAllFieldCandidates({ setAllFieldCandidates({
fieldSelectionMessage: getFieldSelectionMessage( fieldSelectionMessage: getFieldSelectionMessage(
@ -102,7 +106,13 @@ export const fetchFieldCandidates = createAsyncThunk(
fieldFilterUniqueSelectedItems.length fieldFilterUniqueSelectedItems.length
), ),
fieldFilterUniqueItems, fieldFilterUniqueItems,
fieldFilterSkippedItems, initialFieldFilterSkippedItems,
// If the currentFieldFilterSkippedItems is null, we're on the first load,
// only then we set the current skipped fields to the initial skipped fields.
currentFieldFilterSkippedItems:
currentFieldFilterSkippedItems === null && initialFieldFilterSkippedItems.length > 0
? initialFieldFilterSkippedItems
: currentFieldFilterSkippedItems,
keywordFieldCandidates, keywordFieldCandidates,
textFieldCandidates, textFieldCandidates,
selectedKeywordFieldCandidates, selectedKeywordFieldCandidates,
@ -116,18 +126,20 @@ export interface FieldCandidatesState {
isLoading: boolean; isLoading: boolean;
fieldSelectionMessage?: string; fieldSelectionMessage?: string;
fieldFilterUniqueItems: string[]; fieldFilterUniqueItems: string[];
fieldFilterSkippedItems: string[]; initialFieldFilterSkippedItems: string[];
currentFieldFilterSkippedItems: string[] | null;
keywordFieldCandidates: string[]; keywordFieldCandidates: string[];
textFieldCandidates: string[]; textFieldCandidates: string[];
selectedKeywordFieldCandidates: string[]; selectedKeywordFieldCandidates: string[];
selectedTextFieldCandidates: string[]; selectedTextFieldCandidates: string[];
} }
function getDefaultState(): FieldCandidatesState { export function getDefaultState(): FieldCandidatesState {
return { return {
isLoading: false, isLoading: false,
fieldFilterUniqueItems: [], fieldFilterUniqueItems: [],
fieldFilterSkippedItems: [], initialFieldFilterSkippedItems: [],
currentFieldFilterSkippedItems: null,
keywordFieldCandidates: [], keywordFieldCandidates: [],
textFieldCandidates: [], textFieldCandidates: [],
selectedKeywordFieldCandidates: [], selectedKeywordFieldCandidates: [],
@ -145,6 +157,12 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({
) => { ) => {
return { ...state, ...action.payload }; return { ...state, ...action.payload };
}, },
setCurrentFieldFilterSkippedItems: (
state: FieldCandidatesState,
action: PayloadAction<string[]>
) => {
return { ...state, currentFieldFilterSkippedItems: action.payload };
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(fetchFieldCandidates.pending, (state) => { builder.addCase(fetchFieldCandidates.pending, (state) => {
@ -157,4 +175,5 @@ export const logRateAnalysisFieldCandidatesSlice = createSlice({
}); });
// Action creators are generated for each case reducer function // Action creators are generated for each case reducer function
export const { setAllFieldCandidates } = logRateAnalysisFieldCandidatesSlice.actions; export const { setAllFieldCandidates, setCurrentFieldFilterSkippedItems } =
logRateAnalysisFieldCandidatesSlice.actions;

View file

@ -6,7 +6,7 @@
*/ */
import type { FC } from 'react'; import type { FC } from 'react';
import React, { useEffect, useState } from 'react'; import React from 'react';
import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { EuiButtonGroup, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
@ -23,6 +23,7 @@ import {
setSkippedColumns, setSkippedColumns,
type LogRateAnalysisResultsTableColumnName, type LogRateAnalysisResultsTableColumnName,
} from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice'; } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_table_slice';
import { setCurrentFieldFilterSkippedItems } from '@kbn/aiops-log-rate-analysis/state/log_rate_analysis_field_candidates_slice';
import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover'; import { ItemFilterPopover as FieldFilterPopover } from './item_filter_popover';
import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover'; import { ItemFilterPopover as ColumnFilterPopover } from './item_filter_popover';
@ -82,13 +83,11 @@ const resultsGroupedOnId = 'aiopsLogRateAnalysisGroupingOn';
export interface LogRateAnalysisOptionsProps { export interface LogRateAnalysisOptionsProps {
foundGroups: boolean; foundGroups: boolean;
growFirstItem?: boolean; growFirstItem?: boolean;
onFieldsFilterChange: (skippedFieldsUpdate: string[]) => void;
} }
export const LogRateAnalysisOptions: FC<LogRateAnalysisOptionsProps> = ({ export const LogRateAnalysisOptions: FC<LogRateAnalysisOptionsProps> = ({
foundGroups, foundGroups,
growFirstItem = false, growFirstItem = false,
onFieldsFilterChange,
}) => { }) => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -96,20 +95,11 @@ export const LogRateAnalysisOptions: FC<LogRateAnalysisOptionsProps> = ({
const { isRunning } = useAppSelector((s) => s.logRateAnalysisStream); const { isRunning } = useAppSelector((s) => s.logRateAnalysisStream);
const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates); const fieldCandidates = useAppSelector((s) => s.logRateAnalysisFieldCandidates);
const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable); const { skippedColumns } = useAppSelector((s) => s.logRateAnalysisTable);
const { fieldFilterUniqueItems, fieldFilterSkippedItems } = fieldCandidates; const { fieldFilterUniqueItems, initialFieldFilterSkippedItems } = fieldCandidates;
const fieldFilterButtonDisabled = const fieldFilterButtonDisabled =
isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0; isRunning || fieldCandidates.isLoading || fieldFilterUniqueItems.length === 0;
const toggleIdSelected = groupResults ? resultsGroupedOnId : resultsGroupedOffId; const toggleIdSelected = groupResults ? resultsGroupedOnId : resultsGroupedOffId;
// null is used as the uninitialized state to identify the first load.
const [skippedFields, setSkippedFields] = useState<string[] | null>(null);
// Set skipped fields only on first load, otherwise we'd overwrite the user's selection.
useEffect(() => {
if (skippedFields === null && fieldFilterSkippedItems.length > 0)
setSkippedFields(fieldFilterSkippedItems);
}, [fieldFilterSkippedItems, skippedFields]);
const onGroupResultsToggle = (optionId: string) => { const onGroupResultsToggle = (optionId: string) => {
dispatch(setGroupResults(optionId === resultsGroupedOnId)); dispatch(setGroupResults(optionId === resultsGroupedOnId));
// When toggling the group switch, clear all row selections // When toggling the group switch, clear all row selections
@ -120,9 +110,8 @@ export const LogRateAnalysisOptions: FC<LogRateAnalysisOptionsProps> = ({
dispatch(setSkippedColumns(columns)); dispatch(setSkippedColumns(columns));
}; };
const onFieldsFilterChangeHandler = (skippedFieldsUpdate: string[]) => { const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => {
setSkippedFields(skippedFieldsUpdate); dispatch(setCurrentFieldFilterSkippedItems(skippedFieldsUpdate));
onFieldsFilterChange(skippedFieldsUpdate);
}; };
// Disable the grouping switch toggle only if no groups were found, // Disable the grouping switch toggle only if no groups were found,
@ -173,8 +162,8 @@ export const LogRateAnalysisOptions: FC<LogRateAnalysisOptionsProps> = ({
popoverButtonTitle={fieldsButton} popoverButtonTitle={fieldsButton}
selectedItemLimit={1} selectedItemLimit={1}
uniqueItemNames={fieldFilterUniqueItems} uniqueItemNames={fieldFilterUniqueItems}
initialSkippedItems={fieldFilterSkippedItems} initialSkippedItems={initialFieldFilterSkippedItems}
onChange={onFieldsFilterChangeHandler} onChange={onFieldsFilterChange}
/> />
</EuiFlexItem> </EuiFlexItem>
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>

View file

@ -135,21 +135,27 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
setEmbeddableOptionsVisible((s) => !s); setEmbeddableOptionsVisible((s) => !s);
}; };
const onFieldsFilterChange = (skippedFieldsUpdate: string[]) => { const { currentFieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } =
fieldCandidates;
useEffect(() => {
if (currentFieldFilterSkippedItems === null) return;
dispatch(resetResults()); dispatch(resetResults());
setOverrides({ setOverrides({
loaded: 0, loaded: 0,
remainingKeywordFieldCandidates: keywordFieldCandidates.filter( remainingKeywordFieldCandidates: keywordFieldCandidates.filter(
(d) => !skippedFieldsUpdate.includes(d) (d) => !currentFieldFilterSkippedItems.includes(d)
), ),
remainingTextFieldCandidates: textFieldCandidates.filter( remainingTextFieldCandidates: textFieldCandidates.filter(
(d) => !skippedFieldsUpdate.includes(d) (d) => !currentFieldFilterSkippedItems.includes(d)
), ),
regroupOnly: false, regroupOnly: false,
}); });
startHandler(true, false); startHandler(true, false);
}; // custom check to trigger on currentFieldFilterSkippedItems change
const { fieldFilterSkippedItems, keywordFieldCandidates, textFieldCandidates } = fieldCandidates; // eslint-disable-next-line react-hooks/exhaustive-deps
}, [currentFieldFilterSkippedItems]);
function cancelHandler() { function cancelHandler() {
abortCtrl.current.abort(); abortCtrl.current.abort();
@ -209,10 +215,12 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
dispatch(resetResults()); dispatch(resetResults());
setOverrides({ setOverrides({
remainingKeywordFieldCandidates: keywordFieldCandidates.filter( remainingKeywordFieldCandidates: keywordFieldCandidates.filter(
(d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) (d) =>
currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d)
), ),
remainingTextFieldCandidates: textFieldCandidates.filter( remainingTextFieldCandidates: textFieldCandidates.filter(
(d) => fieldFilterSkippedItems !== null && fieldFilterSkippedItems.includes(d) (d) =>
currentFieldFilterSkippedItems === null || !currentFieldFilterSkippedItems.includes(d)
), ),
}); });
} }
@ -325,10 +333,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
> >
<> <>
{embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( {embeddingOrigin !== AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && (
<LogRateAnalysisOptions <LogRateAnalysisOptions foundGroups={foundGroups} />
foundGroups={foundGroups}
onFieldsFilterChange={onFieldsFilterChange}
/>
)} )}
{embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && ( {embeddingOrigin === AIOPS_EMBEDDABLE_ORIGIN.DASHBOARD && (
<EuiFlexItem grow={false}> <EuiFlexItem grow={false}>
@ -354,11 +359,7 @@ export const LogRateAnalysisResults: FC<LogRateAnalysisResultsProps> = ({
<> <>
<EuiSpacer size="m" /> <EuiSpacer size="m" />
<EuiFlexGroup alignItems="center" gutterSize="s"> <EuiFlexGroup alignItems="center" gutterSize="s">
<LogRateAnalysisOptions <LogRateAnalysisOptions foundGroups={foundGroups} growFirstItem={true} />
foundGroups={foundGroups}
growFirstItem={true}
onFieldsFilterChange={onFieldsFilterChange}
/>
</EuiFlexGroup> </EuiFlexGroup>
</> </>
)} )}