mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
* fix timeline persistence against url and events viewer * fix reset fields browser for events view * fix inspect event * review I * review II
This commit is contained in:
parent
ab56606513
commit
a68c93de60
25 changed files with 379 additions and 876 deletions
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
/** The `All Hosts` widget on the `Hosts` page */
|
||||
export const ALL_HOSTS_WIDGET = '[data-test-subj="all-hosts"]';
|
||||
export const ALL_HOSTS_WIDGET = '[data-test-subj="all-hosts-false"]';
|
||||
|
||||
/** A single draggable host in the `All Hosts` widget on the `Hosts` page */
|
||||
export const ALL_HOSTS_WIDGET_HOST = '[data-react-beautiful-dnd-drag-handle]';
|
||||
|
|
|
@ -59,5 +59,5 @@ export const DATE_PICKER_ABSOLUTE_INPUT = '[data-test-subj="superDatePickerAbsol
|
|||
export const KQL_INPUT = '[data-test-subj="kqlInput"]';
|
||||
export const TIMELINE_TITLE = '[data-test-subj="timeline-title"]';
|
||||
|
||||
export const HOST_DETAIL_SIEM_KIBANA = '[data-test-subj="all-hosts"] a.euiLink';
|
||||
export const HOST_DETAIL_SIEM_KIBANA = '[data-test-subj="all-hosts-false"] a.euiLink';
|
||||
export const BREADCRUMBS = '[data-test-subj="breadcrumbs"] a';
|
||||
|
|
|
@ -210,7 +210,7 @@ describe('url state', () => {
|
|||
it('sets KQL in host page and detail page and check if href match on breadcrumb, tabs and subTabs', () => {
|
||||
loginAndWaitForPage(ABSOLUTE_DATE_RANGE.urlHost);
|
||||
cy.get(KQL_INPUT, { timeout: 5000 }).type('host.name: "siem-kibana" {enter}');
|
||||
cy.get(NAVIGATION_HOSTS_ALL_HOSTS)
|
||||
cy.get(NAVIGATION_HOSTS_ALL_HOSTS, { timeout: 5000 })
|
||||
.first()
|
||||
.click({ force: true });
|
||||
waitForAllHostsWidget();
|
||||
|
|
|
@ -11,7 +11,7 @@ import { ActionCreator } from 'typescript-fsa';
|
|||
|
||||
import { WithSource } from '../../containers/source';
|
||||
import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store';
|
||||
import { timelineActions } from '../../store/actions';
|
||||
import { timelineActions, inputsActions } from '../../store/actions';
|
||||
import { KqlMode, TimelineModel } from '../../store/timeline/model';
|
||||
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
|
||||
import { DataProvider } from '../timeline/data_providers/data_provider';
|
||||
|
@ -19,6 +19,7 @@ import { Sort } from '../timeline/body/sort';
|
|||
import { OnChangeItemsPerPage } from '../timeline/events';
|
||||
|
||||
import { EventsViewer } from './events_viewer';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
|
||||
export interface OwnProps {
|
||||
end: number;
|
||||
|
@ -43,6 +44,12 @@ interface DispatchProps {
|
|||
createTimeline: ActionCreator<{
|
||||
id: string;
|
||||
columns: ColumnHeader[];
|
||||
itemsPerPage?: number;
|
||||
sort?: Sort;
|
||||
}>;
|
||||
deleteEventQuery: ActionCreator<{
|
||||
id: string;
|
||||
inputId: InputsModelId;
|
||||
}>;
|
||||
removeColumn: ActionCreator<{
|
||||
id: string;
|
||||
|
@ -66,6 +73,7 @@ const StatefulEventsViewerComponent = React.memo<Props>(
|
|||
createTimeline,
|
||||
columns,
|
||||
dataProviders,
|
||||
deleteEventQuery,
|
||||
end,
|
||||
id,
|
||||
isLive,
|
||||
|
@ -83,8 +91,11 @@ const StatefulEventsViewerComponent = React.memo<Props>(
|
|||
|
||||
useEffect(() => {
|
||||
if (createTimeline != null) {
|
||||
createTimeline({ id, columns });
|
||||
createTimeline({ id, columns, sort, itemsPerPage });
|
||||
}
|
||||
return () => {
|
||||
deleteEventQuery({ id, inputId: 'global' });
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage =>
|
||||
|
@ -180,6 +191,7 @@ export const StatefulEventsViewer = connect(
|
|||
makeMapStateToProps,
|
||||
{
|
||||
createTimeline: timelineActions.createTimeline,
|
||||
deleteEventQuery: inputsActions.deleteOneQuery,
|
||||
updateItemsPerPage: timelineActions.updateItemsPerPage,
|
||||
updateSort: timelineActions.updateSort,
|
||||
removeColumn: timelineActions.removeColumn,
|
||||
|
|
|
@ -45,7 +45,13 @@ PanesFlexGroup.displayName = 'PanesFlexGroup';
|
|||
|
||||
type Props = Pick<
|
||||
FieldBrowserProps,
|
||||
'browserFields' | 'height' | 'onFieldSelected' | 'onUpdateColumns' | 'timelineId' | 'width'
|
||||
| 'browserFields'
|
||||
| 'isEventViewer'
|
||||
| 'height'
|
||||
| 'onFieldSelected'
|
||||
| 'onUpdateColumns'
|
||||
| 'timelineId'
|
||||
| 'width'
|
||||
> & {
|
||||
/**
|
||||
* The current timeline column headers
|
||||
|
@ -112,6 +118,7 @@ export class FieldsBrowser extends React.PureComponent<Props> {
|
|||
browserFields,
|
||||
filteredBrowserFields,
|
||||
searchInput,
|
||||
isEventViewer,
|
||||
isSearching,
|
||||
onCategorySelected,
|
||||
onFieldSelected,
|
||||
|
@ -133,6 +140,7 @@ export class FieldsBrowser extends React.PureComponent<Props> {
|
|||
<Header
|
||||
data-test-subj="header"
|
||||
filteredBrowserFields={filteredBrowserFields}
|
||||
isEventViewer={isEventViewer}
|
||||
isSearching={isSearching}
|
||||
onOutsideClick={onOutsideClick}
|
||||
onSearchInputChange={this.onInputChange}
|
||||
|
|
|
@ -17,6 +17,7 @@ import { pure } from 'recompose';
|
|||
import styled from 'styled-components';
|
||||
|
||||
import { BrowserFields } from '../../containers/source';
|
||||
import { defaultHeaders as eventsDefaultHeaders } from '../events_viewer/default_headers';
|
||||
import { defaultHeaders } from '../timeline/body/column_headers/default_headers';
|
||||
import { OnUpdateColumns } from '../timeline/events';
|
||||
|
||||
|
@ -55,6 +56,7 @@ SearchContainer.displayName = 'SearchContainer';
|
|||
|
||||
interface Props {
|
||||
filteredBrowserFields: BrowserFields;
|
||||
isEventViewer?: boolean;
|
||||
isSearching: boolean;
|
||||
onOutsideClick: () => void;
|
||||
onSearchInputChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
|
@ -91,39 +93,37 @@ const CountRow = pure<Pick<Props, 'filteredBrowserFields'>>(({ filteredBrowserFi
|
|||
|
||||
CountRow.displayName = 'CountRow';
|
||||
|
||||
const TitleRow = pure<{ onOutsideClick: () => void; onUpdateColumns: OnUpdateColumns }>(
|
||||
({ onOutsideClick, onUpdateColumns }) => (
|
||||
<EuiFlexGroup
|
||||
alignItems="center"
|
||||
justifyContent="spaceBetween"
|
||||
direction="row"
|
||||
gutterSize="none"
|
||||
>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle data-test-subj="field-browser-title" size="s">
|
||||
<h2>{i18n.CUSTOMIZE_COLUMNS}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
const TitleRow = pure<{
|
||||
isEventViewer?: boolean;
|
||||
onOutsideClick: () => void;
|
||||
onUpdateColumns: OnUpdateColumns;
|
||||
}>(({ isEventViewer, onOutsideClick, onUpdateColumns }) => (
|
||||
<EuiFlexGroup alignItems="center" justifyContent="spaceBetween" direction="row" gutterSize="none">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiTitle data-test-subj="field-browser-title" size="s">
|
||||
<h2>{i18n.CUSTOMIZE_COLUMNS}</h2>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="reset-fields"
|
||||
onClick={() => {
|
||||
onUpdateColumns(defaultHeaders);
|
||||
onOutsideClick();
|
||||
}}
|
||||
>
|
||||
{i18n.RESET_FIELDS}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
)
|
||||
);
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
data-test-subj="reset-fields"
|
||||
onClick={() => {
|
||||
onUpdateColumns(isEventViewer ? eventsDefaultHeaders : defaultHeaders);
|
||||
onOutsideClick();
|
||||
}}
|
||||
>
|
||||
{i18n.RESET_FIELDS}
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
));
|
||||
|
||||
TitleRow.displayName = 'TitleRow';
|
||||
|
||||
export const Header = pure<Props>(
|
||||
({
|
||||
isEventViewer,
|
||||
isSearching,
|
||||
filteredBrowserFields,
|
||||
onOutsideClick,
|
||||
|
@ -133,7 +133,11 @@ export const Header = pure<Props>(
|
|||
timelineId,
|
||||
}) => (
|
||||
<HeaderContainer>
|
||||
<TitleRow onUpdateColumns={onUpdateColumns} onOutsideClick={onOutsideClick} />
|
||||
<TitleRow
|
||||
isEventViewer={isEventViewer}
|
||||
onUpdateColumns={onUpdateColumns}
|
||||
onOutsideClick={onOutsideClick}
|
||||
/>
|
||||
<SearchContainer>
|
||||
<EuiFieldSearch
|
||||
className={getFieldBrowserSearchInputClassName(timelineId)}
|
||||
|
|
|
@ -159,6 +159,7 @@ export class StatefulFieldsBrowserComponent extends React.PureComponent<
|
|||
: browserFieldsWithDefaultCategory
|
||||
}
|
||||
height={height}
|
||||
isEventViewer={isEventViewer}
|
||||
isSearching={isSearching}
|
||||
onCategorySelected={this.updateSelectedCategoryId}
|
||||
onFieldSelected={onFieldSelected}
|
||||
|
|
|
@ -14,6 +14,7 @@ import { TimelineResult, GetOneTimeline, NoteResult } from '../../graphql/types'
|
|||
import { addNotes as dispatchAddNotes } from '../../store/app/actions';
|
||||
import { setTimelineRangeDatePicker as dispatchSetTimelineRangeDatePicker } from '../../store/inputs/actions';
|
||||
import {
|
||||
setKqlFilterQueryDraft as dispatchSetKqlFilterQueryDraft,
|
||||
applyKqlFilterQuery as dispatchApplyKqlFilterQuery,
|
||||
addTimeline as dispatchAddTimeline,
|
||||
} from '../../store/timeline/actions';
|
||||
|
@ -210,6 +211,15 @@ export const dispatchUpdateTimeline = (dispatch: Dispatch): DispatchUpdateTimeli
|
|||
timeline.kqlQuery.filterQuery.kuery != null &&
|
||||
timeline.kqlQuery.filterQuery.kuery.expression !== ''
|
||||
) {
|
||||
dispatch(
|
||||
dispatchSetKqlFilterQueryDraft({
|
||||
id,
|
||||
filterQueryDraft: {
|
||||
kind: 'kuery',
|
||||
expression: timeline.kqlQuery.filterQuery.kuery.expression || '',
|
||||
},
|
||||
})
|
||||
);
|
||||
dispatch(
|
||||
dispatchApplyKqlFilterQuery({
|
||||
id,
|
||||
|
|
|
@ -41,6 +41,7 @@ import {
|
|||
OpenTimelineReduxProps,
|
||||
} from './types';
|
||||
import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants';
|
||||
import { ColumnHeader } from '../timeline/body/column_headers/column_header';
|
||||
|
||||
export interface OpenTimelineState {
|
||||
/** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */
|
||||
|
@ -363,8 +364,17 @@ const makeMapStateToProps = () => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
createNewTimeline: dispatchCreateNewTimeline,
|
||||
updateIsLoading: dispatchUpdateIsLoading,
|
||||
createNewTimeline: ({
|
||||
id,
|
||||
columns,
|
||||
show,
|
||||
}: {
|
||||
id: string;
|
||||
columns: ColumnHeader[];
|
||||
show?: boolean;
|
||||
}) => dispatch(dispatchCreateNewTimeline({ id, columns, show })),
|
||||
updateIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) =>
|
||||
dispatch(dispatchUpdateIsLoading({ id, isLoading })),
|
||||
updateTimeline: dispatchUpdateTimeline(dispatch),
|
||||
});
|
||||
|
||||
|
|
|
@ -397,8 +397,7 @@ describe('Paginated Table Component', () => {
|
|||
});
|
||||
|
||||
test('should update the page when the activePage is changed from redux', () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ourProps: BasicTableProps<any> = {
|
||||
const ourProps: BasicTableProps<unknown> = {
|
||||
activePage: 3,
|
||||
columns: getHostsColumns(),
|
||||
headerCount: 1,
|
||||
|
|
|
@ -21,10 +21,6 @@ interface TimelineRefetchDispatch {
|
|||
loading: boolean;
|
||||
refetch: inputsModel.Refetch | inputsModel.RefetchKql | null;
|
||||
}>;
|
||||
deleteEventQuery: ActionCreator<{
|
||||
id: string;
|
||||
inputId: InputsModelId;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface TimelineRefetchProps {
|
||||
|
@ -38,14 +34,9 @@ export interface TimelineRefetchProps {
|
|||
type OwnProps = TimelineRefetchProps & TimelineRefetchDispatch;
|
||||
|
||||
const TimelineRefetchComponent = memo<OwnProps>(
|
||||
({ deleteEventQuery, id, inputId, inspect, loading, refetch, setTimelineQuery }) => {
|
||||
({ id, inputId, inspect, loading, refetch, setTimelineQuery }) => {
|
||||
useEffect(() => {
|
||||
setTimelineQuery({ id, inputId, inspect, loading, refetch });
|
||||
if (inputId === 'global') {
|
||||
return () => {
|
||||
deleteEventQuery({ id, inputId });
|
||||
};
|
||||
}
|
||||
}, [id, inputId, loading, refetch, inspect]);
|
||||
|
||||
return null;
|
||||
|
@ -57,7 +48,6 @@ export const TimelineRefetch = compose<React.ComponentClass<TimelineRefetchProps
|
|||
null,
|
||||
{
|
||||
setTimelineQuery: inputsActions.setQuery,
|
||||
deleteEventQuery: inputsActions.deleteOneQuery,
|
||||
}
|
||||
)
|
||||
)(TimelineRefetchComponent);
|
||||
|
|
|
@ -1,547 +0,0 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`UrlStateContainer mounts and renders 1`] = `
|
||||
<MockedProvider
|
||||
addTypename={true}
|
||||
>
|
||||
<ApolloProvider
|
||||
client={
|
||||
ApolloClient {
|
||||
"__operations_cache__": Map {},
|
||||
"cache": InMemoryCache {
|
||||
"addTypename": true,
|
||||
"cacheKeyRoot": KeyTrie {
|
||||
"weakness": true,
|
||||
},
|
||||
"config": Object {
|
||||
"addTypename": true,
|
||||
"dataIdFromObject": [Function],
|
||||
"fragmentMatcher": HeuristicFragmentMatcher {},
|
||||
"freezeResults": false,
|
||||
"resultCaching": true,
|
||||
},
|
||||
"data": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"maybeBroadcastWatch": [Function],
|
||||
"optimisticData": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"silenceBroadcast": false,
|
||||
"storeReader": StoreReader {
|
||||
"executeSelectionSet": [Function],
|
||||
"executeStoreQuery": [Function],
|
||||
"executeSubSelectedArray": [Function],
|
||||
"freezeResults": false,
|
||||
},
|
||||
"storeWriter": StoreWriter {},
|
||||
"typenameDocumentCache": Map {},
|
||||
"watches": Set {},
|
||||
},
|
||||
"defaultOptions": Object {},
|
||||
"disableNetworkFetches": false,
|
||||
"link": ApolloLink {
|
||||
"request": [Function],
|
||||
},
|
||||
"mutate": [Function],
|
||||
"query": [Function],
|
||||
"queryDeduplication": true,
|
||||
"reFetchObservableQueries": [Function],
|
||||
"resetStore": [Function],
|
||||
"resetStoreCallbacks": Array [],
|
||||
"ssrMode": false,
|
||||
"store": DataStore {
|
||||
"cache": InMemoryCache {
|
||||
"addTypename": true,
|
||||
"cacheKeyRoot": KeyTrie {
|
||||
"weakness": true,
|
||||
},
|
||||
"config": Object {
|
||||
"addTypename": true,
|
||||
"dataIdFromObject": [Function],
|
||||
"fragmentMatcher": HeuristicFragmentMatcher {},
|
||||
"freezeResults": false,
|
||||
"resultCaching": true,
|
||||
},
|
||||
"data": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"maybeBroadcastWatch": [Function],
|
||||
"optimisticData": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"silenceBroadcast": false,
|
||||
"storeReader": StoreReader {
|
||||
"executeSelectionSet": [Function],
|
||||
"executeStoreQuery": [Function],
|
||||
"executeSubSelectedArray": [Function],
|
||||
"freezeResults": false,
|
||||
},
|
||||
"storeWriter": StoreWriter {},
|
||||
"typenameDocumentCache": Map {},
|
||||
"watches": Set {},
|
||||
},
|
||||
},
|
||||
"version": "2.3.8",
|
||||
"watchQuery": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<pure(Component)
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<I18nProvider>
|
||||
<IntlProvider
|
||||
defaultLocale="en"
|
||||
formats={
|
||||
Object {
|
||||
"date": Object {
|
||||
"full": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"weekday": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"long": Object {
|
||||
"day": "numeric",
|
||||
"month": "long",
|
||||
"year": "numeric",
|
||||
},
|
||||
"medium": Object {
|
||||
"day": "numeric",
|
||||
"month": "short",
|
||||
"year": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"day": "numeric",
|
||||
"month": "numeric",
|
||||
"year": "2-digit",
|
||||
},
|
||||
},
|
||||
"number": Object {
|
||||
"currency": Object {
|
||||
"style": "currency",
|
||||
},
|
||||
"percent": Object {
|
||||
"style": "percent",
|
||||
},
|
||||
},
|
||||
"relative": Object {
|
||||
"days": Object {
|
||||
"units": "day",
|
||||
},
|
||||
"hours": Object {
|
||||
"units": "hour",
|
||||
},
|
||||
"minutes": Object {
|
||||
"units": "minute",
|
||||
},
|
||||
"months": Object {
|
||||
"units": "month",
|
||||
},
|
||||
"seconds": Object {
|
||||
"units": "second",
|
||||
},
|
||||
"years": Object {
|
||||
"units": "year",
|
||||
},
|
||||
},
|
||||
"time": Object {
|
||||
"full": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"long": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
"timeZoneName": "short",
|
||||
},
|
||||
"medium": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
"second": "numeric",
|
||||
},
|
||||
"short": Object {
|
||||
"hour": "numeric",
|
||||
"minute": "numeric",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
locale="en"
|
||||
messages={Object {}}
|
||||
textComponent={Symbol(react.fragment)}
|
||||
>
|
||||
<PseudoLocaleWrapper>
|
||||
<ApolloProvider
|
||||
client={
|
||||
ApolloClient {
|
||||
"__operations_cache__": Map {},
|
||||
"cache": InMemoryCache {
|
||||
"addTypename": true,
|
||||
"cacheKeyRoot": KeyTrie {
|
||||
"weakness": true,
|
||||
},
|
||||
"config": Object {
|
||||
"addTypename": true,
|
||||
"dataIdFromObject": [Function],
|
||||
"fragmentMatcher": HeuristicFragmentMatcher {},
|
||||
"freezeResults": false,
|
||||
"resultCaching": true,
|
||||
},
|
||||
"data": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"maybeBroadcastWatch": [Function],
|
||||
"optimisticData": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"silenceBroadcast": false,
|
||||
"storeReader": StoreReader {
|
||||
"executeSelectionSet": [Function],
|
||||
"executeStoreQuery": [Function],
|
||||
"executeSubSelectedArray": [Function],
|
||||
"freezeResults": false,
|
||||
},
|
||||
"storeWriter": StoreWriter {},
|
||||
"typenameDocumentCache": Map {},
|
||||
"watches": Set {},
|
||||
},
|
||||
"defaultOptions": Object {},
|
||||
"disableNetworkFetches": false,
|
||||
"link": ApolloLink {
|
||||
"request": [Function],
|
||||
},
|
||||
"mutate": [Function],
|
||||
"query": [Function],
|
||||
"queryDeduplication": true,
|
||||
"reFetchObservableQueries": [Function],
|
||||
"resetStore": [Function],
|
||||
"resetStoreCallbacks": Array [],
|
||||
"ssrMode": false,
|
||||
"store": DataStore {
|
||||
"cache": InMemoryCache {
|
||||
"addTypename": true,
|
||||
"cacheKeyRoot": KeyTrie {
|
||||
"weakness": true,
|
||||
},
|
||||
"config": Object {
|
||||
"addTypename": true,
|
||||
"dataIdFromObject": [Function],
|
||||
"fragmentMatcher": HeuristicFragmentMatcher {},
|
||||
"freezeResults": false,
|
||||
"resultCaching": true,
|
||||
},
|
||||
"data": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"maybeBroadcastWatch": [Function],
|
||||
"optimisticData": DepTrackingCache {
|
||||
"data": Object {},
|
||||
"depend": [Function],
|
||||
},
|
||||
"silenceBroadcast": false,
|
||||
"storeReader": StoreReader {
|
||||
"executeSelectionSet": [Function],
|
||||
"executeStoreQuery": [Function],
|
||||
"executeSubSelectedArray": [Function],
|
||||
"freezeResults": false,
|
||||
},
|
||||
"storeWriter": StoreWriter {},
|
||||
"typenameDocumentCache": Map {},
|
||||
"watches": Set {},
|
||||
},
|
||||
},
|
||||
"version": "2.3.8",
|
||||
"watchQuery": [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Provider
|
||||
store={
|
||||
Object {
|
||||
"dispatch": [Function],
|
||||
"getState": [Function],
|
||||
"replaceReducer": [Function],
|
||||
"subscribe": [Function],
|
||||
Symbol(observable): [Function],
|
||||
}
|
||||
}
|
||||
>
|
||||
<ThemeProvider
|
||||
theme={[Function]}
|
||||
>
|
||||
<DragDropContext
|
||||
onDragEnd={[MockFunction]}
|
||||
>
|
||||
<Router
|
||||
history={
|
||||
Object {
|
||||
"action": "POP",
|
||||
"block": [MockFunction],
|
||||
"createHref": [MockFunction],
|
||||
"go": [MockFunction],
|
||||
"goBack": [MockFunction],
|
||||
"goForward": [MockFunction],
|
||||
"length": 2,
|
||||
"listen": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
[Function],
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
"location": Object {
|
||||
"hash": "",
|
||||
"pathname": "/network",
|
||||
"search": "",
|
||||
"state": "",
|
||||
},
|
||||
"push": [MockFunction],
|
||||
"replace": [MockFunction],
|
||||
}
|
||||
}
|
||||
>
|
||||
<Memo()
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "response",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"title": "logstash-*",
|
||||
}
|
||||
}
|
||||
navTabs={
|
||||
Object {
|
||||
"hosts": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/hosts",
|
||||
"id": "hosts",
|
||||
"name": "Hosts",
|
||||
"urlKey": "host",
|
||||
},
|
||||
"network": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/network",
|
||||
"id": "network",
|
||||
"name": "Network",
|
||||
"urlKey": "network",
|
||||
},
|
||||
"overview": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/overview",
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"urlKey": "overview",
|
||||
},
|
||||
"timelines": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/timelines",
|
||||
"id": "timelines",
|
||||
"name": "Timelines",
|
||||
"urlKey": "timeline",
|
||||
},
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(UrlStateContainer)
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "response",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"title": "logstash-*",
|
||||
}
|
||||
}
|
||||
navTabs={
|
||||
Object {
|
||||
"hosts": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/hosts",
|
||||
"id": "hosts",
|
||||
"name": "Hosts",
|
||||
"urlKey": "host",
|
||||
},
|
||||
"network": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/network",
|
||||
"id": "network",
|
||||
"name": "Network",
|
||||
"urlKey": "network",
|
||||
},
|
||||
"overview": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/overview",
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"urlKey": "overview",
|
||||
},
|
||||
"timelines": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/timelines",
|
||||
"id": "timelines",
|
||||
"name": "Timelines",
|
||||
"urlKey": "timeline",
|
||||
},
|
||||
}
|
||||
}
|
||||
pageName="network"
|
||||
pathName="/network"
|
||||
search=""
|
||||
>
|
||||
<Component
|
||||
addGlobalLinkTo={[Function]}
|
||||
addTimelineLinkTo={[Function]}
|
||||
dispatch={[Function]}
|
||||
indexPattern={
|
||||
Object {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"aggregatable": true,
|
||||
"name": "response",
|
||||
"searchable": true,
|
||||
"type": "number",
|
||||
},
|
||||
],
|
||||
"title": "logstash-*",
|
||||
}
|
||||
}
|
||||
navTabs={
|
||||
Object {
|
||||
"hosts": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/hosts",
|
||||
"id": "hosts",
|
||||
"name": "Hosts",
|
||||
"urlKey": "host",
|
||||
},
|
||||
"network": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/network",
|
||||
"id": "network",
|
||||
"name": "Network",
|
||||
"urlKey": "network",
|
||||
},
|
||||
"overview": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/overview",
|
||||
"id": "overview",
|
||||
"name": "Overview",
|
||||
"urlKey": "overview",
|
||||
},
|
||||
"timelines": Object {
|
||||
"disabled": false,
|
||||
"href": "#/link-to/timelines",
|
||||
"id": "timelines",
|
||||
"name": "Timelines",
|
||||
"urlKey": "timeline",
|
||||
},
|
||||
}
|
||||
}
|
||||
pageName="network"
|
||||
pathName="/network"
|
||||
removeGlobalLinkTo={[Function]}
|
||||
removeTimelineLinkTo={[Function]}
|
||||
search=""
|
||||
setAbsoluteTimerange={[Function]}
|
||||
setHostsKql={[Function]}
|
||||
setNetworkKql={[Function]}
|
||||
setRelativeTimerange={[Function]}
|
||||
updateTimeline={[Function]}
|
||||
updateTimelineIsLoading={[Function]}
|
||||
urlState={
|
||||
Object {
|
||||
"kqlQuery": Object {
|
||||
"filterQuery": null,
|
||||
"queryLocation": "network.page",
|
||||
},
|
||||
"timelineId": "",
|
||||
"timerange": Object {
|
||||
"global": Object {
|
||||
"linkTo": Array [
|
||||
"timeline",
|
||||
],
|
||||
"timerange": Object {
|
||||
"from": 0,
|
||||
"fromStr": "now-24h",
|
||||
"kind": "relative",
|
||||
"to": 1,
|
||||
"toStr": "now",
|
||||
},
|
||||
},
|
||||
"timeline": Object {
|
||||
"linkTo": Array [
|
||||
"global",
|
||||
],
|
||||
"timerange": Object {
|
||||
"from": 0,
|
||||
"fromStr": "now-24h",
|
||||
"kind": "relative",
|
||||
"to": 1,
|
||||
"toStr": "now",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Connect(UrlStateContainer)>
|
||||
</Memo()>
|
||||
</Router>
|
||||
</DragDropContext>
|
||||
</ThemeProvider>
|
||||
</Provider>
|
||||
</ApolloProvider>
|
||||
</PseudoLocaleWrapper>
|
||||
</IntlProvider>
|
||||
</I18nProvider>
|
||||
</Component>
|
||||
</pure(Component)>
|
||||
</ApolloProvider>
|
||||
</MockedProvider>
|
||||
`;
|
|
@ -5,37 +5,26 @@
|
|||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import toJson from 'enzyme-to-json';
|
||||
import * as React from 'react';
|
||||
import { Router } from 'react-router-dom';
|
||||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { apolloClientObservable, HookWrapper, mockGlobalState, TestProviders } from '../../mock';
|
||||
import { createStore, State } from '../../store';
|
||||
|
||||
import { UseUrlState } from './';
|
||||
import { defaultProps, getMockPropsObj, mockHistory, testCases } from './test_dependencies';
|
||||
import { HookWrapper } from '../../mock';
|
||||
import {
|
||||
getMockPropsObj,
|
||||
mockHistory,
|
||||
mockSetAbsoluteRangeDatePicker,
|
||||
mockSetRelativeRangeDatePicker,
|
||||
testCases,
|
||||
mockApplyHostsFilterQuery,
|
||||
mockApplyNetworkFilterQuery,
|
||||
} from './test_dependencies';
|
||||
import { UrlStateContainerPropTypes } from './types';
|
||||
import { useUrlStateHooks } from './use_url_state';
|
||||
import { CONSTANTS } from './constants';
|
||||
import { RouteSpyState } from '../../utils/route/types';
|
||||
import { navTabs, SiemPageName } from '../../pages/home/home_navigations';
|
||||
import { SiemPageName } from '../../pages/home/home_navigations';
|
||||
|
||||
let mockProps: UrlStateContainerPropTypes;
|
||||
|
||||
const indexPattern: StaticIndexPattern = {
|
||||
title: 'logstash-*',
|
||||
fields: [
|
||||
{
|
||||
name: 'response',
|
||||
type: 'number',
|
||||
aggregatable: true,
|
||||
searchable: true,
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
// const mockUseRouteSpy: jest.Mock = useRouteSpy as jest.Mock;
|
||||
const mockRouteSpy: RouteSpyState = {
|
||||
pageName: SiemPageName.network,
|
||||
|
@ -49,31 +38,9 @@ jest.mock('../../utils/route/use_route_spy', () => ({
|
|||
}));
|
||||
|
||||
describe('UrlStateContainer', () => {
|
||||
const state: State = mockGlobalState;
|
||||
|
||||
let store = createStore(state, apolloClientObservable);
|
||||
|
||||
beforeEach(() => {
|
||||
store = createStore(state, apolloClientObservable);
|
||||
});
|
||||
afterEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
test('mounts and renders', () => {
|
||||
const wrapper = mount(
|
||||
<MockedProvider>
|
||||
<TestProviders store={store}>
|
||||
<Router history={mockHistory}>
|
||||
<UseUrlState indexPattern={indexPattern} navTabs={navTabs} />
|
||||
</Router>
|
||||
</TestProviders>
|
||||
</MockedProvider>
|
||||
);
|
||||
const urlStateComponents = wrapper.find('[data-test-subj="urlStateComponents"]');
|
||||
urlStateComponents.exists();
|
||||
expect(toJson(wrapper)).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('handleInitialize', () => {
|
||||
describe('URL state updates redux', () => {
|
||||
describe('relative timerange actions are called with correct data on component mount', () => {
|
||||
|
@ -90,7 +57,7 @@ describe('UrlStateContainer', () => {
|
|||
mount(<HookWrapper hookProps={mockProps} hook={args => useUrlStateHooks(args)} />);
|
||||
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(defaultProps.setRelativeTimerange.mock.calls[1][0]).toEqual({
|
||||
expect(mockSetRelativeRangeDatePicker.mock.calls[1][0]).toEqual({
|
||||
from: 1558591200000,
|
||||
fromStr: 'now-1d/d',
|
||||
kind: 'relative',
|
||||
|
@ -99,7 +66,7 @@ describe('UrlStateContainer', () => {
|
|||
id: 'global',
|
||||
});
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(defaultProps.setRelativeTimerange.mock.calls[0][0]).toEqual({
|
||||
expect(mockSetRelativeRangeDatePicker.mock.calls[0][0]).toEqual({
|
||||
from: 1558732849370,
|
||||
fromStr: 'now-15m',
|
||||
kind: 'relative',
|
||||
|
@ -120,14 +87,14 @@ describe('UrlStateContainer', () => {
|
|||
mount(<HookWrapper hookProps={mockProps} hook={args => useUrlStateHooks(args)} />);
|
||||
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(defaultProps.setAbsoluteTimerange.mock.calls[1][0]).toEqual({
|
||||
expect(mockSetAbsoluteRangeDatePicker.mock.calls[1][0]).toEqual({
|
||||
from: 1556736012685,
|
||||
kind: 'absolute',
|
||||
to: 1556822416082,
|
||||
id: 'global',
|
||||
});
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(defaultProps.setAbsoluteTimerange.mock.calls[0][0]).toEqual({
|
||||
expect(mockSetAbsoluteRangeDatePicker.mock.calls[0][0]).toEqual({
|
||||
from: 1556736012685,
|
||||
kind: 'absolute',
|
||||
to: 1556822416082,
|
||||
|
@ -153,7 +120,9 @@ describe('UrlStateContainer', () => {
|
|||
.relativeTimeSearch.undefinedQuery;
|
||||
mount(<HookWrapper hookProps={mockProps} hook={args => useUrlStateHooks(args)} />);
|
||||
const functionName =
|
||||
namespaceUpper === 'Network' ? defaultProps.setNetworkKql : defaultProps.setHostsKql;
|
||||
namespaceUpper === 'Network'
|
||||
? mockApplyNetworkFilterQuery
|
||||
: mockApplyHostsFilterQuery;
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(functionName.mock.calls[0][0]).toEqual({
|
||||
filterQuery: serializedFilterQuery,
|
||||
|
@ -176,7 +145,9 @@ describe('UrlStateContainer', () => {
|
|||
}).oppositeQueryLocationSearch.undefinedQuery;
|
||||
mount(<HookWrapper hookProps={mockProps} hook={args => useUrlStateHooks(args)} />);
|
||||
const functionName =
|
||||
namespaceUpper === 'Network' ? defaultProps.setNetworkKql : defaultProps.setHostsKql;
|
||||
namespaceUpper === 'Network'
|
||||
? mockApplyNetworkFilterQuery
|
||||
: mockApplyHostsFilterQuery;
|
||||
// @ts-ignore property mock does not exists
|
||||
expect(functionName.mock.calls.length).toEqual(0);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
State,
|
||||
timelineSelectors,
|
||||
} from '../../store';
|
||||
import { hostsActions, inputsActions, networkActions, timelineActions } from '../../store/actions';
|
||||
import { timelineActions } from '../../store/actions';
|
||||
import { RouteSpyState } from '../../utils/route/types';
|
||||
import { useRouteSpy } from '../../utils/route/use_route_spy';
|
||||
|
||||
|
@ -27,6 +27,7 @@ import { UrlStateContainerPropTypes, UrlStateProps, KqlQuery, LocationTypes } fr
|
|||
import { useUrlStateHooks } from './use_url_state';
|
||||
import { dispatchUpdateTimeline } from '../open_timeline/helpers';
|
||||
import { getCurrentLocation } from './helpers';
|
||||
import { dispatchSetInitialStateFromUrl } from './initialize_redux_by_url';
|
||||
|
||||
export const UrlStateContainer = React.memo<UrlStateContainerPropTypes>(
|
||||
(props: UrlStateContainerPropTypes) => {
|
||||
|
@ -108,17 +109,10 @@ const makeMapStateToProps = () => {
|
|||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch: Dispatch) => ({
|
||||
addGlobalLinkTo: inputsActions.addGlobalLinkTo,
|
||||
addTimelineLinkTo: inputsActions.addTimelineLinkTo,
|
||||
removeGlobalLinkTo: inputsActions.removeGlobalLinkTo,
|
||||
removeTimelineLinkTo: inputsActions.removeTimelineLinkTo,
|
||||
setAbsoluteTimerange: inputsActions.setAbsoluteRangeDatePicker,
|
||||
setHostsKql: hostsActions.applyHostsFilterQuery,
|
||||
setNetworkKql: networkActions.applyNetworkFilterQuery,
|
||||
setRelativeTimerange: inputsActions.setRelativeRangeDatePicker,
|
||||
setInitialStateFromUrl: dispatchSetInitialStateFromUrl(dispatch),
|
||||
updateTimeline: dispatchUpdateTimeline(dispatch),
|
||||
updateTimelineIsLoading: timelineActions.updateIsLoading,
|
||||
dispatch,
|
||||
updateTimelineIsLoading: ({ id, isLoading }: { id: string; isLoading: boolean }) =>
|
||||
dispatch(timelineActions.updateIsLoading({ id, isLoading })),
|
||||
});
|
||||
|
||||
export const UrlStateRedux = compose<React.ComponentClass<UrlStateProps & RouteSpyState>>(
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { get, isEmpty } from 'lodash/fp';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { hostsActions, inputsActions, networkActions } from '../../store/actions';
|
||||
import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants';
|
||||
import {
|
||||
UrlInputsModel,
|
||||
LinkTo,
|
||||
AbsoluteTimeRange,
|
||||
RelativeTimeRange,
|
||||
} from '../../store/inputs/model';
|
||||
|
||||
import { CONSTANTS } from './constants';
|
||||
import { decodeRisonUrlState, isKqlForRoute, getCurrentLocation } from './helpers';
|
||||
import { normalizeTimeRange } from './normalize_time_range';
|
||||
import { DispatchSetInitialStateFromUrl, KqlQuery, SetInitialStateFromUrl } from './types';
|
||||
import { convertKueryToElasticSearchQuery } from '../../lib/keury';
|
||||
import { HostsType } from '../../store/hosts/model';
|
||||
import { NetworkType } from '../../store/network/model';
|
||||
import { queryTimelineById } from '../open_timeline/helpers';
|
||||
|
||||
export const dispatchSetInitialStateFromUrl = (
|
||||
dispatch: Dispatch
|
||||
): DispatchSetInitialStateFromUrl => ({
|
||||
apolloClient,
|
||||
detailName,
|
||||
indexPattern,
|
||||
pageName,
|
||||
updateTimeline,
|
||||
updateTimelineIsLoading,
|
||||
urlStateToUpdate,
|
||||
}: SetInitialStateFromUrl<unknown>): (() => void) => () => {
|
||||
urlStateToUpdate.forEach(({ urlKey, newUrlStateString }) => {
|
||||
if (urlKey === CONSTANTS.timerange) {
|
||||
const timerangeStateData: UrlInputsModel = decodeRisonUrlState(newUrlStateString);
|
||||
|
||||
const globalId: InputsModelId = 'global';
|
||||
const globalLinkTo: LinkTo = { linkTo: get('global.linkTo', timerangeStateData) };
|
||||
const globalType: TimeRangeKinds = get('global.timerange.kind', timerangeStateData);
|
||||
|
||||
const timelineId: InputsModelId = 'timeline';
|
||||
const timelineLinkTo: LinkTo = { linkTo: get('timeline.linkTo', timerangeStateData) };
|
||||
const timelineType: TimeRangeKinds = get('timeline.timerange.kind', timerangeStateData);
|
||||
|
||||
if (isEmpty(globalLinkTo.linkTo)) {
|
||||
dispatch(inputsActions.removeGlobalLinkTo());
|
||||
} else {
|
||||
dispatch(inputsActions.addGlobalLinkTo({ linkToId: 'timeline' }));
|
||||
}
|
||||
|
||||
if (isEmpty(timelineLinkTo.linkTo)) {
|
||||
dispatch(inputsActions.removeTimelineLinkTo());
|
||||
} else {
|
||||
dispatch(inputsActions.addTimelineLinkTo({ linkToId: 'global' }));
|
||||
}
|
||||
|
||||
if (timelineType) {
|
||||
if (timelineType === 'absolute') {
|
||||
const absoluteRange = normalizeTimeRange<AbsoluteTimeRange>(
|
||||
get('timeline.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
inputsActions.setAbsoluteRangeDatePicker({
|
||||
...absoluteRange,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (timelineType === 'relative') {
|
||||
const relativeRange = normalizeTimeRange<RelativeTimeRange>(
|
||||
get('timeline.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
inputsActions.setRelativeRangeDatePicker({
|
||||
...relativeRange,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (globalType) {
|
||||
if (globalType === 'absolute') {
|
||||
const absoluteRange = normalizeTimeRange<AbsoluteTimeRange>(
|
||||
get('global.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
inputsActions.setAbsoluteRangeDatePicker({
|
||||
...absoluteRange,
|
||||
id: globalId,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (globalType === 'relative') {
|
||||
const relativeRange = normalizeTimeRange<RelativeTimeRange>(
|
||||
get('global.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
inputsActions.setRelativeRangeDatePicker({
|
||||
...relativeRange,
|
||||
id: globalId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (urlKey === CONSTANTS.kqlQuery && indexPattern != null) {
|
||||
const kqlQueryStateData: KqlQuery = decodeRisonUrlState(newUrlStateString);
|
||||
if (isKqlForRoute(pageName, detailName, kqlQueryStateData.queryLocation)) {
|
||||
const filterQuery = {
|
||||
kuery: kqlQueryStateData.filterQuery,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
kqlQueryStateData.filterQuery ? kqlQueryStateData.filterQuery.expression : '',
|
||||
indexPattern
|
||||
),
|
||||
};
|
||||
const page = getCurrentLocation(pageName, detailName);
|
||||
if ([CONSTANTS.hostsPage, CONSTANTS.hostsDetails].includes(page)) {
|
||||
dispatch(
|
||||
hostsActions.applyHostsFilterQuery({
|
||||
filterQuery,
|
||||
hostsType: page === CONSTANTS.hostsPage ? HostsType.page : HostsType.details,
|
||||
})
|
||||
);
|
||||
} else if ([CONSTANTS.networkPage, CONSTANTS.networkDetails].includes(page)) {
|
||||
dispatch(
|
||||
networkActions.applyNetworkFilterQuery({
|
||||
filterQuery,
|
||||
networkType: page === CONSTANTS.networkPage ? NetworkType.page : NetworkType.details,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (urlKey === CONSTANTS.timelineId) {
|
||||
const timelineId = decodeRisonUrlState(newUrlStateString);
|
||||
if (timelineId != null) {
|
||||
queryTimelineById({
|
||||
apolloClient,
|
||||
duplicate: false,
|
||||
timelineId,
|
||||
updateIsLoading: updateTimelineIsLoading,
|
||||
updateTimeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
|
@ -5,13 +5,14 @@
|
|||
*/
|
||||
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { hostsModel, networkModel, SerializedFilterQuery } from '../../store';
|
||||
import { hostsModel, networkModel } from '../../store';
|
||||
import { UrlStateContainerPropTypes, LocationTypes, KqlQuery } from './types';
|
||||
import { CONSTANTS } from './constants';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
import { DispatchUpdateTimeline } from '../open_timeline/types';
|
||||
import { navTabs, SiemPageName } from '../../pages/home/home_navigations';
|
||||
import { hostsActions, inputsActions, networkActions } from '../../store/actions';
|
||||
import { HostsTableType } from '../../store/hosts/model';
|
||||
import { dispatchSetInitialStateFromUrl } from './initialize_redux_by_url';
|
||||
|
||||
type Action = 'PUSH' | 'POP' | 'REPLACE';
|
||||
const pop: Action = 'POP';
|
||||
|
@ -24,6 +25,32 @@ export const getFilterQuery = (queryLocation: LocationTypes): KqlQuery => ({
|
|||
queryLocation,
|
||||
});
|
||||
|
||||
export const mockApplyHostsFilterQuery: jest.Mock = (hostsActions.applyHostsFilterQuery as unknown) as jest.Mock;
|
||||
export const mockApplyNetworkFilterQuery: jest.Mock = (networkActions.applyNetworkFilterQuery as unknown) as jest.Mock;
|
||||
export const mockAddGlobalLinkTo: jest.Mock = (inputsActions.addGlobalLinkTo as unknown) as jest.Mock;
|
||||
export const mockAddTimelineLinkTo: jest.Mock = (inputsActions.addTimelineLinkTo as unknown) as jest.Mock;
|
||||
export const mockRemoveGlobalLinkTo: jest.Mock = (inputsActions.removeGlobalLinkTo as unknown) as jest.Mock;
|
||||
export const mockRemoveTimelineLinkTo: jest.Mock = (inputsActions.removeTimelineLinkTo as unknown) as jest.Mock;
|
||||
export const mockSetAbsoluteRangeDatePicker: jest.Mock = (inputsActions.setAbsoluteRangeDatePicker as unknown) as jest.Mock;
|
||||
export const mockSetRelativeRangeDatePicker: jest.Mock = (inputsActions.setRelativeRangeDatePicker as unknown) as jest.Mock;
|
||||
|
||||
jest.mock('../../store/actions', () => ({
|
||||
hostsActions: {
|
||||
applyHostsFilterQuery: jest.fn(),
|
||||
},
|
||||
networkActions: {
|
||||
applyNetworkFilterQuery: jest.fn(),
|
||||
},
|
||||
inputsActions: {
|
||||
addGlobalLinkTo: jest.fn(),
|
||||
addTimelineLinkTo: jest.fn(),
|
||||
removeGlobalLinkTo: jest.fn(),
|
||||
removeTimelineLinkTo: jest.fn(),
|
||||
setAbsoluteRangeDatePicker: jest.fn(),
|
||||
setRelativeRangeDatePicker: jest.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
const defaultLocation = {
|
||||
hash: '',
|
||||
pathname: '/network',
|
||||
|
@ -31,6 +58,9 @@ const defaultLocation = {
|
|||
state: '',
|
||||
};
|
||||
|
||||
const mockDispatch = jest.fn();
|
||||
mockDispatch.mockImplementation(fn => fn);
|
||||
|
||||
export const mockHistory = {
|
||||
action: pop,
|
||||
block: jest.fn(),
|
||||
|
@ -92,33 +122,7 @@ export const defaultProps: UrlStateContainerPropTypes = {
|
|||
},
|
||||
[CONSTANTS.timelineId]: '',
|
||||
},
|
||||
addGlobalLinkTo: (jest.fn() as unknown) as ActionCreator<{ linkToId: InputsModelId }>,
|
||||
addTimelineLinkTo: (jest.fn() as unknown) as ActionCreator<{ linkToId: InputsModelId }>,
|
||||
dispatch: jest.fn(),
|
||||
removeGlobalLinkTo: (jest.fn() as unknown) as ActionCreator<void>,
|
||||
removeTimelineLinkTo: (jest.fn() as unknown) as ActionCreator<void>,
|
||||
setAbsoluteTimerange: (jest.fn() as unknown) as ActionCreator<{
|
||||
from: number;
|
||||
fromStr: undefined;
|
||||
id: InputsModelId;
|
||||
to: number;
|
||||
toStr: undefined;
|
||||
}>,
|
||||
setHostsKql: (jest.fn() as unknown) as ActionCreator<{
|
||||
filterQuery: SerializedFilterQuery;
|
||||
hostsType: hostsModel.HostsType;
|
||||
}>,
|
||||
setNetworkKql: (jest.fn() as unknown) as ActionCreator<{
|
||||
filterQuery: SerializedFilterQuery;
|
||||
networkType: networkModel.NetworkType;
|
||||
}>,
|
||||
setRelativeTimerange: (jest.fn() as unknown) as ActionCreator<{
|
||||
from: number;
|
||||
fromStr: string;
|
||||
id: InputsModelId;
|
||||
to: number;
|
||||
toStr: string;
|
||||
}>,
|
||||
setInitialStateFromUrl: dispatchSetInitialStateFromUrl(mockDispatch),
|
||||
updateTimeline: (jest.fn() as unknown) as DispatchUpdateTimeline,
|
||||
updateTimelineIsLoading: (jest.fn() as unknown) as ActionCreator<{
|
||||
id: string;
|
||||
|
|
|
@ -6,11 +6,10 @@
|
|||
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
import { Dispatch } from 'redux';
|
||||
|
||||
import { hostsModel, KueryFilterQuery, networkModel, SerializedFilterQuery } from '../../store';
|
||||
import ApolloClient from 'apollo-client';
|
||||
import { KueryFilterQuery } from '../../store';
|
||||
import { UrlInputsModel } from '../../store/inputs/model';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
import { RouteSpyState } from '../../utils/route/types';
|
||||
import { DispatchUpdateTimeline } from '../open_timeline/types';
|
||||
import { NavTab } from '../navigation/types';
|
||||
|
@ -63,39 +62,15 @@ export interface UrlStateStateToPropsType {
|
|||
urlState: UrlState;
|
||||
}
|
||||
|
||||
export interface UpdateTimelineIsLoading {
|
||||
id: string;
|
||||
isLoading: boolean;
|
||||
}
|
||||
|
||||
export interface UrlStateDispatchToPropsType {
|
||||
addGlobalLinkTo: ActionCreator<{ linkToId: InputsModelId }>;
|
||||
addTimelineLinkTo: ActionCreator<{ linkToId: InputsModelId }>;
|
||||
dispatch: Dispatch;
|
||||
removeGlobalLinkTo: ActionCreator<void>;
|
||||
removeTimelineLinkTo: ActionCreator<void>;
|
||||
setHostsKql: ActionCreator<{
|
||||
filterQuery: SerializedFilterQuery;
|
||||
hostsType: hostsModel.HostsType;
|
||||
}>;
|
||||
setNetworkKql: ActionCreator<{
|
||||
filterQuery: SerializedFilterQuery;
|
||||
networkType: networkModel.NetworkType;
|
||||
}>;
|
||||
setAbsoluteTimerange: ActionCreator<{
|
||||
from: number;
|
||||
fromStr: undefined;
|
||||
id: InputsModelId;
|
||||
to: number;
|
||||
toStr: undefined;
|
||||
}>;
|
||||
setRelativeTimerange: ActionCreator<{
|
||||
from: number;
|
||||
fromStr: string;
|
||||
id: InputsModelId;
|
||||
to: number;
|
||||
toStr: string;
|
||||
}>;
|
||||
setInitialStateFromUrl: DispatchSetInitialStateFromUrl;
|
||||
updateTimeline: DispatchUpdateTimeline;
|
||||
updateTimelineIsLoading: ActionCreator<{
|
||||
id: string;
|
||||
isLoading: boolean;
|
||||
}>;
|
||||
updateTimelineIsLoading: ActionCreator<UpdateTimelineIsLoading>;
|
||||
}
|
||||
|
||||
export type UrlStateContainerPropTypes = RouteSpyState &
|
||||
|
@ -107,3 +82,28 @@ export interface PreviousLocationUrlState {
|
|||
pathName: string | undefined;
|
||||
urlState: UrlState;
|
||||
}
|
||||
|
||||
export interface UrlStateToRedux {
|
||||
urlKey: KeyUrlState;
|
||||
newUrlStateString: string;
|
||||
}
|
||||
|
||||
export interface SetInitialStateFromUrl<TCache> {
|
||||
apolloClient: ApolloClient<TCache> | ApolloClient<{}> | undefined;
|
||||
detailName: string | undefined;
|
||||
indexPattern: StaticIndexPattern | undefined;
|
||||
pageName: string;
|
||||
updateTimeline: DispatchUpdateTimeline;
|
||||
updateTimelineIsLoading: ActionCreator<UpdateTimelineIsLoading>;
|
||||
urlStateToUpdate: UrlStateToRedux[];
|
||||
}
|
||||
|
||||
export type DispatchSetInitialStateFromUrl = <TCache>({
|
||||
apolloClient,
|
||||
detailName,
|
||||
indexPattern,
|
||||
pageName,
|
||||
updateTimeline,
|
||||
updateTimelineIsLoading,
|
||||
urlStateToUpdate,
|
||||
}: SetInitialStateFromUrl<TCache>) => () => void;
|
||||
|
|
|
@ -5,21 +5,11 @@
|
|||
*/
|
||||
|
||||
import { Location } from 'history';
|
||||
import { get, isEqual, difference, isEmpty } from 'lodash/fp';
|
||||
import { isEqual, difference } from 'lodash/fp';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
|
||||
import { convertKueryToElasticSearchQuery } from '../../lib/keury';
|
||||
import { InputsModelId, TimeRangeKinds } from '../../store/inputs/constants';
|
||||
import {
|
||||
AbsoluteTimeRange,
|
||||
LinkTo,
|
||||
RelativeTimeRange,
|
||||
UrlInputsModel,
|
||||
} from '../../store/inputs/model';
|
||||
import { UrlInputsModel } from '../../store/inputs/model';
|
||||
import { useApolloClient } from '../../utils/apollo_context';
|
||||
import { queryTimelineById } from '../open_timeline/helpers';
|
||||
import { HostsType } from '../../store/hosts/model';
|
||||
import { NetworkType } from '../../store/network/model';
|
||||
|
||||
import { CONSTANTS, UrlStateType } from './constants';
|
||||
import {
|
||||
|
@ -29,11 +19,9 @@ import {
|
|||
getParamFromQueryString,
|
||||
decodeRisonUrlState,
|
||||
isKqlForRoute,
|
||||
getCurrentLocation,
|
||||
getUrlType,
|
||||
getTitle,
|
||||
} from './helpers';
|
||||
import { normalizeTimeRange } from './normalize_time_range';
|
||||
import {
|
||||
UrlStateContainerPropTypes,
|
||||
PreviousLocationUrlState,
|
||||
|
@ -41,6 +29,7 @@ import {
|
|||
KeyUrlState,
|
||||
KqlQuery,
|
||||
ALL_URL_STATE_KEYS,
|
||||
UrlStateToRedux,
|
||||
} from './types';
|
||||
|
||||
function usePrevious(value: PreviousLocationUrlState) {
|
||||
|
@ -52,22 +41,14 @@ function usePrevious(value: PreviousLocationUrlState) {
|
|||
}
|
||||
|
||||
export const useUrlStateHooks = ({
|
||||
addGlobalLinkTo,
|
||||
addTimelineLinkTo,
|
||||
detailName,
|
||||
dispatch,
|
||||
indexPattern,
|
||||
history,
|
||||
navTabs,
|
||||
pageName,
|
||||
pathName,
|
||||
removeGlobalLinkTo,
|
||||
removeTimelineLinkTo,
|
||||
search,
|
||||
setAbsoluteTimerange,
|
||||
setHostsKql,
|
||||
setNetworkKql,
|
||||
setRelativeTimerange,
|
||||
setInitialStateFromUrl,
|
||||
tabName,
|
||||
updateTimeline,
|
||||
updateTimelineIsLoading,
|
||||
|
@ -98,8 +79,7 @@ export const useUrlStateHooks = ({
|
|||
getQueryStringFromLocation(latestLocation)
|
||||
)
|
||||
);
|
||||
|
||||
if (history && !isEqual(newLocation.search, latestLocation.search)) {
|
||||
if (history) {
|
||||
history.replace(newLocation);
|
||||
}
|
||||
return newLocation;
|
||||
|
@ -107,6 +87,7 @@ export const useUrlStateHooks = ({
|
|||
|
||||
const handleInitialize = (initLocation: Location, type: UrlStateType) => {
|
||||
let myLocation: Location = initLocation;
|
||||
let urlStateToUpdate: UrlStateToRedux[] = [];
|
||||
URL_STATE_KEYS[type].forEach((urlKey: KeyUrlState) => {
|
||||
const newUrlStateString = getParamFromQueryString(
|
||||
getQueryStringFromLocation(initLocation),
|
||||
|
@ -129,7 +110,7 @@ export const useUrlStateHooks = ({
|
|||
);
|
||||
}
|
||||
if (isInitializing) {
|
||||
setInitialStateFromUrl(urlKey, newUrlStateString);
|
||||
urlStateToUpdate = [...urlStateToUpdate, { urlKey, newUrlStateString }];
|
||||
}
|
||||
} else {
|
||||
myLocation = replaceStateInLocation(urlState[urlKey], urlKey, myLocation);
|
||||
|
@ -138,123 +119,16 @@ export const useUrlStateHooks = ({
|
|||
difference(ALL_URL_STATE_KEYS, URL_STATE_KEYS[type]).forEach((urlKey: KeyUrlState) => {
|
||||
myLocation = replaceStateInLocation('', urlKey, myLocation);
|
||||
});
|
||||
};
|
||||
|
||||
const setInitialStateFromUrl = (urlKey: KeyUrlState, newUrlStateString: string) => {
|
||||
if (urlKey === CONSTANTS.timerange) {
|
||||
const timerangeStateData: UrlInputsModel = decodeRisonUrlState(newUrlStateString);
|
||||
|
||||
const globalId: InputsModelId = 'global';
|
||||
const globalLinkTo: LinkTo = { linkTo: get('global.linkTo', timerangeStateData) };
|
||||
const globalType: TimeRangeKinds = get('global.timerange.kind', timerangeStateData);
|
||||
|
||||
const timelineId: InputsModelId = 'timeline';
|
||||
const timelineLinkTo: LinkTo = { linkTo: get('timeline.linkTo', timerangeStateData) };
|
||||
const timelineType: TimeRangeKinds = get('timeline.timerange.kind', timerangeStateData);
|
||||
|
||||
if (isEmpty(globalLinkTo.linkTo)) {
|
||||
dispatch(removeGlobalLinkTo());
|
||||
} else {
|
||||
dispatch(addGlobalLinkTo({ linkToId: 'timeline' }));
|
||||
}
|
||||
|
||||
if (isEmpty(timelineLinkTo.linkTo)) {
|
||||
dispatch(removeTimelineLinkTo());
|
||||
} else {
|
||||
dispatch(addTimelineLinkTo({ linkToId: 'global' }));
|
||||
}
|
||||
|
||||
if (timelineType) {
|
||||
if (timelineType === 'absolute') {
|
||||
const absoluteRange = normalizeTimeRange<AbsoluteTimeRange>(
|
||||
get('timeline.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
setAbsoluteTimerange({
|
||||
...absoluteRange,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (timelineType === 'relative') {
|
||||
const relativeRange = normalizeTimeRange<RelativeTimeRange>(
|
||||
get('timeline.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
setRelativeTimerange({
|
||||
...relativeRange,
|
||||
id: timelineId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (globalType) {
|
||||
if (globalType === 'absolute') {
|
||||
const absoluteRange = normalizeTimeRange<AbsoluteTimeRange>(
|
||||
get('global.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
setAbsoluteTimerange({
|
||||
...absoluteRange,
|
||||
id: globalId,
|
||||
})
|
||||
);
|
||||
}
|
||||
if (globalType === 'relative') {
|
||||
const relativeRange = normalizeTimeRange<RelativeTimeRange>(
|
||||
get('global.timerange', timerangeStateData)
|
||||
);
|
||||
dispatch(
|
||||
setRelativeTimerange({
|
||||
...relativeRange,
|
||||
id: globalId,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (urlKey === CONSTANTS.kqlQuery && indexPattern != null) {
|
||||
const kqlQueryStateData: KqlQuery = decodeRisonUrlState(newUrlStateString);
|
||||
if (isKqlForRoute(pageName, detailName, kqlQueryStateData.queryLocation)) {
|
||||
const filterQuery = {
|
||||
kuery: kqlQueryStateData.filterQuery,
|
||||
serializedQuery: convertKueryToElasticSearchQuery(
|
||||
kqlQueryStateData.filterQuery ? kqlQueryStateData.filterQuery.expression : '',
|
||||
indexPattern
|
||||
),
|
||||
};
|
||||
const page = getCurrentLocation(pageName, detailName);
|
||||
if ([CONSTANTS.hostsPage, CONSTANTS.hostsDetails].includes(page)) {
|
||||
dispatch(
|
||||
setHostsKql({
|
||||
filterQuery,
|
||||
hostsType: page === CONSTANTS.hostsPage ? HostsType.page : HostsType.details,
|
||||
})
|
||||
);
|
||||
} else if ([CONSTANTS.networkPage, CONSTANTS.networkDetails].includes(page)) {
|
||||
dispatch(
|
||||
setNetworkKql({
|
||||
filterQuery,
|
||||
networkType: page === CONSTANTS.networkPage ? NetworkType.page : NetworkType.details,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (urlKey === CONSTANTS.timelineId) {
|
||||
const timelineId = decodeRisonUrlState(newUrlStateString);
|
||||
if (timelineId != null) {
|
||||
queryTimelineById({
|
||||
apolloClient,
|
||||
duplicate: false,
|
||||
timelineId,
|
||||
updateIsLoading: updateTimelineIsLoading,
|
||||
updateTimeline,
|
||||
});
|
||||
}
|
||||
}
|
||||
setInitialStateFromUrl({
|
||||
apolloClient,
|
||||
detailName,
|
||||
indexPattern,
|
||||
pageName,
|
||||
updateTimeline,
|
||||
updateTimelineIsLoading,
|
||||
urlStateToUpdate,
|
||||
})();
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -269,7 +143,7 @@ export const useUrlStateHooks = ({
|
|||
if (isInitializing && pageName != null && pageName !== '') {
|
||||
handleInitialize(location, type);
|
||||
setIsInitializing(false);
|
||||
} else if (!isEqual(urlState, prevProps.urlState)) {
|
||||
} else if (!isEqual(urlState, prevProps.urlState) && !isInitializing) {
|
||||
let newLocation: Location = location;
|
||||
URL_STATE_KEYS[type].forEach((urlKey: KeyUrlState) => {
|
||||
newLocation = replaceStateInLocation(urlState[urlKey], urlKey, newLocation);
|
||||
|
|
|
@ -40,11 +40,6 @@ export class QueryTemplate<
|
|||
tiebreaker?: string
|
||||
) => FetchMoreOptionsArgs<TData, TVariables>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
constructor(props: T) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public setFetchMore = (
|
||||
val: (fetchMoreOptions: FetchMoreOptionsArgs<TData, TVariables>) => PromiseApolloQueryResult
|
||||
) => {
|
||||
|
|
|
@ -49,7 +49,6 @@ export class QueryTemplatePaginated<
|
|||
refetch: (variables?: TVariables) => Promise<ApolloQueryResult<TData>>
|
||||
) => inputsModel.Refetch;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
|
||||
constructor(props: T) {
|
||||
super(props);
|
||||
this.memoizedRefetchQuery = memoizeOne(this.refetchQuery);
|
||||
|
|
|
@ -229,7 +229,7 @@ describe('Inputs', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('deleteOnlyOneQuery', () => {
|
||||
describe('deleteOneQuery', () => {
|
||||
test('make sure that we only delete one query', () => {
|
||||
const refetch = jest.fn();
|
||||
const newQuery: UpdateQueryParams = {
|
||||
|
|
|
@ -49,7 +49,9 @@ export const applyDeltaToColumnWidth = actionCreator<{
|
|||
export const createTimeline = actionCreator<{
|
||||
id: string;
|
||||
columns: ColumnHeader[];
|
||||
itemsPerPage?: number;
|
||||
show?: boolean;
|
||||
sort?: Sort;
|
||||
}>('CREATE_TIMELINE');
|
||||
|
||||
export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT');
|
||||
|
|
|
@ -94,6 +94,9 @@ const timelineActionsType = [
|
|||
upsertColumn.type,
|
||||
];
|
||||
|
||||
const isItAtimelineAction = (timelineId: string | undefined) =>
|
||||
timelineId && timelineId.toLowerCase().startsWith('timeline');
|
||||
|
||||
export const createTimelineEpic = <State>(): Epic<
|
||||
Action,
|
||||
Action,
|
||||
|
@ -123,19 +126,24 @@ export const createTimelineEpic = <State>(): Epic<
|
|||
action$.pipe(
|
||||
withLatestFrom(timeline$),
|
||||
filter(([action, timeline]) => {
|
||||
const timelineId: TimelineModel = timeline[get('payload.id', action)];
|
||||
const timelineId: string = get('payload.id', action);
|
||||
const timelineObj: TimelineModel = timeline[timelineId];
|
||||
if (action.type === addError.type) {
|
||||
return true;
|
||||
}
|
||||
if (action.type === createTimeline.type) {
|
||||
if (action.type === createTimeline.type && isItAtimelineAction(timelineId)) {
|
||||
myEpicTimelineId.setTimelineId(null);
|
||||
myEpicTimelineId.setTimelineVersion(null);
|
||||
} else if (action.type === addTimeline.type) {
|
||||
} else if (action.type === addTimeline.type && isItAtimelineAction(timelineId)) {
|
||||
const addNewTimeline: TimelineModel = get('payload.timeline', action);
|
||||
myEpicTimelineId.setTimelineId(addNewTimeline.savedObjectId);
|
||||
myEpicTimelineId.setTimelineVersion(addNewTimeline.version);
|
||||
return true;
|
||||
} else if (timelineActionsType.includes(action.type) && !timelineId.isLoading) {
|
||||
} else if (
|
||||
timelineActionsType.includes(action.type) &&
|
||||
!timelineObj.isLoading &&
|
||||
isItAtimelineAction(timelineId)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
|
@ -122,13 +122,16 @@ export const addTimelineToStore = ({
|
|||
[id]: {
|
||||
...timeline,
|
||||
show: true,
|
||||
isLoading: timelineById[id].isLoading,
|
||||
},
|
||||
});
|
||||
|
||||
interface AddNewTimelineParams {
|
||||
columns: ColumnHeader[];
|
||||
id: string;
|
||||
itemsPerPage?: number;
|
||||
show?: boolean;
|
||||
sort?: Sort;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
|
@ -136,6 +139,8 @@ interface AddNewTimelineParams {
|
|||
export const addNewTimeline = ({
|
||||
columns,
|
||||
id,
|
||||
itemsPerPage = timelineDefaults.itemsPerPage,
|
||||
sort = timelineDefaults.sort,
|
||||
show = false,
|
||||
timelineById,
|
||||
}: AddNewTimelineParams): TimelineById => ({
|
||||
|
@ -144,6 +149,8 @@ export const addNewTimeline = ({
|
|||
id,
|
||||
...timelineDefaults,
|
||||
columns,
|
||||
itemsPerPage,
|
||||
sort,
|
||||
show,
|
||||
savedObjectId: null,
|
||||
version: null,
|
||||
|
|
|
@ -99,9 +99,16 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
timelineById: addTimelineToStore({ id, timeline, timelineById: state.timelineById }),
|
||||
}))
|
||||
.case(createTimeline, (state, { id, show, columns }) => ({
|
||||
.case(createTimeline, (state, { id, show, columns, itemsPerPage, sort }) => ({
|
||||
...state,
|
||||
timelineById: addNewTimeline({ columns, id, show, timelineById: state.timelineById }),
|
||||
timelineById: addNewTimeline({
|
||||
columns,
|
||||
id,
|
||||
itemsPerPage,
|
||||
sort,
|
||||
show,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(upsertColumn, (state, { column, id, index }) => ({
|
||||
...state,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue