Skip to content
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

[Security Solution] Timeline UI refactor revision - Design feedback #173015

Merged
merged 17 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import dateMath from '@kbn/datemath';
import type {
EuiSuperDatePickerProps,
EuiSuperDatePickerRecentRange,
EuiSuperUpdateButtonProps,
OnRefreshChangeProps,
OnRefreshProps,
OnTimeChangeProps,
Expand Down Expand Up @@ -42,6 +43,10 @@ import {
} from './selectors';
import type { Inputs } from '../../store/inputs/model';

const refreshButtonProps: EuiSuperUpdateButtonProps = {
fill: false,
};

const MAX_RECENTLY_USED_RANGES = 9;

interface Range {
Expand Down Expand Up @@ -219,6 +224,7 @@ export const SuperDatePickerComponent = React.memo<SuperDatePickerProps>(
isDisabled={disabled}
width={width}
compressed={compressed}
updateButtonProps={refreshButtonProps}
/>
);
},
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import React from 'react';
import styled from 'styled-components';
import { useKibana } from '../../../../common/lib/kibana/kibana_react';
import { APP_ID } from '../../../../../common';
import type { TimelineTabs } from '../../../../../common/types';
Expand All @@ -25,6 +26,12 @@ interface TimelineActionMenuProps {
activeTab: TimelineTabs;
}

const VerticalDivider = styled.span`
width: 0px;
height: 20px;
border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade};
`;

const TimelineActionMenuComponent = ({
mode = 'normal',
timelineId,
Expand All @@ -49,11 +56,6 @@ const TimelineActionMenuComponent = ({
<EuiFlexItem data-test-subj="open-timeline-action">
<OpenTimelineAction />
</EuiFlexItem>
{userCasesPermissions.create && userCasesPermissions.read ? (
<EuiFlexItem>
<AddToCaseButton timelineId={timelineId} />
</EuiFlexItem>
) : null}
<EuiFlexItem data-test-subj="inspect-timeline-action">
<InspectButton
compact={mode === 'compact'}
Expand All @@ -63,6 +65,16 @@ const TimelineActionMenuComponent = ({
title=""
/>
</EuiFlexItem>
{userCasesPermissions.create && userCasesPermissions.read ? (
<>
<EuiFlexItem>
<VerticalDivider />
</EuiFlexItem>
<EuiFlexItem>
<AddToCaseButton timelineId={timelineId} />
</EuiFlexItem>
</>
) : null}
PhilippeOberti marked this conversation as resolved.
Show resolved Hide resolved
<EuiFlexItem data-test-subj="save-timeline-action">
<SaveTimelineButton timelineId={timelineId} />
</EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,11 @@ jest.mock('../../../containers/all', () => {
});

jest.mock('../../timeline/properties/new_template_timeline', () => ({
NewTemplateTimeline: jest.fn(() => <div>{'Create new timeline template'}</div>),
NewTemplateTimeline: jest.fn(() => <div>{'Create new Timeline template'}</div>),
}));

jest.mock('../../timeline/properties/helpers', () => ({
NewTimeline: jest.fn().mockReturnValue(<div>{'Create new timeline'}</div>),
NewTimeline: jest.fn().mockReturnValue(<div>{'Create new Timeline'}</div>),
}));

jest.mock('../../../../common/containers/source', () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ const AddTimelineButtonComponent: React.FC<AddTimelineButtonComponentProps> = ({
() => (
<EuiButtonIcon
className={ADD_TIMELINE_BUTTON_CLASS_NAME}
data-test-subj="settings-plus-in-circle"
data-test-subj="timeline-create-open-control"
iconType="plusInCircle"
iconSize="m"
color="primary"
size="m"
onClick={onButtonClick}
aria-label={i18n.ADD_TIMELINE}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import { TimelineActionMenu } from '../action_menu';
import { AddToFavoritesButton } from '../../timeline/properties/helpers';
import { TimelineStatusInfo } from './timeline_status_info';
import { timelineDefaults } from '../../../store/timeline/defaults';
import { AddTimelineButton } from '../add_timeline_button';

interface FlyoutHeaderPanelProps {
timelineId: string;
Expand Down Expand Up @@ -141,6 +142,14 @@ const FlyoutHeaderPanelComponent: React.FC<FlyoutHeaderPanelProps> = ({ timeline
>
<EuiFlexItem grow={false}>
<EuiFlexGroup gutterSize="xs" alignItems="center" responsive={false}>
{!show ? (
<EuiFlexItem grow={false}>
<AddTimelineButton timelineId={timelineId} />
</EuiFlexItem>
) : null}
PhilippeOberti marked this conversation as resolved.
Show resolved Hide resolved
<EuiFlexItem grow={false}>
<AddToFavoritesButton timelineId={timelineId} compact />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ActiveTimelinesContainer grow={false}>
<ActiveTimelines
Expand All @@ -154,9 +163,6 @@ const FlyoutHeaderPanelComponent: React.FC<FlyoutHeaderPanelProps> = ({ timeline
<EuiFlexItem grow={false}>
<TimelineStatusInfo status={timelineStatus} updated={updated} changed={changed} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<AddToFavoritesButton timelineId={timelineId} compact />
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
{show && (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,6 @@ describe('TestComponent', () => {

it('should render the status correctly when timeline has unsaved changes', () => {
render(<TestComponent status={TimelineStatus.active} changed={true} updated={Date.now()} />);
expect(screen.getByText('Has unsaved changes')).toBeVisible();
});

it('should render the status correctly when timeline is saved', () => {
const updatedTime = Date.now();
render(<TestComponent status={TimelineStatus.active} updated={updatedTime} />);
expect(screen.getByText('Saved')).toBeVisible();
});

it('should render the status correctly when timeline is saved some time ago', () => {
const updatedTime = Date.now() - 10000;
render(<TestComponent status={TimelineStatus.active} updated={updatedTime} />);
expect(screen.getByTestId('timeline-status')).toHaveTextContent(/Saved10 seconds ago/);
expect(screen.getByText('Unsaved changes')).toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@
*/

import React from 'react';
import { EuiTextColor, EuiText } from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n-react';
import { EuiText, EuiBadge } from '@elastic/eui';

import styled from 'styled-components';
import { TimelineStatus } from '../../../../../common/api/timeline';
Expand All @@ -29,21 +28,13 @@ export const TimelineStatusInfo = React.memo<TimelineStatusInfoProps>(

let statusContent: React.ReactNode = null;
if (isUnsaved || !updated) {
statusContent = <EuiTextColor color="warning">{i18n.UNSAVED}</EuiTextColor>;
statusContent = <EuiBadge color="warning">{i18n.UNSAVED}</EuiBadge>;
} else if (changed) {
statusContent = <EuiTextColor color="warning">{i18n.UNSAVED_CHANGES}</EuiTextColor>;
} else {
statusContent = (
<>
{i18n.SAVED}
<FormattedRelative
data-test-subj="timeline-status"
key="timeline-status-autosaved"
value={new Date(updated)}
/>
</>
);
statusContent = <EuiBadge color="warning">{i18n.UNSAVED_CHANGES}</EuiBadge>;
}

if (!statusContent) return null;

return (
<NoWrapText size="xs" data-test-subj="timeline-status">
{statusContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export const SAVED = i18n.translate('xpack.securitySolution.timeline.properties.
export const UNSAVED_CHANGES = i18n.translate(
'xpack.securitySolution.timeline.properties.hasChangesLabel',
{
defaultMessage: 'Has unsaved changes',
defaultMessage: 'Unsaved changes',
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { defaultHeaders } from './body/column_headers/default_headers';
import type { CellValueElementProps } from './cell_rendering';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { FlyoutHeaderPanel } from '../flyout/header';
import type { TimelineId, RowRenderer } from '../../../../common/types/timeline';
import type { TimelineId, RowRenderer, TimelineTabs } from '../../../../common/types/timeline';
import { TimelineType } from '../../../../common/api/timeline';
import { useDeepEqualSelector, useShallowEqualSelector } from '../../../common/hooks/use_selector';
import { activeTimeline } from '../../containers/active_timeline_context';
Expand Down Expand Up @@ -82,6 +82,7 @@ const StatefulTimelineComponent: React.FC<Props> = ({
initialized,
show: isOpen,
isLoading,
activeTab,
} = useDeepEqualSelector((state) =>
pick(
[
Expand All @@ -95,6 +96,7 @@ const StatefulTimelineComponent: React.FC<Props> = ({
'initialized',
'show',
'isLoading',
'activeTab',
],
getTimeline(state, timelineId) ?? timelineDefaults
)
Expand Down Expand Up @@ -195,6 +197,18 @@ const StatefulTimelineComponent: React.FC<Props> = ({

const showTimelineTour = isOpen && !isLoading && canEditTimeline;

const handleSwitchToTab = useCallback(
(tab: TimelineTabs) => {
dispatch(
timelineActions.setActiveTabTimeline({
id: timelineId,
activeTab: tab,
})
);
},
[timelineId, dispatch]
);

return (
<TimelineContext.Provider value={timelineContext}>
<TimelineContainer
Expand Down Expand Up @@ -230,7 +244,9 @@ const StatefulTimelineComponent: React.FC<Props> = ({
/>
</div>
</TimelineContainer>
{showTimelineTour ? <TimelineTour /> : null}
{showTimelineTour ? (
<TimelineTour activeTab={activeTab} switchToTab={handleSwitchToTab} />
) : null}
PhilippeOberti marked this conversation as resolved.
Show resolved Hide resolved
</TimelineContext.Provider>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { useShallowEqualSelector } from '../../../../common/hooks/use_selector';
import * as i18n from './translations';
import { useCreateTimelineButton } from './use_create_timeline';
import { timelineDefaults } from '../../../store/timeline/defaults';
import { TIMELINE_TOUR_CONFIG_ANCHORS } from '../tour/step_config';

const NotesCountBadge = styled(EuiBadge)`
margin-left: 5px;
Expand Down Expand Up @@ -56,7 +57,9 @@ const AddToFavoritesButtonComponent: React.FC<AddToFavoritesButtonProps> = ({

return compact ? (
<EuiButtonIcon
id={TIMELINE_TOUR_CONFIG_ANCHORS.ADD_TO_FAVORITES}
iconType={isFavorite ? 'starFilled' : 'starEmpty'}
iconSize="m"
isSelected={isFavorite}
onClick={handleClick}
data-test-subj={`timeline-favorite-${isFavorite ? 'filled' : 'empty'}-star`}
Expand All @@ -66,6 +69,7 @@ const AddToFavoritesButtonComponent: React.FC<AddToFavoritesButtonProps> = ({
/>
) : (
<EuiButton
id={TIMELINE_TOUR_CONFIG_ANCHORS.ADD_TO_FAVORITES}
isSelected={isFavorite}
fill={isFavorite}
iconType={isFavorite ? 'starFilled' : 'starEmpty'}
Expand Down
Loading
Loading