Add date picker in timeline + clean up queryDate in draggable wrapper… (#35340)

* Add date picker in timeline + clean up queryDate in draggable wrapper + remove all the poll stuff

* fix api integration test

* review I

* review II
This commit is contained in:
Xavier Mouligneau 2019-04-20 11:17:55 -04:00 committed by GitHub
parent 75012de975
commit 1fae72b272
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
131 changed files with 1307 additions and 1901 deletions

View file

@ -14,10 +14,6 @@ exports[`DraggableWrapper rendering it renders against the snapshot 1`] = `
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",

View file

@ -8,12 +8,16 @@ import * as React from 'react';
import { connect } from 'react-redux';
import { pure } from 'recompose';
import { Dispatch } from 'redux';
import { ActionCreator } from 'typescript-fsa';
import { History } from '../../../lib/history';
import { Note } from '../../../lib/note';
import {
appActions,
appSelectors,
inputsActions,
inputsModel,
inputsSelectors,
State,
timelineActions,
timelineModel,
@ -30,18 +34,17 @@ interface OwnProps {
}
interface StateReduxProps {
description?: string;
description: string;
getNotesByIds: (noteIds: string[]) => Note[];
history?: History[];
isFavorite?: boolean;
isLive?: boolean;
isFavorite: boolean;
isDatepickerLocked: boolean;
noteIds: string[];
title?: string;
width?: number;
title: string;
width: number;
}
interface DispatchProps {
associateNote?: (noteId: string) => void;
associateNote: (noteId: string) => void;
applyDeltaToWidth?: (
{
id,
@ -57,12 +60,12 @@ interface DispatchProps {
minWidthPixels: number;
}
) => void;
createTimeline?: ({ id, show }: { id: string; show?: boolean }) => void;
updateDescription?: ({ id, description }: { id: string; description: string }) => void;
updateIsFavorite?: ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void;
updateIsLive?: ({ id, isLive }: { id: string; isLive: boolean }) => void;
updateNote?: UpdateNote;
updateTitle?: ({ id, title }: { id: string; title: string }) => void;
createTimeline: ActionCreator<{ id: string; show?: boolean }>;
toggleLock: ActionCreator<{ linkToId: inputsModel.InputsModelId }>;
updateDescription: ActionCreator<{ id: string; description: string }>;
updateIsFavorite: ActionCreator<{ id: string; isFavorite: boolean }>;
updateNote: UpdateNote;
updateTitle: ActionCreator<{ id: string; title: string }>;
}
type Props = OwnProps & StateReduxProps & DispatchProps;
@ -73,36 +76,34 @@ const statefulFlyoutHeader = pure<Props>(
createTimeline,
description,
getNotesByIds,
history,
isFavorite,
isLive,
isDatepickerLocked,
title,
width = defaultWidth,
noteIds,
timelineId,
toggleLock,
updateDescription,
updateIsFavorite,
updateIsLive,
updateNote,
updateTitle,
usersViewing,
}) => (
<Properties
associateNote={associateNote!}
createTimeline={createTimeline!}
description={description!}
associateNote={associateNote}
createTimeline={createTimeline}
description={description}
getNotesByIds={getNotesByIds}
history={history!}
isFavorite={isFavorite!}
isLive={isLive!}
title={title!}
isDatepickerLocked={isDatepickerLocked}
isFavorite={isFavorite}
title={title}
noteIds={noteIds}
timelineId={timelineId}
updateDescription={updateDescription!}
updateIsFavorite={updateIsFavorite!}
updateIsLive={updateIsLive!}
updateTitle={updateTitle!}
updateNote={updateNote!}
toggleLock={toggleLock}
updateDescription={updateDescription}
updateIsFavorite={updateIsFavorite}
updateTitle={updateTitle}
updateNote={updateNote}
usersViewing={usersViewing}
width={width}
/>
@ -114,13 +115,13 @@ const emptyHistory: History[] = []; // stable reference
const makeMapStateToProps = () => {
const getTimeline = timelineSelectors.getTimelineByIdSelector();
const getNotesByIds = appSelectors.notesByIdsSelector();
const getGlobalInput = inputsSelectors.globalSelector();
const mapStateToProps = (state: State, { timelineId }: OwnProps) => {
const timeline: timelineModel.TimelineModel = getTimeline(state, timelineId);
const globalInput: inputsModel.InputsRange = getGlobalInput(state);
const {
description = '',
isFavorite = false,
isLive = false,
title = '',
noteIds = [],
width = defaultWidth,
@ -133,7 +134,7 @@ const makeMapStateToProps = () => {
getNotesByIds: getNotesByIds(state),
history,
isFavorite,
isLive,
isDatepickerLocked: globalInput.linkTo.includes('timeline'),
noteIds,
title,
width,
@ -187,6 +188,9 @@ const mapDispatchToProps = (dispatch: Dispatch, { timelineId }: OwnProps) => ({
updateTitle: ({ id, title }: { id: string; title: string }) => {
dispatch(timelineActions.updateTitle({ id, title }));
},
toggleLock: ({ linkToId }: { linkToId: inputsModel.InputsModelId }) => {
dispatch(inputsActions.toggleTimelineLinkTo({ linkToId }));
},
});
export const FlyoutHeader = connect(

View file

@ -18,7 +18,7 @@ import { FlyoutHeader } from '../header';
import * as i18n from './translations';
const minWidthPixels = 440; // do not allow the flyout to shrink below this width (pixels)
const minWidthPixels = 550; // do not allow the flyout to shrink below this width (pixels)
const maxWidthPercent = 95; // do not allow the flyout to grow past this percentage of the view
interface OwnProps {
children: React.ReactNode;
@ -60,6 +60,9 @@ const EuiFlyoutContainer = styled.div<{ headerHeight: number; width: number }>`
.timeline-flyout-body {
overflow-y: hidden;
padding: 0;
.euiFlyoutBody__overflow {
padding: 0;
}
}
`;
@ -68,11 +71,12 @@ const FlyoutHeaderContainer = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
width: 100%;
`;
// manually wrap the close button because EuiButtonIcon can't be a wrapped `styled`
const WrappedCloseButton = styled.div`
margin-right: 10px;
margin-right: 5px;
`;
const FlyoutHeaderWithCloseButton = pure<{

View file

@ -25,10 +25,14 @@ export const LocalizedDateTooltip = pure<{
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<FormattedRelative data-test-subj="humanized-relative-date" value={date} />
<FormattedRelative
data-test-subj="humanized-relative-date"
value={moment.utc(date).toDate()}
/>
</EuiFlexItem>
<EuiFlexItem data-test-subj="with-day-of-week" grow={false}>
{moment(date)
{moment
.utc(date)
.local()
.format('llll')}
</EuiFlexItem>

View file

@ -104,7 +104,6 @@ exports[`Authentication Table Component rendering it renders the default Authent
loadMore={[MockFunction]}
loading={false}
nextCursor="aa7ca589f1b8220002f2fc61c64cfbf1"
startDate={1546965070707}
totalCount={4}
type="page"
/>

View file

@ -18,7 +18,6 @@ import { mockData } from './mock';
describe('Authentication Table Component', () => {
const loadMore = jest.fn();
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
const state: State = mockGlobalState;
let store = createStore(state);
@ -38,7 +37,6 @@ describe('Authentication Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.Authentications.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Authentications.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</ReduxStoreProvider>

View file

@ -30,7 +30,6 @@ interface OwnProps {
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
startDate: number;
type: hostsModel.HostsType;
}
@ -75,11 +74,10 @@ const AuthenticationTableComponent = pure<AuthenticationTableProps>(
totalCount,
nextCursor,
updateLimitPagination,
startDate,
type,
}) => (
<LoadMoreTable
columns={getAuthenticationColumns(startDate)}
columns={getAuthenticationColumns()}
loadingTitle={i18n.AUTHENTICATIONS}
loading={loading}
pageOfItems={data}
@ -114,7 +112,7 @@ export const AuthenticationTable = connect(
}
)(AuthenticationTableComponent);
const getAuthenticationColumns = (startDate: number): Array<Columns<AuthenticationsEdges>> => [
const getAuthenticationColumns = (): Array<Columns<AuthenticationsEdges>> => [
{
name: i18n.USER,
truncateText: false,
@ -137,10 +135,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'user.name',
value: userName,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -180,10 +174,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'event.type',
value: 'authentication_failure',
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -235,10 +225,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'source.ip',
value: sourceIp,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -279,7 +265,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'host.name',
value: hostName,
},
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -319,10 +304,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'event.type',
value: 'authentication_success',
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -374,10 +355,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'source.ip',
value: sourceIp,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -418,7 +395,6 @@ const getAuthenticationColumns = (startDate: number): Array<Columns<Authenticati
field: 'host.name',
value: hostName,
},
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -86,7 +86,6 @@ exports[`Load More Events Table Component rendering it renders the default Event
loadMore={[MockFunction]}
loading={false}
nextCursor="1546878704036"
startDate={1546878704036}
tiebreaker="10624"
totalCount={15546}
type="page"

View file

@ -44,7 +44,6 @@ describe('Load More Events Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!}
loadMore={loadMore}
startDate={1546878704036}
type={hostsModel.HostsType.page}
/>
</ReduxStoreProvider>

View file

@ -30,7 +30,6 @@ interface OwnProps {
tiebreaker: string;
totalCount: number;
loadMore: (cursor: string, tiebreaker: string) => void;
startDate: number;
type: hostsModel.HostsType;
}
@ -74,11 +73,10 @@ const EventsTableComponent = pure<EventsTableProps>(
totalCount,
nextCursor,
updateLimitPagination,
startDate,
type,
}) => (
<LoadMoreTable
columns={getEventsColumns(startDate)}
columns={getEventsColumns()}
loadingTitle={i18n.EVENTS}
loading={loading}
pageOfItems={data}
@ -113,7 +111,7 @@ export const EventsTable = connect(
}
)(EventsTableComponent);
const getEventsColumns = (startDate: number): Array<Columns<EcsEdges>> => [
const getEventsColumns = (): Array<Columns<EcsEdges>> => [
{
name: i18n.HOST_NAME,
sortable: true,
@ -137,10 +135,6 @@ const getEventsColumns = (startDate: number): Array<Columns<EcsEdges>> => [
field: 'host.name',
value: hostName,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -7,7 +7,7 @@
import { cloneDeep } from 'lodash/fp';
import * as React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { render, waitForElement } from 'react-testing-library';
import { render } from 'react-testing-library';
import { mockFirstLastSeenHostQuery } from '../../../../containers/hosts/first_last_seen/mock';
import { wait } from '../../../../lib/helpers';
@ -147,22 +147,4 @@ describe('FirstLastSeen Component', async () => {
await wait();
expect(container.textContent).toBe('something-invalid');
});
test('Show error message', async () => {
const myErrorMock = cloneDeep(mockFirstLastSeenHostQuery);
delete myErrorMock[0].result;
myErrorMock[0].result = {
errors: [{ message: 'Error!' }],
};
const { container } = render(
<TestProviders>
<MockedProvider mocks={myErrorMock} addTypename={false}>
<FirstLastSeenHost hostname="kibana-siem" type="last-seen" />
</MockedProvider>
</TestProviders>
);
await wait();
const alertIcon = await waitForElement(() => container.querySelectorAll('svg'), { container });
expect(alertIcon.length).toBe(1);
});
});

View file

@ -511,9 +511,7 @@ exports[`Host Summary Component rendering it renders the default Host Summary 1`
},
}
}
endDate={618472800000}
loading={false}
startDate={552204000000}
/>
</Component>
`;

View file

@ -46,12 +46,7 @@ describe('Host Summary Component', () => {
test('it renders the default Host Summary', () => {
const wrapper = shallow(
<TestProviders>
<HostSummary
loading={false}
data={mockData.Hosts.edges[0].node}
startDate={552204000000}
endDate={618472800000}
/>
<HostSummary loading={false} data={mockData.Hosts.edges[0].node} />
</TestProviders>
);
@ -61,13 +56,7 @@ describe('Host Summary Component', () => {
describe('#createDraggable', () => {
test('if it creates a draggable component', () => {
const draggable = createDraggable(
'debian',
'Platform',
552204000000,
618472800000,
'siem-kibana'
);
const draggable = createDraggable('debian', 'Platform', 'siem-kibana');
const wrapper = mountWithIntl(<TestProviders>{draggable}</TestProviders>);
expect(wrapper.text()).toBe('debian');
});
@ -75,7 +64,7 @@ describe('Host Summary Component', () => {
const { container } = render(
<TestProviders>
<MockedProvider mocks={mockFirstLastSeenHostQuery} addTypename={false}>
{createDraggable(null, 'firstSeen', 552204000000, 618472800000, 'kibana-siem')}
{createDraggable(null, 'firstSeen', 'kibana-siem')}
</MockedProvider>
</TestProviders>
);
@ -85,23 +74,17 @@ describe('Host Summary Component', () => {
);
});
test('if it returns an empty value', () => {
const draggable = createDraggable(
null,
'Platform',
552204000000,
618472800000,
'siem-kibana'
);
const draggable = createDraggable(null, 'Platform', 'siem-kibana');
const wrapper = mountWithIntl(<TestProviders>{draggable}</TestProviders>);
expect(wrapper.text()).toBe(getEmptyValue());
});
test('if it returns a string placeholder with an empty string', () => {
const draggable = createDraggable('', 'Platform', 552204000000, 618472800000, 'siem-kibana');
const draggable = createDraggable('', 'Platform', 'siem-kibana');
const wrapper = mountWithIntl(<TestProviders>{draggable}</TestProviders>);
expect(wrapper.text()).toBe(getEmptyString());
});
test('if works with a string with a single space as a valid value and NOT an empty value', () => {
const draggable = createDraggable(' ', 'Platform', 552204000000, 618472800000, 'siem-kibana');
const draggable = createDraggable(' ', 'Platform', 'siem-kibana');
const wrapper = mountWithIntl(<TestProviders>{draggable}</TestProviders>);
expect(wrapper.text()).toBe(' ');
});
@ -114,7 +97,7 @@ describe('Host Summary Component', () => {
const { container } = render(
<TestProviders>
<MockedProvider mocks={mockFirstLastSeenHostQuery} addTypename={false}>
{getEuiDescriptionList(myMockData, 552204000000, 618472800000, true)}
{getEuiDescriptionList(myMockData, true)}
</MockedProvider>
</TestProviders>
);
@ -127,7 +110,7 @@ describe('Host Summary Component', () => {
const { container } = render(
<TestProviders>
<MockedProvider mocks={mockFirstLastSeenHostQuery} addTypename={false}>
{getEuiDescriptionList(myMockData, 552204000000, 618472800000, false)}
{getEuiDescriptionList(myMockData, false)}
</MockedProvider>
</TestProviders>
);
@ -135,7 +118,7 @@ describe('Host Summary Component', () => {
expect(container).toMatchSnapshot();
});
test('if it creates an empty description list', () => {
const euiDescriptionList = getEuiDescriptionList(null, 552204000000, 618472800000, false);
const euiDescriptionList = getEuiDescriptionList(null, false);
const wrapper = mountWithIntl(<TestProviders>{euiDescriptionList}</TestProviders>);
expect(wrapper.text()).toBe(
'Name--First Seen--Last Seen--Id--IP Address--MAC Addr--Type--Platform--OS Name--Family--Version--Architecture--'

View file

@ -32,13 +32,11 @@ import * as i18n from './translations';
interface OwnProps {
data: HostItem;
loading: boolean;
startDate: number;
endDate: number;
}
type HostSummaryProps = OwnProps;
export const HostSummary = pure<HostSummaryProps>(({ data, startDate, endDate, loading }) => (
export const HostSummary = pure<HostSummaryProps>(({ data, loading }) => (
<EuiFlexGroup>
<StyledEuiFlexItem>
<EuiPanel>
@ -46,7 +44,7 @@ export const HostSummary = pure<HostSummaryProps>(({ data, startDate, endDate, l
<h3>{i18n.SUMMARY}</h3>
</EuiTitle>
<EuiHorizontalRule margin="xs" />
{getEuiDescriptionList(data, startDate, endDate, loading)}
{getEuiDescriptionList(data, loading)}
</EuiPanel>
</StyledEuiFlexItem>
</EuiFlexGroup>
@ -67,12 +65,7 @@ const fieldTitleMapping: Readonly<Record<string, string>> = {
'host.architecture': i18n.ARCHITECTURE,
};
export const getEuiDescriptionList = (
host: HostItem | null,
startDate: number,
endDate: number,
loading: boolean
): JSX.Element => (
export const getEuiDescriptionList = (host: HostItem | null, loading: boolean): JSX.Element => (
<EuiDescriptionList type="column" compressed>
{Object.entries(fieldTitleMapping).map(([field, title]) => {
const summaryValue: string | string[] | null = get(field, host);
@ -88,11 +81,11 @@ export const getEuiDescriptionList = (
getEmptyTagValue()
) : (
summaryValue.map((value: string) =>
createDraggable(value, field, startDate, endDate, get('host.name', host))
createDraggable(value, field, get('host.name', host))
)
)
) : (
createDraggable(summaryValue, field, startDate, endDate, get('host.name', host))
createDraggable(summaryValue, field, get('host.name', host))
)}
</div>
</React.Fragment>
@ -104,8 +97,6 @@ export const getEuiDescriptionList = (
export const createDraggable = (
summaryValue: string | null,
field: string,
startDate: number,
endDate: number,
hostName: string | null | undefined
) =>
summaryValue == null && hostName != null && ['firstSeen', 'lastSeen'].includes(field) ? (
@ -123,7 +114,6 @@ export const createDraggable = (
name: summaryValue,
kqlQuery: '',
queryMatch: { field, value: summaryValue },
queryDate: { from: startDate, to: endDate },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -49,7 +49,6 @@ exports[`Load More Table Component rendering it renders the default Hosts table
loadMore={[MockFunction]}
loading={false}
nextCursor="aa7ca589f1b8220002f2fc61c64cfbf1"
startDate={1546965070707}
totalCount={4}
type="page"
/>

View file

@ -24,10 +24,7 @@ import { AddToKql } from '../../add_to_kql';
import * as i18n from './translations';
export const getHostsColumns = (
startDate: number,
type: hostsModel.HostsType
): Array<Columns<ValueOf<HostItem>>> => [
export const getHostsColumns = (type: hostsModel.HostsType): Array<Columns<ValueOf<HostItem>>> => [
{
field: 'node.host.name',
name: i18n.NAME,
@ -48,7 +45,6 @@ export const getHostsColumns = (
name: hostName,
kqlQuery: '',
queryMatch: { field: 'host.name', value: hostName },
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -20,7 +20,6 @@ import { mockData } from './mock';
describe('Load More Table Component', () => {
const loadMore = jest.fn();
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
const state: State = mockGlobalState;
let store = createStore(state);
@ -41,7 +40,6 @@ describe('Load More Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</KibanaConfigContext.Provider>
@ -62,7 +60,6 @@ describe('Load More Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -80,7 +77,6 @@ describe('Load More Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.Hosts.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.Hosts.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>

View file

@ -24,7 +24,6 @@ interface OwnProps {
loading: boolean;
hasNextPage: boolean;
nextCursor: string;
startDate: number;
totalCount: number;
loadMore: (cursor: string) => void;
type: hostsModel.HostsType;
@ -86,13 +85,12 @@ class HostsTableComponent extends React.PureComponent<HostsTableProps> {
totalCount,
nextCursor,
updateLimitPagination,
startDate,
sortField,
type,
} = this.props;
return (
<LoadMoreTable
columns={getHostsColumns(startDate, type)}
columns={getHostsColumns(type)}
loadingTitle={i18n.HOSTS}
loading={loading}
pageOfItems={data}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
export * from './authentications_table';
export * from './events_table';
export * from './hosts_table';
export * from './types_bar';
export * from './uncommon_process_table';

View file

@ -1,31 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`TypeBar Component rendering it renders the default TypesBar 1`] = `
<Component
data={
Array [
Object {
"x": 12,
"y": "user_login",
},
Object {
"x": 7,
"y": "user_err",
},
Object {
"x": 3,
"y": "user_start",
},
Object {
"x": 1,
"y": "cred_acq",
},
Object {
"x": 1,
"y": "cred_disp",
},
]
}
loading={false}
/>
`;

View file

@ -1,22 +0,0 @@
/*
* 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 { shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import * as React from 'react';
import { TypesBar } from './index';
import { mockData } from './mock';
describe('TypeBar Component', () => {
describe('rendering', () => {
test('it renders the default TypesBar', () => {
const wrapper = shallow(<TypesBar loading={false} data={mockData} />);
expect(toJson(wrapper)).toMatchSnapshot();
});
});
});

View file

@ -1,27 +0,0 @@
/*
* 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 React from 'react';
import { pure } from 'recompose';
import { HorizontalBarChart, HorizontalBarChartData } from '../../../horizontal_bar_chart';
import * as i18n from './translations';
interface TypesBarProps {
data: HorizontalBarChartData[];
loading: boolean;
}
export const TypesBar = pure<TypesBarProps>(({ data, loading }) => (
<HorizontalBarChart
loading={loading}
title={i18n.KPI_EVENT_TYPES}
width={490}
height={279}
barChartdata={data}
/>
));

View file

@ -1,30 +0,0 @@
/*
* 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 { HorizontalBarChartData } from '../../../horizontal_bar_chart';
export const mockData: HorizontalBarChartData[] = [
{
y: 'user_login',
x: 12,
},
{
y: 'user_err',
x: 7,
},
{
y: 'user_start',
x: 3,
},
{
y: 'cred_acq',
x: 1,
},
{
y: 'cred_disp',
x: 1,
},
];

View file

@ -1,11 +0,0 @@
/*
* 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 { i18n } from '@kbn/i18n';
export const KPI_EVENT_TYPES = i18n.translate('xpack.siem.typesBar.KpiEventTypes', {
defaultMessage: 'KPI Event Types',
});

View file

@ -205,7 +205,6 @@ exports[`UncommonProcess Table Component rendering it renders the default Uncomm
loadMore={[MockFunction]}
loading={false}
nextCursor="aa7ca589f1b8220002f2fc61c64cfbf1"
startDate={1546965070707}
totalCount={5}
type="page"
/>

View file

@ -18,7 +18,6 @@ import { mockData } from './mock';
describe('UncommonProcess Table Component', () => {
const loadMore = jest.fn();
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
describe('rendering', () => {
test('it renders the default Uncommon process table', () => {
@ -31,7 +30,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -50,7 +48,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -75,7 +72,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -101,7 +97,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -127,7 +122,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>
@ -152,7 +146,6 @@ describe('UncommonProcess Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.UncommonProcess.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.UncommonProcess.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={hostsModel.HostsType.page}
/>
</TestProviders>

View file

@ -29,7 +29,6 @@ interface OwnProps {
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
startDate: number;
type: hostsModel.HostsType;
}
@ -82,11 +81,10 @@ const UncommonProcessTableComponent = pure<UncommonProcessTableProps>(
totalCount,
nextCursor,
updateLimitPagination,
startDate,
type,
}) => (
<LoadMoreTable
columns={getUncommonColumns(startDate)}
columns={getUncommonColumns()}
loadingTitle={i18n.UNCOMMON_PROCESSES}
loading={loading}
pageOfItems={data}
@ -120,7 +118,7 @@ export const UncommonProcessTable = connect(
}
)(UncommonProcessTableComponent);
const getUncommonColumns = (startDate: number): Array<Columns<UncommonProcessesEdges>> => [
const getUncommonColumns = (): Array<Columns<UncommonProcessesEdges>> => [
{
name: i18n.NAME,
truncateText: false,
@ -145,10 +143,6 @@ const getUncommonColumns = (startDate: number): Array<Columns<UncommonProcessesE
field: 'process.name',
value: processName,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -188,10 +182,6 @@ const getUncommonColumns = (startDate: number): Array<Columns<UncommonProcessesE
field: 'user.name',
value: userName,
},
queryDate: {
from: startDate,
to: Date.now(),
},
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -256,7 +246,6 @@ const getUncommonColumns = (startDate: number): Array<Columns<UncommonProcessesE
field: 'host.name',
value: name[0],
},
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -63,14 +63,12 @@ exports[`Domains Table Component Rendering it renders the default Domains table
},
]
}
endDate={1549643470707}
flowTarget="source"
hasNextPage={false}
ip="10.10.10.10"
loadMore={[MockFunction]}
loading={false}
nextCursor="10"
startDate={1546965070707}
totalCount={1}
type="details"
/>

View file

@ -33,8 +33,6 @@ import * as i18n from './translations';
export const getDomainsColumns = (
ip: string,
startDate: number,
endDate: number,
flowDirection: FlowDirection,
flowTarget: FlowTarget,
type: networkModel.NetworkType,
@ -63,7 +61,6 @@ export const getDomainsColumns = (
excluded: false,
kqlQuery: '',
queryMatch: { field: domainNameAttr, value: domainName },
queryDate: { from: startDate, to: endDate },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -19,8 +19,6 @@ import { DomainsTable } from '.';
import { mockDomainsData } from './mock';
describe('Domains Table Component', () => {
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
const endDate = new Date('2019-02-08T16:31:10.707Z').valueOf();
const loadMore = jest.fn();
const ip = '10.10.10.10';
const state: State = mockGlobalState;
@ -44,8 +42,6 @@ describe('Domains Table Component', () => {
flowTarget={FlowTarget.source}
hasNextPage={getOr(false, 'hasNextPage', mockDomainsData.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockDomainsData.pageInfo)!}
startDate={startDate}
endDate={endDate}
type={networkModel.NetworkType.details}
/>
</ReduxStoreProvider>
@ -69,8 +65,6 @@ describe('Domains Table Component', () => {
flowTarget={FlowTarget.source}
hasNextPage={getOr(false, 'hasNextPage', mockDomainsData.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockDomainsData.pageInfo)!}
startDate={startDate}
endDate={endDate}
type={networkModel.NetworkType.details}
/>
</TestProviders>

View file

@ -35,8 +35,6 @@ interface OwnProps {
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
endDate: number;
startDate: number;
type: networkModel.NetworkType;
}
@ -97,8 +95,6 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
totalCount,
nextCursor,
updateDomainsLimit,
startDate,
endDate,
flowDirection,
flowTarget,
type,
@ -106,15 +102,7 @@ class DomainsTableComponent extends React.PureComponent<DomainsTableProps> {
return (
<LoadMoreTable
columns={getDomainsColumns(
ip,
startDate,
endDate,
flowDirection,
flowTarget,
type,
DomainsTableId
)}
columns={getDomainsColumns(ip, flowDirection, flowTarget, type, DomainsTableId)}
loadingTitle={i18n.DOMAINS}
loading={loading}
pageOfItems={data}

View file

@ -7,7 +7,7 @@
import { cloneDeep } from 'lodash/fp';
import * as React from 'react';
import { MockedProvider } from 'react-apollo/test-utils';
import { render, waitForElement } from 'react-testing-library';
import { render } from 'react-testing-library';
import { mockFirstLastSeenDomainQuery } from '../../../../containers/domains/first_last_seen_domain/mock';
import { FlowTarget } from '../../../../graphql/types';
@ -184,27 +184,4 @@ describe('FirstLastSeen Component', async () => {
await wait();
expect(container.textContent).toBe('something-invalid');
});
test('Show error message', async () => {
const myErrorMock = cloneDeep(mockFirstLastSeenDomainQuery);
delete myErrorMock[0].result;
myErrorMock[0].result = {
errors: [{ message: 'Error!' }],
};
const { container } = render(
<TestProviders>
<MockedProvider mocks={myErrorMock} addTypename={false}>
<FirstLastSeenDomain
ip={ip}
domainName={domainName}
flowTarget={FlowTarget.source}
type="last-seen"
/>
</MockedProvider>
</TestProviders>
);
await wait();
const alertIcon = await waitForElement(() => container.querySelectorAll('svg'), { container });
expect(alertIcon.length).toBe(1);
});
});

View file

@ -140,7 +140,6 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
loadMore={[MockFunction]}
loading={false}
nextCursor="10"
startDate={1546965070707}
totalCount={80}
type="page"
/>

View file

@ -20,7 +20,6 @@ import { Provider } from '../../../timeline/data_providers/provider';
import * as i18n from './translations';
export const getNetworkDnsColumns = (
startDate: number,
type: networkModel.NetworkType
): Array<Columns<ValueOf<NetworkDnsItem>>> => [
{
@ -43,7 +42,6 @@ export const getNetworkDnsColumns = (
excluded: false,
kqlQuery: '',
queryMatch: { field: 'dns.question.etld_plus_one', value: escapeQueryValue(dnsName) },
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -18,7 +18,6 @@ import { NetworkDnsTable } from '.';
import { mockData } from './mock';
describe('NetworkTopNFlow Table Component', () => {
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
const loadMore = jest.fn();
const state: State = mockGlobalState;
@ -39,7 +38,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkDns.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</ReduxStoreProvider>
@ -61,7 +59,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkDns.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkDns.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</TestProviders>

View file

@ -26,7 +26,6 @@ interface OwnProps {
hasNextPage: boolean;
nextCursor: string;
loadMore: (cursor: string) => void;
startDate: number;
totalCount: number;
type: networkModel.NetworkType;
}
@ -89,14 +88,13 @@ class NetworkDnsTableComponent extends React.PureComponent<NetworkDnsTableProps>
loading,
loadMore,
nextCursor,
startDate,
totalCount,
type,
updateDnsLimit,
} = this.props;
return (
<LoadMoreTable
columns={getNetworkDnsColumns(startDate, type)}
columns={getNetworkDnsColumns(type)}
loadingTitle={i18n.TOP_DNS_DOMAINS}
loading={loading}
pageOfItems={data}

View file

@ -55,7 +55,6 @@ exports[`NetworkTopNFlow Table Component rendering it renders the default Networ
loadMore={[MockFunction]}
loading={false}
nextCursor="10"
startDate={1546965070707}
totalCount={524}
type="page"
/>

View file

@ -31,7 +31,6 @@ import { AddToKql } from '../../add_to_kql';
import * as i18n from './translations';
export const getNetworkTopNFlowColumns = (
startDate: number,
flowDirection: FlowDirection,
flowTarget: FlowTarget,
type: networkModel.NetworkType,
@ -57,7 +56,6 @@ export const getNetworkTopNFlowColumns = (
excluded: false,
kqlQuery: '',
queryMatch: { field: ipAttr, value: ip },
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
@ -101,7 +99,6 @@ export const getNetworkTopNFlowColumns = (
excluded: false,
kqlQuery: '',
queryMatch: { field: domainAttr, value: domain },
queryDate: { from: startDate, to: Date.now() },
}}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (

View file

@ -19,7 +19,6 @@ import { NetworkTopNFlowTable, NetworkTopNFlowTableId } from '.';
import { mockData } from './mock';
describe('NetworkTopNFlow Table Component', () => {
const startDate = new Date('2019-01-08T16:31:10.707Z').valueOf();
const loadMore = jest.fn();
const state: State = mockGlobalState;
@ -40,7 +39,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</ReduxStoreProvider>
@ -66,7 +64,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</TestProviders>
@ -100,7 +97,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</TestProviders>
@ -141,7 +137,6 @@ describe('NetworkTopNFlow Table Component', () => {
hasNextPage={getOr(false, 'hasNextPage', mockData.NetworkTopNFlow.pageInfo)!}
nextCursor={getOr(null, 'endCursor.value', mockData.NetworkTopNFlow.pageInfo)!}
loadMore={loadMore}
startDate={startDate}
type={networkModel.NetworkType.page}
/>
</TestProviders>

View file

@ -34,7 +34,6 @@ interface OwnProps {
nextCursor: string;
totalCount: number;
loadMore: (cursor: string) => void;
startDate: number;
type: networkModel.NetworkType;
}
@ -99,7 +98,6 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
totalCount,
nextCursor,
updateTopNFlowLimit,
startDate,
flowDirection,
topNFlowSort,
flowTarget,
@ -114,13 +112,7 @@ class NetworkTopNFlowTableComponent extends React.PureComponent<NetworkTopNFlowT
return (
<LoadMoreTable
columns={getNetworkTopNFlowColumns(
startDate,
flowDirection,
flowTarget,
type,
NetworkTopNFlowTableId
)}
columns={getNetworkTopNFlowColumns(flowDirection, flowTarget, type, NetworkTopNFlowTableId)}
loadingTitle={i18n.TOP_TALKERS}
loading={loading}
pageOfItems={data}

View file

@ -15,7 +15,6 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { pure } from 'recompose';
import { ActionCreator } from 'typescript-fsa';
import { manageQuery } from '../../../../components/page/manage_query';
import { OverviewHostQuery } from '../../../../containers/overview/overview_host';
@ -23,15 +22,16 @@ import { inputsModel } from '../../../../store/inputs';
import { OverviewHostStats } from '../overview_host_stats';
export interface OwnProps {
poll: number;
startDate: number;
endDate: number;
setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
) => void;
}
const OverviewHostStatsManage = manageQuery(OverviewHostStats);
type OverviewHostProps = OwnProps;
export const OverviewHost = pure<OverviewHostProps>(({ endDate, poll, startDate, setQuery }) => (
export const OverviewHost = pure<OverviewHostProps>(({ endDate, startDate, setQuery }) => (
<EuiFlexItem>
<EuiPanel>
<EuiFlexGroup alignItems="center">
@ -55,7 +55,7 @@ export const OverviewHost = pure<OverviewHostProps>(({ endDate, poll, startDate,
<EuiHorizontalRule />
<OverviewHostQuery endDate={endDate} poll={poll} sourceId="default" startDate={startDate}>
<OverviewHostQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewHost, loading, id, refetch }) => (
<OverviewHostStatsManage
loading={loading}

View file

@ -15,7 +15,6 @@ import {
import { FormattedMessage } from '@kbn/i18n/react';
import React from 'react';
import { pure } from 'recompose';
import { ActionCreator } from 'typescript-fsa';
import { manageQuery } from '../../../../components/page/manage_query';
import { OverviewNetworkQuery } from '../../../../containers/overview/overview_network';
@ -23,15 +22,16 @@ import { inputsModel } from '../../../../store/inputs';
import { OverviewNetworkStats } from '../overview_network_stats';
export interface OwnProps {
poll: number;
startDate: number;
endDate: number;
setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
) => void;
}
const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats);
export const OverviewNetwork = pure<OwnProps>(({ endDate, poll, startDate, setQuery }) => (
export const OverviewNetwork = pure<OwnProps>(({ endDate, startDate, setQuery }) => (
<EuiFlexItem>
<EuiPanel>
<EuiFlexGroup alignItems="center">
@ -58,7 +58,7 @@ export const OverviewNetwork = pure<OwnProps>(({ endDate, poll, startDate, setQu
<EuiHorizontalRule />
<OverviewNetworkQuery endDate={endDate} poll={poll} sourceId="default" startDate={startDate}>
<OverviewNetworkQuery endDate={endDate} sourceId="default" startDate={startDate}>
{({ overviewNetwork, loading, id, refetch }) => (
<OverviewNetworkStatsManage
loading={loading}

View file

@ -6,8 +6,9 @@
import { mount } from 'enzyme';
import * as React from 'react';
import { Provider as ReduxStoreProvider } from 'react-redux';
import { mockGlobalState, TestProviders } from '../../mock';
import { mockGlobalState } from '../../mock';
import { createStore, State } from '../../store';
import { SuperDatePicker } from '.';
@ -18,20 +19,21 @@ describe('SIEM Super Date Picker', () => {
let store = createStore(state);
beforeEach(() => {
jest.clearAllMocks();
store = createStore(state);
});
describe('Pick Relative Date', () => {
let wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
beforeEach(() => {
wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
wrapper
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
@ -112,15 +114,15 @@ describe('SIEM Super Date Picker', () => {
describe('Recently used date ranges', () => {
let wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
beforeEach(() => {
wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
wrapper
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
@ -243,15 +245,15 @@ describe('SIEM Super Date Picker', () => {
describe('Refresh Every', () => {
let wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
beforeEach(() => {
wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
wrapper
.find('[data-test-subj="superDatePickerToggleQuickMenuButton"]')
@ -299,15 +301,15 @@ describe('SIEM Super Date Picker', () => {
describe('Pick Absolute Date', () => {
let wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
beforeEach(() => {
wrapper = mount(
<TestProviders store={store}>
<ReduxStoreProvider store={store}>
<SuperDatePicker id="global" />
</TestProviders>
</ReduxStoreProvider>
);
wrapper
.find('[data-test-subj="superDatePickerShowDatesButton"]')

View file

@ -50,20 +50,24 @@ interface SuperDatePickerStateRedux {
}
interface SuperDatePickerDispatchProps {
setAbsoluteSuperDatePicker: ActionCreator<{ id: string; from: number; to: number }>;
setAbsoluteSuperDatePicker: ActionCreator<{
id: inputsModel.InputsModelId;
from: number;
to: number;
}>;
setRelativeSuperDatePicker: ActionCreator<{
id: string;
id: inputsModel.InputsModelId;
fromStr: string;
from: number;
to: number;
toStr: string;
}>;
startAutoReload: ActionCreator<{ id: string }>;
stopAutoReload: ActionCreator<{ id: string }>;
setDuration: ActionCreator<{ id: string; duration: number }>;
startAutoReload: ActionCreator<{ id: inputsModel.InputsModelId }>;
stopAutoReload: ActionCreator<{ id: inputsModel.InputsModelId }>;
setDuration: ActionCreator<{ id: inputsModel.InputsModelId; duration: number }>;
}
interface OwnProps {
id: string;
id: inputsModel.InputsModelId;
disabled?: boolean;
}
@ -77,11 +81,8 @@ export type SuperDatePickerProps = OwnProps &
SuperDatePickerStateRedux;
export interface SuperDatePickerState {
isAutoRefreshOnly: boolean;
isPaused: boolean;
isQuickSelection: boolean;
recentlyUsedRanges: TimeArgs[];
refreshInterval: number;
showUpdateButton: boolean;
}
@ -93,17 +94,14 @@ export const SuperDatePickerComponent = class extends Component<
super(props);
this.state = {
isAutoRefreshOnly: false,
isPaused: true,
isQuickSelection: true,
recentlyUsedRanges: [],
refreshInterval: 300000,
showUpdateButton: true,
};
}
public render() {
const { end, start, kind, fromStr, toStr, isLoading } = this.props;
const { duration, end, start, kind, fromStr, policy, toStr, isLoading } = this.props;
const endDate = kind === 'relative' ? toStr : new Date(end).toISOString();
const startDate = kind === 'relative' ? fromStr : new Date(start).toISOString();
@ -111,12 +109,12 @@ export const SuperDatePickerComponent = class extends Component<
<MyEuiSuperDatePicker
end={endDate}
isLoading={isLoading}
isPaused={this.state.isPaused}
isPaused={policy === 'manual'}
onTimeChange={this.onTimeChange}
onRefreshChange={this.onRefreshChange}
onRefresh={this.onRefresh}
recentlyUsedRanges={this.state.recentlyUsedRanges}
refreshInterval={this.state.refreshInterval}
refreshInterval={duration}
showUpdateButton={this.state.showUpdateButton}
start={startDate}
/>
@ -129,7 +127,16 @@ export const SuperDatePickerComponent = class extends Component<
isQuickSelection: this.state.isQuickSelection,
isInvalid: false,
});
this.refetchQuery(this.props.refetch);
const currentStart = this.formatDate(start);
const currentEnd = this.state.isQuickSelection
? this.formatDate(end, { roundUp: true })
: this.formatDate(end);
if (
!this.state.isQuickSelection ||
(this.props.start === currentStart && this.props.end === currentEnd)
) {
this.refetchQuery(this.props.refetch);
}
};
private onRefreshChange = ({ isPaused, refreshInterval }: OnRefreshChangeProps): void => {
@ -151,11 +158,6 @@ export const SuperDatePickerComponent = class extends Component<
) {
this.refetchQuery(this.props.refetch);
}
this.setState({
...this.state,
isPaused,
});
};
private refetchQuery = (query: inputsModel.Refetch[]) => {

View file

@ -469,10 +469,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",
@ -485,10 +481,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 2",
"kqlQuery": "",
"name": "Provider 2",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 2",
@ -501,10 +493,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 3",
"kqlQuery": "",
"name": "Provider 3",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 3",
@ -517,10 +505,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 4",
"kqlQuery": "",
"name": "Provider 4",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 4",
@ -533,10 +517,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 5",
"kqlQuery": "",
"name": "Provider 5",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 5",
@ -549,10 +529,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 6",
"kqlQuery": "",
"name": "Provider 6",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 6",
@ -565,10 +541,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 7",
"kqlQuery": "",
"name": "Provider 7",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 7",
@ -581,10 +553,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 8",
"kqlQuery": "",
"name": "Provider 8",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 8",
@ -597,10 +565,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 9",
"kqlQuery": "",
"name": "Provider 9",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 9",
@ -613,10 +577,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"id": "id-Provider 10",
"kqlQuery": "",
"name": "Provider 10",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 10",
@ -624,6 +584,7 @@ Can be one or multiple IPv4 or IPv6 addresses.",
},
]
}
end={1521862432253}
flyoutHeaderHeight={48}
flyoutHeight={980}
id="foo"
@ -712,6 +673,7 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"title": "filebeat-*,auditbeat-*,packetbeat-*",
}
}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={
Array [
@ -735,5 +697,6 @@ Can be one or multiple IPv4 or IPv6 addresses.",
"sortDirection": "desc",
}
}
start={1521830963132}
/>
`;

View file

@ -11,10 +11,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",
@ -27,10 +23,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 2",
"kqlQuery": "",
"name": "Provider 2",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 2",
@ -43,10 +35,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 3",
"kqlQuery": "",
"name": "Provider 3",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 3",
@ -59,10 +47,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 4",
"kqlQuery": "",
"name": "Provider 4",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 4",
@ -75,10 +59,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 5",
"kqlQuery": "",
"name": "Provider 5",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 5",
@ -91,10 +71,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 6",
"kqlQuery": "",
"name": "Provider 6",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 6",
@ -107,10 +83,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 7",
"kqlQuery": "",
"name": "Provider 7",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 7",
@ -123,10 +95,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 8",
"kqlQuery": "",
"name": "Provider 8",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 8",
@ -139,10 +107,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 9",
"kqlQuery": "",
"name": "Provider 9",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 9",
@ -155,10 +119,6 @@ exports[`DataProviders rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 10",
"kqlQuery": "",
"name": "Provider 10",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 10",

View file

@ -10,10 +10,6 @@ exports[`Provider rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",

View file

@ -14,10 +14,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",
@ -30,10 +26,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 2",
"kqlQuery": "",
"name": "Provider 2",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 2",
@ -46,10 +38,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 3",
"kqlQuery": "",
"name": "Provider 3",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 3",
@ -62,10 +50,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 4",
"kqlQuery": "",
"name": "Provider 4",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 4",
@ -78,10 +62,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 5",
"kqlQuery": "",
"name": "Provider 5",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 5",
@ -94,10 +74,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 6",
"kqlQuery": "",
"name": "Provider 6",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 6",
@ -110,10 +86,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 7",
"kqlQuery": "",
"name": "Provider 7",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 7",
@ -126,10 +98,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 8",
"kqlQuery": "",
"name": "Provider 8",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 8",
@ -142,10 +110,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 9",
"kqlQuery": "",
"name": "Provider 9",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 9",
@ -158,10 +122,6 @@ exports[`Providers rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 10",
"kqlQuery": "",
"name": "Provider 10",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 10",

View file

@ -4,11 +4,6 @@
* you may not use this file except in compliance with the Elastic License.
*/
export interface QueryDate {
from: number;
to: number;
}
/** Represents the Timeline data providers */
export interface DataProvider {
/** Uniquely identifies a data provider */
@ -38,7 +33,6 @@ export interface DataProvider {
value: string | number;
displayValue?: string | number;
};
queryDate?: QueryDate;
/**
* Additional query clauses that are ANDed with this query to narrow results
*/

View file

@ -49,10 +49,6 @@ export const mockDataProviders: DataProvider[] = Object.keys(mockSourceNameToEve
field: 'name',
value: name,
},
queryDate: {
from: 1521830963132,
to: 1521862432253,
},
and: [],
})
);

View file

@ -23,7 +23,6 @@ export const Provider = pure<OwnProps>(({ dataProvider }) => (
isEnabled={dataProvider.enabled}
isExcluded={dataProvider.excluded}
providerId={dataProvider.id}
queryDate={dataProvider.queryDate}
toggleEnabledProvider={noop}
toggleExcludedProvider={noop}
val={dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value}

View file

@ -4,15 +4,12 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiBadge, EuiIcon, EuiToolTip } from '@elastic/eui';
import { EuiBadge } from '@elastic/eui';
import classNames from 'classnames';
import { isEmpty } from 'lodash/fp';
import moment from 'moment';
import React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
import { QueryDate } from './data_provider';
import * as i18n from './translations';
const ProviderBadgeStyled = styled(EuiBadge)`
@ -43,13 +40,12 @@ interface ProviderBadgeProps {
isEnabled: boolean;
isExcluded: boolean;
providerId: string;
queryDate?: QueryDate;
togglePopover?: () => void;
val: string | number;
}
export const ProviderBadge = pure<ProviderBadgeProps>(
({ deleteProvider, field, isEnabled, isExcluded, queryDate, providerId, togglePopover, val }) => {
({ deleteProvider, field, isEnabled, isExcluded, providerId, togglePopover, val }) => {
const deleteFilter: React.MouseEventHandler<HTMLButtonElement> = (
event: React.MouseEvent<HTMLButtonElement>
) => {
@ -67,10 +63,6 @@ export const ProviderBadge = pure<ProviderBadgeProps>(
const title = `${field}: "${val}"`;
const tooltipStr = isEmpty(queryDate)
? null
: `${moment(queryDate!.from).format('L LTS')} - ${moment(queryDate!.to).format('L LTS')}`;
return (
<ProviderBadgeStyled
id={`${providerId}-${field}-${val}`}
@ -90,11 +82,6 @@ export const ProviderBadge = pure<ProviderBadgeProps>(
}}
data-test-subj="providerBadge"
>
{tooltipStr !== null && (
<EuiToolTip data-test-subj="add-tool-tip" content={tooltipStr} position="bottom">
<EuiIcon type="calendar" />
</EuiToolTip>
)}
{prefix}
<span className="field-value">{field}: </span>
<span className="field-value">&quot;{val}&quot;</span>

View file

@ -6,7 +6,6 @@
import React, { PureComponent } from 'react';
import { QueryDate } from './data_provider';
import { ProviderBadge } from './provider_badge';
import { ProviderItemActions } from './provider_item_actions';
@ -17,7 +16,6 @@ interface ProviderItemBadgeProps {
isEnabled: boolean;
isExcluded: boolean;
providerId: string;
queryDate?: QueryDate;
toggleEnabledProvider: () => void;
toggleExcludedProvider: () => void;
val: string | number;
@ -33,16 +31,7 @@ export class ProviderItemBadge extends PureComponent<ProviderItemBadgeProps, Own
};
public render() {
const {
deleteProvider,
field,
kqlQuery,
isEnabled,
isExcluded,
queryDate,
providerId,
val,
} = this.props;
const { deleteProvider, field, kqlQuery, isEnabled, isExcluded, providerId, val } = this.props;
const badge = (
<ProviderBadge
@ -52,7 +41,6 @@ export class ProviderItemBadge extends PureComponent<ProviderItemBadgeProps, Own
isEnabled={isEnabled}
isExcluded={isExcluded}
providerId={providerId}
queryDate={queryDate}
togglePopover={this.togglePopover}
val={val}
/>

View file

@ -149,7 +149,6 @@ export const Providers = pure<Props>(
toggleEnabledProvider={toggleEnabledProvider}
toggleExcludedProvider={toggleExcludedProvider}
providerId={dataProvider.id}
queryDate={dataProvider.queryDate}
val={
dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value
}

View file

@ -27,6 +27,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -49,6 +50,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={false}
height={100}
isLive={false}
isLoading={true}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -72,6 +74,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -96,6 +99,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={true}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -129,6 +133,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={false}
height={100}
isLive={false}
isLoading={true}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -152,6 +157,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={1}
@ -182,6 +188,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
@ -211,6 +218,7 @@ describe('Footer Timeline Component', () => {
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={1}
@ -236,5 +244,57 @@ describe('Footer Timeline Component', () => {
.simulate('click');
expect(onChangeItemsPerPage).toBeCalled();
});
test('it does render the auto-refresh message instead of load more button when stream live is on', () => {
const wrapper = mount(
<TestProviders>
<Footer
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={true}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
itemsPerPageOptions={[1, 5, 10, 20]}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!}
tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)!}
getUpdatedAt={getUpdatedAt}
width={width}
/>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="paging-control"]').exists()).toBeFalsy();
expect(wrapper.find('[data-test-subj="is-live-on-message"]').exists()).toBeTruthy();
});
test('it does render the load more button when stream live is off', () => {
const wrapper = mount(
<TestProviders>
<Footer
serverSideEventCount={mockData.Events.totalCount}
hasNextPage={getOr(false, 'hasNextPage', mockData.Events.pageInfo)!}
height={100}
isLive={false}
isLoading={false}
itemsCount={mockData.Events.edges.length}
itemsPerPage={2}
itemsPerPageOptions={[1, 5, 10, 20]}
onChangeItemsPerPage={onChangeItemsPerPage}
onLoadMore={loadMore}
nextCursor={getOr(null, 'endCursor.value', mockData.Events.pageInfo)!}
tieBreaker={getOr(null, 'endCursor.tiebreaker', mockData.Events.pageInfo)!}
getUpdatedAt={getUpdatedAt}
width={width}
/>
</TestProviders>
);
expect(wrapper.find('[data-test-subj="paging-control"]').exists()).toBeTruthy();
expect(wrapper.find('[data-test-subj="is-live-on-message"]').exists()).toBeFalsy();
});
});
});

View file

@ -12,9 +12,12 @@ import {
EuiContextMenuPanel,
EuiFlexGroup,
EuiFlexItem,
EuiIconTip,
EuiPopover,
EuiText,
EuiToolTip,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
import * as React from 'react';
import { pure } from 'recompose';
import styled from 'styled-components';
@ -61,6 +64,7 @@ export const isCompactFooter = (width: number): boolean => width < 600;
interface FooterProps {
itemsCount: number;
isLive: boolean;
isLoading: boolean;
itemsPerPage: number;
itemsPerPageOptions: number[];
@ -173,6 +177,7 @@ export class Footer extends React.PureComponent<FooterProps, FooterState> {
public render() {
const {
height,
isLive,
isLoading,
itemsCount,
itemsPerPage,
@ -247,12 +252,32 @@ export class Footer extends React.PureComponent<FooterProps, FooterState> {
</EuiFlexItem>
<EuiFlexItem data-test-subj="paging-control-container" grow={false}>
<PagingControl
data-test-subj="paging-control"
hasNextPage={hasNextPage}
isLoading={isLoading}
loadMore={this.loadMore}
/>
{isLive ? (
<EuiText size="s" data-test-subj="is-live-on-message">
<b>
{i18n.AUTO_REFRESH_ACTIVE}
<EuiIconTip
content={
<FormattedMessage
id="xpack.siem.footer.autoRefreshActiveTooltip"
defaultMessage="While auto-refresh is enabled, timeline will show you the latest {numberOfItems} events that match your query."
values={{
numberOfItems: itemsCount,
}}
/>
}
position="top"
/>
</b>
</EuiText>
) : (
<PagingControl
data-test-subj="paging-control"
hasNextPage={hasNextPage}
isLoading={isLoading}
loadMore={this.loadMore}
/>
)}
</EuiFlexItem>
<EuiFlexItem data-test-subj="last-updated-container" grow={false}>

View file

@ -37,3 +37,10 @@ export const TOTAL_COUNT_OF_EVENTS = i18n.translate('xpack.siem.footer.totalCoun
export const UPDATED = i18n.translate('xpack.siem.footer.updated', {
defaultMessage: 'Updated',
});
export const AUTO_REFRESH_ACTIVE = i18n.translate(
'xpack.siem.footer.autoRefreshActiveDescription',
{
defaultMessage: 'Auto-Refresh Active',
}
);

View file

@ -11,10 +11,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 1",
"kqlQuery": "",
"name": "Provider 1",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 1",
@ -27,10 +23,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 2",
"kqlQuery": "",
"name": "Provider 2",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 2",
@ -43,10 +35,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 3",
"kqlQuery": "",
"name": "Provider 3",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 3",
@ -59,10 +47,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 4",
"kqlQuery": "",
"name": "Provider 4",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 4",
@ -75,10 +59,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 5",
"kqlQuery": "",
"name": "Provider 5",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 5",
@ -91,10 +71,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 6",
"kqlQuery": "",
"name": "Provider 6",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 6",
@ -107,10 +83,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 7",
"kqlQuery": "",
"name": "Provider 7",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 7",
@ -123,10 +95,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 8",
"kqlQuery": "",
"name": "Provider 8",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 8",
@ -139,10 +107,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 9",
"kqlQuery": "",
"name": "Provider 9",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 9",
@ -155,10 +119,6 @@ exports[`Header rendering renders correctly against snapshot 1`] = `
"id": "id-Provider 10",
"kqlQuery": "",
"name": "Provider 10",
"queryDate": Object {
"from": 1521830963132,
"to": 1521862432253,
},
"queryMatch": Object {
"field": "name",
"value": "Provider 10",

View file

@ -12,31 +12,27 @@ import { mockDataProviders } from './data_providers/mock/mock_data_providers';
import { buildGlobalQuery, combineQueries } from './helpers';
const cleanUpKqlQuery = (str: string) => str.replace(/\n/g, '').replace(/\s\s+/g, ' ');
const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf();
const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf();
describe('Build KQL Query', () => {
test('Buld KQL query with one data provider', () => {
const dataProviders = mockDataProviders.slice(0, 1);
const kqlQuery = buildGlobalQuery(dataProviders);
expect(cleanUpKqlQuery(kqlQuery)).toEqual(
'( name : Provider 1 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 )'
);
expect(cleanUpKqlQuery(kqlQuery)).toEqual('( name : Provider 1 )');
});
test('Buld KQL query with two data provider', () => {
const dataProviders = mockDataProviders.slice(0, 2);
const kqlQuery = buildGlobalQuery(dataProviders);
expect(cleanUpKqlQuery(kqlQuery)).toEqual(
'( name : Provider 1 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 ) or ( name : Provider 2 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 )'
);
expect(cleanUpKqlQuery(kqlQuery)).toEqual('( name : Provider 1 ) or ( name : Provider 2 )');
});
test('Buld KQL query with one data provider and one and', () => {
const dataProviders = cloneDeep(mockDataProviders.slice(0, 1));
dataProviders[0].and = mockDataProviders.slice(1, 2);
const kqlQuery = buildGlobalQuery(dataProviders);
expect(cleanUpKqlQuery(kqlQuery)).toEqual(
'( name : Provider 1 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 and name : Provider 2)'
);
expect(cleanUpKqlQuery(kqlQuery)).toEqual('( name : Provider 1 and name : Provider 2)');
});
test('Buld KQL query with two data provider and mutiple and', () => {
@ -45,28 +41,42 @@ describe('Build KQL Query', () => {
dataProviders[1].and = mockDataProviders.slice(4, 5);
const kqlQuery = buildGlobalQuery(dataProviders);
expect(cleanUpKqlQuery(kqlQuery)).toEqual(
'( name : Provider 1 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 and name : Provider 3 and name : Provider 4) or ( name : Provider 2 and @timestamp >= 1521830963132 and @timestamp <= 1521862432253 and name : Provider 5)'
'( name : Provider 1 and name : Provider 3 and name : Provider 4) or ( name : Provider 2 and name : Provider 5)'
);
});
});
describe('Combined Queries', () => {
test('No Data Provider & No kqlQuery', () => {
expect(combineQueries([], mockIndexPattern, '', 'search')).toBeNull();
expect(combineQueries([], mockIndexPattern, '', 'search', startDate, endDate)).toBeNull();
});
test('Only Data Provider', () => {
const dataProviders = mockDataProviders.slice(0, 1);
const { filterQuery } = combineQueries(dataProviders, mockIndexPattern, '', 'search')!;
const { filterQuery } = combineQueries(
dataProviders,
mockIndexPattern,
'',
'search',
startDate,
endDate
)!;
expect(filterQuery).toEqual(
'{"bool":{"filter":[{"bool":{"should":[{"match":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}'
);
});
test('Only KQL search/filter query', () => {
const { filterQuery } = combineQueries([], mockIndexPattern, 'host.name: "host-1"', 'search')!;
const { filterQuery } = combineQueries(
[],
mockIndexPattern,
'host.name: "host-1"',
'search',
startDate,
endDate
)!;
expect(filterQuery).toEqual(
'{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}'
'{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}'
);
});
@ -76,10 +86,12 @@ describe('Combined Queries', () => {
dataProviders,
mockIndexPattern,
'host.name: "host-1"',
'search'
'search',
startDate,
endDate
)!;
expect(filterQuery).toEqual(
'{"bool":{"should":[{"bool":{"filter":[{"bool":{"should":[{"match":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}}'
'{"bool":{"filter":[{"bool":{"should":[{"bool":{"should":[{"match":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}'
);
});
@ -89,10 +101,12 @@ describe('Combined Queries', () => {
dataProviders,
mockIndexPattern,
'host.name: "host-1"',
'filter'
'filter',
startDate,
endDate
)!;
expect(filterQuery).toEqual(
'{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}}'
'{"bool":{"filter":[{"bool":{"filter":[{"bool":{"should":[{"match":{"name":"Provider 1"}}],"minimum_should_match":1}},{"bool":{"should":[{"match_phrase":{"host.name":"host-1"}}],"minimum_should_match":1}}]}},{"bool":{"filter":[{"bool":{"should":[{"range":{"@timestamp":{"gte":1521830963132}}}],"minimum_should_match":1}},{"bool":{"should":[{"range":{"@timestamp":{"lte":1521862432253}}}],"minimum_should_match":1}}]}}]}}'
);
});
});

View file

@ -22,11 +22,6 @@ const buildQueryMatch = (dataProvider: DataProvider) =>
: ''
}`.trim();
const buildQueryDate = (dataProvider: DataProvider) =>
dataProvider.queryDate
? `@timestamp >= ${dataProvider.queryDate.from} and @timestamp <= ${dataProvider.queryDate.to}`
: '';
const buildQueryForAndProvider = (dataAndProviders: DataProvider[]) =>
dataAndProviders
.reduce((andQuery, andDataProvider) => {
@ -44,7 +39,6 @@ export const buildGlobalQuery = (dataProviders: DataProvider[]) =>
return dataProvider.enabled
? `${prepend(query)}(
${buildQueryMatch(dataProvider)}
${dataProvider.queryDate ? ` and ${buildQueryDate(dataProvider)}` : ''}
${
dataProvider.and.length > 0 ? ` and ${buildQueryForAndProvider(dataProvider.and)}` : ''
})`.trim()
@ -56,22 +50,34 @@ export const combineQueries = (
dataProviders: DataProvider[],
indexPattern: StaticIndexPattern,
kqlQuery: string,
kqlMode: string
kqlMode: string,
start: number,
end: number
): { filterQuery: string } | null => {
if (isEmpty(dataProviders) && isEmpty(kqlQuery)) {
return null;
} else if (isEmpty(dataProviders) && !isEmpty(kqlQuery)) {
return {
filterQuery: convertKueryToElasticSearchQuery(kqlQuery, indexPattern),
filterQuery: convertKueryToElasticSearchQuery(
`(${kqlQuery}) and @timestamp >= ${start} and @timestamp <= ${end}`,
indexPattern
),
};
} else if (!isEmpty(dataProviders) && isEmpty(kqlQuery)) {
return {
filterQuery: convertKueryToElasticSearchQuery(buildGlobalQuery(dataProviders), indexPattern),
filterQuery: convertKueryToElasticSearchQuery(
`((${buildGlobalQuery(
dataProviders
)}) and @timestamp >= ${start} and @timestamp <= ${end})`,
indexPattern
),
};
}
const operatorKqlQuery = kqlMode === 'filter' ? 'and' : 'or';
const postpend = (q: string) => `${!isEmpty(q) ? ` ${operatorKqlQuery} (${q})` : ''}`;
const globalQuery = `${buildGlobalQuery(dataProviders)}${postpend(kqlQuery)}`;
const globalQuery = `((${buildGlobalQuery(dataProviders)}${postpend(
kqlQuery
)}) and @timestamp >= ${start} and @timestamp <= ${end})`;
return {
filterQuery: convertKueryToElasticSearchQuery(globalQuery, indexPattern),

View file

@ -10,7 +10,14 @@ import { ActionCreator } from 'typescript-fsa';
import { WithSource } from '../../containers/source';
import { IndexType } from '../../graphql/types';
import { State, timelineActions, timelineModel, timelineSelectors } from '../../store';
import {
inputsModel,
inputsSelectors,
State,
timelineActions,
timelineModel,
timelineSelectors,
} from '../../store';
import { ColumnHeader } from './body/column_headers/column_header';
import { Sort } from './body/sort';
@ -35,12 +42,15 @@ interface StateReduxProps {
activePage?: number;
columns: ColumnHeader[];
dataProviders?: DataProvider[];
end: number;
isLive: boolean;
itemsPerPage?: number;
itemsPerPageOptions?: number[];
kqlMode: timelineModel.KqlMode;
kqlQueryExpression: string;
pageCount?: number;
sort?: Sort;
start: number;
show?: boolean;
}
@ -107,14 +117,17 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
const {
columns,
dataProviders,
end,
flyoutHeight,
flyoutHeaderHeight,
id,
isLive,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
kqlQueryExpression,
show,
start,
sort,
} = this.props;
@ -126,9 +139,11 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
columns={columns}
id={id}
dataProviders={dataProviders!}
end={end}
flyoutHeaderHeight={flyoutHeaderHeight}
flyoutHeight={flyoutHeight}
indexPattern={indexPattern}
isLive={isLive}
itemsPerPage={itemsPerPage!}
itemsPerPageOptions={itemsPerPageOptions!}
kqlMode={kqlMode}
@ -140,6 +155,7 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
onToggleDataProviderEnabled={this.onToggleDataProviderEnabled}
onToggleDataProviderExcluded={this.onToggleDataProviderExcluded}
show={show!}
start={start}
sort={sort!}
/>
)}
@ -189,8 +205,10 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
const makeMapStateToProps = () => {
const getTimeline = timelineSelectors.getTimelineByIdSelector();
const getKqlQueryTimeline = timelineSelectors.getKqlFilterQuerySelector();
const getInputsTimeline = inputsSelectors.getTimelineSelector();
const mapStateToProps = (state: State, { id }: OwnProps) => {
const timeline: timelineModel.TimelineModel = getTimeline(state, id);
const input: inputsModel.InputsRange = getInputsTimeline(state);
const {
columns,
dataProviders,
@ -205,12 +223,15 @@ const makeMapStateToProps = () => {
return {
columns,
dataProviders,
end: input.timerange.to,
id,
isLive: input.policy.kind === 'interval',
itemsPerPage,
itemsPerPageOptions,
kqlMode,
kqlQueryExpression,
sort,
start: input.timerange.from,
show,
};
};

View file

@ -1,112 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Properties renders correctly against snapshot 1`] = `
<styled.div
data-test-subj="timeline-properties"
width={940}
>
<Styled(EuiFlexGroup)
alignItems="center"
data-test-subj="properties-left"
gutterSize="s"
>
<EuiFlexItem
grow={false}
>
<pure(Component)
isFavorite={false}
timelineId="abc"
updateIsFavorite={[MockFunction]}
/>
</EuiFlexItem>
<pure(Component)
timelineId="abc"
title=""
updateTitle={[MockFunction]}
/>
<EuiFlexItem
grow={true}
>
<pure(Component)
description=""
timelineId="abc"
updateDescription={[MockFunction]}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<pure(Component)
animate={true}
associateNote={[MockFunction]}
getNotesByIds={[MockFunction]}
noteIds={Array []}
showNotes={false}
size="l"
text="Notes"
toggleShowNotes={[Function]}
toolTip="Add and review notes about this Timeline. Notes may also be added to events."
updateNote={[MockFunction]}
/>
</EuiFlexItem>
</Styled(EuiFlexGroup)>
<Styled(EuiFlexGroup)
alignItems="center"
data-test-subj="properties-right"
gutterSize="s"
>
<EuiFlexItem
grow={false}
>
<pure(Component)
history={Array []}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<pure(Component)
isLive={false}
timelineId="abc"
updateIsLive={[MockFunction]}
/>
</EuiFlexItem>
<EuiFlexItem
grow={false}
>
<EuiPopover
anchorPosition="downRight"
button={
<EuiIcon
data-test-subj="settings-gear"
onClick={[Function]}
size="l"
type="gear"
/>
}
closePopover={[Function]}
hasArrow={true}
id="timelineSettingsPopover"
isOpen={false}
ownFocus={false}
panelPaddingSize="m"
>
<EuiForm>
<EuiFormRow
describedByIds={Array []}
fullWidth={false}
hasEmptyLabelSpace={false}
labelType="label"
>
<pure(Component)
createTimeline={[MockFunction]}
onClosePopover={[Function]}
timelineId="abc"
/>
</EuiFormRow>
</EuiForm>
</EuiPopover>
</EuiFlexItem>
</Styled(EuiFlexGroup)>
</styled.div>
`;

View file

@ -17,7 +17,6 @@ import * as React from 'react';
import { pure } from 'recompose';
import uuid from 'uuid';
import { History } from '../../../lib/history';
import { Note } from '../../../lib/note';
import { Notes } from '../../notes';
import { AssociateNote, UpdateNote } from '../../notes/helpers';
@ -25,7 +24,6 @@ import { AssociateNote, UpdateNote } from '../../notes/helpers';
import {
ButtonContainer,
DescriptionContainer,
HistoryButtonLabel,
LabelText,
NameField,
NotesButtonLabel,
@ -44,7 +42,6 @@ export const NOTES_PANEL_HEIGHT = 633;
type CreateTimeline = ({ id, show }: { id: string; show?: boolean }) => void;
type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void;
type UpdateIsLive = ({ id, isLive }: { id: string; isLive: boolean }) => void;
type UpdateTitle = ({ id, title }: { id: string; title: string }) => void;
type UpdateDescription = ({ id, description }: { id: string; description: string }) => void;
@ -260,44 +257,3 @@ export const NotesButton = pure<NotesButtonProps>(
</EuiToolTip>
)
);
export const HistoryButton = pure<{ history: History[] }>(({ history }) => (
<EuiToolTip data-test-subj="timeline-history-tool-tip" content={i18n.HISTORY_TOOL_TIP}>
<EuiButton
data-test-subj="timeline-history"
iconType="arrowDown"
iconSide="right"
isDisabled={true}
onClick={() => window.alert('Show history')}
size="l"
>
<HistoryButtonLabel>
<EuiBadge data-test-subj="history-count" color="hollow">
{history.length}
</EuiBadge>
<LabelText>{i18n.HISTORY}</LabelText>
</HistoryButtonLabel>
</EuiButton>
</EuiToolTip>
));
export const StreamLive = pure<{ isLive: boolean; timelineId: string; updateIsLive: UpdateIsLive }>(
({ isLive, timelineId, updateIsLive }) => (
<EuiToolTip data-test-subj="timeline-stream-tool-tip" content={i18n.STREAM_LIVE_TOOL_TIP}>
<ButtonContainer animate={true}>
<EuiButton
data-test-subj="timeline-stream-live"
color={isLive ? 'secondary' : 'primary'}
fill={isLive ? true : false}
iconType="play"
iconSide="left"
isDisabled={true}
onClick={() => updateIsLive({ id: timelineId, isLive: !isLive })}
size="l"
>
{i18n.STREAM_LIVE}
</EuiButton>
</ButtonContainer>
</EuiToolTip>
)
);

View file

@ -4,66 +4,74 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { mount, shallow } from 'enzyme';
import toJson from 'enzyme-to-json';
import { mount } from 'enzyme';
import * as React from 'react';
import { Provider as ReduxStoreProvider } from 'react-redux';
import {
Properties,
showDescriptionThreshold,
showHistoryThreshold,
showStreamLiveThreshold,
} from '.';
import { mockGlobalState } from '../../../mock';
import { createStore, State } from '../../../store';
import { Properties, showDescriptionThreshold, showNotesThreshold } from '.';
describe('Properties', () => {
const usersViewing = ['elastic'];
test('renders correctly against snapshot', () => {
const wrapper = shallow(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
const state: State = mockGlobalState;
let store = createStore(state);
beforeEach(() => {
jest.clearAllMocks();
store = createStore(state);
});
test('renders correctly', () => {
const wrapper = mount(
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(toJson(wrapper)).toMatchSnapshot();
expect(wrapper.find('[data-test-subj="timeline-properties"]').exists()).toEqual(true);
});
test('it renders an empty star icon when it is NOT a favorite', () => {
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="timeline-favorite-empty-star"]').exists()).toEqual(true);
@ -71,25 +79,26 @@ describe('Properties', () => {
test('it renders a filled star icon when it is a favorite', () => {
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={true}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={true}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="timeline-favorite-filled-star"]').exists()).toEqual(true);
@ -99,25 +108,26 @@ describe('Properties', () => {
const title = 'foozle';
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title={title}
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title={title}
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(
@ -128,30 +138,125 @@ describe('Properties', () => {
).toEqual(title);
});
test('it renders the date picker with the lock icon', () => {
const wrapper = mount(
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(
wrapper
.find('[data-test-subj="properties-left"]')
.find('[data-test-subj="timeline-date-picker-container"]')
.exists()
).toEqual(true);
});
test('it renders the lock icon when isDatepickerLocked is true', () => {
const wrapper = mount(
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={true}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(
wrapper
.find('[data-test-subj="properties-left"]')
.find('[data-test-subj="timeline-date-picker-lock-button"]')
.exists()
).toEqual(true);
});
test('it renders the unlock icon when isDatepickerLocked is false', () => {
const wrapper = mount(
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(
wrapper
.find('[data-test-subj="properties-left"]')
.find('[data-test-subj="timeline-date-picker-unlock-button"]')
.exists()
).toEqual(true);
});
test('it renders a description on the left when the width is at least as wide as the threshold', () => {
const description = 'strange';
const width = showDescriptionThreshold;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description={description}
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description={description}
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
</ReduxStoreProvider>
);
expect(
@ -168,25 +273,26 @@ describe('Properties', () => {
const width = showDescriptionThreshold - 1;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description={description}
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description={description}
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
</ReduxStoreProvider>
);
expect(
@ -197,29 +303,30 @@ describe('Properties', () => {
).toEqual(false);
});
test('it renders a notes button on the left', () => {
const width = showDescriptionThreshold - 1;
test('it renders a notes button on the left when the width is at least as wide as the threshold', () => {
const width = showNotesThreshold;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
</ReduxStoreProvider>
);
expect(
@ -230,159 +337,62 @@ describe('Properties', () => {
).toEqual(true);
});
test('it renders a history button on the right when the width is at least as wide as the threshold', () => {
const width = showHistoryThreshold;
test('it does NOT render a a notes button on the left when the width is less than the threshold', () => {
const width = showNotesThreshold - 1;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
</ReduxStoreProvider>
);
expect(
wrapper
.find('[data-test-subj="properties-right"]')
.find('[data-test-subj="timeline-history"]')
.exists()
).toEqual(true);
});
test('it does NOT renders a history button on the right when the width is less than the threshold', () => {
const width = showHistoryThreshold - 1;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
);
expect(
wrapper
.find('[data-test-subj="properties-right"]')
.find('[data-test-subj="timeline-history"]')
.exists()
).toEqual(false);
});
test('it renders a stream live button on the right when the width is at least as wide as the threshold', () => {
const width = showStreamLiveThreshold;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
);
expect(
wrapper
.find('[data-test-subj="properties-right"]')
.find('[data-test-subj="timeline-stream-live"]')
.exists()
).toEqual(true);
});
test('it does NOT render a stream live button on the right when the width is less than the threshold', () => {
const width = showStreamLiveThreshold - 1;
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={width}
/>
);
expect(
wrapper
.find('[data-test-subj="properties-right"]')
.find('[data-test-subj="timeline-stream-live"]')
.find('[data-test-subj="properties-left"]')
.find('[data-test-subj="timeline-notes-button-large"]')
.exists()
).toEqual(false);
});
test('it renders a settings icon', () => {
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="settings-gear"]').exists()).toEqual(true);
@ -392,25 +402,26 @@ describe('Properties', () => {
const title = 'port scan';
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title={title}
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title={title}
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="avatar"]').exists()).toEqual(true);
@ -418,25 +429,26 @@ describe('Properties', () => {
test('it does NOT render an avatar for the current user viewing the timeline when it does NOT have a title', () => {
const wrapper = mount(
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isFavorite={false}
isLive={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
history={[]}
timelineId="abc"
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateIsLive={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
<ReduxStoreProvider store={store}>
<Properties
associateNote={jest.fn()}
createTimeline={jest.fn()}
isDatepickerLocked={false}
isFavorite={false}
title=""
description=""
getNotesByIds={jest.fn()}
noteIds={[]}
timelineId="abc"
toggleLock={jest.fn()}
updateDescription={jest.fn()}
updateIsFavorite={jest.fn()}
updateTitle={jest.fn()}
updateNote={jest.fn()}
usersViewing={usersViewing}
width={1000}
/>
</ReduxStoreProvider>
);
expect(wrapper.find('[data-test-subj="avatar"]').exists()).toEqual(false);

View file

@ -6,6 +6,8 @@
import {
EuiAvatar,
EuiButtonIcon,
EuiFlexGroup,
EuiFlexItem,
EuiForm,
EuiFormRow,
@ -16,27 +18,26 @@ import {
import * as React from 'react';
import styled, { injectGlobal } from 'styled-components';
import { History } from '../../../lib/history';
import { Note } from '../../../lib/note';
import { inputsModel } from '../../../store';
import { AssociateNote, UpdateNote } from '../../notes/helpers';
import { SuperDatePicker } from '../../super_date_picker';
import { Description, Name, NewTimeline, NotesButton, StarIcon } from './helpers';
import {
Description,
HistoryButton,
Name,
NewTimeline,
NotesButton,
StarIcon,
StreamLive,
} from './helpers';
import { PropertiesLeft, PropertiesRight, TimelineProperties } from './styles';
DatePicker,
PropertiesLeft,
PropertiesRight,
TimelineProperties,
LockIconContainer,
} from './styles';
import * as i18n from './translations';
type CreateTimeline = ({ id, show }: { id: string; show?: boolean }) => void;
type UpdateIsFavorite = ({ id, isFavorite }: { id: string; isFavorite: boolean }) => void;
type UpdateIsLive = ({ id, isLive }: { id: string; isLive: boolean }) => void;
type UpdateTitle = ({ id, title }: { id: string; title: string }) => void;
type UpdateDescription = ({ id, description }: { id: string; description: string }) => void;
type ToggleLock = ({ linkToId }: { linkToId: inputsModel.InputsModelId }) => void;
// SIDE EFFECT: the following `injectGlobal` overrides `EuiPopover`
// and `EuiToolTip` global styles:
@ -57,17 +58,16 @@ const Avatar = styled(EuiAvatar)`
interface Props {
associateNote: AssociateNote;
createTimeline: CreateTimeline;
isDatepickerLocked: boolean;
isFavorite: boolean;
isLive: boolean;
title: string;
description: string;
getNotesByIds: (noteIds: string[]) => Note[];
noteIds: string[];
history: History[];
timelineId: string;
toggleLock: ToggleLock;
updateDescription: UpdateDescription;
updateIsFavorite: UpdateIsFavorite;
updateIsLive: UpdateIsLive;
updateTitle: UpdateTitle;
updateNote: UpdateNote;
usersViewing: string[];
@ -80,9 +80,15 @@ interface State {
}
const rightGutter = 60; // px
export const showDescriptionThreshold = 610;
export const showHistoryThreshold = 760;
export const showStreamLiveThreshold = 900;
export const datePickerThreshold = 600;
export const showNotesThreshold = 810;
export const showDescriptionThreshold = 970;
const starIconWidth = 30;
const nameWidth = 155;
const descriptionWidth = 165;
const noteWidth = 110;
const settingsWidth = 50;
/** Displays the properties of a timeline, i.e. name, description, notes, etc */
export class Properties extends React.PureComponent<Props, State> {
@ -118,22 +124,29 @@ export class Properties extends React.PureComponent<Props, State> {
description,
getNotesByIds,
isFavorite,
isLive,
history,
isDatepickerLocked,
title,
noteIds,
timelineId,
updateDescription,
updateIsFavorite,
updateIsLive,
updateTitle,
updateNote,
usersViewing,
width,
} = this.props;
const datePickerWidth =
width -
rightGutter -
starIconWidth -
nameWidth -
(width >= showDescriptionThreshold ? descriptionWidth : 0) -
noteWidth -
settingsWidth;
return (
<TimelineProperties data-test-subj="timeline-properties" width={width - rightGutter}>
<TimelineProperties data-test-subj="timeline-properties" width={width}>
<PropertiesLeft alignItems="center" data-test-subj="properties-left" gutterSize="s">
<EuiFlexItem grow={false}>
<StarIcon
@ -146,7 +159,7 @@ export class Properties extends React.PureComponent<Props, State> {
<Name timelineId={timelineId} title={title} updateTitle={updateTitle} />
{width >= showDescriptionThreshold ? (
<EuiFlexItem grow={true}>
<EuiFlexItem grow={2}>
<Description
description={description}
timelineId={timelineId}
@ -155,35 +168,67 @@ export class Properties extends React.PureComponent<Props, State> {
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<NotesButton
animate={true}
associateNote={associateNote}
getNotesByIds={getNotesByIds}
noteIds={noteIds}
showNotes={this.state.showNotes}
size="l"
text={i18n.NOTES}
toggleShowNotes={this.onToggleShowNotes}
toolTip={i18n.NOTES_TOOL_TIP}
updateNote={updateNote}
/>
{width >= showNotesThreshold ? (
<EuiFlexItem grow={false}>
<NotesButton
animate={true}
associateNote={associateNote}
getNotesByIds={getNotesByIds}
noteIds={noteIds}
showNotes={this.state.showNotes}
size="l"
text={i18n.NOTES}
toggleShowNotes={this.onToggleShowNotes}
toolTip={i18n.NOTES_TOOL_TIP}
updateNote={updateNote}
/>
</EuiFlexItem>
) : null}
<EuiFlexItem grow={1}>
<EuiFlexGroup
alignItems="center"
gutterSize="none"
data-test-subj="timeline-date-picker-container"
>
<LockIconContainer grow={false}>
<EuiToolTip
data-test-subj="timeline-date-picker-lock-tooltip"
position="top"
content={
isDatepickerLocked
? i18n.LOCK_SYNC_MAIN_DATE_PICKER_TOOL_TIP
: i18n.UNLOCK_SYNC_MAIN_DATE_PICKER_TOOL_TIP
}
>
<EuiButtonIcon
data-test-subj={`timeline-date-picker-${
isDatepickerLocked ? 'lock' : 'unlock'
}-button`}
color="primary"
onClick={this.toggleLock}
iconType={isDatepickerLocked ? 'lock' : 'lockOpen'}
aria-label={
isDatepickerLocked
? i18n.UNLOCK_SYNC_MAIN_DATE_PICKER_ARIA
: i18n.LOCK_SYNC_MAIN_DATE_PICKER_ARIA
}
/>
</EuiToolTip>
</LockIconContainer>
<DatePicker
grow={1}
width={
datePickerWidth > datePickerThreshold ? datePickerThreshold : datePickerWidth
}
>
<SuperDatePicker id="timeline" />
</DatePicker>
</EuiFlexGroup>
</EuiFlexItem>
</PropertiesLeft>
<PropertiesRight alignItems="center" data-test-subj="properties-right" gutterSize="s">
{width >= showHistoryThreshold ? (
<EuiFlexItem grow={false}>
<HistoryButton history={history} />
</EuiFlexItem>
) : null}
{width >= showStreamLiveThreshold ? (
<EuiFlexItem grow={false}>
<StreamLive isLive={isLive} timelineId={timelineId} updateIsLive={updateIsLive} />
</EuiFlexItem>
) : null}
<EuiFlexItem grow={false}>
<EuiPopover
anchorPosition="downRight"
@ -208,6 +253,23 @@ export class Properties extends React.PureComponent<Props, State> {
/>
</EuiFormRow>
{width < showNotesThreshold ? (
<EuiFormRow>
<NotesButton
animate={true}
associateNote={associateNote}
getNotesByIds={getNotesByIds}
noteIds={noteIds}
showNotes={this.state.showNotes}
size="l"
text={i18n.NOTES}
toggleShowNotes={this.onToggleShowNotes}
toolTip={i18n.NOTES_TOOL_TIP}
updateNote={updateNote}
/>
</EuiFormRow>
) : null}
{width < showDescriptionThreshold ? (
<EuiFormRow label={i18n.DESCRIPTION}>
<Description
@ -217,22 +279,6 @@ export class Properties extends React.PureComponent<Props, State> {
/>
</EuiFormRow>
) : null}
{width < showHistoryThreshold ? (
<EuiFormRow>
<HistoryButton history={history} />
</EuiFormRow>
) : null}
{width < showStreamLiveThreshold ? (
<EuiFormRow>
<StreamLive
isLive={isLive}
timelineId={timelineId}
updateIsLive={updateIsLive}
/>
</EuiFormRow>
) : null}
</EuiForm>
</EuiPopover>
</EuiFlexItem>
@ -253,4 +299,8 @@ export class Properties extends React.PureComponent<Props, State> {
</TimelineProperties>
);
}
private toggleLock = () => {
this.props.toggleLock({ linkToId: 'timeline' });
};
}

View file

@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { EuiFieldText, EuiFlexGroup, EuiIcon } from '@elastic/eui';
import { EuiFieldText, EuiFlexGroup, EuiFlexItem, EuiIcon } from '@elastic/eui';
import styled, { keyframes } from 'styled-components';
const fadeInEffect = keyframes`
@ -21,6 +21,10 @@ export const TimelineProperties = styled.div<{ width: number }>`
width: ${({ width }) => `${width}px`};
`;
export const DatePicker = styled(EuiFlexItem)<{ width: number }>`
width: ${({ width }) => `${width}px`};
`;
export const NameField = styled(EuiFieldText)`
width: 150px;
margin-right: 5px;
@ -40,7 +44,7 @@ export const NotesButtonLabel = styled.div`
export const NotesIconContainer = styled.div`
position: relative;
margin-left: 5px;
margin-left: 3px;
`;
export const PositionedNotesIcon = styled.div<{ size: 'l' | 's' }>`
@ -59,17 +63,12 @@ export const ButtonContainer = styled.div<{ animate: boolean }>`
animation: ${fadeInEffect} ${({ animate }) => (animate ? '0.3s' : '0s')};
`;
export const HistoryButtonLabel = styled.div`
align-items: center;
display: flex;
`;
export const LabelText = styled.div`
margin-left: 10px;
`;
export const StyledStar = styled(EuiIcon)`
margin-right: 10px;
margin-right: 5px;
cursor: pointer;
`;
@ -78,7 +77,7 @@ export const PropertiesLeft = styled(EuiFlexGroup)`
`;
export const PropertiesRight = styled(EuiFlexGroup)`
margin-left: 5px;
margin-right: 5px;
`;
export const Facet = styled.div`
@ -96,3 +95,7 @@ export const Facet = styled.div`
padding-right: 8px;
user-select: none;
`;
export const LockIconContainer = styled(EuiFlexItem)`
margin-right: 2px;
`;

View file

@ -94,3 +94,33 @@ export const NEW_TIMELINE_TOOL_TIP = i18n.translate(
defaultMessage: 'Create a new timeline',
}
);
export const LOCK_SYNC_MAIN_DATE_PICKER_TOOL_TIP = i18n.translate(
'xpack.siem.timeline.properties.lockDatePickerTooltip',
{
defaultMessage:
'Disable syncing of date/time range between the currently viewed page and your timeline',
}
);
export const UNLOCK_SYNC_MAIN_DATE_PICKER_TOOL_TIP = i18n.translate(
'xpack.siem.timeline.properties.unlockDatePickerTooltip',
{
defaultMessage:
'Enable syncing of date/time range between the currently viewed page and your timeline',
}
);
export const LOCK_SYNC_MAIN_DATE_PICKER_ARIA = i18n.translate(
'xpack.siem.timeline.properties.lockDatePickerDescription',
{
defaultMessage: 'Lock date picker to global date picker',
}
);
export const UNLOCK_SYNC_MAIN_DATE_PICKER_ARIA = i18n.translate(
'xpack.siem.timeline.properties.unlockDatePickerDescription',
{
defaultMessage: 'Unlock date picker to global date picker',
}
);

View file

@ -0,0 +1,49 @@
/*
* 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 React from 'react';
import { connect } from 'react-redux';
import { ActionCreator } from 'typescript-fsa';
import { inputsActions, inputsModel } from '../../store';
interface TimelineRefetchDispatch {
setTimelineQuery: ActionCreator<{
inputId: inputsModel.InputsModelId;
id: string;
loading: boolean;
refetch: inputsModel.Refetch;
}>;
}
interface TimelineRefetchProps {
children: React.ReactNode;
id: string;
loading: boolean;
refetch: inputsModel.Refetch;
}
type OwnProps = TimelineRefetchDispatch & TimelineRefetchProps;
class TimelineRefetchComponent extends React.PureComponent<OwnProps> {
public componentDidUpdate(prevProps: OwnProps) {
const { loading, id, refetch } = this.props;
if (prevProps.loading !== loading) {
this.props.setTimelineQuery({ inputId: 'timeline', id, loading, refetch });
}
}
public render() {
return <>{this.props.children}</>;
}
}
export const TimelineRefetch = connect(
null,
{
setTimelineQuery: inputsActions.setQuery,
}
)(TimelineRefetchComponent);

View file

@ -28,6 +28,8 @@ describe('Timeline', () => {
columnId: '@timestamp',
sortDirection: Direction.desc,
};
const startDate = new Date('2018-03-23T18:49:23.132Z').valueOf();
const endDate = new Date('2018-03-24T03:33:52.253Z').valueOf();
const indexPattern = mockIndexPattern;
@ -43,9 +45,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -57,6 +61,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
);
@ -72,9 +77,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -86,6 +93,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -104,9 +112,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -118,6 +128,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -136,9 +147,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -150,6 +163,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -173,9 +187,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -187,6 +203,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -212,9 +229,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -226,6 +245,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -259,9 +279,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -273,6 +295,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -310,9 +333,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={mockDataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -324,6 +349,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -362,9 +388,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={dataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -376,6 +404,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -406,9 +435,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={dataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -420,6 +451,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -454,9 +486,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={dataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -468,6 +502,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
onToggleDataProviderExcluded={jest.fn()}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>
@ -506,9 +541,11 @@ describe('Timeline', () => {
columns={defaultHeaders}
id="foo"
dataProviders={dataProviders}
end={endDate}
flyoutHeight={testFlyoutHeight}
flyoutHeaderHeight={flyoutHeaderHeight}
indexPattern={indexPattern}
isLive={false}
itemsPerPage={5}
itemsPerPageOptions={[5, 10, 20]}
kqlMode="search"
@ -520,6 +557,7 @@ describe('Timeline', () => {
onToggleDataProviderEnabled={jest.fn()}
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
show={true}
start={startDate}
sort={sort}
/>
</MockedProvider>

View file

@ -33,6 +33,7 @@ import {
import { Footer, footerHeight } from './footer';
import { TimelineHeader } from './header';
import { calculateBodyHeight, combineQueries } from './helpers';
import { TimelineRefetch } from './refetch_timeline';
const WrappedByAutoSizer = styled.div`
width: 100%;
@ -50,10 +51,12 @@ interface Props {
browserFields: BrowserFields;
columns: ColumnHeader[];
dataProviders: DataProvider[];
end: number;
flyoutHeaderHeight: number;
flyoutHeight: number;
id: string;
indexPattern: StaticIndexPattern;
isLive: boolean;
itemsPerPage: number;
itemsPerPageOptions: number[];
kqlMode: timelineModel.KqlMode;
@ -65,6 +68,7 @@ interface Props {
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
show: boolean;
start: number;
sort: Sort;
}
@ -74,10 +78,12 @@ export const Timeline = pure<Props>(
browserFields,
columns,
dataProviders,
end,
flyoutHeaderHeight,
flyoutHeight,
id,
indexPattern,
isLive,
itemsPerPage,
itemsPerPageOptions,
kqlMode,
@ -89,13 +95,16 @@ export const Timeline = pure<Props>(
onToggleDataProviderEnabled,
onToggleDataProviderExcluded,
show,
start,
sort,
}) => {
const combinedQueries = combineQueries(
dataProviders,
indexPattern,
kqlQueryExpression,
kqlMode
kqlMode,
start,
end
);
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
return (
@ -133,8 +142,8 @@ export const Timeline = pure<Props>(
direction: sort.sortDirection as Direction,
}}
>
{({ events, loading, totalCount, pageInfo, loadMore, getUpdatedAt }) => (
<>
{({ events, loading, totalCount, pageInfo, loadMore, getUpdatedAt, refetch }) => (
<TimelineRefetch loading={loading} id={id} refetch={refetch}>
<StatefulBody
browserFields={browserFields}
data={events}
@ -153,6 +162,7 @@ export const Timeline = pure<Props>(
serverSideEventCount={totalCount}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
height={footerHeight}
isLive={isLive}
isLoading={loading}
itemsCount={events.length}
itemsPerPage={itemsPerPage}
@ -164,7 +174,7 @@ export const Timeline = pure<Props>(
getUpdatedAt={getUpdatedAt}
width={width}
/>
</>
</TimelineRefetch>
)}
</TimelineQuery>
) : null}

View file

@ -1,79 +0,0 @@
/*
* 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 { EuiPanel } from '@elastic/eui';
import { range } from 'lodash/fp';
import * as React from 'react';
import { connect } from 'react-redux';
import { Dispatch } from 'redux';
import styled from 'styled-components';
import { WhoAmI } from '../containers/who_am_i';
import { DragEffects, DraggableWrapper } from './drag_and_drop/draggable_wrapper';
import { mockDataProviders } from './timeline/data_providers/mock/mock_data_providers';
import { Provider } from './timeline/data_providers/provider';
export const VisualizationPlaceholder = styled(EuiPanel)`
&& {
align-items: center;
justify-content: center;
display: flex;
flex-direction: column;
margin: 5px;
padding: 5px 5px 5px 10px;
width: 500px;
height: 320px;
user-select: none;
}
`;
export const ProviderContainer = styled.div`
margin: 5px;
user-select: none;
`;
interface Props {
count: number;
myRoute: string;
dispatch: Dispatch;
}
/** TODO: delete this stub */
class PlaceholdersComponent extends React.PureComponent<Props> {
public render() {
const { count, myRoute } = this.props;
return (
<>
{range(0, count).map(i => (
<VisualizationPlaceholder
data-test-subj="visualizationPlaceholder"
key={`visualizationPlaceholder-${i}`}
>
<WhoAmI data-test-subj="whoAmI" sourceId="default">
{() => <div>{myRoute}</div>}
</WhoAmI>
<DraggableWrapper
dataProvider={mockDataProviders[i]}
render={(dataProvider, _, snapshot) =>
snapshot.isDragging ? (
<DragEffects>
<Provider dataProvider={dataProvider} />
</DragEffects>
) : (
mockDataProviders[i].name
)
}
/>
</VisualizationPlaceholder>
))}
</>
);
}
}
export const Placeholders = connect()(PlaceholdersComponent);

View file

@ -51,13 +51,11 @@ class AuthenticationsComponentQuery extends QueryTemplate<
startDate,
endDate,
limit,
poll,
} = this.props;
return (
<Query<GetAuthenticationsQuery.Query, GetAuthenticationsQuery.Variables>
query={authenticationsQuery}
fetchPolicy={getDefaultFetchPolicy()}
pollInterval={poll}
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -58,7 +58,6 @@ class DomainsComponentQuery extends QueryTemplate<
startDate,
endDate,
limit,
poll,
flowTarget,
flowDirection,
} = this.props;
@ -66,7 +65,6 @@ class DomainsComponentQuery extends QueryTemplate<
<Query<GetDomainsQuery.Query, GetDomainsQuery.Variables>
query={domainsQuery}
fetchPolicy="cache-and-network"
pollInterval={poll}
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -48,7 +48,6 @@ class EventsComponentQuery extends QueryTemplate<
filterQuery,
id = 'eventsQuery',
limit,
poll,
sourceId,
startDate,
endDate,
@ -58,7 +57,6 @@ class EventsComponentQuery extends QueryTemplate<
query={eventsQuery}
fetchPolicy={getDefaultFetchPolicy()}
notifyOnNetworkStatusChange
pollInterval={poll}
variables={{
filterQuery: createFilter(filterQuery),
sourceId,

View file

@ -11,10 +11,11 @@ import { ActionCreator } from 'typescript-fsa';
import { inputsActions, inputsModel, inputsSelectors, State } from '../../store';
interface GlobalTimeArgs {
poll: number;
from: number;
to: number;
setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>;
setQuery: (
{ id, loading, refetch }: { id: string; loading: boolean; refetch: inputsModel.Refetch }
) => void;
}
interface OwnProps {
@ -22,32 +23,36 @@ interface OwnProps {
}
interface GlobalTimeDispatch {
setQuery: ActionCreator<{ id: string; loading: boolean; refetch: inputsModel.Refetch }>;
deleteAllQuery: () => void;
setGlobalQuery: ActionCreator<{
inputId: inputsModel.InputsModelId;
id: string;
loading: boolean;
refetch: inputsModel.Refetch;
}>;
deleteAllQuery: ActionCreator<{ id: inputsModel.InputsModelId }>;
}
interface GlobalTimeReduxState {
from: number;
to: number;
poll: number;
}
type GlobalTimeProps = OwnProps & GlobalTimeReduxState & GlobalTimeDispatch;
class GlobalTimeComponent extends React.PureComponent<GlobalTimeProps> {
public componentDidMount() {
this.props.deleteAllQuery();
this.props.deleteAllQuery({ id: 'global' });
}
public render() {
const { children, poll, from, to, setQuery } = this.props;
const { children, from, to, setGlobalQuery } = this.props;
return (
<>
{children({
poll,
from,
to,
setQuery,
setQuery: ({ id, loading, refetch }) =>
setGlobalQuery({ inputId: 'global', id, loading, refetch }),
})}
</>
);
@ -56,9 +61,7 @@ class GlobalTimeComponent extends React.PureComponent<GlobalTimeProps> {
const mapStateToProps = (state: State) => {
const timerange: inputsModel.TimeRange = inputsSelectors.globalTimeRangeSelector(state);
const policy: inputsModel.Policy = inputsSelectors.globalPolicySelector(state);
return {
poll: policy.kind === 'interval' && timerange.kind === 'absolute' ? policy.duration : 0,
from: timerange.from,
to: timerange.to,
};
@ -68,6 +71,6 @@ export const GlobalTime = connect(
mapStateToProps,
{
deleteAllQuery: inputsActions.deleteAllQuery,
setQuery: inputsActions.setQuery,
setGlobalQuery: inputsActions.setQuery,
}
)(GlobalTimeComponent);

View file

@ -17,7 +17,7 @@ import {
PageInfo,
} from '../../graphql/types';
import { hostsModel, hostsSelectors, inputsModel, State } from '../../store';
import { createFilter, getDefaultFetchPolicy } from '../helpers';
import { createFilter } from '../helpers';
import { QueryTemplate, QueryTemplateProps } from '../query_template';
import { HostsTableQuery } from './hosts_table.gql_query';
@ -64,7 +64,6 @@ class HostsComponentQuery extends QueryTemplate<
filterQuery,
endDate,
limit,
poll,
startDate,
sourceId,
sortField,
@ -72,8 +71,7 @@ class HostsComponentQuery extends QueryTemplate<
return (
<Query<GetHostsTableQuery.Query, GetHostsTableQuery.Variables>
query={HostsTableQuery}
fetchPolicy={getDefaultFetchPolicy()}
pollInterval={poll}
fetchPolicy="cache-first"
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -1,32 +0,0 @@
/*
* 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 gql from 'graphql-tag';
export const kpiEventsQuery = gql`
query GetKpiEventsQuery(
$sourceId: ID!
$timerange: TimerangeInput!
$filterQuery: String
$pagination: PaginationInput!
$sortField: SortField!
) {
source(id: $sourceId) {
id
Events(
timerange: $timerange
filterQuery: $filterQuery
pagination: $pagination
sortField: $sortField
) {
kpiEventType {
value
count
}
}
}
}
`;

View file

@ -1,73 +0,0 @@
/*
* 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 { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { pure } from 'recompose';
import { ESQuery } from '../../../common/typed_json';
import { Direction, GetKpiEventsQuery, KpiItem } from '../../graphql/types';
import { inputsModel } from '../../store';
import { createFilter, getDefaultFetchPolicy } from '../helpers';
import { kpiEventsQuery } from './index.gql_query';
export interface KpiEventsArgs {
id: string;
kpiEventType: KpiItem[];
loading: boolean;
refetch: inputsModel.Refetch;
}
export interface OwnProps {
children?: (args: KpiEventsArgs) => React.ReactNode;
id?: string;
filterQuery?: ESQuery | string;
poll: number;
sourceId: string;
startDate: number;
endDate: number;
}
export const KpiEventsQuery = pure<OwnProps>(
({ children, filterQuery, id = 'kpiEventsQuery', poll, sourceId, startDate, endDate }) => (
<Query<GetKpiEventsQuery.Query, GetKpiEventsQuery.Variables>
query={kpiEventsQuery}
fetchPolicy={getDefaultFetchPolicy()}
notifyOnNetworkStatusChange
pollInterval={poll}
variables={{
filterQuery: createFilter(filterQuery),
sourceId,
pagination: {
limit: 0,
cursor: null,
tiebreaker: null,
},
timerange: {
interval: '12h',
from: startDate,
to: endDate,
},
sortField: {
sortFieldId: 'timestamp',
direction: Direction.desc,
},
}}
>
{({ data, loading, fetchMore, refetch }) => {
const kpiEventType = getOr([], 'source.Events.kpiEventType', data);
return children!({
id,
refetch,
loading,
kpiEventType,
});
}}
</Query>
)
);

View file

@ -60,13 +60,11 @@ class NetworkDnsComponentQuery extends QueryTemplate<
startDate,
endDate,
limit,
poll,
} = this.props;
return (
<Query<GetNetworkDnsQuery.Query, GetNetworkDnsQuery.Variables>
query={networkDnsQuery}
fetchPolicy="cache-and-network"
pollInterval={poll}
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -61,7 +61,6 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
startDate,
endDate,
limit,
poll,
flowDirection,
topNFlowSort,
flowTarget,
@ -70,7 +69,6 @@ class NetworkTopNFlowComponentQuery extends QueryTemplate<
<Query<GetNetworkTopNFlowQuery.Query, GetNetworkTopNFlowQuery.Variables>
query={networkTopNFlowQuery}
fetchPolicy="cache-and-network"
pollInterval={poll}
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -27,7 +27,6 @@ export interface OverviewHostProps extends QueryTemplateProps {
children: (args: OverviewHostArgs) => React.ReactNode;
sourceId: string;
endDate: number;
poll: number;
startDate: number;
}

View file

@ -27,7 +27,6 @@ export interface OverviewNetworkProps extends QueryTemplateProps {
children: (args: OverviewNetworkArgs) => React.ReactNode;
sourceId: string;
endDate: number;
poll: number;
startDate: number;
}

View file

@ -16,7 +16,6 @@ export interface QueryTemplateProps {
startDate?: number;
endDate?: number;
filterQuery?: ESQuery | string;
poll?: number;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
type FetchMoreOptionsArgs<TData, TVariables> = FetchMoreQueryOptions<any, any> &

View file

@ -60,7 +60,6 @@ export class TimelineQuery extends QueryTemplate<
limit,
fields,
filterQuery,
poll,
sourceId,
sortField,
} = this.props;
@ -76,7 +75,6 @@ export class TimelineQuery extends QueryTemplate<
query={timelineQuery}
fetchPolicy="network-only"
notifyOnNetworkStatusChange
pollInterval={poll}
variables={variables}
>
{({ data, loading, fetchMore, refetch }) => {
@ -98,7 +96,7 @@ export class TimelineQuery extends QueryTemplate<
...fetchMoreResult,
source: {
...fetchMoreResult.source,
Events: {
Timeline: {
...fetchMoreResult.source.Timeline,
edges: [
...prev.source.Timeline.edges,

View file

@ -51,13 +51,11 @@ class UncommonProcessesComponentQuery extends QueryTemplate<
startDate,
endDate,
limit,
poll,
} = this.props;
return (
<Query<GetUncommonProcessesQuery.Query, GetUncommonProcessesQuery.Variables>
query={uncommonProcessesQuery}
fetchPolicy={getDefaultFetchPolicy()}
pollInterval={poll}
notifyOnNetworkStatusChange
variables={{
sourceId,

View file

@ -1,17 +0,0 @@
/*
* 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 gql from 'graphql-tag';
export const whoAmIQuery = gql`
query WhoAmIQuery($sourceId: ID!) {
source(id: $sourceId) {
whoAmI {
appName
}
}
}
`;

View file

@ -1,37 +0,0 @@
/*
* 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 { getOr } from 'lodash/fp';
import React from 'react';
import { Query } from 'react-apollo';
import { WhoAmIQuery } from '../../graphql/types';
import { whoAmIQuery } from './index.gql_query';
interface WhoAmIArgs {
appName: string;
}
interface WHoAmIProps {
children: (args: WhoAmIArgs) => React.ReactNode;
sourceId: string;
}
export const WhoAmI = ({ children, sourceId }: WHoAmIProps) => (
<Query<WhoAmIQuery.Query, WhoAmIQuery.Variables>
query={whoAmIQuery}
fetchPolicy="no-cache"
notifyOnNetworkStatusChange
variables={{ sourceId }}
>
{({ data }) =>
children({
appName: getOr('', 'source.whoAmI.appName', data),
})
}
</Query>
);

View file

@ -90,8 +90,15 @@ export const mockGlobalState: State = {
inputs: {
global: {
timerange: { kind: 'relative', fromStr: 'now-24h', toStr: 'now', from: 0, to: 1 },
linkTo: ['timeline'],
query: [],
policy: { kind: 'manual', duration: 5000 },
policy: { kind: 'manual', duration: 300000 },
},
timeline: {
timerange: { kind: 'relative', fromStr: 'now-24h', toStr: 'now', from: 0, to: 1 },
linkTo: ['global'],
query: [],
policy: { kind: 'manual', duration: 300000 },
},
},
dragAndDrop: { dataProviders: {} },

View file

@ -62,7 +62,7 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
<PageContent data-test-subj="pageContent" panelPaddingSize="none">
<PageContentBody data-test-subj="pane1ScrollContainer">
<GlobalTime>
{({ poll, to, from, setQuery }) => (
{({ to, from, setQuery }) => (
<>
<HostDetailsByNameQuery
sourceId="default"
@ -75,8 +75,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
id={id}
refetch={refetch}
setQuery={setQuery}
startDate={from}
endDate={to}
data={hostDetails}
loading={loading}
/>
@ -86,7 +84,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
sourceId="default"
startDate={from}
endDate={to}
poll={poll}
filterQuery={getFilterQuery(hostName, filterQueryExpression, indexPattern)}
type={type}
>
@ -104,7 +101,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
refetch={refetch}
setQuery={setQuery}
loading={loading}
startDate={from}
data={authentications}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
@ -118,7 +114,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
sourceId="default"
startDate={from}
endDate={to}
poll={poll}
filterQuery={getFilterQuery(hostName, filterQueryExpression, indexPattern)}
type={type}
>
@ -136,7 +131,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
refetch={refetch}
setQuery={setQuery}
loading={loading}
startDate={from}
data={uncommonProcesses}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
@ -149,7 +143,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
<EventsQuery
endDate={to}
filterQuery={getFilterQuery(hostName, filterQueryExpression, indexPattern)}
poll={poll}
sourceId="default"
startDate={from}
type={type}
@ -161,7 +154,6 @@ const HostDetailsComponent = pure<HostDetailsComponentProps>(
setQuery={setQuery}
data={events!}
loading={loading}
startDate={from}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
tiebreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)!}

View file

@ -49,14 +49,13 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
<PageContent data-test-subj="pageContent" panelPaddingSize="none">
<PageContentBody data-test-subj="pane1ScrollContainer">
<GlobalTime>
{({ poll, to, from, setQuery }) => (
{({ to, from, setQuery }) => (
<>
<HostsQuery
endDate={to}
filterQuery={filterQuery}
sourceId="default"
startDate={from}
poll={poll}
type={hostsModel.HostsType.page}
>
{({ hosts, totalCount, loading, pageInfo, loadMore, id, refetch }) => (
@ -65,7 +64,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
refetch={refetch}
setQuery={setQuery}
loading={loading}
startDate={from}
data={hosts}
totalCount={totalCount}
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
@ -78,7 +76,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
<UncommonProcessesQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
type={hostsModel.HostsType.page}
@ -97,7 +94,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
refetch={refetch}
setQuery={setQuery}
loading={loading}
startDate={from}
data={uncommonProcesses}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
@ -110,7 +106,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
<AuthenticationsQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
type={hostsModel.HostsType.page}
@ -129,7 +124,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
refetch={refetch}
setQuery={setQuery}
loading={loading}
startDate={from}
data={authentications}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
@ -142,7 +136,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
<EventsQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
type={hostsModel.HostsType.page}
@ -154,7 +147,6 @@ const HostsComponent = pure<HostsComponentProps>(({ filterQuery }) => (
setQuery={setQuery}
data={events!}
loading={loading}
startDate={from}
totalCount={totalCount}
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
tiebreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)!}

View file

@ -66,7 +66,7 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
<PageContent data-test-subj="pageContent" panelPaddingSize="none">
<PageContentBody data-test-subj="pane1ScrollContainer">
<GlobalTime>
{({ poll, to, from, setQuery }) => (
{({ to, from, setQuery }) => (
<>
<IpOverviewQuery
sourceId="default"
@ -95,7 +95,6 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
filterQuery={filterQuery}
flowTarget={flowTarget}
ip={decodeIpv6(ip)}
poll={poll}
sourceId="default"
startDate={from}
type={networkModel.NetworkType.details}
@ -112,8 +111,6 @@ const IPDetailsComponent = pure<IPDetailsComponentProps>(
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
refetch={refetch}
setQuery={setQuery}
startDate={from}
endDate={to}
totalCount={totalCount}
type={networkModel.NetworkType.details}
/>

View file

@ -46,12 +46,11 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
<PageContent data-test-subj="pageContent" panelPaddingSize="none">
<PageContentBody data-test-subj="pane1ScrollContainer">
<GlobalTime>
{({ poll, to, from, setQuery }) => (
{({ to, from, setQuery }) => (
<>
<KpiNetworkQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
>
@ -69,7 +68,6 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
<NetworkTopNFlowQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
type={networkModel.NetworkType.page}
@ -92,7 +90,6 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
refetch={refetch}
setQuery={setQuery}
startDate={from}
totalCount={totalCount}
type={networkModel.NetworkType.page}
/>
@ -102,7 +99,6 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
<NetworkDnsQuery
endDate={to}
filterQuery={filterQuery}
poll={poll}
sourceId="default"
startDate={from}
type={networkModel.NetworkType.page}
@ -117,7 +113,6 @@ const NetworkComponent = pure<NetworkComponentProps>(({ filterQuery }) => (
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
refetch={refetch}
setQuery={setQuery}
startDate={from}
totalCount={totalCount}
type={networkModel.NetworkType.page}
/>

View file

@ -20,11 +20,11 @@ export const OverviewComponent = pure(() => (
<Welcome />
<GlobalTime>
{({ poll, to, from, setQuery }) => (
{({ to, from, setQuery }) => (
<EuiFlexGroup gutterSize="xl">
<Summary />
<OverviewHost poll={poll} endDate={to} startDate={from} setQuery={setQuery} />
<OverviewNetwork poll={poll} endDate={to} startDate={from} setQuery={setQuery} />
<OverviewHost endDate={to} startDate={from} setQuery={setQuery} />
<OverviewNetwork endDate={to} startDate={from} setQuery={setQuery} />
</EuiFlexGroup>
)}
</GlobalTime>

View file

@ -1,11 +0,0 @@
/*
* 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 { combineEpics } from 'redux-observable';
import { createGlobalTimeEpic } from './inputs';
export const createRootEpic = <State>() => combineEpics(createGlobalTimeEpic<State>());

View file

@ -27,7 +27,7 @@ export const initialHostsState: HostsState = {
queries: {
authentications: { limit: DEFAULT_TABLE_LIMIT },
hosts: {
limit: 5,
limit: DEFAULT_TABLE_LIMIT,
direction: Direction.desc,
sortField: HostsFields.lastSeen,
},

View file

@ -5,7 +5,6 @@
*/
export * from './actions';
export * from './epic';
export * from './model';
export * from './reducer';
export * from './selectors';

View file

@ -6,32 +6,39 @@
import actionCreatorFactory from 'typescript-fsa';
import { Refetch } from './model';
import { InputsModelId, Refetch } from './model';
const actionCreator = actionCreatorFactory('x-pack/siem/local/inputs');
export const setAbsoluteRangeDatePicker = actionCreator<{
id: string;
id: InputsModelId;
from: number;
to: number;
}>('SET_ABSOLUTE_RANGE_DATE_PICKER');
export const setRelativeRangeDatePicker = actionCreator<{
id: string;
id: InputsModelId;
fromStr: string;
toStr: string;
from: number;
to: number;
}>('SET_RELATIVE_RANGE_DATE_PICKER');
export const setDuration = actionCreator<{ id: string; duration: number }>('SET_DURATION');
export const setDuration = actionCreator<{ id: InputsModelId; duration: number }>('SET_DURATION');
export const startAutoReload = actionCreator<{ id: string }>('START_KQL_AUTO_RELOAD');
export const startAutoReload = actionCreator<{ id: InputsModelId }>('START_KQL_AUTO_RELOAD');
export const stopAutoReload = actionCreator<{ id: string }>('STOP_KQL_AUTO_RELOAD');
export const stopAutoReload = actionCreator<{ id: InputsModelId }>('STOP_KQL_AUTO_RELOAD');
export const setQuery = actionCreator<{ id: string; loading: boolean; refetch: Refetch }>(
'SET_QUERY'
export const setQuery = actionCreator<{
inputId: InputsModelId;
id: string;
loading: boolean;
refetch: Refetch;
}>('SET_QUERY');
export const deleteAllQuery = actionCreator<{ id: InputsModelId }>('DELETE_ALL_QUERY');
export const toggleTimelineLinkTo = actionCreator<{ linkToId: InputsModelId }>(
'TOGGLE_TIMELINE_LINK_TO'
);
export const deleteAllQuery = actionCreator('DELETE_ALL_QUERY');

View file

@ -1,64 +0,0 @@
/*
* 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 dateMath from '@elastic/datemath';
import { get } from 'lodash/fp';
import { Action } from 'redux';
import { Epic } from 'redux-observable';
import { timer } from 'rxjs';
import { exhaustMap, filter, map, takeUntil, withLatestFrom } from 'rxjs/operators';
import { setRelativeRangeDatePicker, startAutoReload, stopAutoReload } from './actions';
import { Policy, TimeRange } from './model';
interface GlobalTimeEpicDependencies<State> {
selectGlobalPolicy: (state: State) => Policy;
selectGlobalTimeRange: (state: State) => TimeRange;
}
export const createGlobalTimeEpic = <State>(): Epic<
Action,
Action,
State,
GlobalTimeEpicDependencies<State>
> => (action$, state$, { selectGlobalPolicy, selectGlobalTimeRange }) => {
const policy$ = state$.pipe(
map(selectGlobalPolicy),
filter(isNotNull)
);
const timerange$ = state$.pipe(
map(selectGlobalTimeRange),
filter(isNotNull)
);
return action$.pipe(
filter(startAutoReload.match),
withLatestFrom(policy$, timerange$),
filter(
([action, policy, timerange]) =>
timerange.kind === 'relative' && timerange.toStr != null && timerange.toStr === 'now'
),
exhaustMap(([action, policy, timerange]) =>
timer(0, policy.duration).pipe(
map(() => {
const fromStr = get('fromStr', timerange);
const momentDate = fromStr != null ? dateMath.parse(fromStr) : null;
return setRelativeRangeDatePicker({
id: 'global',
fromStr: fromStr != null ? fromStr : '',
toStr: 'now',
to: Date.now(),
from: momentDate != null && momentDate.isValid() ? momentDate.valueOf() : 0,
});
}),
takeUntil(action$.pipe(filter(stopAutoReload.match)))
)
)
);
};
const isNotNull = <T>(value: T | null): value is T => value !== null;

View file

@ -0,0 +1,95 @@
/*
* 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 { cloneDeep } from 'lodash/fp';
import { mockGlobalState } from '../../mock';
import { toggleLockTimeline, updateInputTimerange } from './helpers';
import { InputsModel, TimeRange } from './model';
describe('Inputs', () => {
let state = mockGlobalState.inputs;
describe('#toggleLockTimeline', () => {
beforeEach(() => {
state = cloneDeep(mockGlobalState.inputs);
});
test('remove timeline Lock from inputs', () => {
const newState: InputsModel = toggleLockTimeline('timeline', state);
expect(newState.timeline.linkTo).toEqual([]);
expect(newState.global.linkTo).toEqual([]);
});
test('Add timeline Lock from inputs', () => {
state.global.linkTo = [];
const newState: InputsModel = toggleLockTimeline('timeline', state);
expect(newState.timeline.linkTo).toEqual(['global']);
expect(newState.global.linkTo).toEqual(['timeline']);
});
});
describe('#updateInputTimerange when timeline and global are lock', () => {
beforeEach(() => {
state = cloneDeep(mockGlobalState.inputs);
});
test('timeline should stay identical when global change', () => {
const newTimerange: TimeRange = {
kind: 'relative',
fromStr: 'now-48h',
toStr: 'now',
from: 23,
to: 26,
};
const newState: InputsModel = updateInputTimerange('global', newTimerange, state);
expect(newState.timeline.timerange).toEqual(newState.global.timerange);
});
test('global should stay identical when timeline change', () => {
const newTimerange: TimeRange = {
kind: 'relative',
fromStr: 'now-68h',
toStr: 'NOTnow',
from: 29,
to: 33,
};
const newState: InputsModel = updateInputTimerange('timeline', newTimerange, state);
expect(newState.timeline.timerange).toEqual(newState.global.timerange);
});
});
describe('#updateInputTimerange when timeline and global are NOT lock', () => {
beforeEach(() => {
state = cloneDeep(toggleLockTimeline('timeline', mockGlobalState.inputs));
});
test('timeline should stay identical when global change', () => {
const newTimerange: TimeRange = {
kind: 'relative',
fromStr: 'now-48h',
toStr: 'now',
from: 23,
to: 26,
};
const newState: InputsModel = updateInputTimerange('global', newTimerange, state);
expect(newState.timeline.timerange).toEqual(state.timeline.timerange);
expect(newState.global.timerange).toEqual(newTimerange);
});
test('global should stay identical when timeline change', () => {
const newTimerange: TimeRange = {
kind: 'relative',
fromStr: 'now-68h',
toStr: 'NOTnow',
from: 29,
to: 33,
};
const newState: InputsModel = updateInputTimerange('timeline', newTimerange, state);
expect(newState.timeline.timerange).toEqual(newTimerange);
expect(newState.global.timerange).toEqual(state.timeline.timerange);
});
});
});

View file

@ -0,0 +1,54 @@
/*
* 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 } from 'lodash/fp';
import { InputsModel, InputsModelId, TimeRange } from './model';
export const updateInputTimerange = (
inputId: InputsModelId,
timerange: TimeRange,
state: InputsModel
): InputsModel => {
const input = get(inputId, state);
if (input != null) {
return {
...[inputId, ...input.linkTo].reduce<InputsModel>(
(acc: InputsModel, linkToId: InputsModelId) => ({
...acc,
[linkToId]: {
...get(linkToId, state),
timerange,
},
}),
inputId === 'timeline' ? { ...state, global: { ...state.global, linkTo: [] } } : state
),
};
}
return state;
};
export const toggleLockTimeline = (linkToId: InputsModelId, state: InputsModel): InputsModel => {
const linkToIdAlreadyExist = state.global.linkTo.indexOf(linkToId);
return {
...state,
global: {
...state.global,
timerange: linkToIdAlreadyExist > -1 ? state.global.timerange : state.timeline.timerange,
linkTo:
linkToIdAlreadyExist > -1
? [
...state.global.linkTo.slice(0, linkToIdAlreadyExist),
...state.global.linkTo.slice(linkToIdAlreadyExist + 1),
]
: [...state.global.linkTo, linkToId],
},
timeline: {
...state.timeline,
linkTo: linkToIdAlreadyExist > -1 ? [] : ['global'],
},
};
};

Some files were not shown because too many files have changed in this diff Show more