feat: remove empty state from insight accordion (#137872)

Since the UI can get quite cluttered with some insight accordions greyed out and some not, we decided (with the design team) to remove the dedicated empty space for now.
This commit is contained in:
Jan Monschke 2022-08-03 14:17:11 +02:00 committed by GitHub
parent 40ce451d72
commit 041bd90826
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 64 additions and 71 deletions

View file

@ -42,19 +42,6 @@ describe('InsightAccordion', () => {
expect(screen.getByText(errorText)).toBeInTheDocument();
});
it("shows the text and a disabled button when it's in the empty state", () => {
const text = 'the text';
render(
<TestProviders>
<InsightAccordion state="empty" text={text} prefix="" renderContent={noopRenderer} />
</TestProviders>
);
const button = screen.getByRole('button', { name: text });
expect(button).toBeInTheDocument();
expect(button).toHaveAttribute('aria-disabled');
});
it('shows the text and renders the correct content', () => {
const text = 'the text';
const contentText = 'content text';

View file

@ -18,12 +18,7 @@ const StyledAccordion = euiStyled(EuiAccordion)`
border-radius: 6px;
`;
const EmptyAccordion = euiStyled(StyledAccordion)`
color: ${({ theme }) => theme.eui.euiColorDisabledText};
pointer-events: none;
`;
export type InsightAccordionState = 'loading' | 'error' | 'success' | 'empty';
export type InsightAccordionState = 'loading' | 'error' | 'success';
interface Props {
prefix: string;
@ -61,24 +56,6 @@ export const InsightAccordion = React.memo<Props>(
onToggle={onToggle}
/>
);
case 'empty':
// Since EuiAccordions don't have an empty state and they don't allow to style the arrow
// we're using a custom styled Accordion here and we're adding the faded-out button manually.
return (
<EmptyAccordion
id={accordionId}
buttonContent={
<span>
<EuiIcon type="arrowRight" style={{ margin: '0px 8px 0 4px' }} />
{text}
</span>
}
buttonProps={{
'aria-disabled': true,
}}
arrowDisplay="none"
/>
);
case 'success':
// The accordion can display the content now
return (

View file

@ -17,7 +17,12 @@ import { InsightAccordion } from './insight_accordion';
import { SimpleAlertTable } from './simple_alert_table';
import { InvestigateInTimelineButton } from '../table/investigate_in_timeline_button';
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
import { PROCESS_ANCESTRY, PROCESS_ANCESTRY_COUNT, PROCESS_ANCESTRY_ERROR } from './translations';
import {
PROCESS_ANCESTRY,
PROCESS_ANCESTRY_COUNT,
PROCESS_ANCESTRY_EMPTY,
PROCESS_ANCESTRY_ERROR,
} from './translations';
interface Props {
data: TimelineEventsDetailsItem;
@ -65,12 +70,15 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>(
const [cache, setCache] = useState<Partial<Cache>>({});
const onToggle = useCallback((isOpen: boolean) => setShowContent(isOpen), []);
const isEmpty = !!cache.alertIds && cache.alertIds.length === 0;
// Makes sure the component is not fetching data before the accordion
// has been openend.
const renderContent = useCallback(() => {
if (!showContent) {
return null;
} else if (isEmpty) {
return PROCESS_ANCESTRY_EMPTY;
} else if (cache.alertIds) {
return (
<ActualRelatedAlertsByProcessAncestry
@ -90,16 +98,14 @@ export const RelatedAlertsByProcessAncestry = React.memo<Props>(
onCacheLoad={setCache}
/>
);
}, [showContent, cache, data, eventId, timelineId, index, originalDocumentId]);
const isEmpty = !!cache.alertIds && cache.alertIds.length === 0;
}, [showContent, cache, data, eventId, timelineId, index, originalDocumentId, isEmpty]);
return (
<InsightAccordion
prefix="RelatedAlertsByProcessAncestry"
// `renderContent` and the associated sub-components are making sure to
// render the correct loading and error states so we can omit these states here
state={isEmpty ? 'empty' : 'success'}
state="success"
text={
// If we have fetched the alerts, display the count here, otherwise omit the count
cache.alertIds ? PROCESS_ANCESTRY_COUNT(cache.alertIds.length) : PROCESS_ANCESTRY

View file

@ -18,7 +18,7 @@ import { InvestigateInTimelineButton } from '../table/investigate_in_timeline_bu
import { SimpleAlertTable } from './simple_alert_table';
import { getEnrichedFieldInfo } from '../helpers';
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
import { SESSION_LOADING, SESSION_ERROR, SESSION_COUNT } from './translations';
import { SESSION_LOADING, SESSION_EMPTY, SESSION_ERROR, SESSION_COUNT } from './translations';
interface Props {
browserFields: BrowserFields;
@ -64,9 +64,20 @@ export const RelatedAlertsBySession = React.memo<Props>(
fieldType: fieldFromBrowserField?.type,
});
const isEmpty = count === 0;
let state: InsightAccordionState = 'loading';
if (error) {
state = 'error';
} else if (alertIds || isEmpty) {
state = 'success';
}
const renderContent = useCallback(() => {
if (!alertIds || !cellData?.dataProviders) {
return null;
} else if (isEmpty && state !== 'loading') {
return SESSION_EMPTY;
}
return (
<>
@ -80,16 +91,7 @@ export const RelatedAlertsBySession = React.memo<Props>(
</InvestigateInTimelineButton>
</>
);
}, [alertIds, cellData?.dataProviders]);
let state: InsightAccordionState = 'loading';
if (error) {
state = 'error';
} else if (count === 0) {
state = 'empty';
} else if (alertIds) {
state = 'success';
}
}, [alertIds, cellData?.dataProviders, isEmpty, state]);
return (
<InsightAccordion
@ -111,7 +113,6 @@ function getTextFromState(state: InsightAccordionState, count: number | undefine
case 'error':
return SESSION_ERROR;
case 'success':
case 'empty':
return SESSION_COUNT(count);
default:
return '';

View file

@ -18,7 +18,12 @@ import { InvestigateInTimelineButton } from '../table/investigate_in_timeline_bu
import { SimpleAlertTable } from './simple_alert_table';
import { getEnrichedFieldInfo } from '../helpers';
import { ACTION_INVESTIGATE_IN_TIMELINE } from '../../../../detections/components/alerts_table/translations';
import { SOURCE_EVENT_LOADING, SOURCE_EVENT_ERROR, SOURCE_EVENT_COUNT } from './translations';
import {
SOURCE_EVENT_LOADING,
SOURCE_EVENT_EMPTY,
SOURCE_EVENT_ERROR,
SOURCE_EVENT_COUNT,
} from './translations';
interface Props {
browserFields: BrowserFields;
@ -64,9 +69,20 @@ export const RelatedAlertsBySourceEvent = React.memo<Props>(
fieldType: fieldFromBrowserField?.type,
});
const isEmpty = count === 0;
let state: InsightAccordionState = 'loading';
if (error) {
state = 'error';
} else if (alertIds) {
state = 'success';
}
const renderContent = useCallback(() => {
if (!alertIds || !cellData?.dataProviders) {
return null;
} else if (isEmpty && state !== 'loading') {
return SOURCE_EVENT_EMPTY;
}
return (
<>
@ -80,16 +96,7 @@ export const RelatedAlertsBySourceEvent = React.memo<Props>(
</InvestigateInTimelineButton>
</>
);
}, [alertIds, cellData?.dataProviders]);
let state: InsightAccordionState = 'loading';
if (error) {
state = 'error';
} else if (count === 0) {
state = 'empty';
} else if (alertIds) {
state = 'success';
}
}, [alertIds, cellData?.dataProviders, isEmpty, state]);
return (
<InsightAccordion
@ -109,7 +116,6 @@ function getTextFromState(state: InsightAccordionState, count: number | undefine
case 'error':
return SOURCE_EVENT_ERROR;
case 'success':
case 'empty':
return SOURCE_EVENT_COUNT(count);
default:
return '';

View file

@ -31,7 +31,6 @@ export const RelatedCases = React.memo<Props>(({ eventId }) => {
const toasts = useToasts();
const [relatedCases, setRelatedCases] = useState<RelatedCaseList | undefined>(undefined);
const [areCasesLoading, setAreCasesLoading] = useState(true);
const [hasError, setHasError] = useState<boolean>(false);
const renderContent = useCallback(() => renderCaseContent(relatedCases), [relatedCases]);
@ -50,7 +49,6 @@ export const RelatedCases = React.memo<Props>(({ eventId }) => {
toasts.addWarning(CASES_ERROR_TOAST(error));
}
setRelatedCases(relatedCaseList);
setAreCasesLoading(false);
}, [eventId, cases.api, toasts]);
useEffect(() => {
@ -60,8 +58,6 @@ export const RelatedCases = React.memo<Props>(({ eventId }) => {
let state: InsightAccordionState = 'loading';
if (hasError) {
state = 'error';
} else if (!areCasesLoading && relatedCases?.length === 0) {
state = 'empty';
} else if (relatedCases) {
state = 'success';
}
@ -121,7 +117,6 @@ function getTextFromState(state: InsightAccordionState, caseCount = 0) {
case 'error':
return CASES_ERROR;
case 'success':
case 'empty':
return CASES_COUNT(caseCount);
default:
return '';

View file

@ -34,6 +34,13 @@ export const PROCESS_ANCESTRY_ERROR = i18n.translate(
}
);
export const PROCESS_ANCESTRY_EMPTY = i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights.related_alerts_by_process_ancestry_empty',
{
defaultMessage: 'There are no related alerts by process ancestry.',
}
);
export const SESSION_LOADING = i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights.related_alerts_by_source_event_loading',
{ defaultMessage: 'Loading related alerts by source event' }
@ -46,6 +53,13 @@ export const SESSION_ERROR = i18n.translate(
}
);
export const SESSION_EMPTY = i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights.related_alerts_by_session_empty',
{
defaultMessage: 'There are no related alerts by session',
}
);
export const SESSION_COUNT = (count?: number) =>
i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights.related_alerts_by_session_count',
@ -66,6 +80,13 @@ export const SOURCE_EVENT_ERROR = i18n.translate(
}
);
export const SOURCE_EVENT_EMPTY = i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights.related_alerts_by_source_event_empty',
{
defaultMessage: 'There are no related alerts by source event',
}
);
export const SOURCE_EVENT_COUNT = (count?: number) =>
i18n.translate(
'xpack.securitySolution.alertDetails.overview.insights_related_alerts_by_source_event_count',