-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Hide timeline footer when Resolver is open #71516
Changes from 8 commits
32dc074
1ad5679
0e3ad6f
8a75f79
a4a496b
7e5ed4f
68c0e91
b0b048b
96e642e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -67,6 +67,8 @@ interface Props { | |
sort: Sort; | ||
toggleColumn: (column: ColumnHeaderOptions) => void; | ||
utilityBar?: (refetch: inputsModel.Refetch, totalCount: number) => React.ReactNode; | ||
// If truthy, the graph viewer (Resolver) is showing | ||
graphEventId: string | undefined; | ||
} | ||
|
||
const EventsViewerComponent: React.FC<Props> = ({ | ||
|
@@ -90,6 +92,7 @@ const EventsViewerComponent: React.FC<Props> = ({ | |
sort, | ||
toggleColumn, | ||
utilityBar, | ||
graphEventId, | ||
}) => { | ||
const columnsHeader = isEmpty(columns) ? defaultHeaders : columns; | ||
const kibana = useKibana(); | ||
|
@@ -191,22 +194,28 @@ const EventsViewerComponent: React.FC<Props> = ({ | |
toggleColumn={toggleColumn} | ||
/> | ||
|
||
<Footer | ||
getUpdatedAt={getUpdatedAt} | ||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} | ||
height={footerHeight} | ||
id={id} | ||
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)} | ||
/> | ||
{ | ||
/** Hide the footer if Resolver is showing. */ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a guard statement and remove |
||
!graphEventId && ( | ||
<Footer | ||
data-test-subj="events-viewer-footer" | ||
getUpdatedAt={getUpdatedAt} | ||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} | ||
height={footerHeight} | ||
id={id} | ||
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)} | ||
/> | ||
) | ||
} | ||
</EventsContainerLoading> | ||
</> | ||
); | ||
|
@@ -237,5 +246,6 @@ export const EventsViewer = React.memo( | |
deepEqual(prevProps.query, nextProps.query) && | ||
prevProps.start === nextProps.start && | ||
prevProps.sort === nextProps.sort && | ||
prevProps.utilityBar === nextProps.utilityBar | ||
prevProps.utilityBar === nextProps.utilityBar && | ||
prevProps.graphEventId === nextProps.graphEventId | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems to be required for it to work. |
||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -62,6 +62,8 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({ | |
updateItemsPerPage, | ||
upsertColumn, | ||
utilityBar, | ||
// If truthy, the graph viewer (Resolver) is showing | ||
graphEventId, | ||
}) => { | ||
const [{ browserFields, indexPatterns }] = useFetchIndexPatterns( | ||
defaultIndices ?? useUiSetting<string[]>(DEFAULT_INDEX_KEY) | ||
|
@@ -135,6 +137,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({ | |
sort={sort} | ||
toggleColumn={toggleColumn} | ||
utilityBar={utilityBar} | ||
graphEventId={graphEventId} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pass the new prop to the unconnected event viewer |
||
/> | ||
</InspectButtonContainer> | ||
); | ||
|
@@ -145,6 +148,7 @@ const makeMapStateToProps = () => { | |
const getGlobalQuerySelector = inputsSelectors.globalQuerySelector(); | ||
const getGlobalFiltersQuerySelector = inputsSelectors.globalFiltersQuerySelector(); | ||
const getEvents = timelineSelectors.getEventsByIdSelector(); | ||
const getTimeline = timelineSelectors.getTimelineByIdSelector(); | ||
const mapStateToProps = (state: State, { id, defaultModel }: OwnProps) => { | ||
const input: inputsModel.InputsRange = getInputsTimeline(state); | ||
const events: TimelineModel = getEvents(state, id) ?? defaultModel; | ||
|
@@ -174,6 +178,9 @@ const makeMapStateToProps = () => { | |
query: getGlobalQuerySelector(state), | ||
sort, | ||
showCheckboxes, | ||
// Used to determine whether the footer should show (since it is hidden if the graph is showing.) | ||
// `getTimeline` actually returns `TimelineModel | undefined` | ||
graphEventId: (getTimeline(state, id) as TimelineModel | undefined)?.graphEventId, | ||
}; | ||
}; | ||
return mapStateToProps; | ||
|
@@ -213,6 +220,7 @@ export const StatefulEventsViewer = connector( | |
deepEqual(prevProps.pageFilters, nextProps.pageFilters) && | ||
prevProps.showCheckboxes === nextProps.showCheckboxes && | ||
prevProps.start === nextProps.start && | ||
prevProps.utilityBar === nextProps.utilityBar | ||
prevProps.utilityBar === nextProps.utilityBar && | ||
prevProps.graphEventId === nextProps.graphEventId | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. required for rendering to work correctly |
||
) | ||
); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,9 +103,6 @@ export const getEventType = (event: Ecs): Omit<EventType, 'all'> => { | |
return 'raw'; | ||
}; | ||
|
||
export const showGraphView = (graphEventId?: string) => | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed this function. Instead i'm just using the |
||
graphEventId != null && graphEventId.length > 0; | ||
|
||
export const isInvestigateInResolverActionEnabled = (ecsData?: Ecs) => { | ||
return ( | ||
get(['agent', 'type', 0], ecsData) === 'endpoint' && | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,7 +17,7 @@ import { columnRenderers, rowRenderers } from './renderers'; | |
import { Sort } from './sort'; | ||
import { wait } from '../../../../common/lib/helpers'; | ||
import { useMountAppended } from '../../../../common/utils/use_mount_appended'; | ||
import { SELECTOR_TIMELINE_BODY_CLASS_NAME } from '../styles'; | ||
import { SELECTOR_TIMELINE_BODY_CLASS_NAME, TimelineBody } from '../styles'; | ||
|
||
const testBodyHeight = 700; | ||
const mockGetNotesByIds = (eventId: string[]) => []; | ||
|
@@ -33,8 +33,12 @@ jest.mock('react-redux', () => { | |
useSelector: jest.fn(), | ||
}; | ||
}); | ||
|
||
jest.mock('../../../../common/components/link_to'); | ||
|
||
// Prevent Resolver from rendering | ||
jest.mock('../../graph_overlay'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. prevents resolver from rendering. |
||
|
||
jest.mock( | ||
'react-visibility-sensor', | ||
() => ({ children }: { children: (args: { isVisible: boolean }) => React.ReactNode }) => | ||
|
@@ -148,6 +152,20 @@ describe('Body', () => { | |
.exists() | ||
).toEqual(true); | ||
}); | ||
describe('when there is a graphEventId', () => { | ||
beforeEach(() => { | ||
props.graphEventId = 'graphEventId'; // any string w/ length > 0 works | ||
}); | ||
it('should not render the timeline body', () => { | ||
const wrapper = mount( | ||
<TestProviders> | ||
<Body {...props} /> | ||
</TestProviders> | ||
); | ||
// the timeline body still renders, but it gets a `display: none` style via `styled-components`. | ||
expect(wrapper.find(TimelineBody).props().visible).toBe(false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the timeline body still renders, but it gets a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing the component (function) reference here. Seems neat. Is this a good thing to do in enzyme? ty There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Passing the component reference is a totally valid thing to do in Enzyme, however we generally prefer to instrument with The rational for standardizing on Consider replacing the use of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. thanks for the explanation. i took your advice. |
||
}); | ||
}); | ||
}); | ||
|
||
describe('action on event', () => { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -282,27 +282,33 @@ export const TimelineComponent: React.FC<Props> = ({ | |
toggleColumn={toggleColumn} | ||
/> | ||
</StyledEuiFlyoutBody> | ||
<StyledEuiFlyoutFooter | ||
data-test-subj="eui-flyout-footer" | ||
className="timeline-flyout-footer" | ||
> | ||
<Footer | ||
getUpdatedAt={getUpdatedAt} | ||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} | ||
height={footerHeight} | ||
id={id} | ||
isLive={isLive} | ||
isLoading={loading || loadingIndexName} | ||
itemsCount={events.length} | ||
itemsPerPage={itemsPerPage} | ||
itemsPerPageOptions={itemsPerPageOptions} | ||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!} | ||
onChangeItemsPerPage={onChangeItemsPerPage} | ||
onLoadMore={loadMore} | ||
serverSideEventCount={totalCount} | ||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)} | ||
/> | ||
</StyledEuiFlyoutFooter> | ||
{ | ||
/** Hide the footer if Resolver is showing. */ | ||
!graphEventId && ( | ||
<StyledEuiFlyoutFooter | ||
data-test-subj="eui-flyout-footer" | ||
className="timeline-flyout-footer" | ||
> | ||
<Footer | ||
data-test-subj="timeline-footer" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added this |
||
getUpdatedAt={getUpdatedAt} | ||
hasNextPage={getOr(false, 'hasNextPage', pageInfo)!} | ||
height={footerHeight} | ||
id={id} | ||
isLive={isLive} | ||
isLoading={loading || loadingIndexName} | ||
itemsCount={events.length} | ||
itemsPerPage={itemsPerPage} | ||
itemsPerPageOptions={itemsPerPageOptions} | ||
nextCursor={getOr(null, 'endCursor.value', pageInfo)!} | ||
onChangeItemsPerPage={onChangeItemsPerPage} | ||
onLoadMore={loadMore} | ||
serverSideEventCount={totalCount} | ||
tieBreaker={getOr(null, 'endCursor.tiebreaker', pageInfo)} | ||
/> | ||
</StyledEuiFlyoutFooter> | ||
) | ||
} | ||
</> | ||
); | ||
}} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❔ Should this come from a selector like
shouldShowResolver
?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I only ask because I've done sparse business logic like this before and have been asked to move it to a selector to make it more readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see later maybe there is a selector? It's not clear from reading this the way the comment is written.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i'm changing some existing code here, but my thoughts:
TimelineModel
.TimelineModel
hasgraphEventId
as a fieldgraphEventId
is falsy, there is no 'graphEvent' (aka resolver)graphEventId
ts truthy, the value is the_id
of the document that Resolver should use.The existing code doesn't have a specific selector for
graphEventId
. Instead it exposes theTimelineModel
interface throughout the view. If there was a desire to encapsulate the logic of "given aTimelineModel
, how do I know if the graph view should show" then I would add a method to theTimelineModel
. I don't think that would fit the code style of the SIEM team, but I'm open to changing it if they want that.