Skip to content

Commit

Permalink
[Session View] [8.2] Load more style fix (#129676)
Browse files Browse the repository at this point in the history
* Fix for process event pagination in session view

* load more/previous buttons style now matches figma design. also added remaining events indication as per design

updated test to look for total prop

* icon fix for backwards pagination

* missing space added

Co-authored-by: mitodrummer <[email protected]>
  • Loading branch information
mitodrummer and mitodrummer authored Apr 7, 2022
1 parent d92b7fe commit 6afbe52
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 17 deletions.
2 changes: 1 addition & 1 deletion x-pack/plugins/session_view/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const ALERT_STATUS = {
// if not 100s of thousands of events, and to be required to page through these sessions to find more search matches is not a great experience. Future iterations of the
// search functionality will instead use a separate ES backend search to avoid this.
// 3. Fewer round trips to the backend!
export const PROCESS_EVENTS_PER_PAGE = 2000;
export const PROCESS_EVENTS_PER_PAGE = 1000;

// As an initial approach, we won't be implementing pagination for alerts.
// Instead we will load this fixed amount of alerts as a maximum for a session.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export interface Group {
}

export interface ProcessEventResults {
total?: number;
events?: any[];
}

Expand Down Expand Up @@ -147,6 +148,7 @@ export interface ProcessEvent {
export interface ProcessEventsPage {
events?: ProcessEvent[];
cursor?: string;
total?: number; // total count of all items across all pages (as reported by ES client)
}

export interface Process {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React, { useState, useRef, useEffect, useLayoutEffect, useCallback } from 'react';
import { EuiButton } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useState, useRef, useEffect, useLayoutEffect, useCallback, useMemo } from 'react';
import { i18n } from '@kbn/i18n';
import { ProcessTreeNode } from '../process_tree_node';
import { BackToInvestigatedAlert } from '../back_to_investigated_alert';
import { useProcessTree } from './hooks';
import { ProcessTreeLoadMoreButton } from '../process_tree_load_more_button';
import {
AlertStatusEventEntityIdMap,
Process,
Expand All @@ -18,9 +18,24 @@ import {
} from '../../../common/types/process_tree';
import { useScroll } from '../../hooks/use_scroll';
import { useStyles } from './styles';
import { PROCESS_EVENTS_PER_PAGE } from '../../../common/constants';

type FetchFunction = () => void;

const LOAD_NEXT_TEXT = i18n.translate('xpack.sessionView.processTree.loadMore', {
defaultMessage: 'Show {pageSize} next events',
values: {
pageSize: PROCESS_EVENTS_PER_PAGE,
},
});

const LOAD_PREVIOUS_TEXT = i18n.translate('xpack.sessionView.processTree.loadPrevious', {
defaultMessage: 'Show {pageSize} previous events',
values: {
pageSize: PROCESS_EVENTS_PER_PAGE,
},
});

export interface ProcessTreeDeps {
// process.entity_id to act as root node (typically a session (or entry session) leader).
sessionEntityId: string;
Expand Down Expand Up @@ -83,6 +98,15 @@ export const ProcessTree = ({
updatedAlertsStatus,
});

const eventsRemaining = useMemo(() => {
const total = data?.[0]?.total || 0;
const loadedSoFar = data.reduce((prev, current) => {
return prev + (current?.events?.length || 0);
}, 0);

return total - loadedSoFar;
}, [data]);

const scrollerRef = useRef<HTMLDivElement>(null);
const selectionAreaRef = useRef<HTMLDivElement>(null);

Expand Down Expand Up @@ -197,11 +221,6 @@ export const ProcessTree = ({
css={styles.scroller}
data-test-subj="sessionView:sessionViewProcessTree"
>
{hasPreviousPage && (
<EuiButton fullWidth onClick={fetchPreviousPage} isLoading={isFetching}>
<FormattedMessage id="xpack.sessionView.loadPrevious" defaultMessage="Load previous" />
</EuiButton>
)}
{sessionLeader && (
<ProcessTreeNode
isSessionLeader
Expand All @@ -216,18 +235,35 @@ export const ProcessTree = ({
timeStampOn={timeStampOn}
verboseModeOn={verboseModeOn}
searchResults={searchResults}
loadPreviousButton={
hasPreviousPage ? (
<ProcessTreeLoadMoreButton
text={LOAD_PREVIOUS_TEXT}
onClick={fetchPreviousPage}
isFetching={isFetching}
eventsRemaining={eventsRemaining}
forward={false}
/>
) : null
}
loadNextButton={
hasNextPage ? (
<ProcessTreeLoadMoreButton
text={LOAD_NEXT_TEXT}
onClick={fetchNextPage}
isFetching={isFetching}
eventsRemaining={eventsRemaining}
forward={true}
/>
) : null
}
/>
)}
<div
data-test-subj="sessionView:processTreeSelectionArea"
ref={selectionAreaRef}
css={styles.selectionArea}
/>
{hasNextPage && (
<EuiButton fullWidth onClick={fetchNextPage} isLoading={isFetching}>
<FormattedMessage id="xpack.sessionView.loadNext" defaultMessage="Load next" />
</EuiButton>
)}
</div>
{!isInvestigatedEventVisible && (
<BackToInvestigatedAlert
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React from 'react';
import { FormattedMessage } from '@kbn/i18n-react';
import { EuiButtonEmpty } from '@elastic/eui';
import { useStyles } from './styles';

export interface ProcessTreeLoadMoreButtonDeps {
onClick: () => void;
text: string;
isFetching: boolean;
eventsRemaining: number;
forward: boolean;
}

export const ProcessTreeLoadMoreButton = ({
onClick,
text,
isFetching,
eventsRemaining,
forward,
}: ProcessTreeLoadMoreButtonDeps) => {
const styles = useStyles();

return (
<div css={styles.wrapper}>
<EuiButtonEmpty
size="xs"
iconType={forward ? 'arrowDown' : 'arrowUp'}
onClick={onClick}
isLoading={isFetching}
>
{text}
{eventsRemaining !== 0 && (
<FormattedMessage
id="xpack.sessionView.processTreeLoadMoreButton"
defaultMessage=" ({count} left)"
values={{ count: eventsRemaining }}
/>
)}
</EuiButtonEmpty>
<span css={styles.dottedLine} />
</div>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import { useMemo } from 'react';
import { useEuiTheme } from '@elastic/eui';
import { CSSObject } from '@emotion/react';

export const useStyles = () => {
const { euiTheme } = useEuiTheme();

const cached = useMemo(() => {
const { border } = euiTheme;

const wrapper: CSSObject = {
display: 'flex',
alignItems: 'center',
};

const dottedLine: CSSObject = {
flex: 1,
borderTop: `${border.width.thick} dotted ${border.color}`,
};

return {
wrapper,
dottedLine,
};
}, [euiTheme]);

return cached;
};
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import React, {
useCallback,
useMemo,
RefObject,
ReactElement,
} from 'react';
import { EuiButton, EuiIcon, EuiToolTip, formatDate } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
Expand Down Expand Up @@ -47,6 +48,8 @@ export interface ProcessDeps {
scrollerRef: RefObject<HTMLDivElement>;
onChangeJumpToEventVisibility: (isVisible: boolean, isAbove: boolean) => void;
onShowAlertDetails: (alertUuid: string) => void;
loadNextButton?: ReactElement | null;
loadPreviousButton?: ReactElement | null;
}

/**
Expand All @@ -66,6 +69,8 @@ export function ProcessTreeNode({
scrollerRef,
onChangeJumpToEventVisibility,
onShowAlertDetails,
loadPreviousButton,
loadNextButton,
}: ProcessDeps) {
const textRef = useRef<HTMLSpanElement>(null);

Expand Down Expand Up @@ -249,8 +254,8 @@ export function ProcessTreeNode({
</EuiToolTip>{' '}
<span ref={textRef}>
<span css={styles.workingDir}>{dataOrDash(workingDirectory)}</span>&nbsp;
<span css={styles.darkText}>{dataOrDash(args?.[0])}</span>&nbsp;
{dataOrDash(args?.slice(1).join(' '))}
<span css={styles.darkText}>{dataOrDash(args?.[0])}</span>{' '}
{args?.slice(1).join(' ')}
{exitCode !== undefined && (
<small data-test-subj="sessionView:processTreeNodeExitCode">
{' '}
Expand Down Expand Up @@ -303,6 +308,7 @@ export function ProcessTreeNode({

{shouldRenderChildren && (
<div css={styles.children}>
{loadPreviousButton}
{children.map((child) => {
return (
<ProcessTreeNode
Expand All @@ -322,6 +328,7 @@ export function ProcessTreeNode({
/>
);
})}
{loadNextButton}
</div>
)}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export const useFetchSessionViewProcessEvents = (

const events = res.events?.map((event: any) => event._source as ProcessEvent) ?? [];

return { events, cursor };
return { events, cursor, total: res.total };
},
{
getNextPageParam: (lastPage) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ describe('process_events_route.ts', () => {
const body = await doSearch(client, 'asdf', undefined);

expect(body.events.length).toBe(0);
expect(body.total).toBe(0);
});

it('returns results for a particular session entity_id', async () => {
Expand All @@ -44,6 +45,7 @@ describe('process_events_route.ts', () => {
const body = await doSearch(client, 'mockId', undefined);

expect(body.events.length).toBe(mockEvents.length);
expect(body.total).toBe(body.events.length);
});

it('returns hits in reverse order when paginating backwards', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,11 @@ export const doSearch = async (
events.reverse();
}

const total =
typeof search.hits.total === 'number' ? search.hits.total : search.hits.total?.value;

return {
total,
events,
};
};

0 comments on commit 6afbe52

Please sign in to comment.