[SIEM] Fix Inspect query 'request timestamp' value changes when curso… (#54223)
This commit is contained in:
parent
f71142dcd3
commit
7c4a531ae7
|
@ -59,7 +59,6 @@ interface Props {
|
|||
kqlMode: KqlMode;
|
||||
onChangeItemsPerPage: OnChangeItemsPerPage;
|
||||
query: Query;
|
||||
showInspect: boolean;
|
||||
start: number;
|
||||
sort: Sort;
|
||||
timelineTypeContext: TimelineTypeContextProps;
|
||||
|
@ -67,171 +66,171 @@ interface Props {
|
|||
utilityBar?: (totalCount: number) => React.ReactNode;
|
||||
}
|
||||
|
||||
export const EventsViewer = React.memo<Props>(
|
||||
({
|
||||
browserFields,
|
||||
columns,
|
||||
const EventsViewerComponent: React.FC<Props> = ({
|
||||
browserFields,
|
||||
columns,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
end,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
height = DEFAULT_EVENTS_VIEWER_HEIGHT,
|
||||
id,
|
||||
indexPattern,
|
||||
isLive,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
onChangeItemsPerPage,
|
||||
query,
|
||||
start,
|
||||
sort,
|
||||
timelineTypeContext,
|
||||
toggleColumn,
|
||||
utilityBar,
|
||||
}) => {
|
||||
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
|
||||
const kibana = useKibana();
|
||||
const combinedQueries = combineQueries({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
end,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
height = DEFAULT_EVENTS_VIEWER_HEIGHT,
|
||||
id,
|
||||
indexPattern,
|
||||
isLive,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
browserFields,
|
||||
filters,
|
||||
kqlQuery: query,
|
||||
kqlMode,
|
||||
onChangeItemsPerPage,
|
||||
query,
|
||||
showInspect,
|
||||
start,
|
||||
sort,
|
||||
timelineTypeContext,
|
||||
toggleColumn,
|
||||
utilityBar,
|
||||
}) => {
|
||||
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns;
|
||||
const kibana = useKibana();
|
||||
const combinedQueries = combineQueries({
|
||||
config: esQuery.getEsQueryConfig(kibana.services.uiSettings),
|
||||
dataProviders,
|
||||
indexPattern,
|
||||
browserFields,
|
||||
filters,
|
||||
kqlQuery: query,
|
||||
kqlMode,
|
||||
start,
|
||||
end,
|
||||
isEventViewer: true,
|
||||
});
|
||||
const queryFields = useMemo(
|
||||
() =>
|
||||
union(
|
||||
columnsHeader.map(c => c.id),
|
||||
timelineTypeContext.queryFields ?? []
|
||||
),
|
||||
[columnsHeader, timelineTypeContext.queryFields]
|
||||
);
|
||||
end,
|
||||
isEventViewer: true,
|
||||
});
|
||||
const queryFields = useMemo(
|
||||
() =>
|
||||
union(
|
||||
columnsHeader.map(c => c.id),
|
||||
timelineTypeContext.queryFields ?? []
|
||||
),
|
||||
[columnsHeader, timelineTypeContext.queryFields]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiPanel data-test-subj="events-viewer-panel" grow={false}>
|
||||
<AutoSizer detectAnyWindowResize={true} content>
|
||||
{({ measureRef, content: { width = 0 } }) => (
|
||||
<>
|
||||
<WrappedByAutoSizer ref={measureRef}>
|
||||
<div
|
||||
data-test-subj="events-viewer-measured"
|
||||
style={{ height: '0px', width: '100%' }}
|
||||
/>
|
||||
</WrappedByAutoSizer>
|
||||
return (
|
||||
<EuiPanel data-test-subj="events-viewer-panel" grow={false}>
|
||||
<AutoSizer detectAnyWindowResize={true} content>
|
||||
{({ measureRef, content: { width = 0 } }) => (
|
||||
<>
|
||||
<WrappedByAutoSizer ref={measureRef}>
|
||||
<div
|
||||
data-test-subj="events-viewer-measured"
|
||||
style={{ height: '0px', width: '100%' }}
|
||||
/>
|
||||
</WrappedByAutoSizer>
|
||||
|
||||
{combinedQueries != null ? (
|
||||
<TimelineQuery
|
||||
fields={queryFields}
|
||||
filterQuery={combinedQueries.filterQuery}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
limit={itemsPerPage}
|
||||
sortField={{
|
||||
sortFieldId: sort.columnId,
|
||||
direction: sort.sortDirection as Direction,
|
||||
}}
|
||||
sourceId="default"
|
||||
>
|
||||
{({
|
||||
events,
|
||||
getUpdatedAt,
|
||||
inspect,
|
||||
loading,
|
||||
loadMore,
|
||||
pageInfo,
|
||||
refetch,
|
||||
totalCount = 0,
|
||||
}) => {
|
||||
const totalCountMinusDeleted =
|
||||
totalCount > 0 ? totalCount - deletedEventIds.length : 0;
|
||||
{combinedQueries != null ? (
|
||||
<TimelineQuery
|
||||
fields={queryFields}
|
||||
filterQuery={combinedQueries.filterQuery}
|
||||
id={id}
|
||||
indexPattern={indexPattern}
|
||||
limit={itemsPerPage}
|
||||
sortField={{
|
||||
sortFieldId: sort.columnId,
|
||||
direction: sort.sortDirection as Direction,
|
||||
}}
|
||||
sourceId="default"
|
||||
>
|
||||
{({
|
||||
events,
|
||||
getUpdatedAt,
|
||||
inspect,
|
||||
loading,
|
||||
loadMore,
|
||||
pageInfo,
|
||||
refetch,
|
||||
totalCount = 0,
|
||||
}) => {
|
||||
const totalCountMinusDeleted =
|
||||
totalCount > 0 ? totalCount - deletedEventIds.length : 0;
|
||||
|
||||
// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
|
||||
return (
|
||||
<>
|
||||
<HeaderSection
|
||||
id={id}
|
||||
showInspect={showInspect}
|
||||
subtitle={
|
||||
utilityBar
|
||||
? undefined
|
||||
: `${
|
||||
i18n.SHOWING
|
||||
}: ${totalCountMinusDeleted.toLocaleString()} ${i18n.UNIT(
|
||||
totalCountMinusDeleted
|
||||
)}`
|
||||
}
|
||||
title={timelineTypeContext?.title ?? i18n.EVENTS}
|
||||
// TODO: Reset eventDeletedIds/eventLoadingIds on refresh/loadmore (getUpdatedAt)
|
||||
return (
|
||||
<>
|
||||
<HeaderSection
|
||||
id={id}
|
||||
subtitle={
|
||||
utilityBar
|
||||
? undefined
|
||||
: `${
|
||||
i18n.SHOWING
|
||||
}: ${totalCountMinusDeleted.toLocaleString()} ${i18n.UNIT(
|
||||
totalCountMinusDeleted
|
||||
)}`
|
||||
}
|
||||
title={timelineTypeContext?.title ?? i18n.EVENTS}
|
||||
>
|
||||
{headerFilterGroup}
|
||||
</HeaderSection>
|
||||
|
||||
{utilityBar?.(totalCountMinusDeleted)}
|
||||
|
||||
<div
|
||||
data-test-subj={`events-container-loading-${loading}`}
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
<ManageTimelineContext
|
||||
loading={loading}
|
||||
width={width}
|
||||
type={timelineTypeContext}
|
||||
>
|
||||
{headerFilterGroup}
|
||||
</HeaderSection>
|
||||
|
||||
{utilityBar?.(totalCountMinusDeleted)}
|
||||
|
||||
<div
|
||||
data-test-subj={`events-container-loading-${loading}`}
|
||||
style={{ width: `${width}px` }}
|
||||
>
|
||||
<ManageTimelineContext
|
||||
<TimelineRefetch
|
||||
id={id}
|
||||
inputId="global"
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
width={width}
|
||||
type={timelineTypeContext}
|
||||
>
|
||||
<TimelineRefetch
|
||||
id={id}
|
||||
inputId="global"
|
||||
inspect={inspect}
|
||||
loading={loading}
|
||||
refetch={refetch}
|
||||
/>
|
||||
refetch={refetch}
|
||||
/>
|
||||
|
||||
<StatefulBody
|
||||
browserFields={browserFields}
|
||||
data={events.filter(e => !deletedEventIds.includes(e._id))}
|
||||
id={id}
|
||||
isEventViewer={true}
|
||||
height={height}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
<StatefulBody
|
||||
browserFields={browserFields}
|
||||
data={events.filter(e => !deletedEventIds.includes(e._id))}
|
||||
id={id}
|
||||
isEventViewer={true}
|
||||
height={height}
|
||||
sort={sort}
|
||||
toggleColumn={toggleColumn}
|
||||
/>
|
||||
|
||||
<Footer
|
||||
compact={isCompactFooter(width)}
|
||||
getUpdatedAt={getUpdatedAt}
|
||||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
|
||||
height={footerHeight}
|
||||
isEventViewer={true}
|
||||
isLive={isLive}
|
||||
isLoading={loading}
|
||||
itemsCount={events.length}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onLoadMore={loadMore}
|
||||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
|
||||
serverSideEventCount={totalCountMinusDeleted}
|
||||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
|
||||
/>
|
||||
</ManageTimelineContext>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TimelineQuery>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</EuiPanel>
|
||||
);
|
||||
},
|
||||
<Footer
|
||||
compact={isCompactFooter(width)}
|
||||
getUpdatedAt={getUpdatedAt}
|
||||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!}
|
||||
height={footerHeight}
|
||||
isEventViewer={true}
|
||||
isLive={isLive}
|
||||
isLoading={loading}
|
||||
itemsCount={events.length}
|
||||
itemsPerPage={itemsPerPage}
|
||||
itemsPerPageOptions={itemsPerPageOptions}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
onLoadMore={loadMore}
|
||||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!}
|
||||
serverSideEventCount={totalCountMinusDeleted}
|
||||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)}
|
||||
/>
|
||||
</ManageTimelineContext>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}}
|
||||
</TimelineQuery>
|
||||
) : null}
|
||||
</>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</EuiPanel>
|
||||
);
|
||||
};
|
||||
|
||||
export const EventsViewer = React.memo(
|
||||
EventsViewerComponent,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.browserFields === nextProps.browserFields &&
|
||||
prevProps.columns === nextProps.columns &&
|
||||
|
@ -247,10 +246,8 @@ export const EventsViewer = React.memo<Props>(
|
|||
prevProps.itemsPerPageOptions === nextProps.itemsPerPageOptions &&
|
||||
prevProps.kqlMode === nextProps.kqlMode &&
|
||||
isEqual(prevProps.query, nextProps.query) &&
|
||||
prevProps.showInspect === nextProps.showInspect &&
|
||||
prevProps.start === nextProps.start &&
|
||||
prevProps.sort === nextProps.sort &&
|
||||
isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) &&
|
||||
prevProps.utilityBar === nextProps.utilityBar
|
||||
);
|
||||
EventsViewer.displayName = 'EventsViewer';
|
||||
|
|
|
@ -57,7 +57,8 @@ describe('StatefulEventsViewer', () => {
|
|||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders a transparent inspect button when it does NOT have mouse focus', async () => {
|
||||
// InspectButtonContainer controls displaying InspectButton components
|
||||
test('it renders InspectButtonContainer', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mockEventViewerResponse} addTypename={false}>
|
||||
|
@ -74,39 +75,6 @@ describe('StatefulEventsViewer', () => {
|
|||
await wait();
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="transparent-inspect-container"]`)
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders an opaque inspect button when it has mouse focus', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<MockedProvider mocks={mockEventViewerResponse} addTypename={false}>
|
||||
<StatefulEventsViewer
|
||||
defaultModel={eventsDefaultModel}
|
||||
end={to}
|
||||
id={'test-stateful-events-viewer'}
|
||||
start={from}
|
||||
/>
|
||||
</MockedProvider>
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
await wait();
|
||||
wrapper.update();
|
||||
|
||||
wrapper.simulate('mouseenter');
|
||||
wrapper.update();
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find(`[data-test-subj="opaque-inspect-container"]`)
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
expect(wrapper.find(`InspectButtonContainer`).exists()).toBe(true);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { isEqual } from 'lodash/fp';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import { inputsModel, inputsSelectors, State, timelineSelectors } from '../../store';
|
||||
|
@ -23,6 +23,7 @@ import { InputsModelId } from '../../store/inputs/constants';
|
|||
import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/fetch_index_patterns';
|
||||
import { TimelineTypeContextProps } from '../timeline/timeline_context';
|
||||
import { DEFAULT_INDEX_KEY } from '../../../common/constants';
|
||||
import { InspectButtonContainer } from '../inspect';
|
||||
import * as i18n from './translations';
|
||||
|
||||
export interface OwnProps {
|
||||
|
@ -83,133 +84,102 @@ interface DispatchProps {
|
|||
|
||||
type Props = OwnProps & StateReduxProps & DispatchProps;
|
||||
|
||||
const StatefulEventsViewerComponent = React.memo<Props>(
|
||||
({
|
||||
createTimeline,
|
||||
columns,
|
||||
dataProviders,
|
||||
defaultModel,
|
||||
deletedEventIds,
|
||||
defaultIndices,
|
||||
deleteEventQuery,
|
||||
end,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
id,
|
||||
isLive,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
pageFilters = [],
|
||||
query,
|
||||
removeColumn,
|
||||
start,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
sort,
|
||||
timelineTypeContext = {
|
||||
loadingText: i18n.LOADING_EVENTS,
|
||||
},
|
||||
updateItemsPerPage,
|
||||
upsertColumn,
|
||||
utilityBar,
|
||||
}) => {
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns(
|
||||
defaultIndices ?? useUiSetting<string[]>(DEFAULT_INDEX_KEY)
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (createTimeline != null) {
|
||||
createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers });
|
||||
}
|
||||
return () => {
|
||||
deleteEventQuery({ id, inputId: 'global' });
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback(
|
||||
itemsChangedPerPage => updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }),
|
||||
[id, updateItemsPerPage]
|
||||
);
|
||||
|
||||
const toggleColumn = useCallback(
|
||||
(column: ColumnHeader) => {
|
||||
const exists = columns.findIndex(c => c.id === column.id) !== -1;
|
||||
|
||||
if (!exists && upsertColumn != null) {
|
||||
upsertColumn({
|
||||
column,
|
||||
id,
|
||||
index: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (exists && removeColumn != null) {
|
||||
removeColumn({
|
||||
columnId: column.id,
|
||||
id,
|
||||
});
|
||||
}
|
||||
},
|
||||
[columns, id, upsertColumn, removeColumn]
|
||||
);
|
||||
|
||||
const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
|
||||
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);
|
||||
|
||||
return (
|
||||
<div onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave}>
|
||||
<EventsViewer
|
||||
browserFields={browserFields ?? {}}
|
||||
columns={columns}
|
||||
id={id}
|
||||
dataProviders={dataProviders!}
|
||||
deletedEventIds={deletedEventIds}
|
||||
end={end}
|
||||
filters={filters}
|
||||
headerFilterGroup={headerFilterGroup}
|
||||
indexPattern={indexPatterns ?? { fields: [], title: '' }}
|
||||
isLive={isLive}
|
||||
itemsPerPage={itemsPerPage!}
|
||||
itemsPerPageOptions={itemsPerPageOptions!}
|
||||
kqlMode={kqlMode}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
query={query}
|
||||
showInspect={showInspect}
|
||||
start={start}
|
||||
sort={sort!}
|
||||
timelineTypeContext={timelineTypeContext}
|
||||
toggleColumn={toggleColumn}
|
||||
utilityBar={utilityBar}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
const StatefulEventsViewerComponent: React.FC<Props> = ({
|
||||
createTimeline,
|
||||
columns,
|
||||
dataProviders,
|
||||
deletedEventIds,
|
||||
defaultIndices,
|
||||
deleteEventQuery,
|
||||
end,
|
||||
filters,
|
||||
headerFilterGroup,
|
||||
id,
|
||||
isLive,
|
||||
itemsPerPage,
|
||||
itemsPerPageOptions,
|
||||
kqlMode,
|
||||
pageFilters = [],
|
||||
query,
|
||||
removeColumn,
|
||||
start,
|
||||
showCheckboxes,
|
||||
showRowRenderers,
|
||||
sort,
|
||||
timelineTypeContext = {
|
||||
loadingText: i18n.LOADING_EVENTS,
|
||||
},
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.id === nextProps.id &&
|
||||
isEqual(prevProps.columns, nextProps.columns) &&
|
||||
isEqual(prevProps.dataProviders, nextProps.dataProviders) &&
|
||||
prevProps.deletedEventIds === nextProps.deletedEventIds &&
|
||||
prevProps.end === nextProps.end &&
|
||||
isEqual(prevProps.filters, nextProps.filters) &&
|
||||
prevProps.isLive === nextProps.isLive &&
|
||||
prevProps.itemsPerPage === nextProps.itemsPerPage &&
|
||||
isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
|
||||
prevProps.kqlMode === nextProps.kqlMode &&
|
||||
isEqual(prevProps.query, nextProps.query) &&
|
||||
prevProps.pageCount === nextProps.pageCount &&
|
||||
isEqual(prevProps.sort, nextProps.sort) &&
|
||||
prevProps.start === nextProps.start &&
|
||||
isEqual(prevProps.pageFilters, nextProps.pageFilters) &&
|
||||
prevProps.showCheckboxes === nextProps.showCheckboxes &&
|
||||
prevProps.showRowRenderers === nextProps.showRowRenderers &&
|
||||
prevProps.start === nextProps.start &&
|
||||
isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) &&
|
||||
prevProps.utilityBar === nextProps.utilityBar
|
||||
);
|
||||
updateItemsPerPage,
|
||||
upsertColumn,
|
||||
utilityBar,
|
||||
}) => {
|
||||
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns(
|
||||
defaultIndices ?? useUiSetting<string[]>(DEFAULT_INDEX_KEY)
|
||||
);
|
||||
|
||||
StatefulEventsViewerComponent.displayName = 'StatefulEventsViewerComponent';
|
||||
useEffect(() => {
|
||||
if (createTimeline != null) {
|
||||
createTimeline({ id, columns, sort, itemsPerPage, showCheckboxes, showRowRenderers });
|
||||
}
|
||||
return () => {
|
||||
deleteEventQuery({ id, inputId: 'global' });
|
||||
};
|
||||
}, []);
|
||||
|
||||
const onChangeItemsPerPage: OnChangeItemsPerPage = useCallback(
|
||||
itemsChangedPerPage => updateItemsPerPage({ id, itemsPerPage: itemsChangedPerPage }),
|
||||
[id, updateItemsPerPage]
|
||||
);
|
||||
|
||||
const toggleColumn = useCallback(
|
||||
(column: ColumnHeader) => {
|
||||
const exists = columns.findIndex(c => c.id === column.id) !== -1;
|
||||
|
||||
if (!exists && upsertColumn != null) {
|
||||
upsertColumn({
|
||||
column,
|
||||
id,
|
||||
index: 1,
|
||||
});
|
||||
}
|
||||
|
||||
if (exists && removeColumn != null) {
|
||||
removeColumn({
|
||||
columnId: column.id,
|
||||
id,
|
||||
});
|
||||
}
|
||||
},
|
||||
[columns, id, upsertColumn, removeColumn]
|
||||
);
|
||||
|
||||
return (
|
||||
<InspectButtonContainer>
|
||||
<EventsViewer
|
||||
browserFields={browserFields ?? {}}
|
||||
columns={columns}
|
||||
id={id}
|
||||
dataProviders={dataProviders!}
|
||||
deletedEventIds={deletedEventIds}
|
||||
end={end}
|
||||
filters={filters}
|
||||
headerFilterGroup={headerFilterGroup}
|
||||
indexPattern={indexPatterns ?? { fields: [], title: '' }}
|
||||
isLive={isLive}
|
||||
itemsPerPage={itemsPerPage!}
|
||||
itemsPerPageOptions={itemsPerPageOptions!}
|
||||
kqlMode={kqlMode}
|
||||
onChangeItemsPerPage={onChangeItemsPerPage}
|
||||
query={query}
|
||||
start={start}
|
||||
sort={sort!}
|
||||
timelineTypeContext={timelineTypeContext}
|
||||
toggleColumn={toggleColumn}
|
||||
utilityBar={utilityBar}
|
||||
/>
|
||||
</InspectButtonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getInputsTimeline = inputsSelectors.getTimelineSelector();
|
||||
|
@ -256,4 +226,29 @@ export const StatefulEventsViewer = connect(makeMapStateToProps, {
|
|||
updateItemsPerPage: timelineActions.updateItemsPerPage,
|
||||
removeColumn: timelineActions.removeColumn,
|
||||
upsertColumn: timelineActions.upsertColumn,
|
||||
})(StatefulEventsViewerComponent);
|
||||
})(
|
||||
React.memo(
|
||||
StatefulEventsViewerComponent,
|
||||
(prevProps, nextProps) =>
|
||||
prevProps.id === nextProps.id &&
|
||||
isEqual(prevProps.columns, nextProps.columns) &&
|
||||
isEqual(prevProps.dataProviders, nextProps.dataProviders) &&
|
||||
prevProps.deletedEventIds === nextProps.deletedEventIds &&
|
||||
prevProps.end === nextProps.end &&
|
||||
isEqual(prevProps.filters, nextProps.filters) &&
|
||||
prevProps.isLive === nextProps.isLive &&
|
||||
prevProps.itemsPerPage === nextProps.itemsPerPage &&
|
||||
isEqual(prevProps.itemsPerPageOptions, nextProps.itemsPerPageOptions) &&
|
||||
prevProps.kqlMode === nextProps.kqlMode &&
|
||||
isEqual(prevProps.query, nextProps.query) &&
|
||||
prevProps.pageCount === nextProps.pageCount &&
|
||||
isEqual(prevProps.sort, nextProps.sort) &&
|
||||
prevProps.start === nextProps.start &&
|
||||
isEqual(prevProps.pageFilters, nextProps.pageFilters) &&
|
||||
prevProps.showCheckboxes === nextProps.showCheckboxes &&
|
||||
prevProps.showRowRenderers === nextProps.showRowRenderers &&
|
||||
prevProps.start === nextProps.start &&
|
||||
isEqual(prevProps.timelineTypeContext, nextProps.timelineTypeContext) &&
|
||||
prevProps.utilityBar === nextProps.utilityBar
|
||||
)
|
||||
);
|
||||
|
|
|
@ -63,36 +63,6 @@ describe('HeaderSection', () => {
|
|||
).toBe(false);
|
||||
});
|
||||
|
||||
test('it renders a transparent inspect button when showInspect is false', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderSection title="Test title" id="test" showInspect={false} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="transparent-inspect-container"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders an opaque inspect button when showInspect is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
<HeaderSection title="Test title" id="test" showInspect={true} />
|
||||
</TestProviders>
|
||||
);
|
||||
|
||||
expect(
|
||||
wrapper
|
||||
.find('[data-test-subj="opaque-inspect-container"]')
|
||||
.first()
|
||||
.exists()
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
test('it renders supplements when children provided', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviders>
|
||||
|
|
|
@ -35,48 +35,54 @@ export interface HeaderSectionProps extends HeaderProps {
|
|||
id?: string;
|
||||
split?: boolean;
|
||||
subtitle?: string | React.ReactNode;
|
||||
showInspect?: boolean;
|
||||
title: string | React.ReactNode;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export const HeaderSection = React.memo<HeaderSectionProps>(
|
||||
({ border, children, id, showInspect = false, split, subtitle, title, tooltip }) => (
|
||||
<Header border={border}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="header-section-title">
|
||||
{title}
|
||||
{tooltip && (
|
||||
<>
|
||||
{' '}
|
||||
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
|
||||
border,
|
||||
children,
|
||||
id,
|
||||
split,
|
||||
subtitle,
|
||||
title,
|
||||
tooltip,
|
||||
}) => (
|
||||
<Header border={border}>
|
||||
<EuiFlexGroup alignItems="center">
|
||||
<EuiFlexItem>
|
||||
<EuiFlexGroup alignItems="center" responsive={false}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle>
|
||||
<h2 data-test-subj="header-section-title">
|
||||
{title}
|
||||
{tooltip && (
|
||||
<>
|
||||
{' '}
|
||||
<EuiIconTip color="subdued" content={tooltip} size="l" type="iInCircle" />
|
||||
</>
|
||||
)}
|
||||
</h2>
|
||||
</EuiTitle>
|
||||
|
||||
{subtitle && <Subtitle data-test-subj="header-section-subtitle" items={subtitle} />}
|
||||
</EuiFlexItem>
|
||||
|
||||
{id && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButton queryId={id} inspectIndex={0} show={showInspect} title={title} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && (
|
||||
<EuiFlexItem data-test-subj="header-section-supplements" grow={split ? true : false}>
|
||||
{children}
|
||||
{subtitle && <Subtitle data-test-subj="header-section-subtitle" items={subtitle} />}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
)
|
||||
|
||||
{id && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButton queryId={id} inspectIndex={0} title={title} />
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
|
||||
{children && (
|
||||
<EuiFlexItem data-test-subj="header-section-supplements" grow={split ? true : false}>
|
||||
{children}
|
||||
</EuiFlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</Header>
|
||||
);
|
||||
HeaderSection.displayName = 'HeaderSection';
|
||||
|
||||
export const HeaderSection = React.memo(HeaderSectionComponent);
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { createStore, State } from '../../store';
|
||||
import { UpdateQueryParams, upsertQuery } from '../../store/inputs/helpers';
|
||||
|
||||
import { InspectButton } from '.';
|
||||
import { InspectButton, InspectButtonContainer, BUTTON_CLASS } from '.';
|
||||
import { cloneDeep } from 'lodash/fp';
|
||||
|
||||
describe('Inspect Button', () => {
|
||||
|
@ -44,7 +44,7 @@ describe('Inspect Button', () => {
|
|||
test('Eui Empty Button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton queryId={newQuery.id} inputId="timeline" show={true} title="My title" />
|
||||
<InspectButton queryId={newQuery.id} inputId="timeline" title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(
|
||||
|
@ -58,13 +58,7 @@ describe('Inspect Button', () => {
|
|||
test('it does NOT render the Eui Empty Button when timeline is timeline and compact is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton
|
||||
compact={true}
|
||||
queryId={newQuery.id}
|
||||
inputId="timeline"
|
||||
show={true}
|
||||
title="My title"
|
||||
/>
|
||||
<InspectButton compact={true} queryId={newQuery.id} inputId="timeline" title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(
|
||||
|
@ -78,7 +72,7 @@ describe('Inspect Button', () => {
|
|||
test('Eui Icon Button', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(
|
||||
|
@ -92,13 +86,7 @@ describe('Inspect Button', () => {
|
|||
test('renders the Icon Button when inputId does NOT equal global, but compact is true', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton
|
||||
compact={true}
|
||||
inputId="timeline"
|
||||
queryId={newQuery.id}
|
||||
show={true}
|
||||
title="My title"
|
||||
/>
|
||||
<InspectButton compact={true} inputId="timeline" queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(
|
||||
|
@ -112,7 +100,7 @@ describe('Inspect Button', () => {
|
|||
test('Eui Empty Button disabled', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton isDisabled={true} queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton isDisabled={true} queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true);
|
||||
|
@ -121,11 +109,41 @@ describe('Inspect Button', () => {
|
|||
test('Eui Icon Button disabled', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton isDisabled={true} queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton isDisabled={true} queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
expect(wrapper.find('.euiButtonIcon').get(0).props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
describe('InspectButtonContainer', () => {
|
||||
test('it renders a transparent inspect button by default', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButtonContainer>
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</InspectButtonContainer>
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '0', {
|
||||
modifier: `.${BUTTON_CLASS}`,
|
||||
});
|
||||
});
|
||||
|
||||
test('it renders an opaque inspect button when it has mouse focus', async () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButtonContainer>
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</InspectButtonContainer>
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
|
||||
expect(wrapper.find(`InspectButtonContainer`)).toHaveStyleRule('opacity', '1', {
|
||||
modifier: `:hover .${BUTTON_CLASS}`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Modal Inspect - happy path', () => {
|
||||
|
@ -143,7 +161,7 @@ describe('Inspect Button', () => {
|
|||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -167,7 +185,7 @@ describe('Inspect Button', () => {
|
|||
const wrapper = mount(
|
||||
<ThemeProvider theme={theme}>
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
</ThemeProvider>
|
||||
);
|
||||
|
@ -197,7 +215,7 @@ describe('Inspect Button', () => {
|
|||
test('Do not Open Inspect Modal if it is loading', () => {
|
||||
const wrapper = mount(
|
||||
<TestProviderWithoutDragAndDrop store={store}>
|
||||
<InspectButton queryId={newQuery.id} show={true} title="My title" />
|
||||
<InspectButton queryId={newQuery.id} title="My title" />
|
||||
</TestProviderWithoutDragAndDrop>
|
||||
);
|
||||
store.getState().inputs.global.queries[0].loading = true;
|
||||
|
|
|
@ -9,7 +9,7 @@ import { getOr } from 'lodash/fp';
|
|||
import React, { useCallback } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { ActionCreator } from 'typescript-fsa';
|
||||
import styled from 'styled-components';
|
||||
import styled, { css } from 'styled-components';
|
||||
|
||||
import { inputsModel, inputsSelectors, State } from '../../store';
|
||||
import { InputsModelId } from '../../store/inputs/constants';
|
||||
|
@ -18,14 +18,31 @@ import { inputsActions } from '../../store/inputs';
|
|||
import { ModalInspectQuery } from './modal';
|
||||
import * as i18n from './translations';
|
||||
|
||||
const InspectContainer = styled.div<{ showInspect: boolean }>`
|
||||
.euiButtonIcon {
|
||||
${props => (props.showInspect ? 'opacity: 1;' : 'opacity: 0;')}
|
||||
export const BUTTON_CLASS = 'inspectButtonComponent';
|
||||
|
||||
export const InspectButtonContainer = styled.div<{ show?: boolean }>`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
.${BUTTON_CLASS} {
|
||||
opacity: 0;
|
||||
transition: opacity ${props => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease;
|
||||
}
|
||||
|
||||
${({ show }) =>
|
||||
show &&
|
||||
css`
|
||||
&:hover .${BUTTON_CLASS} {
|
||||
opacity: 1;
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
InspectContainer.displayName = 'InspectContainer';
|
||||
InspectButtonContainer.displayName = 'InspectButtonContainer';
|
||||
|
||||
InspectButtonContainer.defaultProps = {
|
||||
show: true,
|
||||
};
|
||||
|
||||
interface OwnProps {
|
||||
compact?: boolean;
|
||||
|
@ -34,7 +51,6 @@ interface OwnProps {
|
|||
inspectIndex?: number;
|
||||
isDisabled?: boolean;
|
||||
onCloseInspect?: () => void;
|
||||
show: boolean;
|
||||
title: string | React.ReactElement | React.ReactNode;
|
||||
}
|
||||
|
||||
|
@ -57,89 +73,84 @@ interface InspectButtonDispatch {
|
|||
|
||||
type InspectButtonProps = OwnProps & InspectButtonReducer & InspectButtonDispatch;
|
||||
|
||||
const InspectButtonComponent = React.memo<InspectButtonProps>(
|
||||
({
|
||||
compact = false,
|
||||
inputId = 'global',
|
||||
inspect,
|
||||
isDisabled,
|
||||
isInspected,
|
||||
loading,
|
||||
inspectIndex = 0,
|
||||
onCloseInspect,
|
||||
queryId = '',
|
||||
selectedInspectIndex,
|
||||
setIsInspected,
|
||||
show,
|
||||
title = '',
|
||||
}: InspectButtonProps) => {
|
||||
const handleClick = useCallback(() => {
|
||||
setIsInspected({
|
||||
id: queryId,
|
||||
inputId,
|
||||
isInspected: true,
|
||||
selectedInspectIndex: inspectIndex,
|
||||
});
|
||||
}, [setIsInspected, queryId, inputId, inspectIndex]);
|
||||
const InspectButtonComponent: React.FC<InspectButtonProps> = ({
|
||||
compact = false,
|
||||
inputId = 'global',
|
||||
inspect,
|
||||
isDisabled,
|
||||
isInspected,
|
||||
loading,
|
||||
inspectIndex = 0,
|
||||
onCloseInspect,
|
||||
queryId = '',
|
||||
selectedInspectIndex,
|
||||
setIsInspected,
|
||||
title = '',
|
||||
}) => {
|
||||
const isShowingModal = !loading && selectedInspectIndex === inspectIndex && isInspected;
|
||||
const handleClick = useCallback(() => {
|
||||
setIsInspected({
|
||||
id: queryId,
|
||||
inputId,
|
||||
isInspected: true,
|
||||
selectedInspectIndex: inspectIndex,
|
||||
});
|
||||
}, [setIsInspected, queryId, inputId, inspectIndex]);
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
if (onCloseInspect != null) {
|
||||
onCloseInspect();
|
||||
}
|
||||
setIsInspected({
|
||||
id: queryId,
|
||||
inputId,
|
||||
isInspected: false,
|
||||
selectedInspectIndex: inspectIndex,
|
||||
});
|
||||
}, [onCloseInspect, setIsInspected, queryId, inputId, inspectIndex]);
|
||||
const handleCloseModal = useCallback(() => {
|
||||
if (onCloseInspect != null) {
|
||||
onCloseInspect();
|
||||
}
|
||||
setIsInspected({
|
||||
id: queryId,
|
||||
inputId,
|
||||
isInspected: false,
|
||||
selectedInspectIndex: inspectIndex,
|
||||
});
|
||||
}, [onCloseInspect, setIsInspected, queryId, inputId, inspectIndex]);
|
||||
|
||||
return (
|
||||
<InspectContainer
|
||||
data-test-subj={`${show ? 'opaque' : 'transparent'}-inspect-container`}
|
||||
showInspect={show}
|
||||
>
|
||||
{inputId === 'timeline' && !compact && (
|
||||
<EuiButtonEmpty
|
||||
aria-label={i18n.INSPECT}
|
||||
data-test-subj="inspect-empty-button"
|
||||
color="text"
|
||||
iconSide="left"
|
||||
iconType="inspect"
|
||||
isDisabled={loading || isDisabled}
|
||||
isLoading={loading}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{i18n.INSPECT}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
{(inputId === 'global' || compact) && (
|
||||
<EuiButtonIcon
|
||||
aria-label={i18n.INSPECT}
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
isDisabled={loading || isDisabled}
|
||||
title={i18n.INSPECT}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
)}
|
||||
<ModalInspectQuery
|
||||
closeModal={handleCloseModal}
|
||||
isShowing={!loading && selectedInspectIndex === inspectIndex && isInspected}
|
||||
request={inspect != null && inspect.dsl.length > 0 ? inspect.dsl[inspectIndex] : null}
|
||||
response={
|
||||
inspect != null && inspect.response.length > 0 ? inspect.response[inspectIndex] : null
|
||||
}
|
||||
title={title}
|
||||
data-test-subj="inspect-modal"
|
||||
return (
|
||||
<>
|
||||
{inputId === 'timeline' && !compact && (
|
||||
<EuiButtonEmpty
|
||||
className={BUTTON_CLASS}
|
||||
aria-label={i18n.INSPECT}
|
||||
data-test-subj="inspect-empty-button"
|
||||
color="text"
|
||||
iconSide="left"
|
||||
iconType="inspect"
|
||||
isDisabled={loading || isDisabled}
|
||||
isLoading={loading}
|
||||
onClick={handleClick}
|
||||
>
|
||||
{i18n.INSPECT}
|
||||
</EuiButtonEmpty>
|
||||
)}
|
||||
{(inputId === 'global' || compact) && (
|
||||
<EuiButtonIcon
|
||||
className={BUTTON_CLASS}
|
||||
aria-label={i18n.INSPECT}
|
||||
data-test-subj="inspect-icon-button"
|
||||
iconSize="m"
|
||||
iconType="inspect"
|
||||
isDisabled={loading || isDisabled}
|
||||
title={i18n.INSPECT}
|
||||
onClick={handleClick}
|
||||
/>
|
||||
</InspectContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
InspectButtonComponent.displayName = 'InspectButtonComponent';
|
||||
)}
|
||||
<ModalInspectQuery
|
||||
closeModal={handleCloseModal}
|
||||
isShowing={isShowingModal}
|
||||
request={inspect != null && inspect.dsl.length > 0 ? inspect.dsl[inspectIndex] : null}
|
||||
response={
|
||||
inspect != null && inspect.response.length > 0 ? inspect.response[inspectIndex] : null
|
||||
}
|
||||
title={title}
|
||||
data-test-subj="inspect-modal"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getGlobalQuery = inputsSelectors.globalQueryByIdSelector();
|
||||
|
@ -150,6 +161,11 @@ const makeMapStateToProps = () => {
|
|||
return mapStateToProps;
|
||||
};
|
||||
|
||||
export const InspectButton = connect(makeMapStateToProps, {
|
||||
const mapDispatchToProps = {
|
||||
setIsInspected: inputsActions.setInspectionParameter,
|
||||
})(InspectButtonComponent);
|
||||
};
|
||||
|
||||
export const InspectButton = connect(
|
||||
makeMapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(React.memo(InspectButtonComponent));
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { ScaleType } from '@elastic/charts';
|
||||
|
||||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
|
@ -17,10 +17,11 @@ import { DEFAULT_DARK_MODE } from '../../../common/constants';
|
|||
import { useUiSetting$ } from '../../lib/kibana';
|
||||
import { Loader } from '../loader';
|
||||
import { Panel } from '../panel';
|
||||
import { InspectButtonContainer } from '../inspect';
|
||||
import { getBarchartConfigs, getCustomChartData } from './utils';
|
||||
import { MatrixHistogramProps, MatrixHistogramDataTypes } from './types';
|
||||
|
||||
export const MatrixHistogram = ({
|
||||
export const MatrixHistogramComponent: React.FC<MatrixHistogramProps<MatrixHistogramDataTypes>> = ({
|
||||
data,
|
||||
dataKey,
|
||||
endDate,
|
||||
|
@ -35,7 +36,7 @@ export const MatrixHistogram = ({
|
|||
updateDateRange,
|
||||
yTickFormatter,
|
||||
showLegend,
|
||||
}: MatrixHistogramProps<MatrixHistogramDataTypes>) => {
|
||||
}) => {
|
||||
const barchartConfigs = getBarchartConfigs({
|
||||
from: startDate,
|
||||
to: endDate,
|
||||
|
@ -44,7 +45,6 @@ export const MatrixHistogram = ({
|
|||
yTickFormatter,
|
||||
showLegend,
|
||||
});
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
const [loadingInitial, setLoadingInitial] = useState(false);
|
||||
|
||||
|
@ -56,40 +56,31 @@ export const MatrixHistogram = ({
|
|||
}
|
||||
}, [loading, loadingInitial, totalCount]);
|
||||
|
||||
const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
|
||||
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
data-test-subj={`${dataKey}Panel`}
|
||||
loading={loading}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
>
|
||||
<HeaderSection
|
||||
id={id}
|
||||
title={title}
|
||||
showInspect={!loadingInitial && showInspect}
|
||||
subtitle={!loadingInitial && subtitle}
|
||||
/>
|
||||
<InspectButtonContainer show={!loadingInitial}>
|
||||
<Panel data-test-subj={`${dataKey}Panel`} loading={loading}>
|
||||
<HeaderSection id={id} title={title} subtitle={!loadingInitial && subtitle} />
|
||||
|
||||
{loadingInitial ? (
|
||||
<EuiLoadingContent data-test-subj="initialLoadingPanelMatrixOverTime" lines={10} />
|
||||
) : (
|
||||
<>
|
||||
<BarChart barChart={barChartData} configs={barchartConfigs} />
|
||||
{loadingInitial ? (
|
||||
<EuiLoadingContent data-test-subj="initialLoadingPanelMatrixOverTime" lines={10} />
|
||||
) : (
|
||||
<>
|
||||
<BarChart barChart={barChartData} configs={barchartConfigs} />
|
||||
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Panel>
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</Panel>
|
||||
</InspectButtonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
export const MatrixHistogram = React.memo(MatrixHistogramComponent);
|
||||
|
|
|
@ -8,14 +8,14 @@ import { EuiFlexItem } from '@elastic/eui';
|
|||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import { getOr } from 'lodash/fp';
|
||||
import React, { useContext, useState, useCallback } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
|
||||
import { DescriptionList } from '../../../../../common/utility_types';
|
||||
import { useUiSetting$ } from '../../../../lib/kibana';
|
||||
import { getEmptyTagValue } from '../../../empty_value';
|
||||
import { DefaultFieldRenderer, hostIdRenderer } from '../../../field_renderers/field_renderers';
|
||||
import { InspectButton } from '../../../inspect';
|
||||
import { InspectButton, InspectButtonContainer } from '../../../inspect';
|
||||
import { HostItem } from '../../../../graphql/types';
|
||||
import { Loader } from '../../../loader';
|
||||
import { IPDetailsLink } from '../../../links';
|
||||
|
@ -56,7 +56,6 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
anomaliesData,
|
||||
narrowDateRange,
|
||||
}) => {
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
|
@ -165,32 +164,26 @@ export const HostOverview = React.memo<HostSummaryProps>(
|
|||
],
|
||||
];
|
||||
|
||||
const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
|
||||
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);
|
||||
|
||||
return (
|
||||
<OverviewWrapper onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave}>
|
||||
<InspectButton
|
||||
queryId={id}
|
||||
show={showInspect}
|
||||
title={i18n.INSPECT_TITLE}
|
||||
inspectIndex={0}
|
||||
/>
|
||||
<InspectButtonContainer>
|
||||
<OverviewWrapper>
|
||||
<InspectButton queryId={id} title={i18n.INSPECT_TITLE} inspectIndex={0} />
|
||||
|
||||
{descriptionLists.map((descriptionList, index) =>
|
||||
getDescriptionList(descriptionList, index)
|
||||
)}
|
||||
{descriptionLists.map((descriptionList, index) =>
|
||||
getDescriptionList(descriptionList, index)
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</OverviewWrapper>
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</OverviewWrapper>
|
||||
</InspectButtonContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import { EuiFlexItem } from '@elastic/eui';
|
||||
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
|
||||
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
|
||||
import React, { useContext, useState, useCallback } from 'react';
|
||||
import React, { useContext } from 'react';
|
||||
|
||||
import { DEFAULT_DARK_MODE } from '../../../../../common/constants';
|
||||
import { DescriptionList } from '../../../../../common/utility_types';
|
||||
|
@ -32,7 +32,7 @@ import { Anomalies, NarrowDateRange } from '../../../ml/types';
|
|||
import { AnomalyScores } from '../../../ml/score/anomaly_scores';
|
||||
import { MlCapabilitiesContext } from '../../../ml/permissions/ml_capabilities_provider';
|
||||
import { hasMlUserPermissions } from '../../../ml/permissions/has_ml_user_permissions';
|
||||
import { InspectButton } from '../../../inspect';
|
||||
import { InspectButton, InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
interface OwnProps {
|
||||
data: IpOverviewData;
|
||||
|
@ -71,7 +71,6 @@ export const IpOverview = React.memo<IpOverviewProps>(
|
|||
anomaliesData,
|
||||
narrowDateRange,
|
||||
}) => {
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const capabilities = useContext(MlCapabilitiesContext);
|
||||
const userPermissions = hasMlUserPermissions(capabilities);
|
||||
const [darkMode] = useUiSetting$<boolean>(DEFAULT_DARK_MODE);
|
||||
|
@ -140,32 +139,26 @@ export const IpOverview = React.memo<IpOverviewProps>(
|
|||
],
|
||||
];
|
||||
|
||||
const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
|
||||
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);
|
||||
|
||||
return (
|
||||
<OverviewWrapper onMouseEnter={handleOnMouseEnter} onMouseLeave={handleOnMouseLeave}>
|
||||
<InspectButton
|
||||
queryId={id}
|
||||
show={showInspect}
|
||||
title={i18n.INSPECT_TITLE}
|
||||
inspectIndex={0}
|
||||
/>
|
||||
<InspectButtonContainer>
|
||||
<OverviewWrapper>
|
||||
<InspectButton queryId={id} title={i18n.INSPECT_TITLE} inspectIndex={0} />
|
||||
|
||||
{descriptionLists.map((descriptionList, index) =>
|
||||
getDescriptionList(descriptionList, index)
|
||||
)}
|
||||
{descriptionLists.map((descriptionList, index) =>
|
||||
getDescriptionList(descriptionList, index)
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</OverviewWrapper>
|
||||
{loading && (
|
||||
<Loader
|
||||
overlay
|
||||
overlayBackground={
|
||||
darkMode ? darkTheme.euiPageBackgroundColor : lightTheme.euiPageBackgroundColor
|
||||
}
|
||||
size="xl"
|
||||
/>
|
||||
)}
|
||||
</OverviewWrapper>
|
||||
</InspectButtonContainer>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewHostStats } from '../overview_host_stats';
|
||||
import { getHostsUrl } from '../../../link_to';
|
||||
import { InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
export interface OwnProps {
|
||||
startDate: number;
|
||||
|
@ -36,18 +37,14 @@ export interface OwnProps {
|
|||
|
||||
const OverviewHostStatsManage = manageQuery(OverviewHostStats);
|
||||
type OverviewHostProps = OwnProps;
|
||||
export const OverviewHost = React.memo<OverviewHostProps>(({ endDate, startDate, setQuery }) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]);
|
||||
const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]);
|
||||
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
const OverviewHostComponent: React.FC<OverviewHostProps> = ({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewHostQueryId}
|
||||
showInspect={isHover}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.hostsSubtitle"
|
||||
|
@ -76,8 +73,8 @@ export const OverviewHost = React.memo<OverviewHostProps>(({ endDate, startDate,
|
|||
)}
|
||||
</OverviewHostQuery>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
OverviewHost.displayName = 'OverviewHost';
|
||||
export const OverviewHost = React.memo(OverviewHostComponent);
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { EuiButton, EuiFlexItem, EuiPanel } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import React from 'react';
|
||||
|
||||
import { HeaderSection } from '../../../header_section';
|
||||
import { manageQuery } from '../../../page/manage_query';
|
||||
|
@ -17,6 +17,7 @@ import {
|
|||
import { inputsModel } from '../../../../store/inputs';
|
||||
import { OverviewNetworkStats } from '../overview_network_stats';
|
||||
import { getNetworkUrl } from '../../../link_to';
|
||||
import { InspectButtonContainer } from '../../../inspect';
|
||||
|
||||
export interface OwnProps {
|
||||
startDate: number;
|
||||
|
@ -36,18 +37,13 @@ export interface OwnProps {
|
|||
|
||||
const OverviewNetworkStatsManage = manageQuery(OverviewNetworkStats);
|
||||
|
||||
export const OverviewNetwork = React.memo<OwnProps>(({ endDate, startDate, setQuery }) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]);
|
||||
const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]);
|
||||
|
||||
return (
|
||||
<EuiFlexItem>
|
||||
<EuiPanel onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
const OverviewNetworkComponent: React.FC<OwnProps> = ({ endDate, startDate, setQuery }) => (
|
||||
<EuiFlexItem>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<HeaderSection
|
||||
border
|
||||
id={OverviewNetworkQueryId}
|
||||
showInspect={isHover}
|
||||
subtitle={
|
||||
<FormattedMessage
|
||||
id="xpack.siem.overview.networkSubtitle"
|
||||
|
@ -82,8 +78,8 @@ export const OverviewNetwork = React.memo<OwnProps>(({ endDate, startDate, setQu
|
|||
)}
|
||||
</OverviewNetworkQuery>
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
});
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
);
|
||||
|
||||
OverviewNetwork.displayName = 'OverviewNetwork';
|
||||
export const OverviewNetwork = React.memo(OverviewNetworkComponent);
|
||||
|
|
|
@ -520,7 +520,7 @@ exports[`Paginated Table Component rendering it renders the default load more ta
|
|||
}
|
||||
}
|
||||
>
|
||||
<PaginatedTable
|
||||
<PaginatedTableComponent
|
||||
activePage={0}
|
||||
columns={
|
||||
Array [
|
||||
|
|
|
@ -18,7 +18,7 @@ import {
|
|||
Direction,
|
||||
} from '@elastic/eui';
|
||||
import { noop } from 'lodash/fp';
|
||||
import React, { memo, useState, useEffect, useCallback, ComponentType } from 'react';
|
||||
import React, { FC, memo, useState, useEffect, ComponentType } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { AuthTableColumns } from '../page/hosts/authentications_table';
|
||||
|
@ -43,6 +43,7 @@ import { DEFAULT_MAX_TABLE_QUERY_SIZE } from '../../../common/constants';
|
|||
|
||||
import * as i18n from './translations';
|
||||
import { Panel } from '../panel';
|
||||
import { InspectButtonContainer } from '../inspect';
|
||||
|
||||
const DEFAULT_DATA_TEST_SUBJ = 'paginated-table';
|
||||
|
||||
|
@ -124,122 +125,113 @@ export interface Columns<T, U = T> {
|
|||
width?: string;
|
||||
}
|
||||
|
||||
export const PaginatedTable = memo<SiemTables>(
|
||||
({
|
||||
activePage,
|
||||
columns,
|
||||
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
||||
headerCount,
|
||||
headerSupplement,
|
||||
headerTitle,
|
||||
headerTooltip,
|
||||
headerUnit,
|
||||
id,
|
||||
isInspect,
|
||||
itemsPerRow,
|
||||
limit,
|
||||
loading,
|
||||
loadPage,
|
||||
onChange = noop,
|
||||
pageOfItems,
|
||||
showMorePagesIndicator,
|
||||
sorting = null,
|
||||
totalCount,
|
||||
updateActivePage,
|
||||
updateLimitPagination,
|
||||
}) => {
|
||||
const [myLoading, setMyLoading] = useState(loading);
|
||||
const [myActivePage, setActivePage] = useState(activePage);
|
||||
const [showInspect, setShowInspect] = useState(false);
|
||||
const [loadingInitial, setLoadingInitial] = useState(headerCount === -1);
|
||||
const [isPopoverOpen, setPopoverOpen] = useState(false);
|
||||
const PaginatedTableComponent: FC<SiemTables> = ({
|
||||
activePage,
|
||||
columns,
|
||||
dataTestSubj = DEFAULT_DATA_TEST_SUBJ,
|
||||
headerCount,
|
||||
headerSupplement,
|
||||
headerTitle,
|
||||
headerTooltip,
|
||||
headerUnit,
|
||||
id,
|
||||
isInspect,
|
||||
itemsPerRow,
|
||||
limit,
|
||||
loading,
|
||||
loadPage,
|
||||
onChange = noop,
|
||||
pageOfItems,
|
||||
showMorePagesIndicator,
|
||||
sorting = null,
|
||||
totalCount,
|
||||
updateActivePage,
|
||||
updateLimitPagination,
|
||||
}) => {
|
||||
const [myLoading, setMyLoading] = useState(loading);
|
||||
const [myActivePage, setActivePage] = useState(activePage);
|
||||
const [loadingInitial, setLoadingInitial] = useState(headerCount === -1);
|
||||
const [isPopoverOpen, setPopoverOpen] = useState(false);
|
||||
|
||||
const pageCount = Math.ceil(totalCount / limit);
|
||||
const dispatchToaster = useStateToaster()[1];
|
||||
const pageCount = Math.ceil(totalCount / limit);
|
||||
const dispatchToaster = useStateToaster()[1];
|
||||
|
||||
useEffect(() => {
|
||||
setActivePage(activePage);
|
||||
}, [activePage]);
|
||||
useEffect(() => {
|
||||
setActivePage(activePage);
|
||||
}, [activePage]);
|
||||
|
||||
useEffect(() => {
|
||||
if (headerCount >= 0 && loadingInitial) {
|
||||
setLoadingInitial(false);
|
||||
}
|
||||
}, [loadingInitial, headerCount]);
|
||||
useEffect(() => {
|
||||
if (headerCount >= 0 && loadingInitial) {
|
||||
setLoadingInitial(false);
|
||||
}
|
||||
}, [loadingInitial, headerCount]);
|
||||
|
||||
useEffect(() => {
|
||||
setMyLoading(loading);
|
||||
}, [loading]);
|
||||
useEffect(() => {
|
||||
setMyLoading(loading);
|
||||
}, [loading]);
|
||||
|
||||
const onButtonClick = () => {
|
||||
setPopoverOpen(!isPopoverOpen);
|
||||
};
|
||||
const onButtonClick = () => {
|
||||
setPopoverOpen(!isPopoverOpen);
|
||||
};
|
||||
|
||||
const closePopover = () => {
|
||||
setPopoverOpen(false);
|
||||
};
|
||||
const closePopover = () => {
|
||||
setPopoverOpen(false);
|
||||
};
|
||||
|
||||
const goToPage = (newActivePage: number) => {
|
||||
if ((newActivePage + 1) * limit >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
const toast: Toast = {
|
||||
id: 'PaginationWarningMsg',
|
||||
title: headerTitle + i18n.TOAST_TITLE,
|
||||
color: 'warning',
|
||||
iconType: 'alert',
|
||||
toastLifeTimeMs: 10000,
|
||||
text: i18n.TOAST_TEXT,
|
||||
};
|
||||
return dispatchToaster({
|
||||
type: 'addToaster',
|
||||
toast,
|
||||
});
|
||||
}
|
||||
setActivePage(newActivePage);
|
||||
loadPage(newActivePage);
|
||||
updateActivePage(newActivePage);
|
||||
};
|
||||
const goToPage = (newActivePage: number) => {
|
||||
if ((newActivePage + 1) * limit >= DEFAULT_MAX_TABLE_QUERY_SIZE) {
|
||||
const toast: Toast = {
|
||||
id: 'PaginationWarningMsg',
|
||||
title: headerTitle + i18n.TOAST_TITLE,
|
||||
color: 'warning',
|
||||
iconType: 'alert',
|
||||
toastLifeTimeMs: 10000,
|
||||
text: i18n.TOAST_TEXT,
|
||||
};
|
||||
return dispatchToaster({
|
||||
type: 'addToaster',
|
||||
toast,
|
||||
});
|
||||
}
|
||||
setActivePage(newActivePage);
|
||||
loadPage(newActivePage);
|
||||
updateActivePage(newActivePage);
|
||||
};
|
||||
|
||||
const button = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={onButtonClick}
|
||||
const button = (
|
||||
<EuiButtonEmpty
|
||||
size="xs"
|
||||
color="text"
|
||||
iconType="arrowDown"
|
||||
iconSide="right"
|
||||
onClick={onButtonClick}
|
||||
>
|
||||
{`${i18n.ROWS}: ${limit}`}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
|
||||
const rowItems =
|
||||
itemsPerRow &&
|
||||
itemsPerRow.map((item: ItemsPerRow) => (
|
||||
<EuiContextMenuItem
|
||||
key={item.text}
|
||||
icon={limit === item.numberOfRow ? 'check' : 'empty'}
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
updateLimitPagination(item.numberOfRow);
|
||||
updateActivePage(0); // reset results to first page
|
||||
}}
|
||||
>
|
||||
{`${i18n.ROWS}: ${limit}`}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
{item.text}
|
||||
</EuiContextMenuItem>
|
||||
));
|
||||
const PaginationWrapper = showMorePagesIndicator ? PaginationEuiFlexItem : EuiFlexItem;
|
||||
|
||||
const rowItems =
|
||||
itemsPerRow &&
|
||||
itemsPerRow.map((item: ItemsPerRow) => (
|
||||
<EuiContextMenuItem
|
||||
key={item.text}
|
||||
icon={limit === item.numberOfRow ? 'check' : 'empty'}
|
||||
onClick={() => {
|
||||
closePopover();
|
||||
updateLimitPagination(item.numberOfRow);
|
||||
updateActivePage(0); // reset results to first page
|
||||
}}
|
||||
>
|
||||
{item.text}
|
||||
</EuiContextMenuItem>
|
||||
));
|
||||
const PaginationWrapper = showMorePagesIndicator ? PaginationEuiFlexItem : EuiFlexItem;
|
||||
const handleOnMouseEnter = useCallback(() => setShowInspect(true), []);
|
||||
const handleOnMouseLeave = useCallback(() => setShowInspect(false), []);
|
||||
|
||||
return (
|
||||
<Panel
|
||||
data-test-subj={`${dataTestSubj}-loading-${loading}`}
|
||||
loading={loading}
|
||||
onMouseEnter={handleOnMouseEnter}
|
||||
onMouseLeave={handleOnMouseLeave}
|
||||
>
|
||||
return (
|
||||
<InspectButtonContainer show={!loadingInitial}>
|
||||
<Panel data-test-subj={`${dataTestSubj}-loading-${loading}`} loading={loading}>
|
||||
<HeaderSection
|
||||
id={id}
|
||||
showInspect={!loadingInitial && showInspect}
|
||||
subtitle={
|
||||
!loadingInitial &&
|
||||
`${i18n.SHOWING}: ${headerCount >= 0 ? headerCount.toLocaleString() : 0} ${headerUnit}`
|
||||
|
@ -306,11 +298,11 @@ export const PaginatedTable = memo<SiemTables>(
|
|||
</>
|
||||
)}
|
||||
</Panel>
|
||||
);
|
||||
}
|
||||
);
|
||||
</InspectButtonContainer>
|
||||
);
|
||||
};
|
||||
|
||||
PaginatedTable.displayName = 'PaginatedTable';
|
||||
export const PaginatedTable = memo(PaginatedTableComponent);
|
||||
|
||||
type BasicTableType = ComponentType<EuiBasicTableProps<any>>; // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||
const BasicTable: typeof EuiBasicTable & { displayName: string } = styled(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -21,7 +21,7 @@ import {
|
|||
IconType,
|
||||
} from '@elastic/eui';
|
||||
import { get, getOr } from 'lodash/fp';
|
||||
import React, { useState, useEffect, useCallback } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { KpiHostsData, KpiNetworkData } from '../../graphql/types';
|
||||
|
@ -30,7 +30,7 @@ import { BarChart } from '../charts/barchart';
|
|||
import { ChartSeriesData, ChartData, ChartSeriesConfigs, UpdateDateRange } from '../charts/common';
|
||||
import { getEmptyTagValue } from '../empty_value';
|
||||
|
||||
import { InspectButton } from '../inspect';
|
||||
import { InspectButton, InspectButtonContainer } from '../inspect';
|
||||
|
||||
const FlexItem = styled(EuiFlexItem)`
|
||||
min-width: 0;
|
||||
|
@ -209,9 +209,6 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
|
|||
statKey = 'item',
|
||||
to,
|
||||
}) => {
|
||||
const [isHover, setIsHover] = useState(false);
|
||||
const handleMouseEnter = useCallback(() => setIsHover(true), [setIsHover]);
|
||||
const handleMouseLeave = useCallback(() => setIsHover(false), [setIsHover]);
|
||||
const isBarChartDataAvailable =
|
||||
barChart &&
|
||||
barChart.length &&
|
||||
|
@ -223,72 +220,69 @@ export const StatItemsComponent = React.memo<StatItemsProps>(
|
|||
|
||||
return (
|
||||
<FlexItem grow={grow} data-test-subj={`stat-${statKey}`}>
|
||||
<EuiPanel onMouseEnter={handleMouseEnter} onMouseLeave={handleMouseLeave}>
|
||||
<EuiFlexGroup gutterSize={'none'}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxxs">
|
||||
<h6>{description}</h6>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButton
|
||||
queryId={id}
|
||||
title={`KPI ${description}`}
|
||||
inspectIndex={index}
|
||||
show={isHover}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<InspectButtonContainer>
|
||||
<EuiPanel>
|
||||
<EuiFlexGroup gutterSize={'none'}>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xxxs">
|
||||
<h6>{description}</h6>
|
||||
</EuiTitle>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButton queryId={id} title={`KPI ${description}`} inspectIndex={index} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
|
||||
<EuiFlexGroup>
|
||||
{fields.map(field => (
|
||||
<FlexItem key={`stat-items-field-${field.key}`}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
|
||||
{(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && (
|
||||
<FlexItem grow={false}>
|
||||
<EuiIcon
|
||||
type={field.icon}
|
||||
color={field.color}
|
||||
size="l"
|
||||
data-test-subj="stat-icon"
|
||||
/>
|
||||
<EuiFlexGroup>
|
||||
{fields.map(field => (
|
||||
<FlexItem key={`stat-items-field-${field.key}`}>
|
||||
<EuiFlexGroup alignItems="center" gutterSize="m" responsive={false}>
|
||||
{(isAreaChartDataAvailable || isBarChartDataAvailable) && field.icon && (
|
||||
<FlexItem grow={false}>
|
||||
<EuiIcon
|
||||
type={field.icon}
|
||||
color={field.color}
|
||||
size="l"
|
||||
data-test-subj="stat-icon"
|
||||
/>
|
||||
</FlexItem>
|
||||
)}
|
||||
|
||||
<FlexItem>
|
||||
<StatValue>
|
||||
<p data-test-subj="stat-title">
|
||||
{field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '}
|
||||
{field.description}
|
||||
</p>
|
||||
</StatValue>
|
||||
</FlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
|
||||
<FlexItem>
|
||||
<StatValue>
|
||||
<p data-test-subj="stat-title">
|
||||
{field.value != null ? field.value.toLocaleString() : getEmptyTagValue()}{' '}
|
||||
{field.description}
|
||||
</p>
|
||||
</StatValue>
|
||||
</FlexItem>
|
||||
</EuiFlexGroup>
|
||||
</FlexItem>
|
||||
))}
|
||||
</EuiFlexGroup>
|
||||
{(enableAreaChart || enableBarChart) && <EuiHorizontalRule />}
|
||||
<EuiFlexGroup>
|
||||
{enableBarChart && (
|
||||
<FlexItem>
|
||||
<BarChart barChart={barChart} configs={barchartConfigs()} />
|
||||
</FlexItem>
|
||||
)}
|
||||
|
||||
{(enableAreaChart || enableBarChart) && <EuiHorizontalRule />}
|
||||
<EuiFlexGroup>
|
||||
{enableBarChart && (
|
||||
<FlexItem>
|
||||
<BarChart barChart={barChart} configs={barchartConfigs()} />
|
||||
</FlexItem>
|
||||
)}
|
||||
|
||||
{enableAreaChart && from != null && to != null && (
|
||||
<FlexItem>
|
||||
<AreaChart
|
||||
areaChart={areaChart}
|
||||
configs={areachartConfigs({
|
||||
xTickFormatter: niceTimeFormatter([from, to]),
|
||||
onBrushEnd: narrowDateRange,
|
||||
})}
|
||||
/>
|
||||
</FlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
{enableAreaChart && from != null && to != null && (
|
||||
<FlexItem>
|
||||
<AreaChart
|
||||
areaChart={areaChart}
|
||||
configs={areachartConfigs({
|
||||
xTickFormatter: niceTimeFormatter([from, to]),
|
||||
onBrushEnd: narrowDateRange,
|
||||
})}
|
||||
/>
|
||||
</FlexItem>
|
||||
)}
|
||||
</EuiFlexGroup>
|
||||
</EuiPanel>
|
||||
</InspectButtonContainer>
|
||||
</FlexItem>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { NewTimeline, Description, NotesButton } from './helpers';
|
||||
import { OpenTimelineModalButton } from '../../open_timeline/open_timeline_modal/open_timeline_modal_button';
|
||||
import { OpenTimelineModal } from '../../open_timeline/open_timeline_modal';
|
||||
import { InspectButton } from '../../inspect';
|
||||
import { InspectButton, InspectButtonContainer } from '../../inspect';
|
||||
|
||||
import * as i18n from './translations';
|
||||
import { AssociateNote } from '../../notes/helpers';
|
||||
|
@ -82,32 +82,32 @@ interface Props {
|
|||
updateNote: UpdateNote;
|
||||
}
|
||||
|
||||
export const PropertiesRight = React.memo<Props>(
|
||||
({
|
||||
onButtonClick,
|
||||
showActions,
|
||||
onClosePopover,
|
||||
createTimeline,
|
||||
timelineId,
|
||||
isDataInTimeline,
|
||||
showNotesFromWidth,
|
||||
showNotes,
|
||||
showDescription,
|
||||
showUsersView,
|
||||
usersViewing,
|
||||
description,
|
||||
updateDescription,
|
||||
associateNote,
|
||||
getNotesByIds,
|
||||
noteIds,
|
||||
onToggleShowNotes,
|
||||
updateNote,
|
||||
showTimelineModal,
|
||||
onCloseTimelineModal,
|
||||
onOpenTimelineModal,
|
||||
}) => (
|
||||
<PropertiesRightStyle alignItems="flexStart" data-test-subj="properties-right" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
const PropertiesRightComponent: React.FC<Props> = ({
|
||||
onButtonClick,
|
||||
showActions,
|
||||
onClosePopover,
|
||||
createTimeline,
|
||||
timelineId,
|
||||
isDataInTimeline,
|
||||
showNotesFromWidth,
|
||||
showNotes,
|
||||
showDescription,
|
||||
showUsersView,
|
||||
usersViewing,
|
||||
description,
|
||||
updateDescription,
|
||||
associateNote,
|
||||
getNotesByIds,
|
||||
noteIds,
|
||||
onToggleShowNotes,
|
||||
updateNote,
|
||||
showTimelineModal,
|
||||
onCloseTimelineModal,
|
||||
onOpenTimelineModal,
|
||||
}) => (
|
||||
<PropertiesRightStyle alignItems="flexStart" data-test-subj="properties-right" gutterSize="s">
|
||||
<EuiFlexItem grow={false}>
|
||||
<InspectButtonContainer>
|
||||
<EuiPopover
|
||||
anchorPosition="downRight"
|
||||
button={
|
||||
|
@ -142,7 +142,6 @@ export const PropertiesRight = React.memo<Props>(
|
|||
inspectIndex={0}
|
||||
isDisabled={!isDataInTimeline}
|
||||
onCloseInspect={onClosePopover}
|
||||
show={true}
|
||||
title={i18n.INSPECT_TIMELINE_TITLE}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
|
@ -177,26 +176,26 @@ export const PropertiesRight = React.memo<Props>(
|
|||
) : null}
|
||||
</EuiFlexGroup>
|
||||
</EuiPopover>
|
||||
</EuiFlexItem>
|
||||
</InspectButtonContainer>
|
||||
</EuiFlexItem>
|
||||
|
||||
{showUsersView
|
||||
? usersViewing.map(user => (
|
||||
// Hide the hard-coded elastic user avatar as the 7.2 release does not implement
|
||||
// support for multi-user-collaboration as proposed in elastic/ingest-dev#395
|
||||
<HiddenFlexItem key={user}>
|
||||
<EuiToolTip
|
||||
data-test-subj="timeline-action-pin-tool-tip"
|
||||
content={`${user} ${i18n.IS_VIEWING}`}
|
||||
>
|
||||
<Avatar data-test-subj="avatar" size="s" name={user} />
|
||||
</EuiToolTip>
|
||||
</HiddenFlexItem>
|
||||
))
|
||||
: null}
|
||||
{showUsersView
|
||||
? usersViewing.map(user => (
|
||||
// Hide the hard-coded elastic user avatar as the 7.2 release does not implement
|
||||
// support for multi-user-collaboration as proposed in elastic/ingest-dev#395
|
||||
<HiddenFlexItem key={user}>
|
||||
<EuiToolTip
|
||||
data-test-subj="timeline-action-pin-tool-tip"
|
||||
content={`${user} ${i18n.IS_VIEWING}`}
|
||||
>
|
||||
<Avatar data-test-subj="avatar" size="s" name={user} />
|
||||
</EuiToolTip>
|
||||
</HiddenFlexItem>
|
||||
))
|
||||
: null}
|
||||
|
||||
{showTimelineModal ? <OpenTimelineModal onClose={onCloseTimelineModal} /> : null}
|
||||
</PropertiesRightStyle>
|
||||
)
|
||||
{showTimelineModal ? <OpenTimelineModal onClose={onCloseTimelineModal} /> : null}
|
||||
</PropertiesRightStyle>
|
||||
);
|
||||
|
||||
PropertiesRight.displayName = 'PropertiesRight';
|
||||
export const PropertiesRight = React.memo(PropertiesRightComponent);
|
||||
|
|
Loading…
Reference in a new issue