Skip to content

Commit

Permalink
[Security Solution] Timeline UI refactor revision - Design feedback (#…
Browse files Browse the repository at this point in the history
…173015)

## Summary

This PR implements Design feedback for the Timeline UI refactoring:

1. Adds ⨁ back in timeline bottom bar.

![image](https://github.com/elastic/kibana/assets/7485038/1572527a-471b-4edb-826a-29610eae0a4a)
2. Add `Unsaved` badge for better visibility

![image](https://github.com/elastic/kibana/assets/7485038/b1baf745-fcda-4346-a104-a78b88853ce4)
===>
![image](https://github.com/elastic/kibana/assets/7485038/9d279276-cc86-44d4-903c-53ebe60e55fb)
3. Adds a new tour step for `Add to Favorites` Icon.


https://github.com/elastic/kibana/assets/7485038/2d4f3e2e-7868-4fbb-8a73-c9b427d86e04









### Checklist

Delete any items that are not applicable to this PR.

- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] [Flaky Test
Runner](https://ci-stats.kibana.dev/trigger_flaky_test_runner/1) was
used on any tests changed
  • Loading branch information
logeekal authored Dec 13, 2023
1 parent 780ca37 commit 1ba247e
Show file tree
Hide file tree
Showing 20 changed files with 222 additions and 88 deletions.
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}
<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}
<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}
</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

0 comments on commit 1ba247e

Please sign in to comment.