mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
Timeline filter with and
& or
(#29101)
* option1 * wip filter and -or * wip provider item with or/and design as wanted * just add the and and or to the kql query * fix types * fix merge with feature-secops branch * do not show date tooltip when it is an And Provider * fix existing unit testing * add unit testing * put back original theme * review * review-part 1 * review part-2 * add translation for i18n
This commit is contained in:
parent
873763f320
commit
d784ca5c69
45 changed files with 2886 additions and 576 deletions
|
@ -9,9 +9,10 @@
|
|||
"@types/boom": "^7.2.0",
|
||||
"@types/color": "^3.0.0",
|
||||
"@types/lodash": "^4.14.110",
|
||||
"@types/react-beautiful-dnd": "^7.1.2"
|
||||
"@types/react-beautiful-dnd": "^10.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"react-beautiful-dnd": "^10.0.1",
|
||||
"lodash": "^4.17.10"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,9 +34,7 @@ interface OnDragEndHandlerParams {
|
|||
const onDragEndHandler = ({ result, dataProviders, dispatch }: OnDragEndHandlerParams) => {
|
||||
if (providerWasDroppedOnTimeline(result)) {
|
||||
addProviderToTimeline({ dataProviders, result, dispatch });
|
||||
}
|
||||
|
||||
if (providerWasDroppedOnTimelineButton(result)) {
|
||||
} else if (providerWasDroppedOnTimelineButton(result)) {
|
||||
addProviderToTimeline({ dataProviders, result, dispatch });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -21,10 +21,51 @@ const getBackgroundColor = (theme: Theme): string =>
|
|||
|
||||
const ReactDndDropTarget = styled.div<{ isDraggingOver: boolean; themeName: Theme }>`
|
||||
transition: background-color 0.7s ease;
|
||||
min-height: 100px;
|
||||
.euiPanel {
|
||||
background-color: ${({ isDraggingOver, themeName }) =>
|
||||
isDraggingOver ? '#f0f8ff' : getBackgroundColor(themeName)};
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.flyout-overlay {
|
||||
.euiPanel {
|
||||
background-color: ${({ themeName }) => getBackgroundColor(themeName)};
|
||||
}
|
||||
}
|
||||
${({ isDraggingOver }) =>
|
||||
isDraggingOver
|
||||
? `
|
||||
.drop-and-provider-timeline {
|
||||
&:hover {
|
||||
background: repeating-linear-gradient(
|
||||
-55deg,
|
||||
rgb(52, 55, 65),
|
||||
rgb(52, 55, 65) 10px,
|
||||
rgb(245, 247, 250) 10px,
|
||||
rgb(245, 247, 250) 20px
|
||||
);
|
||||
}
|
||||
}
|
||||
> div.timeline-drop-area {
|
||||
background-color: rgb(245, 247, 250);
|
||||
.provider-item-filter-container div:first-child{
|
||||
/// Ooverwride dragNdrop beautifull so we do not have our droppable moving around for no good reason
|
||||
transform: none !important;
|
||||
}
|
||||
}
|
||||
.flyout-overlay {
|
||||
.euiPanel {
|
||||
background-color: rgb(245, 247, 250);
|
||||
}
|
||||
+ div {
|
||||
// Ooverwride dragNdrop beautifull so we do not have our droppable moving around for no good reason
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
|
||||
`
|
||||
: ''}
|
||||
> div.timeline-drop-area {
|
||||
& + div {
|
||||
// overwride dragNdrop beautifull so we do not have our droppable moving around for no good reason
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ describe('helpers', () => {
|
|||
reason: 'DROP',
|
||||
source: { index: 0, droppableId: getDroppableId('2119990039033485') },
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -64,6 +65,7 @@ describe('helpers', () => {
|
|||
reason: 'DROP',
|
||||
source: { index: 0, droppableId: `${droppableIdPrefix}.somethingElse.2119990039033485` },
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -84,6 +86,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -99,6 +102,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -119,6 +123,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -137,6 +142,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -157,6 +163,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -172,6 +179,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -190,6 +198,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -210,6 +219,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -225,6 +235,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -243,6 +254,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -263,6 +275,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual('timeline');
|
||||
});
|
||||
|
@ -281,6 +294,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual('timeline');
|
||||
});
|
||||
|
@ -296,6 +310,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual('');
|
||||
});
|
||||
|
@ -314,6 +329,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual('');
|
||||
});
|
||||
|
@ -333,6 +349,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
});
|
||||
const expected = '2119990039033485';
|
||||
|
||||
|
@ -355,6 +372,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(true);
|
||||
});
|
||||
|
@ -373,6 +391,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -388,6 +407,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -400,6 +420,7 @@ describe('helpers', () => {
|
|||
reason: 'DROP',
|
||||
source: { index: 0, droppableId: `${droppableIdPrefix}.somethingElse.2119990039033485` },
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
@ -418,6 +439,7 @@ describe('helpers', () => {
|
|||
index: 0,
|
||||
},
|
||||
type: 'DEFAULT',
|
||||
mode: 'FLUID',
|
||||
})
|
||||
).toEqual(false);
|
||||
});
|
||||
|
|
|
@ -100,17 +100,17 @@ export const getItems = ({ data, populatedFields }: GetItemsParams): Item[] => {
|
|||
data,
|
||||
fieldName: field.name,
|
||||
})}`,
|
||||
queryMatch: `${getOr(
|
||||
field.name,
|
||||
field.name,
|
||||
mappedEcsSchemaFieldNames
|
||||
)}: "${escapeQueryValue(
|
||||
getMappedEcsValue({
|
||||
data,
|
||||
fieldName: field.name,
|
||||
})
|
||||
)}"`,
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: getOr(field.name, field.name, mappedEcsSchemaFieldNames),
|
||||
value: escapeQueryValue(
|
||||
getMappedEcsValue({
|
||||
data,
|
||||
fieldName: field.name,
|
||||
})
|
||||
),
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
and: [],
|
||||
}}
|
||||
render={() => `${getMappedEcsValue({ data, fieldName: field.name })}`}
|
||||
|
|
|
@ -78,7 +78,11 @@ export const FlyoutButton = pure(
|
|||
droppableId={`${droppableTimelineFlyoutButtonPrefix}${timelineId}`}
|
||||
theme={theme}
|
||||
>
|
||||
<BadgeButtonContainer data-test-subj="flyoutOverlay" onClick={onOpen}>
|
||||
<BadgeButtonContainer
|
||||
className="flyout-overlay"
|
||||
data-test-subj="flyoutOverlay"
|
||||
onClick={onOpen}
|
||||
>
|
||||
{dataProviders.length !== 0 && (
|
||||
<Badge data-test-subj="badge" color="primary">
|
||||
{dataProviders.length}
|
||||
|
|
|
@ -6,14 +6,12 @@
|
|||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { FormattedRelative } from '@kbn/i18n/react';
|
||||
import { noop } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import moment from 'moment';
|
||||
import { AuthenticationsEdges } from '../../../../graphql/types';
|
||||
import { escapeQueryValue } from '../../../../lib/keury';
|
||||
import { authenticationsSelector, hostsActions, State } from '../../../../store';
|
||||
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
|
||||
import { defaultToEmpty, getEmptyValue } from '../../../empty_value';
|
||||
|
@ -117,18 +115,21 @@ const getAuthenticationColumns = (startDate: number) => [
|
|||
enabled: true,
|
||||
id: node._id,
|
||||
name: userName!,
|
||||
negated: false,
|
||||
queryMatch: `auditd.data.acct: "${escapeQueryValue(userName!)}"`,
|
||||
queryDate: `@timestamp >= ${startDate} and @timestamp <= ${moment().valueOf()}`,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'auditd.data.acct',
|
||||
value: userName!,
|
||||
},
|
||||
queryDate: {
|
||||
from: startDate,
|
||||
to: moment().valueOf(),
|
||||
},
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
userName
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { has, noop } from 'lodash/fp';
|
||||
import { has } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { Ecs, EcsEdges } from '../../../../graphql/types';
|
||||
import { escapeQueryValue } from '../../../../lib/keury';
|
||||
import { eventsSelector, hostsActions, State } from '../../../../store';
|
||||
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
|
||||
import { getEmptyValue, getOrEmpty } from '../../../empty_value';
|
||||
|
@ -119,18 +118,23 @@ const getEventsColumns = (startDate: number) => [
|
|||
enabled: true,
|
||||
id: node._id!,
|
||||
name: hostName,
|
||||
negated: false,
|
||||
queryMatch: `host.id: "${escapeQueryValue(node.host!.id!)}"`,
|
||||
queryDate: `@timestamp >= ${startDate} and @timestamp <= ${moment().valueOf()}`,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
displayField: 'host.name',
|
||||
displayValue: hostName,
|
||||
field: 'host.id',
|
||||
value: node.host!.id!,
|
||||
},
|
||||
queryDate: {
|
||||
from: startDate,
|
||||
to: moment().valueOf(),
|
||||
},
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
hostName
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge, EuiLink } from '@elastic/eui';
|
||||
import { get, isNil, noop } from 'lodash/fp';
|
||||
import { get, isNil } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { HostsEdges } from '../../../../graphql/types';
|
||||
import { escapeQueryValue } from '../../../../lib/keury';
|
||||
import { hostsActions, hostsSelector, State } from '../../../../store';
|
||||
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
|
||||
import { defaultToEmpty, getOrEmpty } from '../../../empty_value';
|
||||
|
@ -115,22 +114,25 @@ const getHostsColumns = () => [
|
|||
dataProvider={{
|
||||
and: [],
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
id: node._id!,
|
||||
name: hostName,
|
||||
negated: false,
|
||||
queryMatch: `host.id: "${escapeQueryValue(node.host!.id!)}"`,
|
||||
queryDate: `@timestamp >= ${moment(
|
||||
node.firstSeen!
|
||||
).valueOf()} and @timestamp <= ${moment().valueOf()}`,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
displayField: 'host.name',
|
||||
displayValue: hostName,
|
||||
field: 'host.id',
|
||||
value: node.host!.id!,
|
||||
},
|
||||
queryDate: {
|
||||
from: moment(node.firstSeen!).valueOf(),
|
||||
to: moment().valueOf(),
|
||||
},
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : isNil(get('host.id', node)) ? (
|
||||
<>{hostName}</>
|
||||
|
|
|
@ -5,14 +5,12 @@
|
|||
*/
|
||||
|
||||
import { EuiBadge } from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { HostEcsFields, UncommonProcessesEdges } from '../../../../graphql/types';
|
||||
import { escapeQueryValue } from '../../../../lib/keury';
|
||||
import { hostsActions, State, uncommonProcessesSelector } from '../../../../store';
|
||||
import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper';
|
||||
import { defaultToEmpty, getEmptyValue, getOrEmpty } from '../../../empty_value';
|
||||
|
@ -118,18 +116,21 @@ const getUncommonColumns = (startDate: number) => [
|
|||
enabled: true,
|
||||
id: node._id,
|
||||
name: processName!,
|
||||
negated: false,
|
||||
queryMatch: `process.name: "${escapeQueryValue(processName!)}"`,
|
||||
queryDate: `@timestamp >= ${startDate} and @timestamp <= ${moment().valueOf()}`,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'process.name',
|
||||
value: processName!,
|
||||
},
|
||||
queryDate: {
|
||||
from: startDate,
|
||||
to: moment().valueOf(),
|
||||
},
|
||||
}}
|
||||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
processName
|
||||
|
|
|
@ -1,57 +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 { EuiSpacer } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OnDataProviderRemoved, OnToggleDataProviderEnabled } from '../events';
|
||||
import { CloseButton } from './close_button';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { SwitchButton } from './switch_button';
|
||||
|
||||
interface Props {
|
||||
dataProvider: DataProvider;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
}
|
||||
|
||||
const Spacer = styled(EuiSpacer)`
|
||||
border-left: 1px solid #ccc;
|
||||
margin: 0 5px 0 5px;
|
||||
`;
|
||||
|
||||
const ActionsContainer = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 10px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* Renders an interactive card representation of the data providers. It also
|
||||
* affords uniform UI controls for the following actions:
|
||||
* 1) removing a data provider
|
||||
* 2) temporarily disabling a data provider
|
||||
* 3) TODO: applying boolean negation to the data provider
|
||||
*/
|
||||
export const Actions = pure<Props>(
|
||||
({ dataProvider, onDataProviderRemoved, onToggleDataProviderEnabled }: Props) => (
|
||||
<ActionsContainer data-test-subj="data-provider-actions">
|
||||
<SwitchButton
|
||||
data-test-subj="data-provider-action-toggle-enabled"
|
||||
dataProvider={dataProvider}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
/>
|
||||
<Spacer />
|
||||
<CloseButton
|
||||
data-test-subj="data-provider-action-close"
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
/>
|
||||
</ActionsContainer>
|
||||
)
|
||||
);
|
|
@ -1,34 +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 { EuiButtonIcon } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { OnDataProviderRemoved } from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
dataProvider: DataProvider;
|
||||
}
|
||||
|
||||
/** An affordance for removing a data provider. It invokes `onDataProviderRemoved` when clicked */
|
||||
export const CloseButton = pure<Props>(({ onDataProviderRemoved, dataProvider }) => {
|
||||
const onClick = () => {
|
||||
onDataProviderRemoved(dataProvider);
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
data-test-subj="closeButton"
|
||||
onClick={onClick}
|
||||
iconType="cross"
|
||||
aria-label={i18n.REMOVE_DATA_PROVIDER}
|
||||
/>
|
||||
);
|
||||
});
|
|
@ -4,6 +4,11 @@
|
|||
* 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 */
|
||||
|
@ -15,17 +20,25 @@ export interface DataProvider {
|
|||
* the timeline. default: `true`
|
||||
*/
|
||||
enabled: boolean;
|
||||
/**
|
||||
* When `true`, a data provider is excluding the match, but not removed from
|
||||
* the timeline. default: `false`
|
||||
*/
|
||||
excluded: boolean;
|
||||
/**
|
||||
* Return the KQL query who have been added by user
|
||||
*/
|
||||
kqlQuery: string;
|
||||
/**
|
||||
* Returns a query properties that, when executed, returns the data for this provider
|
||||
*/
|
||||
queryMatch: string;
|
||||
queryDate?: string;
|
||||
/**
|
||||
* When `true`, boolean logic is applied to the data provider to negate it.
|
||||
* default: `false`
|
||||
*/
|
||||
negated: boolean;
|
||||
|
||||
queryMatch: {
|
||||
field: string;
|
||||
displayField?: string;
|
||||
value: string | number;
|
||||
displayValue?: string | number;
|
||||
};
|
||||
queryDate?: QueryDate;
|
||||
/**
|
||||
* Additional query clauses that are ANDed with this query to narrow results
|
||||
*/
|
||||
|
|
|
@ -10,7 +10,7 @@ import * as React from 'react';
|
|||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
import { DataProviders } from '.';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { mockDataProviderNames, mockDataProviders } from './mock/mock_data_providers';
|
||||
import { mockDataProviders } from './mock/mock_data_providers';
|
||||
|
||||
describe('DataProviders', () => {
|
||||
describe('rendering', () => {
|
||||
|
@ -24,8 +24,11 @@ describe('DataProviders', () => {
|
|||
<DataProviders
|
||||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
show={true}
|
||||
theme="dark"
|
||||
/>
|
||||
|
@ -35,21 +38,24 @@ describe('DataProviders', () => {
|
|||
dropMessage.forEach(word => expect(wrapper.text()).toContain(word));
|
||||
});
|
||||
|
||||
test('it should NOT render a placeholder given a non-empty collection of data providers', () => {
|
||||
test('it should STILL render a placeholder given a non-empty collection of data providers', () => {
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DataProviders
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
show={true}
|
||||
theme="dark"
|
||||
/>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
dropMessage.forEach(word => expect(wrapper.text()).not.toContain(word));
|
||||
dropMessage.forEach(word => expect(wrapper.text()).toContain(word));
|
||||
});
|
||||
|
||||
test('it renders the data providers', () => {
|
||||
|
@ -58,15 +64,22 @@ describe('DataProviders', () => {
|
|||
<DataProviders
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
show={true}
|
||||
theme="dark"
|
||||
/>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
mockDataProviderNames().forEach(name => expect(wrapper.text()).toContain(name));
|
||||
mockDataProviders.forEach(dataProvider =>
|
||||
expect(wrapper.text()).toContain(
|
||||
dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -40,6 +40,11 @@ const EmptyContainer = styled.div`
|
|||
justify-content: center;
|
||||
min-height: 100px;
|
||||
user-select: none;
|
||||
flex: 1;
|
||||
align-content: center;
|
||||
+ div {
|
||||
display: none !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const NoWrap = styled.div`
|
||||
|
@ -53,7 +58,7 @@ const NoWrap = styled.div`
|
|||
* Prompts the user to drop anything with a facet count into the data providers section.
|
||||
*/
|
||||
export const Empty = pure(() => (
|
||||
<EmptyContainer data-test-subj="empty">
|
||||
<EmptyContainer className="timeline-drop-area" data-test-subj="empty">
|
||||
<NoWrap>
|
||||
<Text>{i18n.DROP_ANYTHING}</Text>
|
||||
<BadgeHighlighted color="#d9d9d9">{i18n.HIGHLIGHTED}</BadgeHighlighted>
|
||||
|
|
|
@ -1,52 +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 { EuiFlexGroup, EuiFlexItem, EuiHorizontalRule, EuiIcon, EuiToolTip } from '@elastic/eui';
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import moment from 'moment';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
import { DataProvider } from './data_provider';
|
||||
|
||||
const HorizontalBar = styled(EuiHorizontalRule)`
|
||||
margin: 4px 0px;
|
||||
`;
|
||||
|
||||
const GroupIcons = styled(EuiFlexGroup)`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
interface OwnProps {
|
||||
dataProvider: DataProvider;
|
||||
}
|
||||
export const IconsFooter = pure<OwnProps>(({ dataProvider }: OwnProps) => {
|
||||
if (!dataProvider.queryDate || isEmpty(dataProvider.queryDate)) {
|
||||
return null;
|
||||
}
|
||||
const dates = dataProvider.queryDate.trim().match(/\d+/g);
|
||||
const tooltipStr = `${moment(parseInt(dates![0], 10)).format('L LTS')} - ${moment(
|
||||
parseInt(dates![1], 10)
|
||||
).format('L LTS')}`;
|
||||
return (
|
||||
<>
|
||||
<HorizontalBar margin="xs" />
|
||||
<GroupIcons
|
||||
data-test-subj="data-provider-icons-footer"
|
||||
gutterSize="none"
|
||||
alignItems="center"
|
||||
justifyContent="flexStart"
|
||||
direction="row"
|
||||
>
|
||||
<EuiFlexItem>
|
||||
<EuiToolTip data-test-subj="add-tool-tip" content={tooltipStr} position="bottom">
|
||||
<EuiIcon type="calendar" />
|
||||
</EuiToolTip>
|
||||
</EuiFlexItem>
|
||||
</GroupIcons>
|
||||
</>
|
||||
);
|
||||
});
|
|
@ -11,7 +11,13 @@ import styled from 'styled-components';
|
|||
import { Theme } from '../../../store/local/app/model';
|
||||
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
|
||||
import { droppableTimelineProvidersPrefix } from '../../drag_and_drop/helpers';
|
||||
import { OnDataProviderRemoved, OnToggleDataProviderEnabled } from '../events';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnDataProviderRemoved,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { Empty } from './empty';
|
||||
import { Providers } from './providers';
|
||||
|
@ -19,21 +25,26 @@ import { Providers } from './providers';
|
|||
interface Props {
|
||||
id: string;
|
||||
dataProviders: DataProvider[];
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onChangeDroppableAndProvider: OnChangeDroppableAndProvider;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
show: boolean;
|
||||
theme: Theme;
|
||||
}
|
||||
|
||||
const DropTargetDataProviders = styled.div`
|
||||
border: 0.3rem dashed #999999;
|
||||
position: relative;
|
||||
border: 0.2rem dashed #999999;
|
||||
border-radius: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 5px;
|
||||
padding: 0px;
|
||||
min-height: 100px;
|
||||
padding: 5px;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const getDroppableId = (id: string): string => `${droppableTimelineProvidersPrefix}${id}`;
|
||||
|
@ -59,19 +70,25 @@ export const DataProviders = pure<Props>(
|
|||
({
|
||||
id,
|
||||
dataProviders,
|
||||
onChangeDataProviderKqlQuery,
|
||||
onChangeDroppableAndProvider,
|
||||
onDataProviderRemoved,
|
||||
onToggleDataProviderEnabled,
|
||||
onToggleDataProviderExcluded,
|
||||
show,
|
||||
theme,
|
||||
}: Props) => (
|
||||
}) => (
|
||||
<DropTargetDataProviders data-test-subj="dataProviders">
|
||||
<DroppableWrapper isDropDisabled={!show} droppableId={getDroppableId(id)} theme={theme}>
|
||||
{dataProviders.length ? (
|
||||
<Providers
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
) : (
|
||||
<Empty />
|
||||
|
|
|
@ -4,11 +4,6 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { EuiBadge, EuiText } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { EventsQuery } from '../../../../containers/events';
|
||||
import { DataProvider } from '../data_provider';
|
||||
|
||||
interface NameToEventCount<TValue> {
|
||||
|
@ -39,31 +34,25 @@ export const mockDataProviderNames = (): string[] => Object.keys(mockSourceNameT
|
|||
export const getEventCount = (dataProviderName: string): number =>
|
||||
mockSourceNameToEventCount[dataProviderName] || 0;
|
||||
|
||||
const Text = styled(EuiText)`
|
||||
display: inline;
|
||||
padding-left: 5px;
|
||||
`;
|
||||
|
||||
/**
|
||||
* A collection of mock data providers, that can both be rendered
|
||||
* in the browser, and also used as mocks in unit and functional tests.
|
||||
*/
|
||||
export const mockDataProviders: DataProvider[] = Object.keys(mockSourceNameToEventCount).map(
|
||||
name => ({
|
||||
enabled: true,
|
||||
id: `id-${name}`,
|
||||
name,
|
||||
componentResultParam: 'events',
|
||||
componentQuery: EventsQuery,
|
||||
queryMatch: 'host.name: "testHostName"',
|
||||
queryDate: '@timestamp >= 1521830963132 and @timestamp <= 1521862432253',
|
||||
negated: false,
|
||||
render: () => (
|
||||
<div data-test-subj="mockDataProvider">
|
||||
<EuiBadge color="primary">{getEventCount(name)}</EuiBadge>
|
||||
<Text> {name} </Text>
|
||||
</div>
|
||||
),
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: 'name',
|
||||
value: name,
|
||||
},
|
||||
queryDate: {
|
||||
from: 1521830963132,
|
||||
to: 1521862432253,
|
||||
},
|
||||
and: [],
|
||||
})
|
||||
);
|
||||
|
|
|
@ -5,11 +5,11 @@
|
|||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { noop, pick } from 'lodash/fp';
|
||||
import { noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
|
||||
import { mockDataProviderNames, mockDataProviders } from './mock/mock_data_providers';
|
||||
import { mockDataProviders } from './mock/mock_data_providers';
|
||||
import { Provider } from './provider';
|
||||
|
||||
describe('Provider', () => {
|
||||
|
@ -18,88 +18,12 @@ describe('Provider', () => {
|
|||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Provider
|
||||
dataProvider={mockDataProviders[0]}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={mockDataProviders[0]} />
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
expect(wrapper.text()).toContain(mockDataProviderNames()[0]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onDataProviderRemoved', () => {
|
||||
test('it invokes the onDataProviderRemoved callback when the close button is clicked', () => {
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Provider
|
||||
dataProvider={mockDataProviders[0]}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="closeButton"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'id', 'name', 'negated'],
|
||||
mockOnDataProviderRemoved.mock.calls[0][0]
|
||||
);
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
enabled: true,
|
||||
id: 'id-Provider 1',
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onToggleDataProviderEnabled', () => {
|
||||
test('it invokes the onToggleDataProviderEnabled callback when the switch button is clicked', () => {
|
||||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Provider
|
||||
dataProvider={mockDataProviders[0]}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="switchButton"]')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'dataProvider.id', 'dataProvider.name', 'dataProvider.negated'],
|
||||
mockOnToggleDataProviderEnabled.mock.calls[0][0]
|
||||
);
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
dataProvider: {
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
id: 'id-Provider 1',
|
||||
},
|
||||
enabled: false,
|
||||
});
|
||||
expect(wrapper.text()).toContain('name: "Provider 1"');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,45 +4,28 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
import { noop } from 'lodash/fp';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { DataProvider } from './data_provider';
|
||||
import { ProviderItemBadge } from './provider_item_badge';
|
||||
|
||||
interface OwnProps {
|
||||
dataProvider: DataProvider;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
}
|
||||
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OnDataProviderRemoved, OnToggleDataProviderEnabled } from '../events';
|
||||
import { Actions } from './actions';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { IconsFooter } from './icons_footer';
|
||||
|
||||
const PanelProvider = styled(EuiPanel)`
|
||||
&& {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
margin: 5px;
|
||||
min-height: 60px;
|
||||
padding: 5px 5px 5px 10px;
|
||||
min-width: 150px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const Provider = pure<Props>(
|
||||
({ dataProvider, onDataProviderRemoved, onToggleDataProviderEnabled }: Props) => (
|
||||
<PanelProvider data-test-subj="provider" key={dataProvider.id}>
|
||||
{dataProvider.name}
|
||||
<Actions
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
/>
|
||||
<IconsFooter dataProvider={dataProvider} />
|
||||
</PanelProvider>
|
||||
)
|
||||
);
|
||||
export const Provider = pure<OwnProps>(({ dataProvider }) => (
|
||||
<ProviderItemBadge
|
||||
deleteProvider={noop}
|
||||
field={dataProvider.queryMatch.displayField || dataProvider.queryMatch.field}
|
||||
kqlQuery={dataProvider.kqlQuery}
|
||||
isEnabled={dataProvider.enabled}
|
||||
isExcluded={dataProvider.excluded}
|
||||
providerId={dataProvider.id}
|
||||
queryDate={dataProvider.queryDate}
|
||||
toggleEnabledProvider={noop}
|
||||
toggleExcludedProvider={noop}
|
||||
val={dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value}
|
||||
/>
|
||||
));
|
||||
|
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 { EuiBadge, EuiIcon, EuiToolTip } 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)`
|
||||
border: none;
|
||||
.euiToolTipAnchor {
|
||||
&::after {
|
||||
font-style: normal;
|
||||
content: '|';
|
||||
padding: 0px 3px;
|
||||
}
|
||||
}
|
||||
.field-value {
|
||||
font-weight: 200;
|
||||
}
|
||||
&.globalFilterItem {
|
||||
line-height: 28px;
|
||||
border: none;
|
||||
&.globalFilterItem-isDisabled {
|
||||
text-decoration: line-through;
|
||||
font-weight: 400;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProviderBadgeProps {
|
||||
deleteProvider: () => void;
|
||||
field: string;
|
||||
kqlQuery: string;
|
||||
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 }) => {
|
||||
const deleteFilter: React.MouseEventHandler<HTMLButtonElement> = (
|
||||
event: React.MouseEvent<HTMLButtonElement>
|
||||
) => {
|
||||
// Make sure it doesn't also trigger the onclick for the whole badge
|
||||
if (event.stopPropagation) {
|
||||
event.stopPropagation();
|
||||
}
|
||||
deleteProvider();
|
||||
};
|
||||
const classes = classNames('globalFilterItem', {
|
||||
'globalFilterItem-isDisabled': !isEnabled,
|
||||
'globalFilterItem-isExcluded': isExcluded,
|
||||
});
|
||||
const prefix = isExcluded ? <span>{i18n.NOT} </span> : null;
|
||||
|
||||
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}`}
|
||||
className={classes}
|
||||
title={title}
|
||||
iconOnClick={deleteFilter}
|
||||
iconOnClickAriaLabel={i18n.REMOVE_DATA_PROVIDER}
|
||||
iconType="cross"
|
||||
iconSide="right"
|
||||
onClick={togglePopover}
|
||||
onClickAriaLabel={`${i18n.SHOW_OPTIONS_DATA_PROVIDER} ${val}`}
|
||||
closeButtonProps={{
|
||||
// Removing tab focus on close button because the same option can be obtained through the context menu
|
||||
// TODO: add a `DEL` keyboard press functionality
|
||||
tabIndex: '-1',
|
||||
}}
|
||||
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">"{val}"</span>
|
||||
</ProviderBadgeStyled>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
* 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 { EuiContextMenu, EuiPopover } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface OwnProps {
|
||||
button: JSX.Element;
|
||||
closePopover: () => void;
|
||||
deleteProvider: () => void;
|
||||
field: string;
|
||||
kqlQuery: string;
|
||||
isEnabled: boolean;
|
||||
isExcluded: boolean;
|
||||
isOpen: boolean;
|
||||
providerId: string;
|
||||
toggleEnabledProvider: () => void;
|
||||
toggleExcludedProvider: () => void;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
const MyEuiPopover = styled(EuiPopover)`
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
export const getProviderActions = (
|
||||
deleteItem: () => void,
|
||||
isEnabled: boolean,
|
||||
isExcluded: boolean,
|
||||
toggleEnabled: () => void,
|
||||
toggleExcluded: () => void
|
||||
) => [
|
||||
{
|
||||
id: 0,
|
||||
items: [
|
||||
// {
|
||||
// name: 'Edit filter query',
|
||||
// icon: 'pencil',
|
||||
// panel: 1,
|
||||
// },
|
||||
{
|
||||
name: isExcluded ? i18n.INCLUDE_DATA_PROVIDER : i18n.EXCLUDE_DATA_PROVIDER,
|
||||
icon: `${isExcluded ? 'plusInCircle' : 'minusInCircle'}`,
|
||||
onClick: toggleExcluded,
|
||||
},
|
||||
{
|
||||
name: isEnabled ? i18n.TEMPORARILY_DISABLE_DATA_PROVIDER : i18n.RE_ENABLE_DATA_PROVIDER,
|
||||
icon: `${isEnabled ? 'eyeClosed' : 'eye'}`,
|
||||
onClick: toggleEnabled,
|
||||
},
|
||||
{
|
||||
name: 'Delete',
|
||||
icon: 'trash',
|
||||
onClick: deleteItem,
|
||||
},
|
||||
],
|
||||
},
|
||||
// {
|
||||
// id: 1,
|
||||
// width: 400,
|
||||
// content: <div style={{ padding: 16 }}>ADD KQL BAR</div>,
|
||||
// },
|
||||
];
|
||||
|
||||
export const ProviderItemActions = pure<OwnProps>(
|
||||
({
|
||||
button,
|
||||
closePopover,
|
||||
deleteProvider,
|
||||
kqlQuery,
|
||||
field,
|
||||
isEnabled,
|
||||
isExcluded,
|
||||
isOpen,
|
||||
providerId,
|
||||
toggleEnabledProvider,
|
||||
toggleExcludedProvider,
|
||||
value,
|
||||
}) => {
|
||||
const panelTree = getProviderActions(
|
||||
deleteProvider,
|
||||
isEnabled,
|
||||
isExcluded,
|
||||
toggleEnabledProvider,
|
||||
toggleExcludedProvider
|
||||
);
|
||||
return (
|
||||
<MyEuiPopover
|
||||
id={`popoverFor_${providerId}-${field}-${value}`}
|
||||
isOpen={isOpen}
|
||||
closePopover={closePopover}
|
||||
button={button}
|
||||
anchorPosition="downCenter"
|
||||
panelPaddingSize="none"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panelTree} data-test-subj="providerActions" />
|
||||
</MyEuiPopover>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnDataProviderRemoved,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { ProviderItemAndPopover } from './provider_item_and_popover';
|
||||
|
||||
const DropAndTargetDataProviders = styled.div<{ hasAndItem: boolean }>`
|
||||
border: 0.1rem dashed #999999;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
padding: 2px 3px;
|
||||
${({ hasAndItem }) =>
|
||||
hasAndItem
|
||||
? `&:hover {
|
||||
transition: background-color 0.7s ease;
|
||||
background-color: rgb(52, 55, 65);
|
||||
}`
|
||||
: ''};
|
||||
cursor: ${({ hasAndItem }) => (!hasAndItem ? `default` : 'inherit')};
|
||||
.euiPopover {
|
||||
display: inherit;
|
||||
.euiPopover__anchor {
|
||||
display: inherit;
|
||||
.euiButtonEmpty {
|
||||
width: 100%;
|
||||
.euiButtonEmpty__content {
|
||||
padding: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProviderItemDropProps {
|
||||
dataProvider: DataProvider;
|
||||
mousePosition?: { x: number; y: number; boundLeft: number; boundTop: number };
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onChangeDroppableAndProvider: OnChangeDroppableAndProvider;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
}
|
||||
|
||||
export const ProviderItemAndDragDrop = pure<ProviderItemDropProps>(
|
||||
({
|
||||
dataProvider,
|
||||
onChangeDataProviderKqlQuery,
|
||||
onChangeDroppableAndProvider,
|
||||
onDataProviderRemoved,
|
||||
onToggleDataProviderEnabled,
|
||||
onToggleDataProviderExcluded,
|
||||
}) => {
|
||||
const onMouseEnter = () => onChangeDroppableAndProvider(dataProvider.id);
|
||||
const onMouseLeave = () => onChangeDroppableAndProvider('');
|
||||
|
||||
return (
|
||||
<DropAndTargetDataProviders
|
||||
className="drop-and-provider-timeline"
|
||||
hasAndItem={dataProvider.and.length > 0}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
>
|
||||
<ProviderItemAndPopover
|
||||
dataProvidersAnd={dataProvider.and}
|
||||
providerId={dataProvider.id}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
</DropAndTargetDataProviders>
|
||||
);
|
||||
}
|
||||
);
|
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
* 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 {
|
||||
EuiAccordion,
|
||||
EuiBadge,
|
||||
EuiButtonEmpty,
|
||||
EuiButtonEmptyProps,
|
||||
EuiContextMenu,
|
||||
EuiHorizontalRule,
|
||||
EuiPopover,
|
||||
} from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnDataProviderRemoved,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { ProviderBadge } from './provider_badge';
|
||||
import { getProviderActions } from './provider_item_actions';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const NumberProviderAndBadge = styled(EuiBadge)`
|
||||
margin: 0px 5px;
|
||||
`;
|
||||
|
||||
const EuiBadgeAndStyled = styled(EuiBadge)`
|
||||
position: absolute;
|
||||
left: calc(50% - 15px);
|
||||
top: -18px;
|
||||
z-index: 1;
|
||||
width: 27px;
|
||||
height: 27px;
|
||||
padding: 8px 3px 0px 3px;
|
||||
border-radius: 100%;
|
||||
`;
|
||||
|
||||
const AndStyled = styled.div`
|
||||
position: relative;
|
||||
.euiHorizontalRule {
|
||||
margin: 28px 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
const EuiButtonContent = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
align-items: center;
|
||||
.euiBadge {
|
||||
position: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
interface ProviderItemAndPopoverProps {
|
||||
dataProvidersAnd: DataProvider[];
|
||||
providerId: string;
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
}
|
||||
|
||||
interface ProviderItemAndPopoverState {
|
||||
isPopoverOpen: boolean;
|
||||
}
|
||||
|
||||
export class ProviderItemAndPopover extends React.PureComponent<
|
||||
ProviderItemAndPopoverProps,
|
||||
ProviderItemAndPopoverState
|
||||
> {
|
||||
public readonly state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const { dataProvidersAnd, providerId } = this.props;
|
||||
|
||||
const hasAndItem = dataProvidersAnd.length > 0;
|
||||
const euiButtonProps: EuiButtonEmptyProps = hasAndItem
|
||||
? { iconType: 'arrowDown', iconSide: 'right' }
|
||||
: {};
|
||||
const button = (
|
||||
<EuiButtonEmpty
|
||||
{...euiButtonProps}
|
||||
onClick={this.togglePopover}
|
||||
style={hasAndItem ? {} : { cursor: 'default' }}
|
||||
>
|
||||
<EuiButtonContent>
|
||||
{hasAndItem && (
|
||||
<NumberProviderAndBadge color="primary">
|
||||
{dataProvidersAnd.length}
|
||||
</NumberProviderAndBadge>
|
||||
)}
|
||||
<EuiBadgeAndStyled>{i18n.AND}</EuiBadgeAndStyled>
|
||||
</EuiButtonContent>
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPopover
|
||||
id={`${providerId}-popover`}
|
||||
ownFocus
|
||||
button={button}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
closePopover={this.closePopover}
|
||||
data-test-subj="andProviderButton"
|
||||
>
|
||||
<div style={{ width: 'auto' }}>
|
||||
{dataProvidersAnd.map((providerAnd: DataProvider, index: number) => {
|
||||
const badge = (
|
||||
<ProviderBadge
|
||||
deleteProvider={() => this.deleteAndProvider(providerId, providerAnd.id)}
|
||||
field={providerAnd.queryMatch.displayField || providerAnd.queryMatch.field}
|
||||
kqlQuery={providerAnd.kqlQuery}
|
||||
isEnabled={providerAnd.enabled}
|
||||
isExcluded={providerAnd.excluded}
|
||||
providerId={`${providerId}.${providerAnd.id}`}
|
||||
val={providerAnd.queryMatch.displayValue || providerAnd.queryMatch.value}
|
||||
/>
|
||||
);
|
||||
const panelTree = getProviderActions(
|
||||
() => this.deleteAndProvider(providerId, providerAnd.id),
|
||||
providerAnd.enabled,
|
||||
providerAnd.excluded,
|
||||
() => this.toggleEnabledAndProvider(providerId, !providerAnd.enabled, providerAnd.id),
|
||||
() =>
|
||||
this.toggleExcludedAndProvider(providerId, !providerAnd.excluded, providerAnd.id)
|
||||
);
|
||||
return (
|
||||
<div key={`${providerId}-${providerAnd.id}-accordion`}>
|
||||
<EuiAccordion
|
||||
id={`${providerId}-${providerAnd.id}-accordion`}
|
||||
buttonContent={badge}
|
||||
paddingSize="l"
|
||||
data-test-subj="andProviderAccordion"
|
||||
>
|
||||
<EuiContextMenu initialPanelId={0} panels={panelTree} />
|
||||
</EuiAccordion>
|
||||
{index < dataProvidersAnd.length - 1 && (
|
||||
<AndStyled>
|
||||
<EuiBadgeAndStyled color="default">{i18n.AND}</EuiBadgeAndStyled>
|
||||
<EuiHorizontalRule />
|
||||
</AndStyled>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</EuiPopover>
|
||||
);
|
||||
}
|
||||
|
||||
private closePopover = () => {
|
||||
this.setState({
|
||||
...this.state,
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
private togglePopover = () => {
|
||||
if (this.props.dataProvidersAnd.length > 0) {
|
||||
this.setState({
|
||||
...this.state,
|
||||
isPopoverOpen: !this.state.isPopoverOpen,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
private deleteAndProvider = (providerId: string, andProviderId: string) => {
|
||||
this.props.onDataProviderRemoved(providerId, andProviderId);
|
||||
this.closePopover();
|
||||
};
|
||||
|
||||
private toggleEnabledAndProvider = (
|
||||
providerId: string,
|
||||
enabled: boolean,
|
||||
andProviderId: string
|
||||
) => {
|
||||
this.props.onToggleDataProviderEnabled({ providerId, enabled, andProviderId });
|
||||
this.closePopover();
|
||||
};
|
||||
|
||||
private toggleExcludedAndProvider = (
|
||||
providerId: string,
|
||||
excluded: boolean,
|
||||
andProviderId: string
|
||||
) => {
|
||||
this.props.onToggleDataProviderExcluded({ providerId, excluded, andProviderId });
|
||||
this.closePopover();
|
||||
};
|
||||
}
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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, { PureComponent } from 'react';
|
||||
|
||||
import { QueryDate } from './data_provider';
|
||||
import { ProviderBadge } from './provider_badge';
|
||||
import { ProviderItemActions } from './provider_item_actions';
|
||||
|
||||
interface ProviderItemBadgeProps {
|
||||
deleteProvider: () => void;
|
||||
field: string;
|
||||
kqlQuery: string;
|
||||
isEnabled: boolean;
|
||||
isExcluded: boolean;
|
||||
providerId: string;
|
||||
queryDate?: QueryDate;
|
||||
toggleEnabledProvider: () => void;
|
||||
toggleExcludedProvider: () => void;
|
||||
val: string | number;
|
||||
}
|
||||
|
||||
interface OwnState {
|
||||
isPopoverOpen: boolean;
|
||||
}
|
||||
|
||||
export class ProviderItemBadge extends PureComponent<ProviderItemBadgeProps, OwnState> {
|
||||
public readonly state = {
|
||||
isPopoverOpen: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const {
|
||||
deleteProvider,
|
||||
field,
|
||||
kqlQuery,
|
||||
isEnabled,
|
||||
isExcluded,
|
||||
queryDate,
|
||||
providerId,
|
||||
val,
|
||||
} = this.props;
|
||||
|
||||
const badge = (
|
||||
<ProviderBadge
|
||||
deleteProvider={deleteProvider}
|
||||
field={field}
|
||||
kqlQuery={kqlQuery}
|
||||
isEnabled={isEnabled}
|
||||
isExcluded={isExcluded}
|
||||
providerId={providerId}
|
||||
queryDate={queryDate}
|
||||
togglePopover={this.togglePopover}
|
||||
val={val}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<ProviderItemActions
|
||||
button={badge}
|
||||
closePopover={this.closePopover}
|
||||
deleteProvider={deleteProvider}
|
||||
field={field}
|
||||
kqlQuery={kqlQuery}
|
||||
isEnabled={isEnabled}
|
||||
isExcluded={isExcluded}
|
||||
isOpen={this.state.isPopoverOpen}
|
||||
providerId={providerId}
|
||||
toggleEnabledProvider={this.toggleEnabledProvider}
|
||||
toggleExcludedProvider={this.toggleExcludedProvider}
|
||||
value={val}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private togglePopover = () => {
|
||||
this.setState(prevState => ({
|
||||
isPopoverOpen: !prevState.isPopoverOpen,
|
||||
}));
|
||||
};
|
||||
|
||||
private closePopover = () => {
|
||||
this.setState({
|
||||
isPopoverOpen: false,
|
||||
});
|
||||
};
|
||||
|
||||
private toggleEnabledProvider = () => {
|
||||
this.props.toggleEnabledProvider();
|
||||
this.closePopover();
|
||||
};
|
||||
|
||||
private toggleExcludedProvider = () => {
|
||||
this.props.toggleExcludedProvider();
|
||||
this.closePopover();
|
||||
};
|
||||
}
|
|
@ -5,11 +5,11 @@
|
|||
*/
|
||||
|
||||
import { mount } from 'enzyme';
|
||||
import { noop, pick } from 'lodash/fp';
|
||||
import { noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper';
|
||||
import { mockDataProviderNames, mockDataProviders } from './mock/mock_data_providers';
|
||||
import { mockDataProviders } from './mock/mock_data_providers';
|
||||
import { getDraggableId, Providers } from './providers';
|
||||
|
||||
describe('Providers', () => {
|
||||
|
@ -21,14 +21,21 @@ describe('Providers', () => {
|
|||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
mockDataProviderNames().forEach(name => expect(wrapper.text()).toContain(name));
|
||||
mockDataProviders.forEach(dataProvider =>
|
||||
expect(wrapper.text()).toContain(
|
||||
dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value
|
||||
)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -42,29 +49,54 @@ describe('Providers', () => {
|
|||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="closeButton"]')
|
||||
.find('[data-test-subj="providerBadge"] svg')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'id', 'name', 'negated'],
|
||||
mockOnDataProviderRemoved.mock.calls[0][0]
|
||||
);
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
|
||||
});
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
enabled: true,
|
||||
id: 'id-Provider 1',
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
});
|
||||
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.at(2)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -77,7 +109,7 @@ describe('Providers', () => {
|
|||
});
|
||||
|
||||
describe('#onToggleDataProviderEnabled', () => {
|
||||
test('it invokes the onToggleDataProviderEnabled callback when the switch button is clicked', () => {
|
||||
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
|
||||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -86,30 +118,215 @@ describe('Providers', () => {
|
|||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="switchButton"]')
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'dataProvider.id', 'dataProvider.name', 'dataProvider.negated'],
|
||||
mockOnToggleDataProviderEnabled.mock.calls[0][0]
|
||||
expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({
|
||||
enabled: false,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#onToggleDataProviderExcluded', () => {
|
||||
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
|
||||
const onToggleDataProviderExcluded = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
dataProvider: {
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
id: 'id-Provider 1',
|
||||
},
|
||||
wrapper
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(onToggleDataProviderExcluded.mock.calls[0][0]).toEqual({
|
||||
excluded: true,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ProviderWithAndProvider', () => {
|
||||
test('Rendering And Provider', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
const andProviderBadge = wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first();
|
||||
|
||||
expect(andProviderBadge.text()).toEqual('2');
|
||||
});
|
||||
|
||||
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.at(2)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0]).toEqual(['id-Provider 1', 'id-Provider 2']);
|
||||
});
|
||||
|
||||
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({
|
||||
andProviderId: 'id-Provider 2',
|
||||
enabled: false,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
|
||||
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnToggleDataProviderExcluded = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<DroppableWrapper droppableId="unitTest" theme="dark">
|
||||
<Providers
|
||||
id="foo"
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
|
||||
/>
|
||||
</DroppableWrapper>
|
||||
</DragDropContext>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnToggleDataProviderExcluded.mock.calls[0][0]).toEqual({
|
||||
andProviderId: 'id-Provider 2',
|
||||
excluded: true,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -4,26 +4,75 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import {
|
||||
EuiBadge,
|
||||
// @ts-ignore
|
||||
EuiFlexGroup,
|
||||
EuiFlexItem,
|
||||
EuiHorizontalRule,
|
||||
} from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { pure } from 'recompose';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OnDataProviderRemoved, OnToggleDataProviderEnabled } from '../events';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnDataProviderRemoved,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import { Provider } from './provider';
|
||||
import { Empty } from './empty';
|
||||
import { ProviderItemAndDragDrop } from './provider_item_and_drag_drop';
|
||||
import { ProviderItemBadge } from './provider_item_badge';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
id: string;
|
||||
dataProviders: DataProvider[];
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onChangeDroppableAndProvider: OnChangeDroppableAndProvider;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
}
|
||||
|
||||
const PanelProviders = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
min-height: 100px;
|
||||
padding: 10px;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const EuiBadgeOrStyled = styled(EuiBadge)`
|
||||
position: absolute;
|
||||
right: -37px;
|
||||
top: 27px;
|
||||
z-index: 1;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
padding: 7px 6px 4px 6px;
|
||||
border-radius: 100%;
|
||||
`;
|
||||
|
||||
const PanelProvidersGroupContainer = styled(EuiFlexGroup)`
|
||||
position: relative;
|
||||
flex-grow: unset;
|
||||
margin-right: 40px;
|
||||
`;
|
||||
|
||||
const PanelProviderItemContainer = styled(EuiFlexItem)`
|
||||
height: 100%;
|
||||
.euiHorizontalRule {
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
top: 23px;
|
||||
width: 80px;
|
||||
right: -60px;
|
||||
}
|
||||
`;
|
||||
|
||||
interface GetDraggableIdParams {
|
||||
|
@ -42,33 +91,92 @@ export const getDraggableId = ({ id, dataProviderId }: GetDraggableIdParams): st
|
|||
* 3) applying boolean negation to the data provider
|
||||
*/
|
||||
export const Providers = pure<Props>(
|
||||
({ id, dataProviders, onDataProviderRemoved, onToggleDataProviderEnabled }: Props) => (
|
||||
<PanelProviders data-test-subj="providers">
|
||||
{dataProviders.map((dataProvider, i) => (
|
||||
// Providers are a special drop target that can't be drag-and-dropped
|
||||
// to another destination, so it doesn't use our DraggableWrapper
|
||||
<Draggable
|
||||
draggableId={getDraggableId({ id, dataProviderId: dataProvider.id })}
|
||||
index={i}
|
||||
key={dataProvider.id}
|
||||
>
|
||||
{provided => (
|
||||
<div
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
>
|
||||
<Provider
|
||||
data-test-subj="provider"
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
))}
|
||||
({
|
||||
id,
|
||||
dataProviders,
|
||||
onChangeDataProviderKqlQuery,
|
||||
onChangeDroppableAndProvider,
|
||||
onDataProviderRemoved,
|
||||
onToggleDataProviderEnabled,
|
||||
onToggleDataProviderExcluded,
|
||||
}) => (
|
||||
<PanelProviders className="timeline-drop-area" data-test-subj="providers">
|
||||
{dataProviders.map((dataProvider, i) => {
|
||||
const deleteProvider = () => onDataProviderRemoved(dataProvider.id);
|
||||
const toggleEnabledProvider = () =>
|
||||
onToggleDataProviderEnabled({
|
||||
providerId: dataProvider.id,
|
||||
enabled: !dataProvider.enabled,
|
||||
});
|
||||
const toggleExcludedProvider = () =>
|
||||
onToggleDataProviderExcluded({
|
||||
providerId: dataProvider.id,
|
||||
excluded: !dataProvider.excluded,
|
||||
});
|
||||
return (
|
||||
// Providers are a special drop target that can't be drag-and-dropped
|
||||
// to another destination, so it doesn't use our DraggableWrapper
|
||||
<PanelProvidersGroupContainer
|
||||
key={dataProvider.id}
|
||||
direction="row"
|
||||
className="provider-item-container"
|
||||
alignItems="center"
|
||||
gutterSize="none"
|
||||
>
|
||||
<PanelProviderItemContainer grow={false}>
|
||||
<EuiFlexGroup direction="column" gutterSize="none" justifyContent="spaceAround">
|
||||
<EuiFlexItem className="provider-item-filter-container" grow={false}>
|
||||
<Draggable
|
||||
draggableId={getDraggableId({ id, dataProviderId: dataProvider.id })}
|
||||
index={i}
|
||||
>
|
||||
{provided => (
|
||||
<div
|
||||
{...provided.draggableProps}
|
||||
{...provided.dragHandleProps}
|
||||
ref={provided.innerRef}
|
||||
data-test-subj="providerContainer"
|
||||
>
|
||||
<ProviderItemBadge
|
||||
field={
|
||||
dataProvider.queryMatch.displayField || dataProvider.queryMatch.field
|
||||
}
|
||||
kqlQuery={dataProvider.kqlQuery}
|
||||
isEnabled={dataProvider.enabled}
|
||||
isExcluded={dataProvider.excluded}
|
||||
deleteProvider={deleteProvider}
|
||||
toggleEnabledProvider={toggleEnabledProvider}
|
||||
toggleExcludedProvider={toggleExcludedProvider}
|
||||
providerId={dataProvider.id}
|
||||
queryDate={dataProvider.queryDate}
|
||||
val={
|
||||
dataProvider.queryMatch.displayValue || dataProvider.queryMatch.value
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Draggable>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<ProviderItemAndDragDrop
|
||||
dataProvider={dataProvider}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</PanelProviderItemContainer>
|
||||
<PanelProviderItemContainer grow={false}>
|
||||
<EuiBadgeOrStyled color="default">{i18n.OR.toLocaleUpperCase()}</EuiBadgeOrStyled>
|
||||
<EuiHorizontalRule />
|
||||
</PanelProviderItemContainer>
|
||||
</PanelProvidersGroupContainer>
|
||||
);
|
||||
})}
|
||||
<Empty />
|
||||
</PanelProviders>
|
||||
)
|
||||
);
|
||||
|
|
|
@ -1,34 +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 { EuiSwitch } from '@elastic/eui';
|
||||
import * as React from 'react';
|
||||
import { pure } from 'recompose';
|
||||
|
||||
import { OnToggleDataProviderEnabled } from '../events';
|
||||
import { DataProvider } from './data_provider';
|
||||
import * as i18n from './translations';
|
||||
|
||||
interface Props {
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
dataProvider: DataProvider;
|
||||
}
|
||||
|
||||
/** An affordance for enabling/disabling a data provider. It invokes `onToggleDataProviderEnabled` when clicked */
|
||||
export const SwitchButton = pure<Props>(({ onToggleDataProviderEnabled, dataProvider }) => {
|
||||
const onClick = () => {
|
||||
onToggleDataProviderEnabled({ dataProvider, enabled: !dataProvider.enabled });
|
||||
};
|
||||
|
||||
return (
|
||||
<EuiSwitch
|
||||
aria-label={i18n.TOGGLE}
|
||||
data-test-subj="switchButton"
|
||||
defaultChecked={dataProvider.enabled}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
});
|
|
@ -6,10 +6,28 @@
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const AND = i18n.translate('xpack.secops.dataProviders.and', {
|
||||
defaultMessage: 'AND',
|
||||
});
|
||||
|
||||
export const DELETE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.deleteDataProvider',
|
||||
{
|
||||
defaultMessage: 'Delete',
|
||||
}
|
||||
);
|
||||
|
||||
export const DROP_ANYTHING = i18n.translate('xpack.secops.dataProviders.dropAnything', {
|
||||
defaultMessage: 'Drop anything',
|
||||
});
|
||||
|
||||
export const EXCLUDE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.excludeDataProvider',
|
||||
{
|
||||
defaultMessage: 'Exclude results',
|
||||
}
|
||||
);
|
||||
|
||||
export const HIGHLIGHTED = i18n.translate('xpack.secops.dataProviders.highlighted', {
|
||||
defaultMessage: 'highlighted',
|
||||
});
|
||||
|
@ -18,6 +36,17 @@ export const HERE_TO_BUILD_AN = i18n.translate('xpack.secops.dataProviders.hereT
|
|||
defaultMessage: 'here to build an',
|
||||
});
|
||||
|
||||
export const INCLUDE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.includeDataProvider',
|
||||
{
|
||||
defaultMessage: 'Include results',
|
||||
}
|
||||
);
|
||||
|
||||
export const NOT = i18n.translate('xpack.secops.dataProviders.not', {
|
||||
defaultMessage: 'not',
|
||||
});
|
||||
|
||||
export const OR = i18n.translate('xpack.secops.dataProviders.or', {
|
||||
defaultMessage: 'or',
|
||||
});
|
||||
|
@ -30,9 +59,30 @@ export const TOGGLE = i18n.translate('xpack.secops.dataProviders.toggle', {
|
|||
defaultMessage: 'toggle',
|
||||
});
|
||||
|
||||
export const RE_ENABLE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.reEnableDataProvider',
|
||||
{
|
||||
defaultMessage: 'Re-enable',
|
||||
}
|
||||
);
|
||||
|
||||
export const REMOVE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.removeDataProvider',
|
||||
{
|
||||
defaultMessage: 'Remove Data Provider',
|
||||
}
|
||||
);
|
||||
|
||||
export const SHOW_OPTIONS_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.showOptionsDataProvider',
|
||||
{
|
||||
defaultMessage: 'Show options for',
|
||||
}
|
||||
);
|
||||
|
||||
export const TEMPORARILY_DISABLE_DATA_PROVIDER = i18n.translate(
|
||||
'xpack.secops.dataProviders.temporaryDisableDataProvider',
|
||||
{
|
||||
defaultMessage: 'Temporarily disable',
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,24 +6,33 @@
|
|||
|
||||
import { ColumnId } from './body/column_id';
|
||||
import { SortDirection } from './body/sort';
|
||||
import { DataProvider } from './data_providers/data_provider';
|
||||
|
||||
/** Invoked when a user clicks the close button to remove a data provider */
|
||||
export type OnDataProviderRemoved = (removed: DataProvider) => void;
|
||||
export type OnDataProviderRemoved = (providerId: string, andProviderId?: string) => void;
|
||||
|
||||
/** Invoked when a user temporarily disables or re-enables a data provider */
|
||||
export type OnToggleDataProviderEnabled = (
|
||||
toggled: {
|
||||
dataProvider: DataProvider;
|
||||
providerId: string;
|
||||
enabled: boolean;
|
||||
andProviderId?: string;
|
||||
}
|
||||
) => void;
|
||||
|
||||
/** Invoked when a user toggles negation ("boolean NOT") of a data provider */
|
||||
export type OnToggleDataProviderNegated = (
|
||||
negated: {
|
||||
dataProvider: DataProvider;
|
||||
negated: boolean;
|
||||
export type OnToggleDataProviderExcluded = (
|
||||
excluded: {
|
||||
providerId: string;
|
||||
excluded: boolean;
|
||||
andProviderId?: string;
|
||||
}
|
||||
) => void;
|
||||
|
||||
/** Invoked when a user change the kql query of our data provider */
|
||||
export type OnChangeDataProviderKqlQuery = (
|
||||
edit: {
|
||||
providerId: string;
|
||||
kqlQuery: string;
|
||||
}
|
||||
) => void;
|
||||
|
||||
|
@ -51,3 +60,5 @@ export type OnChangeItemsPerPage = (itemsPerPage: number) => void;
|
|||
|
||||
/** Invoked when a user clicks to load more item */
|
||||
export type OnLoadMore = (cursor: string, tieBreaker: string) => void;
|
||||
|
||||
export type OnChangeDroppableAndProvider = (providerId: string) => void;
|
||||
|
|
|
@ -34,11 +34,14 @@ describe('Header', () => {
|
|||
id="foo"
|
||||
columnHeaders={[]}
|
||||
dataProviders={mockDataProviders}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range="1 Day"
|
||||
show={true}
|
||||
sort={{
|
||||
|
|
|
@ -16,11 +16,14 @@ import { Sort } from '../body/sort';
|
|||
import { DataProviders } from '../data_providers';
|
||||
import { DataProvider } from '../data_providers/data_provider';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnColumnSorted,
|
||||
OnDataProviderRemoved,
|
||||
OnFilterChange,
|
||||
OnRangeSelected,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from '../events';
|
||||
import { StatefulSearchOrFilter } from '../search_or_filter';
|
||||
|
||||
|
@ -28,11 +31,14 @@ interface Props {
|
|||
columnHeaders: ColumnHeader[];
|
||||
id: string;
|
||||
dataProviders: DataProvider[];
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onChangeDroppableAndProvider: OnChangeDroppableAndProvider;
|
||||
onColumnSorted: OnColumnSorted;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onFilterChange: OnFilterChange;
|
||||
onRangeSelected: OnRangeSelected;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
range: string;
|
||||
show: boolean;
|
||||
sort: Sort;
|
||||
|
@ -48,11 +54,14 @@ export const TimelineHeader = pure<Props>(
|
|||
columnHeaders,
|
||||
id,
|
||||
dataProviders,
|
||||
onChangeDataProviderKqlQuery,
|
||||
onChangeDroppableAndProvider,
|
||||
onColumnSorted,
|
||||
onDataProviderRemoved,
|
||||
onFilterChange,
|
||||
onRangeSelected,
|
||||
onToggleDataProviderEnabled,
|
||||
onToggleDataProviderExcluded,
|
||||
range,
|
||||
show,
|
||||
sort,
|
||||
|
@ -62,8 +71,11 @@ export const TimelineHeader = pure<Props>(
|
|||
<DataProviders
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
show={show}
|
||||
theme={theme}
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { mockDataProviders } from './data_providers/mock/mock_data_providers';
|
||||
import { buildGlobalQuery } from './helpers';
|
||||
|
||||
const cleanUpKqlQuery = (str: string) => str.replace(/\n/g, '').replace(/\s\s+/g, ' ');
|
||||
|
||||
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 )'
|
||||
);
|
||||
});
|
||||
|
||||
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 )'
|
||||
);
|
||||
});
|
||||
|
||||
test('Buld KQL query with one data provider and one and', () => {
|
||||
const dataProviders = 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)'
|
||||
);
|
||||
});
|
||||
|
||||
test('Buld KQL query with two data provider and mutiple and', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 2);
|
||||
dataProviders[0].and = mockDataProviders.slice(2, 4);
|
||||
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)'
|
||||
);
|
||||
});
|
||||
});
|
|
@ -4,12 +4,53 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { isEmpty } from 'lodash/fp';
|
||||
import { isEmpty, isNumber } from 'lodash/fp';
|
||||
import { StaticIndexPattern } from 'ui/index_patterns';
|
||||
|
||||
import { convertKueryToElasticSearchQuery } from '../../lib/keury';
|
||||
import { convertKueryToElasticSearchQuery, escapeQueryValue } from '../../lib/keury';
|
||||
import { DataProvider } from './data_providers/data_provider';
|
||||
|
||||
const buildQueryMatch = (dataProvider: DataProvider) =>
|
||||
`${dataProvider.excluded ? 'NOT ' : ''}${
|
||||
dataProvider.queryMatch
|
||||
? `${dataProvider.queryMatch.field} : ${
|
||||
isNumber(dataProvider.queryMatch.value)
|
||||
? dataProvider.queryMatch.value
|
||||
: escapeQueryValue(dataProvider.queryMatch.value)
|
||||
}`
|
||||
: ''
|
||||
}`.trim();
|
||||
|
||||
const buildQueryDate = (dataProvider: DataProvider) =>
|
||||
dataProvider.queryDate
|
||||
? `@timestamp >= ${dataProvider.queryDate.from} and @timestamp <= ${dataProvider.queryDate.to}`
|
||||
: '';
|
||||
|
||||
const buildQueryForAndProvider = (dataAndProviders: DataProvider[]) =>
|
||||
dataAndProviders
|
||||
.reduce((andQuery, andDataProvider) => {
|
||||
const prepend = (q: string) => `${q !== '' ? `${q} and ` : ''}`;
|
||||
return andDataProvider.enabled
|
||||
? `${prepend(andQuery)} ${buildQueryMatch(andDataProvider)}`
|
||||
: andQuery;
|
||||
}, '')
|
||||
.trim();
|
||||
|
||||
export const buildGlobalQuery = (dataProviders: DataProvider[]) =>
|
||||
dataProviders
|
||||
.reduce((query, dataProvider) => {
|
||||
const prepend = (q: string) => `${q !== '' ? `${q} or ` : ''}`;
|
||||
return dataProvider.enabled
|
||||
? `${prepend(query)}(
|
||||
${buildQueryMatch(dataProvider)}
|
||||
${dataProvider.queryDate ? ` and ${buildQueryDate(dataProvider)}` : ''}
|
||||
${
|
||||
dataProvider.and.length > 0 ? ` and ${buildQueryForAndProvider(dataProvider.and)}` : ''
|
||||
})`.trim()
|
||||
: query;
|
||||
}, '')
|
||||
.trim();
|
||||
|
||||
export const combineQueries = (
|
||||
dataProviders: DataProvider[],
|
||||
indexPattern: StaticIndexPattern
|
||||
|
@ -18,19 +59,11 @@ export const combineQueries = (
|
|||
return null;
|
||||
}
|
||||
|
||||
const globalQuery = dataProviders.reduce((query, dataProvider) => {
|
||||
const prepend = (q: string) => `${q !== '' ? `${q} or ` : ''}`;
|
||||
|
||||
return dataProvider.enabled
|
||||
? `${prepend(query)} (${dataProvider.queryMatch}${
|
||||
dataProvider.queryDate ? ` and ${dataProvider.queryDate})` : ')'
|
||||
}`
|
||||
: query;
|
||||
}, '');
|
||||
|
||||
const globalQuery = buildGlobalQuery(dataProviders);
|
||||
if (isEmpty(globalQuery)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
filterQuery: convertKueryToElasticSearchQuery(globalQuery, indexPattern),
|
||||
};
|
||||
|
|
|
@ -21,11 +21,14 @@ import { columnRenderers, rowRenderers } from './body/renderers';
|
|||
import { Sort } from './body/sort';
|
||||
import { DataProvider } from './data_providers/data_provider';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnChangeItemsPerPage,
|
||||
OnColumnSorted,
|
||||
OnDataProviderRemoved,
|
||||
OnRangeSelected,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from './events';
|
||||
import { Timeline } from './timeline';
|
||||
|
||||
|
@ -69,11 +72,24 @@ interface DispatchProps {
|
|||
removeProvider?: ActionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
andProviderId?: string;
|
||||
}>;
|
||||
updateDataProviderEnabled?: ActionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
enabled: boolean;
|
||||
andProviderId?: string;
|
||||
}>;
|
||||
updateDataProviderExcluded?: ActionCreator<{
|
||||
id: string;
|
||||
excluded: boolean;
|
||||
providerId: string;
|
||||
andProviderId?: string;
|
||||
}>;
|
||||
updateDataProviderKqlQuery?: ActionCreator<{
|
||||
id: string;
|
||||
kqlQuery: string;
|
||||
providerId: string;
|
||||
}>;
|
||||
updateItemsPerPage?: ActionCreator<{
|
||||
id: string;
|
||||
|
@ -87,6 +103,10 @@ interface DispatchProps {
|
|||
id: string;
|
||||
activePage: number;
|
||||
}>;
|
||||
updateHighlightedDropAndProviderId?: ActionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
type Props = OwnProps & StateReduxProps & DispatchProps;
|
||||
|
@ -115,23 +135,43 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
|
|||
updateRange,
|
||||
updateSort,
|
||||
updateDataProviderEnabled,
|
||||
updateDataProviderExcluded,
|
||||
updateDataProviderKqlQuery,
|
||||
updateHighlightedDropAndProviderId,
|
||||
updateItemsPerPage,
|
||||
} = this.props;
|
||||
|
||||
const onColumnSorted: OnColumnSorted = sorted => updateSort!({ id, sort: sorted });
|
||||
|
||||
const onDataProviderRemoved: OnDataProviderRemoved = dataProvider =>
|
||||
removeProvider!({ id, providerId: dataProvider.id });
|
||||
const onDataProviderRemoved: OnDataProviderRemoved = (
|
||||
providerId: string,
|
||||
andProviderId?: string
|
||||
) => removeProvider!({ id, providerId, andProviderId });
|
||||
|
||||
const onRangeSelected: OnRangeSelected = selectedRange =>
|
||||
updateRange!({ id, range: selectedRange });
|
||||
|
||||
const onToggleDataProviderEnabled: OnToggleDataProviderEnabled = ({ dataProvider, enabled }) =>
|
||||
updateDataProviderEnabled!({ id, enabled, providerId: dataProvider.id });
|
||||
const onToggleDataProviderEnabled: OnToggleDataProviderEnabled = ({
|
||||
providerId,
|
||||
enabled,
|
||||
andProviderId,
|
||||
}) => updateDataProviderEnabled!({ id, enabled, providerId, andProviderId });
|
||||
|
||||
const onToggleDataProviderExcluded: OnToggleDataProviderExcluded = ({
|
||||
providerId,
|
||||
excluded,
|
||||
andProviderId,
|
||||
}) => updateDataProviderExcluded!({ id, excluded, providerId, andProviderId });
|
||||
|
||||
const onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery = ({ providerId, kqlQuery }) =>
|
||||
updateDataProviderKqlQuery!({ id, kqlQuery, providerId });
|
||||
|
||||
const onChangeItemsPerPage: OnChangeItemsPerPage = itemsChangedPerPage =>
|
||||
updateItemsPerPage!({ id, itemsPerPage: itemsChangedPerPage });
|
||||
|
||||
const onChangeDroppableAndProvider: OnChangeDroppableAndProvider = providerId =>
|
||||
updateHighlightedDropAndProviderId!({ id, providerId });
|
||||
|
||||
return (
|
||||
<WithSource sourceId="default">
|
||||
{({ indexPattern }) => (
|
||||
|
@ -144,12 +184,15 @@ class StatefulTimelineComponent extends React.PureComponent<Props> {
|
|||
flyoutHeight={flyoutHeight}
|
||||
itemsPerPage={itemsPerPage!}
|
||||
itemsPerPageOptions={itemsPerPageOptions!}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onColumnSorted={onColumnSorted}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onFilterChange={noop} // TODO: this is the callback for column filters, which is out scope for this phase of delivery
|
||||
onRangeSelected={onRangeSelected}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
range={range!}
|
||||
rowRenderers={rowRenderers}
|
||||
show={show!}
|
||||
|
@ -181,6 +224,9 @@ export const StatefulTimeline = connect(
|
|||
updateRange: timelineActions.updateRange,
|
||||
updateSort: timelineActions.updateSort,
|
||||
updateDataProviderEnabled: timelineActions.updateDataProviderEnabled,
|
||||
updateDataProviderExcluded: timelineActions.updateDataProviderExcluded,
|
||||
updateDataProviderKqlQuery: timelineActions.updateDataProviderKqlQuery,
|
||||
updateHighlightedDropAndProviderId: timelineActions.updateHighlightedDropAndProviderId,
|
||||
updateItemsPerPage: timelineActions.updateItemsPerPage,
|
||||
updateItemsPerPageOptions: timelineActions.updateItemsPerPageOptions,
|
||||
removeProvider: timelineActions.removeProvider,
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { I18nProvider } from '@kbn/i18n/react';
|
||||
import { mount } from 'enzyme';
|
||||
import { noop, pick } from 'lodash/fp';
|
||||
import { noop } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { MockedProvider } from 'react-apollo/test-utils';
|
||||
import { DragDropContext } from 'react-beautiful-dnd';
|
||||
|
@ -104,12 +104,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -141,12 +144,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -178,12 +184,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -220,12 +229,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onColumnSorted={mockOnColumnSorted}
|
||||
onChangeItemsPerPage={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -252,7 +264,7 @@ describe('Timeline', () => {
|
|||
});
|
||||
|
||||
describe('onDataProviderRemoved', () => {
|
||||
test('it invokes the onDataProviderRemoved callback when the close button on a provider is clicked', () => {
|
||||
test('it invokes the onDataProviderRemoved callback when the delete button on a provider is clicked', () => {
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
|
@ -269,12 +281,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -289,21 +304,64 @@ describe('Timeline', () => {
|
|||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="closeButton"]')
|
||||
.find('[data-test-subj="providerBadge"] svg')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'id', 'name', 'negated'],
|
||||
mockOnDataProviderRemoved.mock.calls[0][0]
|
||||
);
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
|
||||
});
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
enabled: true,
|
||||
id: 'id-Provider 1',
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
});
|
||||
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the provider menu', () => {
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={headers}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={mockDataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
wrapper
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.at(2)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0][0]).toEqual('id-Provider 1');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -332,12 +390,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={mockOnFilterChange}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -364,7 +425,7 @@ describe('Timeline', () => {
|
|||
});
|
||||
|
||||
describe('onToggleDataProviderEnabled', () => {
|
||||
test('it invokes the onToggleDataProviderEnabled callback when the input is updated', () => {
|
||||
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the provider menu', () => {
|
||||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
|
||||
// for this test, all columns have text filters
|
||||
|
@ -387,12 +448,15 @@ describe('Timeline', () => {
|
|||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
|
@ -407,22 +471,334 @@ describe('Timeline', () => {
|
|||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="switchButton"]')
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
const callbackParams = pick(
|
||||
['enabled', 'dataProvider.id', 'dataProvider.name', 'dataProvider.negated'],
|
||||
mockOnToggleDataProviderEnabled.mock.calls[0][0]
|
||||
expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({
|
||||
providerId: 'id-Provider 1',
|
||||
enabled: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('onToggleDataProviderExcluded', () => {
|
||||
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the provider menu', () => {
|
||||
const mockOnToggleDataProviderExcluded = jest.fn();
|
||||
|
||||
// for this test, all columns have text filters
|
||||
const allColumnsHaveTextFilters = headers.map(header => ({
|
||||
...header,
|
||||
columnHeaderType: 'text-filter' as ColumnHeaderType,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={allColumnsHaveTextFilters}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={mockDataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
expect(callbackParams).toEqual({
|
||||
dataProvider: {
|
||||
name: 'Provider 1',
|
||||
negated: false,
|
||||
id: 'id-Provider 1',
|
||||
},
|
||||
wrapper
|
||||
.find('[data-test-subj="providerBadge"]')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="providerActions"] button.euiContextMenuItem')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnToggleDataProviderExcluded.mock.calls[0][0]).toEqual({
|
||||
providerId: 'id-Provider 1',
|
||||
excluded: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('#ProviderWithAndProvider', () => {
|
||||
test('Rendering And Provider', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
|
||||
// for this test, all columns have text filters
|
||||
const allColumnsHaveTextFilters = headers.map(header => ({
|
||||
...header,
|
||||
columnHeaderType: 'text-filter' as ColumnHeaderType,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={allColumnsHaveTextFilters}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={dataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
const andProviderBadge = wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first();
|
||||
|
||||
expect(andProviderBadge.text()).toEqual('2');
|
||||
});
|
||||
|
||||
test('it invokes the onDataProviderRemoved callback when you click on the option "Delete" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnDataProviderRemoved = jest.fn();
|
||||
|
||||
// for this test, all columns have text filters
|
||||
const allColumnsHaveTextFilters = headers.map(header => ({
|
||||
...header,
|
||||
columnHeaderType: 'text-filter' as ColumnHeaderType,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={allColumnsHaveTextFilters}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={dataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={mockOnDataProviderRemoved}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.at(2)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnDataProviderRemoved.mock.calls[0]).toEqual(['id-Provider 1', 'id-Provider 2']);
|
||||
});
|
||||
|
||||
test('it invokes the onToggleDataProviderEnabled callback when you click on the option "Temporary disable" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnToggleDataProviderEnabled = jest.fn();
|
||||
|
||||
// for this test, all columns have text filters
|
||||
const allColumnsHaveTextFilters = headers.map(header => ({
|
||||
...header,
|
||||
columnHeaderType: 'text-filter' as ColumnHeaderType,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={allColumnsHaveTextFilters}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={dataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={mockOnToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={noop}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.at(1)
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnToggleDataProviderEnabled.mock.calls[0][0]).toEqual({
|
||||
andProviderId: 'id-Provider 2',
|
||||
enabled: false,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
|
||||
test('it invokes the onToggleDataProviderExcluded callback when you click on the option "Exclude results" in the accordeon menu', () => {
|
||||
const dataProviders = mockDataProviders.slice(0, 1);
|
||||
dataProviders[0].and = mockDataProviders.slice(1, 3);
|
||||
const mockOnToggleDataProviderExcluded = jest.fn();
|
||||
|
||||
// for this test, all columns have text filters
|
||||
const allColumnsHaveTextFilters = headers.map(header => ({
|
||||
...header,
|
||||
columnHeaderType: 'text-filter' as ColumnHeaderType,
|
||||
}));
|
||||
|
||||
const wrapper = mount(
|
||||
<I18nProvider>
|
||||
<ReduxStoreProvider store={store}>
|
||||
<DragDropContext onDragEnd={noop}>
|
||||
<MockedProvider mocks={mocks}>
|
||||
<Timeline
|
||||
id="foo"
|
||||
columnHeaders={allColumnsHaveTextFilters}
|
||||
columnRenderers={columnRenderers}
|
||||
dataProviders={dataProviders}
|
||||
flyoutHeight={testFlyoutHeight}
|
||||
flyoutHeaderHeight={flyoutHeaderHeight}
|
||||
itemsPerPage={5}
|
||||
itemsPerPageOptions={[5, 10, 20]}
|
||||
onChangeDataProviderKqlQuery={noop}
|
||||
onChangeDroppableAndProvider={noop}
|
||||
onChangeItemsPerPage={noop}
|
||||
onColumnSorted={noop}
|
||||
onDataProviderRemoved={noop}
|
||||
onFilterChange={noop}
|
||||
onRangeSelected={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
onToggleDataProviderExcluded={mockOnToggleDataProviderExcluded}
|
||||
range={'1 Day'}
|
||||
rowRenderers={rowRenderers}
|
||||
show={true}
|
||||
sort={sort}
|
||||
theme="dark"
|
||||
indexPattern={indexPattern}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</DragDropContext>
|
||||
</ReduxStoreProvider>
|
||||
</I18nProvider>
|
||||
);
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderButton"] span.euiBadge')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
wrapper.update();
|
||||
|
||||
wrapper
|
||||
.find('[data-test-subj="andProviderAccordion"] button.euiContextMenuItem')
|
||||
.first()
|
||||
.simulate('click');
|
||||
|
||||
expect(mockOnToggleDataProviderExcluded.mock.calls[0][0]).toEqual({
|
||||
andProviderId: 'id-Provider 2',
|
||||
excluded: true,
|
||||
providerId: 'id-Provider 1',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -20,12 +20,15 @@ import { ColumnRenderer } from './body/renderers';
|
|||
import { Sort } from './body/sort';
|
||||
import { DataProvider } from './data_providers/data_provider';
|
||||
import {
|
||||
OnChangeDataProviderKqlQuery,
|
||||
OnChangeDroppableAndProvider,
|
||||
OnChangeItemsPerPage,
|
||||
OnColumnSorted,
|
||||
OnDataProviderRemoved,
|
||||
OnFilterChange,
|
||||
OnRangeSelected,
|
||||
OnToggleDataProviderEnabled,
|
||||
OnToggleDataProviderExcluded,
|
||||
} from './events';
|
||||
import { Footer, footerHeight } from './footer';
|
||||
import { TimelineHeader } from './header/timeline_header';
|
||||
|
@ -41,12 +44,15 @@ interface Props {
|
|||
indexPattern: StaticIndexPattern;
|
||||
itemsPerPage: number;
|
||||
itemsPerPageOptions: number[];
|
||||
onChangeDataProviderKqlQuery: OnChangeDataProviderKqlQuery;
|
||||
onChangeDroppableAndProvider: OnChangeDroppableAndProvider;
|
||||
onChangeItemsPerPage: OnChangeItemsPerPage;
|
||||
onColumnSorted: OnColumnSorted;
|
||||
onDataProviderRemoved: OnDataProviderRemoved;
|
||||
onFilterChange: OnFilterChange;
|
||||
onRangeSelected: OnRangeSelected;
|
||||
onToggleDataProviderEnabled: OnToggleDataProviderEnabled;
|
||||
onToggleDataProviderExcluded: OnToggleDataProviderExcluded;
|
||||
range: string;
|
||||
rowRenderers: RowRenderer[];
|
||||
show: boolean;
|
||||
|
@ -70,12 +76,15 @@ export const Timeline = pure<Props>(
|
|||
indexPattern,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
onChangeDataProviderKqlQuery,
|
||||
onChangeDroppableAndProvider,
|
||||
onChangeItemsPerPage,
|
||||
onColumnSorted,
|
||||
onDataProviderRemoved,
|
||||
onFilterChange,
|
||||
onRangeSelected,
|
||||
onToggleDataProviderEnabled,
|
||||
onToggleDataProviderExcluded,
|
||||
range,
|
||||
rowRenderers,
|
||||
show,
|
||||
|
@ -93,11 +102,14 @@ export const Timeline = pure<Props>(
|
|||
columnHeaders={columnHeaders}
|
||||
id={id}
|
||||
dataProviders={dataProviders}
|
||||
onChangeDataProviderKqlQuery={onChangeDataProviderKqlQuery}
|
||||
onChangeDroppableAndProvider={onChangeDroppableAndProvider}
|
||||
onColumnSorted={onColumnSorted}
|
||||
onDataProviderRemoved={onDataProviderRemoved}
|
||||
onFilterChange={onFilterChange}
|
||||
onRangeSelected={onRangeSelected}
|
||||
onToggleDataProviderEnabled={onToggleDataProviderEnabled}
|
||||
onToggleDataProviderExcluded={onToggleDataProviderExcluded}
|
||||
range={range}
|
||||
show={show}
|
||||
sort={sort}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { EuiPanel } from '@elastic/eui';
|
||||
import { noop, range } from 'lodash/fp';
|
||||
import { range } from 'lodash/fp';
|
||||
import * as React from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { Dispatch } from 'redux';
|
||||
|
@ -61,11 +61,7 @@ class PlaceholdersComponent extends React.PureComponent<Props> {
|
|||
render={(dataProvider, _, snapshot) =>
|
||||
snapshot.isDragging ? (
|
||||
<DragEffects>
|
||||
<Provider
|
||||
dataProvider={dataProvider}
|
||||
onDataProviderRemoved={noop}
|
||||
onToggleDataProviderEnabled={noop}
|
||||
/>
|
||||
<Provider dataProvider={dataProvider} />
|
||||
</DragEffects>
|
||||
) : (
|
||||
mockDataProviders[i].name
|
||||
|
|
|
@ -56,6 +56,7 @@ export const mockGlobalState: State = {
|
|||
dataProviders: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
|
|
@ -34,7 +34,11 @@ export const createTimeline = actionCreator<{ id: string; show?: boolean }>('CRE
|
|||
|
||||
export const pinEvent = actionCreator<{ id: string; eventId: string }>('PIN_EVENT');
|
||||
|
||||
export const removeProvider = actionCreator<{ id: string; providerId: string }>('REMOVE_PROVIDER');
|
||||
export const removeProvider = actionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
andProviderId?: string;
|
||||
}>('REMOVE_PROVIDER');
|
||||
|
||||
export const showTimeline = actionCreator<{ id: string; show: boolean }>('SHOW_TIMELINE');
|
||||
|
||||
|
@ -44,8 +48,27 @@ export const updateDataProviderEnabled = actionCreator<{
|
|||
id: string;
|
||||
enabled: boolean;
|
||||
providerId: string;
|
||||
andProviderId?: string;
|
||||
}>('TOGGLE_PROVIDER_ENABLED');
|
||||
|
||||
export const updateDataProviderExcluded = actionCreator<{
|
||||
id: string;
|
||||
excluded: boolean;
|
||||
providerId: string;
|
||||
andProviderId?: string;
|
||||
}>('TOGGLE_PROVIDER_EXCLUDED');
|
||||
|
||||
export const updateDataProviderKqlQuery = actionCreator<{
|
||||
id: string;
|
||||
kqlQuery: string;
|
||||
providerId: string;
|
||||
}>('PROVIDER_EDIT_KQL_QUERY');
|
||||
|
||||
export const updateHighlightedDropAndProviderId = actionCreator<{
|
||||
id: string;
|
||||
providerId: string;
|
||||
}>('UPDATE_DROP_AND_PROVIDER');
|
||||
|
||||
export const updateDescription = actionCreator<{ id: string; description: string }>(
|
||||
'UPDATE_DESCRIPTION'
|
||||
);
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { filter, getOr, omit, uniq } from 'lodash/fp';
|
||||
import { getOr, omit, uniq } from 'lodash/fp';
|
||||
import { TimelineById, TimelineState } from '.';
|
||||
import { Sort } from '../../../components/timeline/body/sort';
|
||||
import { DataProvider } from '../../../components/timeline/data_providers/data_provider';
|
||||
import { KqlMode, timelineDefaults } from './model';
|
||||
import { KqlMode, timelineDefaults, TimelineModel } from './model';
|
||||
|
||||
const EMPTY_TIMELINE_BY_ID: TimelineById = {}; // stable reference
|
||||
|
||||
|
@ -187,20 +187,50 @@ export const applyDeltaToCurrentWidth = ({
|
|||
};
|
||||
};
|
||||
|
||||
interface AddTimelineProviderParams {
|
||||
id: string;
|
||||
provider: DataProvider;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
const addAndToProviderInTimeline = (
|
||||
id: string,
|
||||
provider: DataProvider,
|
||||
timeline: TimelineModel,
|
||||
timelineById: TimelineById
|
||||
): TimelineById => {
|
||||
const alreadyExistsProviderIndex = timeline.dataProviders.findIndex(
|
||||
p => p.id === timeline.highlightedDropAndProviderId
|
||||
);
|
||||
const newProvider = timeline.dataProviders[alreadyExistsProviderIndex];
|
||||
const alreadyExistsAndProviderIndex = newProvider.and.findIndex(p => p.id === provider.id);
|
||||
|
||||
export const addTimelineProvider = ({
|
||||
id,
|
||||
provider,
|
||||
timelineById,
|
||||
}: AddTimelineProviderParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
const dataProviders = [
|
||||
...timeline.dataProviders.slice(0, alreadyExistsProviderIndex),
|
||||
{
|
||||
...timeline.dataProviders[alreadyExistsProviderIndex],
|
||||
and:
|
||||
alreadyExistsAndProviderIndex > -1
|
||||
? [
|
||||
...newProvider.and.slice(0, alreadyExistsAndProviderIndex),
|
||||
provider,
|
||||
...newProvider.and.slice(alreadyExistsAndProviderIndex + 1),
|
||||
]
|
||||
: [...newProvider.and, provider],
|
||||
},
|
||||
...timeline.dataProviders.slice(alreadyExistsProviderIndex + 1),
|
||||
];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
dataProviders,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const addProviderToTimeline = (
|
||||
id: string,
|
||||
provider: DataProvider,
|
||||
timeline: TimelineModel,
|
||||
timelineById: TimelineById
|
||||
): TimelineById => {
|
||||
const alreadyExistsAtIndex = timeline.dataProviders.findIndex(p => p.id === provider.id);
|
||||
|
||||
const dataProviders =
|
||||
alreadyExistsAtIndex > -1
|
||||
? [
|
||||
|
@ -218,6 +248,25 @@ export const addTimelineProvider = ({
|
|||
},
|
||||
};
|
||||
};
|
||||
interface AddTimelineProviderParams {
|
||||
id: string;
|
||||
provider: DataProvider;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const addTimelineProvider = ({
|
||||
id,
|
||||
provider,
|
||||
timelineById,
|
||||
}: AddTimelineProviderParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
if (timeline.highlightedDropAndProviderId !== '') {
|
||||
return addAndToProviderInTimeline(id, provider, timeline, timelineById);
|
||||
} else {
|
||||
return addProviderToTimeline(id, provider, timeline, timelineById);
|
||||
}
|
||||
};
|
||||
|
||||
interface UpdateTimelineKqlModeParams {
|
||||
id: string;
|
||||
|
@ -415,11 +464,39 @@ export const updateTimelineSort = ({
|
|||
};
|
||||
};
|
||||
|
||||
const updateEnabledAndProvider = (
|
||||
andProviderId: string,
|
||||
enabled: boolean,
|
||||
providerId: string,
|
||||
timeline: TimelineModel
|
||||
) =>
|
||||
timeline.dataProviders.map(provider =>
|
||||
provider.id === providerId
|
||||
? {
|
||||
...provider,
|
||||
and: provider.and.map(andProvider =>
|
||||
andProvider.id === andProviderId ? { ...andProvider, enabled } : andProvider
|
||||
),
|
||||
}
|
||||
: provider
|
||||
);
|
||||
|
||||
const updateEnabledProvider = (enabled: boolean, providerId: string, timeline: TimelineModel) =>
|
||||
timeline.dataProviders.map(provider =>
|
||||
provider.id === providerId
|
||||
? {
|
||||
...provider,
|
||||
enabled,
|
||||
}
|
||||
: provider
|
||||
);
|
||||
|
||||
interface UpdateTimelineProviderEnabledParams {
|
||||
id: string;
|
||||
providerId: string;
|
||||
enabled: boolean;
|
||||
timelineById: TimelineById;
|
||||
andProviderId?: string;
|
||||
}
|
||||
|
||||
export const updateTimelineProviderEnabled = ({
|
||||
|
@ -427,14 +504,94 @@ export const updateTimelineProviderEnabled = ({
|
|||
providerId,
|
||||
enabled,
|
||||
timelineById,
|
||||
andProviderId,
|
||||
}: UpdateTimelineProviderEnabledParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
dataProviders: andProviderId
|
||||
? updateEnabledAndProvider(andProviderId, enabled, providerId, timeline)
|
||||
: updateEnabledProvider(enabled, providerId, timeline),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const updateExcludedAndProvider = (
|
||||
andProviderId: string,
|
||||
excluded: boolean,
|
||||
providerId: string,
|
||||
timeline: TimelineModel
|
||||
) =>
|
||||
timeline.dataProviders.map(provider =>
|
||||
provider.id === providerId
|
||||
? {
|
||||
...provider,
|
||||
and: provider.and.map(andProvider =>
|
||||
andProvider.id === andProviderId ? { ...andProvider, excluded } : andProvider
|
||||
),
|
||||
}
|
||||
: provider
|
||||
);
|
||||
|
||||
const updateExcludedProvider = (excluded: boolean, providerId: string, timeline: TimelineModel) =>
|
||||
timeline.dataProviders.map(provider =>
|
||||
provider.id === providerId
|
||||
? {
|
||||
...provider,
|
||||
excluded,
|
||||
}
|
||||
: provider
|
||||
);
|
||||
|
||||
interface UpdateTimelineProviderExcludedParams {
|
||||
id: string;
|
||||
providerId: string;
|
||||
excluded: boolean;
|
||||
timelineById: TimelineById;
|
||||
andProviderId?: string;
|
||||
}
|
||||
|
||||
export const updateTimelineProviderExcluded = ({
|
||||
id,
|
||||
providerId,
|
||||
excluded,
|
||||
timelineById,
|
||||
andProviderId,
|
||||
}: UpdateTimelineProviderExcludedParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
dataProviders: andProviderId
|
||||
? updateExcludedAndProvider(andProviderId, excluded, providerId, timeline)
|
||||
: updateExcludedProvider(excluded, providerId, timeline),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateTimelineProviderKqlQueryParams {
|
||||
id: string;
|
||||
providerId: string;
|
||||
kqlQuery: string;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const updateTimelineProviderKqlQuery = ({
|
||||
id,
|
||||
providerId,
|
||||
kqlQuery,
|
||||
timelineById,
|
||||
}: UpdateTimelineProviderKqlQueryParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
dataProviders: timeline.dataProviders.map(provider =>
|
||||
provider.id === providerId ? { ...provider, ...{ enabled } } : provider
|
||||
provider.id === providerId ? { ...provider, ...{ kqlQuery } } : provider
|
||||
),
|
||||
},
|
||||
};
|
||||
|
@ -503,23 +660,54 @@ export const updateTimelinePerPageOptions = ({
|
|||
};
|
||||
};
|
||||
|
||||
const removeAndProvider = (andProviderId: string, providerId: string, timeline: TimelineModel) => {
|
||||
const providerIndex = timeline.dataProviders.findIndex(p => p.id === providerId);
|
||||
const providerAndIndex = timeline.dataProviders[providerIndex].and.findIndex(
|
||||
p => p.id === andProviderId
|
||||
);
|
||||
return [
|
||||
...timeline.dataProviders.slice(0, providerIndex),
|
||||
{
|
||||
...timeline.dataProviders[providerIndex],
|
||||
and: [
|
||||
...timeline.dataProviders[providerIndex].and.slice(0, providerAndIndex),
|
||||
...timeline.dataProviders[providerIndex].and.slice(providerAndIndex + 1),
|
||||
],
|
||||
},
|
||||
...timeline.dataProviders.slice(providerIndex + 1),
|
||||
];
|
||||
};
|
||||
|
||||
const removeProvider = (providerId: string, timeline: TimelineModel) => {
|
||||
const providerIndex = timeline.dataProviders.findIndex(p => p.id === providerId);
|
||||
return [
|
||||
...timeline.dataProviders.slice(0, providerIndex),
|
||||
...timeline.dataProviders.slice(providerIndex + 1),
|
||||
];
|
||||
};
|
||||
|
||||
interface RemoveTimelineProviderParams {
|
||||
id: string;
|
||||
providerId: string;
|
||||
timelineById: TimelineById;
|
||||
andProviderId?: string;
|
||||
}
|
||||
|
||||
export const removeTimelineProvider = ({
|
||||
id,
|
||||
providerId,
|
||||
timelineById,
|
||||
andProviderId,
|
||||
}: RemoveTimelineProviderParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
dataProviders: filter(p => p.id !== providerId, timeline.dataProviders),
|
||||
dataProviders: andProviderId
|
||||
? removeAndProvider(andProviderId, providerId, timeline)
|
||||
: removeProvider(providerId, timeline),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
@ -544,3 +732,25 @@ export const unPinTimelineEvent = ({
|
|||
},
|
||||
};
|
||||
};
|
||||
|
||||
interface UpdateHighlightedDropAndProviderIdParams {
|
||||
id: string;
|
||||
providerId: string;
|
||||
timelineById: TimelineById;
|
||||
}
|
||||
|
||||
export const updateHighlightedDropAndProvider = ({
|
||||
id,
|
||||
providerId,
|
||||
timelineById,
|
||||
}: UpdateHighlightedDropAndProviderIdParams): TimelineById => {
|
||||
const timeline = timelineById[id];
|
||||
|
||||
return {
|
||||
...timelineById,
|
||||
[id]: {
|
||||
...timeline,
|
||||
highlightedDropAndProviderId: providerId,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ export interface TimelineModel {
|
|||
eventIdToNoteIds: { [eventId: string]: string[] };
|
||||
/** The chronological history of actions related to this timeline */
|
||||
historyIds: string[];
|
||||
/** The chronological history of actions related to this timeline */
|
||||
highlightedDropAndProviderId: string;
|
||||
/** Uniquely identifies the timeline */
|
||||
id: string;
|
||||
/** When true, this timeline was marked as "favorite" by the user */
|
||||
|
@ -57,6 +59,7 @@ export const timelineDefaults: Readonly<
|
|||
| 'dataProviders'
|
||||
| 'description'
|
||||
| 'eventIdToNoteIds'
|
||||
| 'highlightedDropAndProviderId'
|
||||
| 'historyIds'
|
||||
| 'isFavorite'
|
||||
| 'isLive'
|
||||
|
@ -76,6 +79,7 @@ export const timelineDefaults: Readonly<
|
|||
dataProviders: [],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
updateTimelineItemsPerPage,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineProviderEnabled,
|
||||
updateTimelineProviderExcluded,
|
||||
updateTimelineProviders,
|
||||
updateTimelineRange,
|
||||
updateTimelineShowTimeline,
|
||||
|
@ -30,13 +31,21 @@ const timelineByIdMock: TimelineById = {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'foo',
|
||||
isFavorite: false,
|
||||
|
@ -109,9 +118,16 @@ describe('Timeline', () => {
|
|||
id: '567',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
|
@ -124,9 +140,16 @@ describe('Timeline', () => {
|
|||
id: '567',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
const update = addTimelineProvider({
|
||||
id: 'foo',
|
||||
|
@ -143,9 +166,16 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
const update = addTimelineProvider({
|
||||
id: 'foo',
|
||||
|
@ -161,9 +191,16 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'my name changed',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
const update = addTimelineProvider({
|
||||
id: 'foo',
|
||||
|
@ -174,6 +211,132 @@ describe('Timeline', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#addAndProviderToTimelineProvider', () => {
|
||||
test('should add a new and provider to an existing timeline provider', () => {
|
||||
const providerToAdd = {
|
||||
and: [],
|
||||
id: '567',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
const newTimeline = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: providerToAdd,
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
|
||||
newTimeline.foo.highlightedDropAndProviderId = '567';
|
||||
|
||||
const andProviderToAdd = {
|
||||
and: [],
|
||||
id: '568',
|
||||
name: 'And Data Provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
const update = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: andProviderToAdd,
|
||||
timelineById: newTimeline,
|
||||
});
|
||||
const indexProvider = update.foo.dataProviders.findIndex(i => i.id === '567');
|
||||
const addedAndDataProvider = update.foo.dataProviders[indexProvider].and[0];
|
||||
expect(addedAndDataProvider).toEqual(andProviderToAdd);
|
||||
newTimeline.foo.highlightedDropAndProviderId = '';
|
||||
});
|
||||
|
||||
test('should NOT add a new timeline and provider if it already exists', () => {
|
||||
const providerToAdd = {
|
||||
and: [
|
||||
{
|
||||
and: [],
|
||||
id: '568',
|
||||
name: 'And Data Provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
id: '567',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
const newTimeline = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: providerToAdd,
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
|
||||
newTimeline.foo.highlightedDropAndProviderId = '567';
|
||||
|
||||
const andProviderToAdd = {
|
||||
and: [],
|
||||
id: '568',
|
||||
name: 'And Data Provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
const update = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: andProviderToAdd,
|
||||
timelineById: newTimeline,
|
||||
});
|
||||
const indexProvider = update.foo.dataProviders.findIndex(i => i.id === '567');
|
||||
expect(update.foo.dataProviders[indexProvider].and.length).toEqual(1);
|
||||
newTimeline.foo.highlightedDropAndProviderId = '';
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineProviders', () => {
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineProviders({
|
||||
|
@ -184,9 +347,16 @@ describe('Timeline', () => {
|
|||
id: '567',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
timelineById: timelineByIdMock,
|
||||
|
@ -200,9 +370,16 @@ describe('Timeline', () => {
|
|||
id: '567',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
const update = updateTimelineProviders({
|
||||
id: 'foo',
|
||||
|
@ -302,13 +479,21 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: false, // This value changed from true to false
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
@ -337,9 +522,16 @@ describe('Timeline', () => {
|
|||
id: '456',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
});
|
||||
const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock);
|
||||
const update = updateTimelineProviderEnabled({
|
||||
|
@ -357,22 +549,37 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: false, // value we are updating from true to false
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
and: [],
|
||||
id: '456',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
@ -396,6 +603,405 @@ describe('Timeline', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineAndProviderEnabled', () => {
|
||||
let timelineByIdwithAndMock: TimelineById = timelineByIdMock;
|
||||
beforeEach(() => {
|
||||
const providerToAdd = {
|
||||
and: [
|
||||
{
|
||||
and: [],
|
||||
id: '568',
|
||||
name: 'And Data Provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
id: '567',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
timelineByIdwithAndMock = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: providerToAdd,
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineProviderEnabled({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
enabled: false, // value we are updating from true to false
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
expect(update).not.toBe(timelineByIdwithAndMock);
|
||||
});
|
||||
|
||||
test('should return a new reference for and data provider and not the same reference of data and provider', () => {
|
||||
const update = updateTimelineProviderEnabled({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
enabled: false, // value we are updating from true to false
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders);
|
||||
});
|
||||
|
||||
test('should update the timeline and provider enabled from true to false', () => {
|
||||
const update = updateTimelineProviderEnabled({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
enabled: false, // value we are updating from true to false
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
const indexProvider = update.foo.dataProviders.findIndex(i => i.id === '567');
|
||||
expect(update.foo.dataProviders[indexProvider].and[0].enabled).toEqual(false);
|
||||
});
|
||||
|
||||
test('should update only one and data provider and not two and data providers', () => {
|
||||
const indexProvider = timelineByIdwithAndMock.foo.dataProviders.findIndex(
|
||||
i => i.id === '567'
|
||||
);
|
||||
const multiAndDataProvider = timelineByIdwithAndMock.foo.dataProviders[
|
||||
indexProvider
|
||||
].and.concat({
|
||||
and: [],
|
||||
id: '456',
|
||||
name: 'new and data provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
});
|
||||
const multiAndDataProviderMock = set(
|
||||
`foo.dataProviders[${indexProvider}].and`,
|
||||
multiAndDataProvider,
|
||||
timelineByIdwithAndMock
|
||||
);
|
||||
const update = updateTimelineProviderEnabled({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
enabled: false, // value we are updating from true to false
|
||||
timelineById: multiAndDataProviderMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
const oldAndProvider = update.foo.dataProviders[indexProvider].and.find(i => i.id === '568');
|
||||
const newAndProvider = update.foo.dataProviders[indexProvider].and.find(i => i.id === '456');
|
||||
expect(oldAndProvider!.enabled).toEqual(false);
|
||||
expect(newAndProvider!.enabled).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineProviderExcluded', () => {
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '123',
|
||||
excluded: true, // value we are updating from false to true
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
expect(update).not.toBe(timelineByIdMock);
|
||||
});
|
||||
|
||||
test('should return a new reference for data provider and not the same reference of data provider', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '123',
|
||||
excluded: true, // value we are updating from false to true
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders);
|
||||
});
|
||||
|
||||
test('should update the timeline provider excluded from true to false', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '123',
|
||||
excluded: true, // value we are updating from false to true
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
const expected: TimelineById = {
|
||||
foo: {
|
||||
id: 'foo',
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
excluded: true, // This value changed from true to false
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: '',
|
||||
title: '',
|
||||
noteIds: [],
|
||||
range: '1 Day',
|
||||
show: true,
|
||||
sort: {
|
||||
columnId: 'timestamp',
|
||||
sortDirection: Direction.descending,
|
||||
},
|
||||
pinnedEventIds: {},
|
||||
itemsPerPage: 25,
|
||||
itemsPerPageOptions: [10, 25, 50],
|
||||
width: defaultWidth,
|
||||
},
|
||||
};
|
||||
expect(update).toEqual(expected);
|
||||
});
|
||||
|
||||
test('should update only one data provider and not two data providers', () => {
|
||||
const multiDataProvider = timelineByIdMock.foo.dataProviders.concat({
|
||||
and: [],
|
||||
id: '456',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
});
|
||||
const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock);
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '123',
|
||||
excluded: true, // value we are updating from false to true
|
||||
timelineById: multiDataProviderMock,
|
||||
});
|
||||
const expected: TimelineById = {
|
||||
foo: {
|
||||
id: 'foo',
|
||||
dataProviders: [
|
||||
{
|
||||
and: [],
|
||||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
excluded: true, // value we are updating from false to true
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
and: [],
|
||||
id: '456',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
kqlMode: 'filter',
|
||||
kqlQuery: '',
|
||||
title: '',
|
||||
noteIds: [],
|
||||
range: '1 Day',
|
||||
show: true,
|
||||
sort: {
|
||||
columnId: 'timestamp',
|
||||
sortDirection: Direction.descending,
|
||||
},
|
||||
pinnedEventIds: {},
|
||||
itemsPerPage: 25,
|
||||
itemsPerPageOptions: [10, 25, 50],
|
||||
width: defaultWidth,
|
||||
},
|
||||
};
|
||||
expect(update).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineAndProviderExcluded', () => {
|
||||
let timelineByIdwithAndMock: TimelineById = timelineByIdMock;
|
||||
beforeEach(() => {
|
||||
const providerToAdd = {
|
||||
and: [
|
||||
{
|
||||
and: [],
|
||||
id: '568',
|
||||
name: 'And Data Provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
id: '567',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
};
|
||||
|
||||
timelineByIdwithAndMock = addTimelineProvider({
|
||||
id: 'foo',
|
||||
provider: providerToAdd,
|
||||
timelineById: timelineByIdMock,
|
||||
});
|
||||
});
|
||||
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
excluded: true, // value we are updating from true to false
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
expect(update).not.toBe(timelineByIdwithAndMock);
|
||||
});
|
||||
|
||||
test('should return a new reference for and data provider and not the same reference of data and provider', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
excluded: true, // value we are updating from false to true
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
expect(update.foo.dataProviders).not.toBe(timelineByIdMock.foo.dataProviders);
|
||||
});
|
||||
|
||||
test('should update the timeline and provider excluded from true to false', () => {
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
excluded: true, // value we are updating from true to false
|
||||
timelineById: timelineByIdwithAndMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
const indexProvider = update.foo.dataProviders.findIndex(i => i.id === '567');
|
||||
expect(update.foo.dataProviders[indexProvider].and[0].enabled).toEqual(true);
|
||||
});
|
||||
|
||||
test('should update only one and data provider and not two and data providers', () => {
|
||||
const indexProvider = timelineByIdwithAndMock.foo.dataProviders.findIndex(
|
||||
i => i.id === '567'
|
||||
);
|
||||
const multiAndDataProvider = timelineByIdwithAndMock.foo.dataProviders[
|
||||
indexProvider
|
||||
].and.concat({
|
||||
and: [],
|
||||
id: '456',
|
||||
name: 'new and data provider',
|
||||
enabled: true,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
});
|
||||
const multiAndDataProviderMock = set(
|
||||
`foo.dataProviders[${indexProvider}].and`,
|
||||
multiAndDataProvider,
|
||||
timelineByIdwithAndMock
|
||||
);
|
||||
const update = updateTimelineProviderExcluded({
|
||||
id: 'foo',
|
||||
providerId: '567',
|
||||
excluded: true, // value we are updating from true to false
|
||||
timelineById: multiAndDataProviderMock,
|
||||
andProviderId: '568',
|
||||
});
|
||||
const oldAndProvider = update.foo.dataProviders[indexProvider].and.find(i => i.id === '568');
|
||||
const newAndProvider = update.foo.dataProviders[indexProvider].and.find(i => i.id === '456');
|
||||
expect(oldAndProvider!.excluded).toEqual(true);
|
||||
expect(newAndProvider!.excluded).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('#updateTimelineItemsPerPage', () => {
|
||||
test('should return a new reference and not the same reference', () => {
|
||||
const update = updateTimelineItemsPerPage({
|
||||
|
@ -421,13 +1027,21 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
@ -475,13 +1089,21 @@ describe('Timeline', () => {
|
|||
id: '123',
|
||||
name: 'data provider 1',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
isFavorite: false,
|
||||
isLive: false,
|
||||
|
@ -531,9 +1153,16 @@ describe('Timeline', () => {
|
|||
id: '456',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
});
|
||||
const multiDataProviderMock = set('foo.dataProviders', multiDataProvider, timelineByIdMock);
|
||||
const update = removeTimelineProvider({
|
||||
|
@ -549,13 +1178,21 @@ describe('Timeline', () => {
|
|||
id: '456',
|
||||
name: 'data provider 2',
|
||||
enabled: true,
|
||||
queryMatch: '',
|
||||
queryDate: '',
|
||||
negated: false,
|
||||
queryMatch: {
|
||||
field: '',
|
||||
value: '',
|
||||
},
|
||||
queryDate: {
|
||||
from: 0,
|
||||
to: 1,
|
||||
},
|
||||
excluded: false,
|
||||
kqlQuery: '',
|
||||
},
|
||||
],
|
||||
description: '',
|
||||
eventIdToNoteIds: {},
|
||||
highlightedDropAndProviderId: '',
|
||||
historyIds: [],
|
||||
id: 'foo',
|
||||
isFavorite: false,
|
||||
|
|
|
@ -18,7 +18,10 @@ import {
|
|||
showTimeline,
|
||||
unPinEvent,
|
||||
updateDataProviderEnabled,
|
||||
updateDataProviderExcluded,
|
||||
updateDataProviderKqlQuery,
|
||||
updateDescription,
|
||||
updateHighlightedDropAndProviderId,
|
||||
updateIsFavorite,
|
||||
updateIsLive,
|
||||
updateItemsPerPage,
|
||||
|
@ -41,6 +44,7 @@ import {
|
|||
pinTimelineEvent,
|
||||
removeTimelineProvider,
|
||||
unPinTimelineEvent,
|
||||
updateHighlightedDropAndProvider,
|
||||
updateTimelineDescription,
|
||||
updateTimelineIsFavorite,
|
||||
updateTimelineIsLive,
|
||||
|
@ -50,6 +54,8 @@ import {
|
|||
updateTimelinePageIndex,
|
||||
updateTimelinePerPageOptions,
|
||||
updateTimelineProviderEnabled,
|
||||
updateTimelineProviderExcluded,
|
||||
updateTimelineProviderKqlQuery,
|
||||
updateTimelineProviders,
|
||||
updateTimelineRange,
|
||||
updateTimelineShowTimeline,
|
||||
|
@ -118,9 +124,14 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
timelineById: pinTimelineEvent({ id, eventId, timelineById: state.timelineById }),
|
||||
}))
|
||||
.case(removeProvider, (state, { id, providerId }) => ({
|
||||
.case(removeProvider, (state, { id, providerId, andProviderId }) => ({
|
||||
...state,
|
||||
timelineById: removeTimelineProvider({ id, providerId, timelineById: state.timelineById }),
|
||||
timelineById: removeTimelineProvider({
|
||||
id,
|
||||
providerId,
|
||||
timelineById: state.timelineById,
|
||||
andProviderId,
|
||||
}),
|
||||
}))
|
||||
.case(unPinEvent, (state, { id, eventId }) => ({
|
||||
...state,
|
||||
|
@ -162,13 +173,33 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
...state,
|
||||
timelineById: updateTimelineSort({ id, sort, timelineById: state.timelineById }),
|
||||
}))
|
||||
.case(updateDataProviderEnabled, (state, { id, enabled, providerId }) => ({
|
||||
.case(updateDataProviderEnabled, (state, { id, enabled, providerId, andProviderId }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineProviderEnabled({
|
||||
id,
|
||||
enabled,
|
||||
providerId,
|
||||
timelineById: state.timelineById,
|
||||
andProviderId,
|
||||
}),
|
||||
}))
|
||||
.case(updateDataProviderExcluded, (state, { id, excluded, providerId, andProviderId }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineProviderExcluded({
|
||||
id,
|
||||
excluded,
|
||||
providerId,
|
||||
timelineById: state.timelineById,
|
||||
andProviderId,
|
||||
}),
|
||||
}))
|
||||
.case(updateDataProviderKqlQuery, (state, { id, kqlQuery, providerId }) => ({
|
||||
...state,
|
||||
timelineById: updateTimelineProviderKqlQuery({
|
||||
id,
|
||||
kqlQuery,
|
||||
providerId,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(updateItemsPerPage, (state, { id, itemsPerPage }) => ({
|
||||
|
@ -195,4 +226,12 @@ export const timelineReducer = reducerWithInitialState(initialTimelineState)
|
|||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.case(updateHighlightedDropAndProviderId, (state, { id, providerId }) => ({
|
||||
...state,
|
||||
timelineById: updateHighlightedDropAndProvider({
|
||||
id,
|
||||
providerId,
|
||||
timelineById: state.timelineById,
|
||||
}),
|
||||
}))
|
||||
.build();
|
||||
|
|
57
yarn.lock
57
yarn.lock
|
@ -721,6 +721,14 @@
|
|||
"@babel/helper-plugin-utils" "^7.0.0"
|
||||
"@babel/plugin-transform-typescript" "^7.1.0"
|
||||
|
||||
"@babel/runtime-corejs2@^7.2.0":
|
||||
version "7.2.0"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs2/-/runtime-corejs2-7.2.0.tgz#5ccd722b72d2c18c6a7224b5751f4b9816b60ada"
|
||||
integrity sha512-kPfmKoRI8Hpo5ZJGACWyrc9Eq1j3ZIUpUAQT2yH045OuYpccFJ9kYA/eErwzOM2jeBG1sC8XX1nl1EArtuM8tg==
|
||||
dependencies:
|
||||
core-js "^2.5.7"
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@7.0.0-beta.54":
|
||||
version "7.0.0-beta.54"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.0.0-beta.54.tgz#39ebb42723fe7ca4b3e1b00e967e80138d47cadf"
|
||||
|
@ -1709,10 +1717,10 @@
|
|||
"@types/events" "*"
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/react-beautiful-dnd@^7.1.2":
|
||||
version "7.1.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-7.1.2.tgz#98a5bc1dca5dadfe462ba73180e8c66b5a275dcf"
|
||||
integrity sha512-SY+1maGYsCAQEurbnVEca8u97hwhN+/jGitsXG9aXNaySEFJInBgx1HknhtMxJDdcqKAkNMRRVlWyfBC1fwe3A==
|
||||
"@types/react-beautiful-dnd@^10.0.1":
|
||||
version "10.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-10.0.1.tgz#8be4449a2f7843433542c97fcf18333383cc3e24"
|
||||
integrity sha512-RA3mPgJFJP+zpkhVQL9T2SIkFDbtPx+R09hApQt2BvBUHYpiLaO3PygmnPgcDwCAultn3l+8jABbnVgOFtvZTg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
|
@ -6395,6 +6403,13 @@ css-box-model@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.0.0.tgz#60142814f2b25be00c4aac65ea1a55a531b18922"
|
||||
integrity sha512-MGipbCM6/HGmsOwN6Enq1OvNKy8H5Q1XKoyBszxwv2efly7ZVg+HcFILX8O6S0xfj27l1+6P7FyCjcQ90m5HBQ==
|
||||
|
||||
css-box-model@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/css-box-model/-/css-box-model-1.1.1.tgz#c9fd8e7a8b1d59d41d6812fd1765433f671b2ee0"
|
||||
integrity sha512-ZxbuLFeAPEDb0wPbGfT7783Vb00MVAkvOlMKwr0kA2PD5EGxk6P3MAhedvVuyVJCWb54bb+6HQ7pdPYENf8AZw==
|
||||
dependencies:
|
||||
tiny-invariant "^1.0.3"
|
||||
|
||||
css-color-keywords@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/css-color-keywords/-/css-color-keywords-1.0.0.tgz#fea2616dc676b2962686b3af8dbdbe180b244e05"
|
||||
|
@ -14402,7 +14417,7 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.2.0, loose-envify@^1.3
|
|||
dependencies:
|
||||
js-tokens "^3.0.0"
|
||||
|
||||
loose-envify@^1.3.0:
|
||||
loose-envify@^1.3.0, loose-envify@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
|
||||
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
|
||||
|
@ -14713,6 +14728,11 @@ memoize-one@^4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.0.2.tgz#3fb8db695aa14ab9c0f1644e1585a8806adc1aee"
|
||||
integrity sha512-ucx2DmXTeZTsS4GPPUZCbULAN7kdPT1G+H49Y34JjbQ5ESc6OGhVxKvb1iKhr9v19ZB9OtnHwNnhUnNR/7Wteg==
|
||||
|
||||
memoize-one@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-4.1.0.tgz#a2387c58c03fff27ca390c31b764a79addf3f906"
|
||||
integrity sha512-2GApq0yI/b22J2j9rhbrAlsHb0Qcz+7yWxeLG8h+95sl1XPUgeLimQSOdur4Vw7cUhrBHwaUZxWFZueojqNRzA==
|
||||
|
||||
memoizee@0.4.X:
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/memoizee/-/memoizee-0.4.14.tgz#07a00f204699f9a95c2d9e77218271c7cd610d57"
|
||||
|
@ -17826,6 +17846,20 @@ react-apollo@^2.1.4:
|
|||
lodash "^4.17.10"
|
||||
prop-types "^15.6.0"
|
||||
|
||||
react-beautiful-dnd@^10.0.1:
|
||||
version "10.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-10.0.3.tgz#75e7989b639493cefcf71a678e93c171ac6a207b"
|
||||
integrity sha512-A6N50lv2RnXH9kNRKy+HYcQYOgRmgQRdn8qaKTTb4sYzX0H/4UhEYuOnh5PQB8i66bvCpfkxxD9El/OjgbtpPw==
|
||||
dependencies:
|
||||
"@babel/runtime-corejs2" "^7.2.0"
|
||||
css-box-model "^1.1.1"
|
||||
memoize-one "^4.1.0"
|
||||
prop-types "^15.6.1"
|
||||
raf-schd "^4.0.0"
|
||||
react-redux "^5.0.7"
|
||||
redux "^4.0.1"
|
||||
tiny-invariant "^1.0.3"
|
||||
|
||||
react-beautiful-dnd@^8.0.7:
|
||||
version "8.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-8.0.7.tgz#2cc7ba62bffe08d3dad862fd8f48204440901b43"
|
||||
|
@ -18600,6 +18634,14 @@ redux@4.0.0, redux@^4.0.0:
|
|||
loose-envify "^1.1.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
||||
redux@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/redux/-/redux-4.0.1.tgz#436cae6cc40fbe4727689d7c8fae44808f1bfef5"
|
||||
integrity sha512-R7bAtSkk7nY6O/OYMVR9RiBI+XghjF9rlbl5806HJbQph0LJVHZrU5oaO4q70eUKiqMRqm4y07KLTlMZ2BlVmg==
|
||||
dependencies:
|
||||
loose-envify "^1.4.0"
|
||||
symbol-observable "^1.2.0"
|
||||
|
||||
regenerate-unicode-properties@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-7.0.0.tgz#107405afcc4a190ec5ed450ecaa00ed0cafa7a4c"
|
||||
|
@ -21425,6 +21467,11 @@ tiny-invariant@^0.0.3:
|
|||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-0.0.3.tgz#4c7283c950e290889e9e94f64d3586ec9156cf44"
|
||||
integrity sha512-SA2YwvDrCITM9fTvHTHRpq9W6L2fBsClbqm3maT5PZux4Z73SPPDYwJMtnoWh6WMgmCkJij/LaOlWiqJqFMK8g==
|
||||
|
||||
tiny-invariant@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.3.tgz#91efaaa0269ccb6271f0296aeedb05fc3e067b7a"
|
||||
integrity sha512-ytQx8T4DL8PjlX53yYzcIC0WhIZbpR0p1qcYjw2pHu3w6UtgWwFJQ/02cnhOnBBhlFx/edUIfcagCaQSe3KMWg==
|
||||
|
||||
tiny-lr@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/tiny-lr/-/tiny-lr-1.1.1.tgz#9fa547412f238fedb068ee295af8b682c98b2aab"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue