mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Co-authored-by: Steph Milovic <stephanie.milovic@elastic.co>
This commit is contained in:
parent
c918e7a620
commit
eeec2e7246
10 changed files with 307 additions and 119 deletions
|
@ -6,12 +6,7 @@
|
|||
*/
|
||||
|
||||
import {
|
||||
EuiButton,
|
||||
EuiCallOut,
|
||||
EuiCheckbox,
|
||||
EuiComboBox,
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiForm,
|
||||
EuiOutsideClickDetector,
|
||||
EuiPopover,
|
||||
|
@ -19,7 +14,7 @@ import {
|
|||
EuiSpacer,
|
||||
EuiSuperSelect,
|
||||
} from '@elastic/eui';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { ChangeEventHandler, useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
@ -27,12 +22,12 @@ import { sourcererActions, sourcererModel, sourcererSelectors } from '../../stor
|
|||
import { useDeepEqualSelector } from '../../hooks/use_selector';
|
||||
import { SourcererScopeName } from '../../store/sourcerer/model';
|
||||
import { usePickIndexPatterns } from './use_pick_index_patterns';
|
||||
import { FormRow, PopoverContent, ResetButton, StyledButton, StyledFormRow } from './helpers';
|
||||
import { FormRow, PopoverContent, StyledButton, StyledFormRow } from './helpers';
|
||||
import { TemporarySourcerer } from './temporary';
|
||||
import { UpdateDefaultDataViewModal } from './update_default_data_view_modal';
|
||||
import { useSourcererDataView } from '../../containers/sourcerer';
|
||||
import { useUpdateDataView } from './use_update_data_view';
|
||||
import { Trigger } from './trigger';
|
||||
import { AlertsCheckbox, SaveButtons, SourcererCallout } from './sub_components';
|
||||
|
||||
interface SourcererComponentProps {
|
||||
scope: sourcererModel.SourcererScopeName;
|
||||
|
@ -91,11 +86,12 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
|
|||
kibanaDataViews,
|
||||
missingPatterns,
|
||||
scopeId,
|
||||
selectedDataViewId,
|
||||
selectedPatterns,
|
||||
signalIndexName,
|
||||
});
|
||||
|
||||
const onCheckboxChanged = useCallback(
|
||||
const onCheckboxChanged: ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||
(e) => {
|
||||
setIsOnlyDetectionAlertsChecked(e.target.checked);
|
||||
setDataViewId(defaultDataView.id);
|
||||
|
@ -251,49 +247,35 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
|
|||
<EuiPopoverTitle data-test-subj="sourcerer-title">
|
||||
<>{i18n.SELECT_DATA_VIEW}</>
|
||||
</EuiPopoverTitle>
|
||||
{isOnlyDetectionAlerts && (
|
||||
<EuiCallOut
|
||||
data-test-subj="sourcerer-callout"
|
||||
iconType="iInCircle"
|
||||
size="s"
|
||||
title={isTimelineSourcerer ? i18n.CALL_OUT_TIMELINE_TITLE : i18n.CALL_OUT_TITLE}
|
||||
/>
|
||||
)}
|
||||
<SourcererCallout
|
||||
isOnlyDetectionAlerts={isOnlyDetectionAlerts}
|
||||
title={isTimelineSourcerer ? i18n.CALL_OUT_TIMELINE_TITLE : i18n.CALL_OUT_TITLE}
|
||||
/>
|
||||
<EuiSpacer size="s" />
|
||||
{isModified === 'deprecated' || isModified === 'missingPatterns' ? (
|
||||
<>
|
||||
<TemporarySourcerer
|
||||
activePatterns={activePatterns}
|
||||
indicesExist={indicesExist}
|
||||
isModified={isModified}
|
||||
missingPatterns={missingPatterns}
|
||||
onClick={resetDataSources}
|
||||
onClose={setPopoverIsOpenCb}
|
||||
onUpdate={isModified === 'deprecated' ? onUpdateDeprecated : onUpdateDataView}
|
||||
selectedPatterns={selectedPatterns}
|
||||
/>
|
||||
<UpdateDefaultDataViewModal
|
||||
isShowing={isShowingUpdateModal}
|
||||
missingPatterns={missingPatterns}
|
||||
onClose={() => setIsShowingUpdateModal(false)}
|
||||
onContinue={onContinueUpdateDeprecated}
|
||||
onUpdate={onUpdateDataView}
|
||||
/>
|
||||
</>
|
||||
{(dataViewId === null && isModified === 'deprecated') ||
|
||||
isModified === 'missingPatterns' ? (
|
||||
<TemporarySourcerer
|
||||
activePatterns={activePatterns}
|
||||
indicesExist={indicesExist}
|
||||
isModified={isModified}
|
||||
isShowingUpdateModal={isShowingUpdateModal}
|
||||
missingPatterns={missingPatterns}
|
||||
onContinueWithoutUpdate={onContinueUpdateDeprecated}
|
||||
onDismiss={setPopoverIsOpenCb}
|
||||
onDismissModal={() => setIsShowingUpdateModal(false)}
|
||||
onReset={resetDataSources}
|
||||
onUpdateStepOne={isModified === 'deprecated' ? onUpdateDeprecated : onUpdateDataView}
|
||||
onUpdateStepTwo={onUpdateDataView}
|
||||
selectedPatterns={selectedPatterns}
|
||||
/>
|
||||
) : (
|
||||
<EuiForm component="form">
|
||||
<>
|
||||
{isTimelineSourcerer && (
|
||||
<StyledFormRow>
|
||||
<EuiCheckbox
|
||||
checked={isOnlyDetectionAlertsChecked}
|
||||
data-test-subj="sourcerer-alert-only-checkbox"
|
||||
id="sourcerer-alert-only-checkbox"
|
||||
label={i18n.ALERTS_CHECKBOX_LABEL}
|
||||
onChange={onCheckboxChanged}
|
||||
/>
|
||||
</StyledFormRow>
|
||||
)}
|
||||
<AlertsCheckbox
|
||||
isShow={isTimelineSourcerer}
|
||||
checked={isOnlyDetectionAlertsChecked}
|
||||
onChange={onCheckboxChanged}
|
||||
/>
|
||||
{dataViewId && (
|
||||
<StyledFormRow label={i18n.INDEX_PATTERNS_CHOOSE_DATA_VIEW_LABEL}>
|
||||
<EuiSuperSelect
|
||||
|
@ -335,35 +317,12 @@ export const Sourcerer = React.memo<SourcererComponentProps>(({ scope: scopeId }
|
|||
/>
|
||||
</FormRow>
|
||||
|
||||
{!isDetectionsSourcerer && (
|
||||
<StyledFormRow>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ResetButton
|
||||
aria-label={i18n.INDEX_PATTERNS_RESET}
|
||||
data-test-subj="sourcerer-reset"
|
||||
flush="left"
|
||||
onClick={resetDataSources}
|
||||
title={i18n.INDEX_PATTERNS_RESET}
|
||||
>
|
||||
{i18n.INDEX_PATTERNS_RESET}
|
||||
</ResetButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={handleSaveIndices}
|
||||
disabled={selectedOptions.length === 0}
|
||||
data-test-subj="sourcerer-save"
|
||||
fill
|
||||
fullWidth
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE_INDEX_PATTERNS}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StyledFormRow>
|
||||
)}
|
||||
<SaveButtons
|
||||
disableSave={selectedOptions.length === 0}
|
||||
isShow={!isDetectionsSourcerer}
|
||||
onReset={resetDataSources}
|
||||
onSave={handleSaveIndices}
|
||||
/>
|
||||
</>
|
||||
<EuiSpacer size="s" />
|
||||
</EuiForm>
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { ChangeEventHandler } from 'react';
|
||||
import { EuiButton, EuiCallOut, EuiCheckbox, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { ResetButton, StyledFormRow } from './helpers';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface SourcererCalloutProps {
|
||||
isOnlyDetectionAlerts: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
export const SourcererCallout = React.memo<SourcererCalloutProps>(
|
||||
({ isOnlyDetectionAlerts, title }) =>
|
||||
isOnlyDetectionAlerts ? (
|
||||
<EuiCallOut data-test-subj="sourcerer-callout" iconType="iInCircle" size="s" title={title} />
|
||||
) : null
|
||||
);
|
||||
|
||||
SourcererCallout.displayName = 'SourcererCallout';
|
||||
|
||||
interface AlertsCheckboxProps {
|
||||
checked: boolean;
|
||||
isShow: boolean;
|
||||
onChange: ChangeEventHandler<HTMLInputElement>;
|
||||
}
|
||||
|
||||
export const AlertsCheckbox = React.memo<AlertsCheckboxProps>(({ onChange, checked, isShow }) =>
|
||||
isShow ? (
|
||||
<StyledFormRow>
|
||||
<EuiCheckbox
|
||||
checked={checked}
|
||||
data-test-subj="sourcerer-alert-only-checkbox"
|
||||
id="sourcerer-alert-only-checkbox"
|
||||
label={i18n.ALERTS_CHECKBOX_LABEL}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</StyledFormRow>
|
||||
) : null
|
||||
);
|
||||
|
||||
AlertsCheckbox.displayName = 'AlertsCheckbox';
|
||||
|
||||
interface SaveButtonsProps {
|
||||
disableSave: boolean;
|
||||
isShow: boolean;
|
||||
onReset: () => void;
|
||||
onSave: () => void;
|
||||
}
|
||||
|
||||
export const SaveButtons = React.memo<SaveButtonsProps>(
|
||||
({ disableSave, isShow, onReset, onSave }) =>
|
||||
isShow ? (
|
||||
<StyledFormRow>
|
||||
<EuiFlexGroup alignItems="center" justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<ResetButton
|
||||
aria-label={i18n.INDEX_PATTERNS_RESET}
|
||||
data-test-subj="sourcerer-reset"
|
||||
flush="left"
|
||||
onClick={onReset}
|
||||
title={i18n.INDEX_PATTERNS_RESET}
|
||||
>
|
||||
{i18n.INDEX_PATTERNS_RESET}
|
||||
</ResetButton>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
onClick={onSave}
|
||||
disabled={disableSave}
|
||||
data-test-subj="sourcerer-save"
|
||||
fill
|
||||
fullWidth
|
||||
size="s"
|
||||
>
|
||||
{i18n.SAVE_INDEX_PATTERNS}
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</StyledFormRow>
|
||||
) : null
|
||||
);
|
||||
|
||||
SaveButtons.displayName = 'SaveButtons';
|
|
@ -21,14 +21,15 @@ import {
|
|||
import React, { useMemo } from 'react';
|
||||
import * as i18n from './translations';
|
||||
import { Blockquote, ResetButton } from './helpers';
|
||||
import { UpdateDefaultDataViewModal } from './update_default_data_view_modal';
|
||||
|
||||
interface Props {
|
||||
activePatterns?: string[];
|
||||
indicesExist: boolean;
|
||||
isModified: 'deprecated' | 'missingPatterns';
|
||||
missingPatterns: string[];
|
||||
onClick: () => void;
|
||||
onClose: () => void;
|
||||
onDismiss: () => void;
|
||||
onReset: () => void;
|
||||
onUpdate: () => void;
|
||||
selectedPatterns: string[];
|
||||
}
|
||||
|
@ -44,13 +45,13 @@ const translations = {
|
|||
},
|
||||
};
|
||||
|
||||
export const TemporarySourcerer = React.memo<Props>(
|
||||
export const TemporarySourcererComp = React.memo<Props>(
|
||||
({
|
||||
activePatterns,
|
||||
indicesExist,
|
||||
isModified,
|
||||
onClose,
|
||||
onClick,
|
||||
onDismiss,
|
||||
onReset,
|
||||
onUpdate,
|
||||
selectedPatterns,
|
||||
missingPatterns,
|
||||
|
@ -141,7 +142,7 @@ export const TemporarySourcerer = React.memo<Props>(
|
|||
id="xpack.securitySolution.indexPatterns.toggleToNewSourcerer"
|
||||
defaultMessage="We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can recreate your temporary data view with the new data view selector. You can also manually select a data view {link}."
|
||||
values={{
|
||||
link: <EuiLink onClick={onClick}>{i18n.TOGGLE_TO_NEW_SOURCERER}</EuiLink>,
|
||||
link: <EuiLink onClick={onReset}>{i18n.TOGGLE_TO_NEW_SOURCERER}</EuiLink>,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -158,7 +159,7 @@ export const TemporarySourcerer = React.memo<Props>(
|
|||
id="xpack.securitySolution.indexPatterns.missingPatterns.description"
|
||||
defaultMessage="We have preserved your timeline by creating a temporary data view. If you'd like to modify your data, we can add the missing index patterns to the Security Data View. You can also manually select a data view {link}."
|
||||
values={{
|
||||
link: <EuiLink onClick={onClick}>{i18n.TOGGLE_TO_NEW_SOURCERER}</EuiLink>,
|
||||
link: <EuiLink onClick={onReset}>{i18n.TOGGLE_TO_NEW_SOURCERER}</EuiLink>,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
|
@ -172,7 +173,7 @@ export const TemporarySourcerer = React.memo<Props>(
|
|||
aria-label={i18n.INDEX_PATTERNS_CLOSE}
|
||||
data-test-subj="sourcerer-deprecated-close"
|
||||
flush="left"
|
||||
onClick={onClose}
|
||||
onClick={onDismiss}
|
||||
title={i18n.INDEX_PATTERNS_CLOSE}
|
||||
>
|
||||
{i18n.INDEX_PATTERNS_CLOSE}
|
||||
|
@ -185,4 +186,58 @@ export const TemporarySourcerer = React.memo<Props>(
|
|||
}
|
||||
);
|
||||
|
||||
TemporarySourcererComp.displayName = 'TemporarySourcererComp';
|
||||
|
||||
interface TemporarySourcererProps {
|
||||
activePatterns?: string[];
|
||||
indicesExist: boolean;
|
||||
isModified: 'deprecated' | 'missingPatterns';
|
||||
isShowingUpdateModal: boolean;
|
||||
missingPatterns: string[];
|
||||
onContinueWithoutUpdate: () => void;
|
||||
onDismiss: () => void;
|
||||
onDismissModal: () => void;
|
||||
onReset: () => void;
|
||||
onUpdateStepOne: () => void;
|
||||
onUpdateStepTwo: () => void;
|
||||
selectedPatterns: string[];
|
||||
}
|
||||
|
||||
export const TemporarySourcerer = React.memo<TemporarySourcererProps>(
|
||||
({
|
||||
activePatterns,
|
||||
indicesExist,
|
||||
isModified,
|
||||
missingPatterns,
|
||||
onContinueWithoutUpdate,
|
||||
onDismiss,
|
||||
onReset,
|
||||
onUpdateStepOne,
|
||||
onUpdateStepTwo,
|
||||
selectedPatterns,
|
||||
isShowingUpdateModal,
|
||||
onDismissModal,
|
||||
}) => (
|
||||
<>
|
||||
<TemporarySourcererComp
|
||||
activePatterns={activePatterns}
|
||||
indicesExist={indicesExist}
|
||||
isModified={isModified}
|
||||
missingPatterns={missingPatterns}
|
||||
onDismiss={onDismiss}
|
||||
onReset={onReset}
|
||||
onUpdate={onUpdateStepOne}
|
||||
selectedPatterns={selectedPatterns}
|
||||
/>
|
||||
<UpdateDefaultDataViewModal
|
||||
isShowing={isShowingUpdateModal}
|
||||
missingPatterns={missingPatterns}
|
||||
onDismissModal={onDismissModal}
|
||||
onContinue={onContinueWithoutUpdate}
|
||||
onUpdate={onUpdateStepTwo}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
);
|
||||
|
||||
TemporarySourcerer.displayName = 'TemporarySourcerer';
|
||||
|
|
|
@ -25,7 +25,7 @@ import { Blockquote, ResetButton } from './helpers';
|
|||
interface Props {
|
||||
isShowing: boolean;
|
||||
missingPatterns: string[];
|
||||
onClose: () => void;
|
||||
onDismissModal: () => void;
|
||||
onContinue: () => void;
|
||||
onUpdate: () => void;
|
||||
}
|
||||
|
@ -41,9 +41,9 @@ const MyEuiModal = styled(EuiModal)`
|
|||
`;
|
||||
|
||||
export const UpdateDefaultDataViewModal = React.memo<Props>(
|
||||
({ isShowing, onClose, onContinue, onUpdate, missingPatterns }) =>
|
||||
({ isShowing, onDismissModal, onContinue, onUpdate, missingPatterns }) =>
|
||||
isShowing ? (
|
||||
<MyEuiModal onClose={onClose} data-test-subj="sourcerer-update-data-view-modal">
|
||||
<MyEuiModal onClose={onDismissModal} data-test-subj="sourcerer-update-data-view-modal">
|
||||
<EuiModalHeader>
|
||||
<EuiModalHeaderTitle>
|
||||
<h1>{i18n.UPDATE_SECURITY_DATA_VIEW}</h1>
|
||||
|
|
|
@ -19,6 +19,7 @@ interface UsePickIndexPatternsProps {
|
|||
kibanaDataViews: sourcererModel.SourcererModel['kibanaDataViews'];
|
||||
missingPatterns: string[];
|
||||
scopeId: sourcererModel.SourcererScopeName;
|
||||
selectedDataViewId: string | null;
|
||||
selectedPatterns: string[];
|
||||
signalIndexName: string | null;
|
||||
}
|
||||
|
@ -49,6 +50,7 @@ export const usePickIndexPatterns = ({
|
|||
kibanaDataViews,
|
||||
missingPatterns,
|
||||
scopeId,
|
||||
selectedDataViewId,
|
||||
selectedPatterns,
|
||||
signalIndexName,
|
||||
}: UsePickIndexPatternsProps): UsePickIndexPatterns => {
|
||||
|
@ -155,11 +157,11 @@ export const usePickIndexPatterns = ({
|
|||
// when scope updates, check modified to set/remove alerts label
|
||||
useEffect(() => {
|
||||
onSetIsModified(
|
||||
selectedOptions.map(({ label }) => label),
|
||||
dataViewId
|
||||
selectedPatterns.map((pattern) => pattern),
|
||||
selectedDataViewId
|
||||
);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [dataViewId, missingPatterns, scopeId, selectedOptions]);
|
||||
}, [selectedDataViewId, missingPatterns, scopeId, selectedPatterns]);
|
||||
|
||||
const onChangeCombo = useCallback((newSelectedOptions) => {
|
||||
setSelectedOptions(newSelectedOptions);
|
||||
|
|
|
@ -39,8 +39,8 @@ const getEsFields = memoizeOne(
|
|||
|
||||
export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string) => void } => {
|
||||
const { data } = useKibana().services;
|
||||
const abortCtrl = useRef(new AbortController());
|
||||
const searchSubscription$ = useRef(new Subscription());
|
||||
const abortCtrl = useRef<Record<string, AbortController>>({});
|
||||
const searchSubscription$ = useRef<Record<string, Subscription>>({});
|
||||
const dispatch = useDispatch();
|
||||
const { addError, addWarning } = useAppToasts();
|
||||
|
||||
|
@ -54,16 +54,19 @@ export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string)
|
|||
const indexFieldsSearch = useCallback(
|
||||
(selectedDataViewId: string) => {
|
||||
const asyncSearch = async () => {
|
||||
abortCtrl.current = new AbortController();
|
||||
abortCtrl.current = {
|
||||
...abortCtrl.current,
|
||||
[selectedDataViewId]: new AbortController(),
|
||||
};
|
||||
setLoading({ id: selectedDataViewId, loading: true });
|
||||
searchSubscription$.current = data.search
|
||||
const subscription = data.search
|
||||
.search<IndexFieldsStrategyRequest<'dataView'>, IndexFieldsStrategyResponse>(
|
||||
{
|
||||
dataViewId: selectedDataViewId,
|
||||
onlyCheckIfIndicesExist: false,
|
||||
},
|
||||
{
|
||||
abortSignal: abortCtrl.current.signal,
|
||||
abortSignal: abortCtrl.current[selectedDataViewId].signal,
|
||||
strategy: 'indexFields',
|
||||
}
|
||||
)
|
||||
|
@ -82,11 +85,15 @@ export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string)
|
|||
runtimeMappings: response.runtimeMappings,
|
||||
})
|
||||
);
|
||||
searchSubscription$.current.unsubscribe();
|
||||
if (searchSubscription$.current[selectedDataViewId]) {
|
||||
searchSubscription$.current[selectedDataViewId].unsubscribe();
|
||||
}
|
||||
} else if (isErrorResponse(response)) {
|
||||
setLoading({ id: selectedDataViewId, loading: false });
|
||||
addWarning(i18n.ERROR_BEAT_FIELDS);
|
||||
searchSubscription$.current.unsubscribe();
|
||||
if (searchSubscription$.current[selectedDataViewId]) {
|
||||
searchSubscription$.current[selectedDataViewId].unsubscribe();
|
||||
}
|
||||
}
|
||||
},
|
||||
error: (msg) => {
|
||||
|
@ -98,12 +105,23 @@ export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string)
|
|||
addError(msg, {
|
||||
title: i18n.FAIL_BEAT_FIELDS,
|
||||
});
|
||||
searchSubscription$.current.unsubscribe();
|
||||
if (searchSubscription$.current[selectedDataViewId]) {
|
||||
searchSubscription$.current[selectedDataViewId].unsubscribe();
|
||||
}
|
||||
},
|
||||
});
|
||||
searchSubscription$.current = {
|
||||
...searchSubscription$.current,
|
||||
[selectedDataViewId]: subscription,
|
||||
};
|
||||
};
|
||||
searchSubscription$.current.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
if (searchSubscription$.current[selectedDataViewId] != null) {
|
||||
searchSubscription$.current[selectedDataViewId].unsubscribe();
|
||||
}
|
||||
|
||||
if (abortCtrl.current[selectedDataViewId] != null) {
|
||||
abortCtrl.current[selectedDataViewId].abort();
|
||||
}
|
||||
asyncSearch();
|
||||
},
|
||||
[addError, addWarning, data.search, dispatch, setLoading]
|
||||
|
@ -111,8 +129,10 @@ export const useDataView = (): { indexFieldsSearch: (selectedDataViewId: string)
|
|||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
searchSubscription$.current.unsubscribe();
|
||||
abortCtrl.current.abort();
|
||||
Object.values(searchSubscription$.current).forEach((subscription) =>
|
||||
subscription.unsubscribe()
|
||||
);
|
||||
Object.values(abortCtrl.current).forEach((signal) => signal.abort());
|
||||
};
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -52,13 +52,15 @@ jest.mock('../../utils/route/use_route_spy', () => ({
|
|||
useRouteSpy: () => [mockRouteSpy],
|
||||
}));
|
||||
|
||||
const mockSearch = jest.fn();
|
||||
|
||||
jest.mock('../../lib/kibana', () => ({
|
||||
useToasts: jest.fn().mockReturnValue({
|
||||
useToasts: () => ({
|
||||
addError: jest.fn(),
|
||||
addSuccess: jest.fn(),
|
||||
addWarning: jest.fn(),
|
||||
}),
|
||||
useKibana: jest.fn().mockReturnValue({
|
||||
useKibana: () => ({
|
||||
services: {
|
||||
application: {
|
||||
capabilities: {
|
||||
|
@ -72,7 +74,7 @@ jest.mock('../../lib/kibana', () => ({
|
|||
getTitles: jest.fn().mockImplementation(() => Promise.resolve(mockPatterns)),
|
||||
},
|
||||
search: {
|
||||
search: jest.fn().mockImplementation(() => ({
|
||||
search: mockSearch.mockImplementation(() => ({
|
||||
subscribe: jest.fn().mockImplementation(() => ({
|
||||
error: jest.fn(),
|
||||
next: jest.fn(),
|
||||
|
@ -188,6 +190,8 @@ describe('Sourcerer Hooks', () => {
|
|||
type: 'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING',
|
||||
payload: { loading: false },
|
||||
});
|
||||
expect(mockDispatch).toHaveBeenCalledTimes(7);
|
||||
expect(mockSearch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -216,6 +220,48 @@ describe('Sourcerer Hooks', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
it('index field search is not repeated when default and timeline have same dataViewId', async () => {
|
||||
await act(async () => {
|
||||
const { rerender, waitForNextUpdate } = renderHook<string, void>(() => useInitSourcerer(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
rerender();
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
it('index field search called twice when default and timeline have different dataViewId', async () => {
|
||||
store = createStore(
|
||||
{
|
||||
...mockGlobalState,
|
||||
sourcerer: {
|
||||
...mockGlobalState.sourcerer,
|
||||
sourcererScopes: {
|
||||
...mockGlobalState.sourcerer.sourcererScopes,
|
||||
[SourcererScopeName.timeline]: {
|
||||
...mockGlobalState.sourcerer.sourcererScopes[SourcererScopeName.timeline],
|
||||
selectedDataViewId: 'different-id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
SUB_PLUGINS_REDUCER,
|
||||
kibanaObservable,
|
||||
storage
|
||||
);
|
||||
await act(async () => {
|
||||
const { rerender, waitForNextUpdate } = renderHook<string, void>(() => useInitSourcerer(), {
|
||||
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
|
||||
});
|
||||
await waitForNextUpdate();
|
||||
rerender();
|
||||
await waitFor(() => {
|
||||
expect(mockSearch).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useSourcererDataView', () => {
|
||||
it('Should exclude elastic cloud alias when selected patterns include "logs-*" as an alias', async () => {
|
||||
|
|
|
@ -84,8 +84,15 @@ export const useInitSourcerer = (
|
|||
);
|
||||
const { indexFieldsSearch } = useDataView();
|
||||
|
||||
const searchedIds = useRef<string[]>([]);
|
||||
useEffect(
|
||||
() => activeDataViewIds.forEach((id) => id != null && id.length > 0 && indexFieldsSearch(id)),
|
||||
() =>
|
||||
activeDataViewIds.forEach((id) => {
|
||||
if (id != null && id.length > 0 && !searchedIds.current.includes(id)) {
|
||||
searchedIds.current = [...searchedIds.current, id];
|
||||
indexFieldsSearch(id);
|
||||
}
|
||||
}),
|
||||
[activeDataViewIds, indexFieldsSearch]
|
||||
);
|
||||
|
||||
|
@ -180,28 +187,33 @@ export const useInitSourcerer = (
|
|||
},
|
||||
[defaultDataView.title, dispatch, indexFieldsSearch, addError]
|
||||
);
|
||||
useEffect(() => {
|
||||
|
||||
const onSignalIndexUpdated = useCallback(() => {
|
||||
if (
|
||||
!loadingSignalIndex &&
|
||||
signalIndexName != null &&
|
||||
signalIndexNameSourcerer == null &&
|
||||
defaultDataView.id.length > 0
|
||||
) {
|
||||
// update signal name also updates sourcerer
|
||||
// we hit this the first time signal index is created
|
||||
updateSourcererDataView(signalIndexName);
|
||||
dispatch(sourcererActions.setSignalIndexName({ signalIndexName }));
|
||||
}
|
||||
}, [
|
||||
defaultDataView.id,
|
||||
defaultDataView.id.length,
|
||||
dispatch,
|
||||
indexFieldsSearch,
|
||||
isSignalIndexExists,
|
||||
loadingSignalIndex,
|
||||
signalIndexName,
|
||||
signalIndexNameSourcerer,
|
||||
updateSourcererDataView,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
onSignalIndexUpdated();
|
||||
// because we only want onSignalIndexUpdated to run when signalIndexName updates,
|
||||
// but we want to know about the updates from the dependencies of onSignalIndexUpdated
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [signalIndexName]);
|
||||
|
||||
// Related to the detection page
|
||||
useEffect(() => {
|
||||
if (
|
||||
|
|
|
@ -282,13 +282,13 @@ export const EqlTabContentComponent: React.FC<Props> = ({
|
|||
setFullScreen={setTimelineFullScreen}
|
||||
/>
|
||||
)}
|
||||
<DatePicker grow={1}>
|
||||
<DatePicker grow={10}>
|
||||
<SuperDatePicker id="timeline" timelineId={timelineId} />
|
||||
</DatePicker>
|
||||
<EuiFlexItem grow={false}>
|
||||
<TimelineDatePickerLock />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexItem grow={1}>
|
||||
{activeTab === TimelineTabs.eql && (
|
||||
<Sourcerer scope={SourcererScopeName.timeline} />
|
||||
)}
|
||||
|
|
|
@ -120,9 +120,14 @@ const DatePicker = styled(EuiFlexItem)`
|
|||
width: auto;
|
||||
}
|
||||
`;
|
||||
|
||||
DatePicker.displayName = 'DatePicker';
|
||||
|
||||
const SourcererFlex = styled(EuiFlexItem)`
|
||||
align-items: flex-end;
|
||||
`;
|
||||
|
||||
SourcererFlex.displayName = 'SourcererFlex';
|
||||
|
||||
const VerticalRule = styled.div`
|
||||
width: 2px;
|
||||
height: 100%;
|
||||
|
@ -355,7 +360,7 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
setFullScreen={setTimelineFullScreen}
|
||||
/>
|
||||
)}
|
||||
<DatePicker grow={1}>
|
||||
<DatePicker grow={10}>
|
||||
<SuperDatePicker
|
||||
id="timeline"
|
||||
timelineId={timelineId}
|
||||
|
@ -365,11 +370,11 @@ export const QueryTabContentComponent: React.FC<Props> = ({
|
|||
<EuiFlexItem grow={false}>
|
||||
<TimelineDatePickerLock />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<SourcererFlex grow={1}>
|
||||
{activeTab === TimelineTabs.query && (
|
||||
<Sourcerer scope={SourcererScopeName.timeline} />
|
||||
)}
|
||||
</EuiFlexItem>
|
||||
</SourcererFlex>
|
||||
</EuiFlexGroup>
|
||||
<TimelineHeaderContainer data-test-subj="timelineHeader">
|
||||
<TimelineHeader
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue