[Security Solution][Endpoint] Use super date picker instead of date range picker (#108722)

* Use super date picker instead of date range picker

fixes elastic/security-team/issues/1571

* fix test target

Super date picker's `data-test-subj` prop gets garbled and doesn't show up in rendered DOM. In other words, the component is entirely void of a data-test-subj attribute.

* make auto refresh work!!

fixes https://github.com/elastic/security-team/issues/1571

* set max width as per mock

fixes elastic/security-team/issues/1571

* show a callout to inform users to select different date ranges

fixes elastic/security-team/issues/1571

* persist recently used date ranges on the component only

fixes elastic/security-team/issues/1571

* use commonly used ranges from default common security solution ranges

fixes elastic/security-team/issues/1571

* Better align date picker

* full width panel for date picker so content flows below it

review comments

* mock time picker settings for tests

* use eui token for bg color

review comment

* persist recently used dates

fixes elastic/security-team/issues/1571

* persist date range selection over new endpoint selection

review comments

* remove obsolete local state since update button is not visible.

review comments

* fix bg color for dark mode and relative path

* update relative path

review comments

* cleanup - the action doesn't allow for undefined start and end dates anyway

refs 28a859ab3a

* fix types after sync

* update test title

* add a test for callout when empty data

* fix lint

* show update button when dates are changed

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ashokaditya 2021-09-03 13:47:36 +02:00 committed by GitHub
parent df8ed81195
commit 5b4d265571
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 362 additions and 147 deletions

View file

@ -23,8 +23,8 @@ export const EndpointActionLogRequestSchema = {
query: schema.object({
page: schema.number({ defaultValue: 1, min: 1 }),
page_size: schema.number({ defaultValue: 10, min: 1, max: 100 }),
start_date: schema.maybe(schema.string()),
end_date: schema.maybe(schema.string()),
start_date: schema.string(),
end_date: schema.string(),
}),
params: schema.object({
agent_id: schema.string(),

View file

@ -65,8 +65,8 @@ export type ActivityLogEntry = ActivityLogAction | ActivityLogActionResponse;
export interface ActivityLog {
page: number;
pageSize: number;
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
data: ActivityLogEntry[];
}

View file

@ -126,6 +126,8 @@ export const endpointActivityLogHttpMock = httpHandlerMockFactory<EndpointActivi
body: {
page: 1,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
data: [
{
type: 'response',

View file

@ -6,6 +6,7 @@
*/
import { Action } from 'redux';
import { EuiSuperDatePickerRecentRange } from '@elastic/eui';
import {
HostResultList,
HostInfo,
@ -161,11 +162,23 @@ export interface EndpointDetailsActivityLogUpdatePaging {
disabled?: boolean;
page: number;
pageSize: number;
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
};
}
export interface UserUpdatedActivityLogRefreshOptions {
type: 'userUpdatedActivityLogRefreshOptions';
payload: {
autoRefreshOptions: { enabled: boolean; duration: number };
};
}
export interface UserUpdatedActivityLogRecentlyUsedDateRanges {
type: 'userUpdatedActivityLogRecentlyUsedDateRanges';
payload: EuiSuperDatePickerRecentRange[];
}
export interface EndpointDetailsLoad {
type: 'endpointDetailsLoad';
payload: {
@ -194,6 +207,8 @@ export type EndpointAction =
| EndpointDetailsActivityLogUpdatePaging
| EndpointDetailsActivityLogUpdateIsInvalidDateRange
| EndpointDetailsActivityLogChanged
| UserUpdatedActivityLogRefreshOptions
| UserUpdatedActivityLogRecentlyUsedDateRanges
| EndpointDetailsLoad
| ServerReturnedEndpointPolicyResponse
| ServerFailedToReturnEndpointPolicyResponse

View file

@ -24,9 +24,14 @@ export const initialEndpointPageState = (): Immutable<EndpointState> => {
disabled: false,
page: 1,
pageSize: 50,
startDate: undefined,
endDate: undefined,
startDate: 'now-1d',
endDate: 'now',
isInvalidDateRange: false,
autoRefreshOptions: {
enabled: false,
duration: DEFAULT_POLL_INTERVAL,
},
recentlyUsedDateRanges: [],
},
logData: createUninitialisedResourceState(),
},

View file

@ -48,7 +48,14 @@ describe('EndpointList store concerns', () => {
disabled: false,
page: 1,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
isInvalidDateRange: false,
autoRefreshOptions: {
enabled: false,
duration: DEFAULT_POLL_INTERVAL,
},
recentlyUsedDateRanges: [],
},
logData: { type: 'UninitialisedResourceState' },
},

View file

@ -267,6 +267,8 @@ describe('endpoint list middleware', () => {
payload: {
page,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
},
});
};
@ -311,6 +313,8 @@ describe('endpoint list middleware', () => {
expect(mockedApis.responseProvider.activityLogResponse).toHaveBeenCalledWith({
path: expect.any(String),
query: {
end_date: 'now',
start_date: 'now-1d',
page: 1,
page_size: 50,
},
@ -396,6 +400,8 @@ describe('endpoint list middleware', () => {
query: {
page: 3,
page_size: 50,
start_date: 'now-1d',
end_date: 'now',
},
});
});

View file

@ -640,12 +640,12 @@ async function endpointDetailsActivityLogChangedMiddleware({
});
try {
const { page, pageSize } = getActivityLogDataPaging(getState());
const { page, pageSize, startDate, endDate } = getActivityLogDataPaging(getState());
const route = resolvePathVariables(ENDPOINT_ACTION_LOG_ROUTE, {
agent_id: selectedAgent(getState()),
});
const activityLog = await coreStart.http.get<ActivityLog>(route, {
query: { page, page_size: pageSize },
query: { page, page_size: pageSize, start_date: startDate, end_date: endDate },
});
dispatch({
type: 'endpointDetailsActivityLogChanged',

View file

@ -24,6 +24,7 @@ import { AppAction } from '../../../../common/store/actions';
import { ImmutableReducer } from '../../../../common/store';
import { Immutable } from '../../../../../common/endpoint/types';
import { createUninitialisedResourceState, isUninitialisedResourceState } from '../../../state';
import { DEFAULT_POLL_INTERVAL } from '../../../common/constants';
type StateReducer = ImmutableReducer<EndpointState, AppAction>;
type CaseReducer<T extends AppAction> = (
@ -172,7 +173,11 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
},
},
};
} else if (action.type === 'endpointDetailsActivityLogUpdatePaging') {
} else if (
action.type === 'endpointDetailsActivityLogUpdatePaging' ||
action.type === 'endpointDetailsActivityLogUpdateIsInvalidDateRange' ||
action.type === 'userUpdatedActivityLogRefreshOptions'
) {
return {
...state,
endpointDetails: {
@ -186,7 +191,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
},
},
};
} else if (action.type === 'endpointDetailsActivityLogUpdateIsInvalidDateRange') {
} else if (action.type === 'userUpdatedActivityLogRecentlyUsedDateRanges') {
return {
...state,
endpointDetails: {
@ -195,7 +200,7 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...state.endpointDetails.activityLog,
paging: {
...state.endpointDetails.activityLog.paging,
...action.payload,
recentlyUsedDateRanges: action.payload,
},
},
},
@ -315,9 +320,16 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
logData: createUninitialisedResourceState(),
paging: {
disabled: false,
isInvalidDateRange: false,
page: 1,
pageSize: 50,
isInvalidDateRange: false,
startDate: 'now-1d',
endDate: 'now',
autoRefreshOptions: {
enabled: false,
duration: DEFAULT_POLL_INTERVAL,
},
recentlyUsedDateRanges: [],
},
};
@ -337,7 +349,16 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
activityLog: {
...activityLog,
paging: {
...activityLog.paging,
startDate: state.endpointDetails.activityLog.paging.startDate,
endDate: state.endpointDetails.activityLog.paging.endDate,
recentlyUsedDateRanges:
state.endpointDetails.activityLog.paging.recentlyUsedDateRanges,
},
},
hostDetails: {
...state.endpointDetails.hostDetails,
detailsError: undefined,
@ -355,7 +376,16 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
activityLog: {
...activityLog,
paging: {
...activityLog.paging,
startDate: state.endpointDetails.activityLog.paging.startDate,
endDate: state.endpointDetails.activityLog.paging.endDate,
recentlyUsedDateRanges:
state.endpointDetails.activityLog.paging.recentlyUsedDateRanges,
},
},
hostDetails: {
...state.endpointDetails.hostDetails,
detailsLoading: !isNotLoadingDetails,
@ -372,7 +402,16 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
activityLog: {
...activityLog,
paging: {
...activityLog.paging,
startDate: state.endpointDetails.activityLog.paging.startDate,
endDate: state.endpointDetails.activityLog.paging.endDate,
recentlyUsedDateRanges:
state.endpointDetails.activityLog.paging.recentlyUsedDateRanges,
},
},
hostDetails: {
...state.endpointDetails.hostDetails,
detailsLoading: true,
@ -391,7 +430,15 @@ export const endpointListReducer: StateReducer = (state = initialEndpointPageSta
...stateUpdates,
endpointDetails: {
...state.endpointDetails,
activityLog,
activityLog: {
...activityLog,
paging: {
...activityLog.paging,
startDate: state.endpointDetails.activityLog.paging.startDate,
endDate: state.endpointDetails.activityLog.paging.endDate,
recentlyUsedDateRanges: state.endpointDetails.activityLog.paging.recentlyUsedDateRanges,
},
},
hostDetails: {
...state.endpointDetails.hostDetails,
detailsError: undefined,

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import { EuiSuperDatePickerRecentRange } from '@elastic/eui';
import {
ActivityLog,
HostInfo,
@ -41,9 +42,14 @@ export interface EndpointState {
disabled?: boolean;
page: number;
pageSize: number;
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
isInvalidDateRange: boolean;
autoRefreshOptions: {
enabled: boolean;
duration: number;
};
recentlyUsedDateRanges: EuiSuperDatePickerRecentRange[];
};
logData: AsyncResourceState<ActivityLog>;
};

View file

@ -10,12 +10,14 @@ import { getIsInvalidDateRange } from './utils';
describe('utils', () => {
describe('getIsInvalidDateRange', () => {
it('should return FALSE when either dates are undefined', () => {
expect(getIsInvalidDateRange({})).toBe(false);
expect(getIsInvalidDateRange({ startDate: moment().subtract(1, 'd').toISOString() })).toBe(
false
);
expect(getIsInvalidDateRange({ endDate: moment().toISOString() })).toBe(false);
it('should return FALSE when startDate is before endDate', () => {
expect(getIsInvalidDateRange({ startDate: 'now-1d', endDate: 'now' })).toBe(false);
expect(
getIsInvalidDateRange({
startDate: moment().subtract(1, 'd').toISOString(),
endDate: moment().toISOString(),
})
).toBe(false);
});
it('should return TRUE when startDate is after endDate', () => {

View file

@ -5,6 +5,7 @@
* 2.0.
*/
import dateMath from '@elastic/datemath';
import moment from 'moment';
import { HostInfo, HostMetadata } from '../../../../common/endpoint/types';
@ -29,12 +30,12 @@ export const getIsInvalidDateRange = ({
startDate,
endDate,
}: {
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
}) => {
if (startDate && endDate) {
const start = moment(startDate);
const end = moment(endDate);
const start = moment(dateMath.parse(startDate));
const end = moment(dateMath.parse(endDate));
if (start.isValid() && end.isValid()) {
return start.isAfter(end);
}
return false;

View file

@ -8,95 +8,140 @@
import { useDispatch } from 'react-redux';
import React, { memo, useCallback } from 'react';
import styled from 'styled-components';
import moment from 'moment';
import { EuiFlexGroup, EuiFlexItem, EuiDatePicker, EuiDatePickerRange } from '@elastic/eui';
import dateMath from '@elastic/datemath';
import {
EuiFlexGroup,
EuiFlexItem,
EuiSuperDatePicker,
EuiSuperDatePickerRecentRange,
} from '@elastic/eui';
import * as i18 from '../../../translations';
import { useEndpointSelector } from '../../../hooks';
import { getActivityLogDataPaging } from '../../../../store/selectors';
import {
getActivityLogDataPaging,
getActivityLogRequestLoading,
} from '../../../../store/selectors';
import { DEFAULT_TIMEPICKER_QUICK_RANGES } from '../../../../../../../../common/constants';
import { useUiSetting$ } from '../../../../../../../common/lib/kibana';
interface Range {
from: string;
to: string;
display: string;
}
const DatePickerWrapper = styled.div`
width: ${(props) => props.theme.eui.fractions.single.percentage};
background: white;
max-width: 350px;
`;
const StickyFlexItem = styled(EuiFlexItem)`
max-width: 350px;
background: ${(props) => `${props.theme.eui.euiHeaderBackgroundColor}`};
position: sticky;
top: ${(props) => props.theme.eui.euiSizeM};
top: 0;
z-index: 1;
padding: ${(props) => `0 ${props.theme.eui.paddingSizes.m}`};
padding: ${(props) => `${props.theme.eui.paddingSizes.m}`};
`;
export const DateRangePicker = memo(() => {
const dispatch = useDispatch();
const { page, pageSize, startDate, endDate, isInvalidDateRange } = useEndpointSelector(
getActivityLogDataPaging
);
const {
page,
pageSize,
startDate,
endDate,
autoRefreshOptions,
recentlyUsedDateRanges,
} = useEndpointSelector(getActivityLogDataPaging);
const onChangeStartDate = useCallback(
(date) => {
const activityLogLoading = useEndpointSelector(getActivityLogRequestLoading);
const dispatchActionUpdateActivityLogPaging = useCallback(
async ({ start, end }) => {
dispatch({
type: 'endpointDetailsActivityLogUpdatePaging',
payload: {
disabled: false,
page,
pageSize,
startDate: date ? date?.toISOString() : undefined,
endDate: endDate ? endDate : undefined,
startDate: dateMath.parse(start)?.toISOString(),
endDate: dateMath.parse(end)?.toISOString(),
},
});
},
[dispatch, endDate, page, pageSize]
[dispatch, page, pageSize]
);
const onChangeEndDate = useCallback(
(date) => {
const onRefreshChange = useCallback(
(evt) => {
dispatch({
type: 'endpointDetailsActivityLogUpdatePaging',
type: 'userUpdatedActivityLogRefreshOptions',
payload: {
disabled: false,
page,
pageSize,
startDate: startDate ? startDate : undefined,
endDate: date ? date.toISOString() : undefined,
autoRefreshOptions: { enabled: !evt.isPaused, duration: evt.refreshInterval },
},
});
},
[dispatch, startDate, page, pageSize]
[dispatch]
);
const onRefresh = useCallback(() => {
dispatch({
type: 'endpointDetailsActivityLogUpdatePaging',
payload: {
disabled: false,
page,
pageSize,
startDate,
endDate,
},
});
}, [dispatch, page, pageSize, startDate, endDate]);
const onTimeChange = useCallback(
({ start: newStart, end: newEnd }) => {
const newRecentlyUsedDateRanges = [
{ start: newStart, end: newEnd },
...recentlyUsedDateRanges
.filter(
(recentlyUsedRange) =>
!(recentlyUsedRange.start === newStart && recentlyUsedRange.end === newEnd)
)
.slice(0, 9),
];
dispatch({
type: 'userUpdatedActivityLogRecentlyUsedDateRanges',
payload: newRecentlyUsedDateRanges,
});
dispatchActionUpdateActivityLogPaging({ start: newStart, end: newEnd });
},
[dispatch, recentlyUsedDateRanges, dispatchActionUpdateActivityLogPaging]
);
const [quickRanges] = useUiSetting$<Range[]>(DEFAULT_TIMEPICKER_QUICK_RANGES);
const commonlyUsedRanges = !quickRanges.length
? []
: quickRanges.map(({ from, to, display }) => ({
start: from,
end: to,
label: display,
}));
return (
<StickyFlexItem grow={false}>
<EuiFlexGroup justifyContent="flexEnd" responsive>
<DatePickerWrapper>
<EuiFlexGroup justifyContent="flexStart" responsive>
<DatePickerWrapper data-test-subj="activityLogSuperDatePicker">
<EuiFlexItem>
<EuiDatePickerRange
fullWidth={true}
data-test-subj="activityLogDateRangePicker"
startDateControl={
<EuiDatePicker
aria-label="Start date"
endDate={endDate ? moment(endDate) : undefined}
isInvalid={isInvalidDateRange}
onChange={onChangeStartDate}
placeholderText={i18.ACTIVITY_LOG.datePicker.startDate}
selected={startDate ? moment(startDate) : undefined}
showTimeSelect
startDate={startDate ? moment(startDate) : undefined}
/>
}
endDateControl={
<EuiDatePicker
aria-label="End date"
endDate={endDate ? moment(endDate) : undefined}
isInvalid={isInvalidDateRange}
onChange={onChangeEndDate}
placeholderText={i18.ACTIVITY_LOG.datePicker.endDate}
selected={endDate ? moment(endDate) : undefined}
showTimeSelect
startDate={startDate ? moment(startDate) : undefined}
/>
}
<EuiSuperDatePicker
isLoading={activityLogLoading}
commonlyUsedRanges={commonlyUsedRanges}
end={dateMath.parse(endDate)?.toISOString()}
isPaused={!autoRefreshOptions.enabled}
onTimeChange={onTimeChange}
onRefreshChange={onRefreshChange}
refreshInterval={autoRefreshOptions.duration}
onRefresh={onRefresh}
recentlyUsedRanges={recentlyUsedDateRanges as EuiSuperDatePickerRecentRange[]}
start={dateMath.parse(startDate)?.toISOString()}
/>
</EuiFlexItem>
</DatePickerWrapper>

View file

@ -9,11 +9,13 @@ import React, { memo, useCallback, useEffect, useMemo, useRef } from 'react';
import styled from 'styled-components';
import {
EuiCallOut,
EuiText,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingContent,
EuiEmptyPrompt,
EuiSpacer,
} from '@elastic/eui';
import { useDispatch } from 'react-redux';
import { LogEntry } from './components/log_entry';
@ -114,6 +116,17 @@ export const EndpointActivityLog = memo(
<>
<DateRangePicker />
<EuiFlexItem grow={true}>
{!isPagingDisabled && activityLogLoaded && !activityLogData.length && (
<>
<EuiSpacer size="m" />
<EuiCallOut
data-test-subj="activityLogNoDataCallout"
size="s"
title={i18.ACTIVITY_LOG.LogEntry.dateRangeMessage}
iconType="alert"
/>
</>
)}
{activityLogLoaded &&
activityLogData.map((logEntry) => (
<LogEntry key={`${logEntry.item.id}`} logEntry={logEntry} />

View file

@ -22,6 +22,8 @@ export const dummyEndpointActivityLog = (
data: {
page: 1,
pageSize: 50,
startDate: moment().subtract(5, 'day').fromNow().toString(),
endDate: moment().toString(),
data: [
{
type: 'action',

View file

@ -10,6 +10,8 @@ import * as reactTestingLibrary from '@testing-library/react';
import { EndpointList } from './index';
import '../../../../common/mock/match_media';
import { createUseUiSetting$Mock } from '../../../../../public/common/lib/kibana/kibana_react.mock';
import {
mockEndpointDetailsApiResult,
mockEndpointResultList,
@ -28,7 +30,7 @@ import { EndpointDocGenerator } from '../../../../../common/endpoint/generate_da
import { POLICY_STATUS_TO_HEALTH_COLOR, POLICY_STATUS_TO_TEXT } from './host_constants';
import { mockPolicyResultList } from '../../policy/store/test_mock_utils';
import { getEndpointDetailsPath } from '../../../common/routing';
import { KibanaServices, useKibana, useToasts } from '../../../../common/lib/kibana';
import { KibanaServices, useKibana, useToasts, useUiSetting$ } from '../../../../common/lib/kibana';
import { hostIsolationHttpMocks } from '../../../../common/lib/endpoint_isolation/mocks';
import {
createFailedResourceState,
@ -40,7 +42,11 @@ import {
import { getCurrentIsolationRequestState } from '../store/selectors';
import { licenseService } from '../../../../common/hooks/use_license';
import { FleetActionGenerator } from '../../../../../common/endpoint/data_generators/fleet_action_generator';
import { APP_PATH, MANAGEMENT_PATH } from '../../../../../common/constants';
import {
APP_PATH,
MANAGEMENT_PATH,
DEFAULT_TIMEPICKER_QUICK_RANGES,
} from '../../../../../common/constants';
import { TransformStats, TRANSFORM_STATE } from '../types';
import { metadataTransformPrefix } from '../../../../../common/endpoint/constants';
@ -63,6 +69,59 @@ jest.mock('../../policy/store/services/ingest', () => {
sendGetEndpointSecurityPackage: () => Promise.resolve({}),
};
});
const mockUseUiSetting$ = useUiSetting$ as jest.Mock;
const timepickerRanges = [
{
from: 'now/d',
to: 'now/d',
display: 'Today',
},
{
from: 'now/w',
to: 'now/w',
display: 'This week',
},
{
from: 'now-15m',
to: 'now',
display: 'Last 15 minutes',
},
{
from: 'now-30m',
to: 'now',
display: 'Last 30 minutes',
},
{
from: 'now-1h',
to: 'now',
display: 'Last 1 hour',
},
{
from: 'now-24h',
to: 'now',
display: 'Last 24 hours',
},
{
from: 'now-7d',
to: 'now',
display: 'Last 7 days',
},
{
from: 'now-30d',
to: 'now',
display: 'Last 30 days',
},
{
from: 'now-90d',
to: 'now',
display: 'Last 90 days',
},
{
from: 'now-1y',
to: 'now',
display: 'Last 1 year',
},
];
jest.mock('../../../../common/lib/kibana');
jest.mock('../../../../common/hooks/use_license');
@ -759,6 +818,14 @@ describe('when on the endpoint list page', () => {
disconnect: jest.fn(),
}));
mockUseUiSetting$.mockImplementation((key, defaultValue) => {
const useUiSetting$Mock = createUseUiSetting$Mock();
return key === DEFAULT_TIMEPICKER_QUICK_RANGES
? [timepickerRanges, jest.fn()]
: useUiSetting$Mock(key, defaultValue);
});
const fleetActionGenerator = new FleetActionGenerator('seed');
const responseData = fleetActionGenerator.generateResponse({
agent_id: agentId,
@ -766,9 +833,12 @@ describe('when on the endpoint list page', () => {
const actionData = fleetActionGenerator.generate({
agents: [agentId],
});
getMockData = () => ({
page: 1,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
data: [
{
type: 'response',
@ -838,7 +908,7 @@ describe('when on the endpoint list page', () => {
expect(emptyState).not.toBe(null);
});
it('should display empty state when no log data', async () => {
it('should not display empty state when no log data', async () => {
const activityLogTab = await renderResult.findByTestId('activity_log');
reactTestingLibrary.act(() => {
reactTestingLibrary.fireEvent.click(activityLogTab);
@ -848,33 +918,17 @@ describe('when on the endpoint list page', () => {
dispatchEndpointDetailsActivityLogChanged('success', {
page: 1,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
data: [],
});
});
const emptyState = await renderResult.queryByTestId('activityLogEmpty');
expect(emptyState).not.toBe(null);
});
it('should not display empty state with no log data while date range filter is active', async () => {
const activityLogTab = await renderResult.findByTestId('activity_log');
reactTestingLibrary.act(() => {
reactTestingLibrary.fireEvent.click(activityLogTab);
});
await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged');
reactTestingLibrary.act(() => {
dispatchEndpointDetailsActivityLogChanged('success', {
page: 1,
pageSize: 50,
startDate: new Date().toISOString(),
data: [],
});
});
const emptyState = await renderResult.queryByTestId('activityLogEmpty');
const dateRangePicker = await renderResult.queryByTestId('activityLogDateRangePicker');
expect(emptyState).toBe(null);
expect(dateRangePicker).not.toBe(null);
const superDatePicker = await renderResult.queryByTestId('activityLogSuperDatePicker');
expect(superDatePicker).not.toBe(null);
});
it('should display activity log when tab is loaded using the URL', async () => {
@ -895,6 +949,32 @@ describe('when on the endpoint list page', () => {
const logEntries = await renderResult.queryAllByTestId('timelineEntry');
expect(logEntries.length).toEqual(2);
});
it('should display a callout message if no log data', async () => {
const userChangedUrlChecker = middlewareSpy.waitForAction('userChangedUrl');
reactTestingLibrary.act(() => {
history.push(
`${MANAGEMENT_PATH}/endpoints?page_index=0&page_size=10&selected_endpoint=1&show=activity_log`
);
});
const changedUrlAction = await userChangedUrlChecker;
expect(changedUrlAction.payload.search).toEqual(
'?page_index=0&page_size=10&selected_endpoint=1&show=activity_log'
);
await middlewareSpy.waitForAction('endpointDetailsActivityLogChanged');
reactTestingLibrary.act(() => {
dispatchEndpointDetailsActivityLogChanged('success', {
page: 1,
pageSize: 50,
startDate: 'now-1d',
endDate: 'now',
data: [],
});
});
const activityLogCallout = await renderResult.findByTestId('activityLogNoDataCallout');
expect(activityLogCallout).not.toBeNull();
});
});
describe('when showing host Policy Response panel', () => {

View file

@ -15,20 +15,6 @@ export const ACTIVITY_LOG = {
tabTitle: i18n.translate('xpack.securitySolution.endpointDetails.activityLog', {
defaultMessage: 'Activity Log',
}),
datePicker: {
startDate: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.datePicker.startDate',
{
defaultMessage: 'Pick a start date',
}
),
endDate: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.datePicker.endDate',
{
defaultMessage: 'Pick an end date',
}
),
},
LogEntry: {
endOfLog: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.logEntry.action.endOfLog',
@ -36,6 +22,13 @@ export const ACTIVITY_LOG = {
defaultMessage: 'Nothing more to show',
}
),
dateRangeMessage: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.logEntry.dateRangeMessage.title',
{
defaultMessage:
'Nothing to show for selected date range, please select another and try again.',
}
),
emptyState: {
title: i18n.translate(
'xpack.securitySolution.endpointDetails.activityLog.logEntry.emptyState.title',

View file

@ -48,19 +48,13 @@ describe('Action Log API', () => {
}).not.toThrow();
});
it('should work without query params', () => {
it('should not work when no params while requesting with query params', () => {
expect(() => {
EndpointActionLogRequestSchema.query.validate({});
}).not.toThrow();
}).toThrow();
});
it('should work with query params', () => {
expect(() => {
EndpointActionLogRequestSchema.query.validate({ page: 10, page_size: 100 });
}).not.toThrow();
});
it('should work with all query params', () => {
it('should work with all required query params', () => {
expect(() => {
EndpointActionLogRequestSchema.query.validate({
page: 10,
@ -71,24 +65,24 @@ describe('Action Log API', () => {
}).not.toThrow();
});
it('should work with just startDate', () => {
it('should not work without endDate', () => {
expect(() => {
EndpointActionLogRequestSchema.query.validate({
page: 1,
page_size: 100,
start_date: new Date(new Date().setDate(new Date().getDate() - 1)).toISOString(), // yesterday
});
}).not.toThrow();
}).toThrow();
});
it('should work with just endDate', () => {
it('should not work without startDate', () => {
expect(() => {
EndpointActionLogRequestSchema.query.validate({
page: 1,
page_size: 100,
end_date: new Date().toISOString(), // today
});
}).not.toThrow();
}).toThrow();
});
it('should not work without allowed page and page_size params', () => {

View file

@ -31,8 +31,8 @@ export const getAuditLogResponse = async ({
elasticAgentId: string;
page: number;
pageSize: number;
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
context: SecuritySolutionRequestHandlerContext;
logger: Logger;
}): Promise<ActivityLog> => {
@ -71,8 +71,8 @@ const getActivityLog = async ({
elasticAgentId: string;
size: number;
from: number;
startDate?: string;
endDate?: string;
startDate: string;
endDate: string;
logger: Logger;
}) => {
const options = {
@ -84,13 +84,10 @@ const getActivityLog = async ({
let actionsResult;
let responsesResult;
const dateFilters = [];
if (startDate) {
dateFilters.push({ range: { '@timestamp': { gte: startDate } } });
}
if (endDate) {
dateFilters.push({ range: { '@timestamp': { lte: endDate } } });
}
const dateFilters = [
{ range: { '@timestamp': { gte: startDate } } },
{ range: { '@timestamp': { lte: endDate } } },
];
try {
// fetch actions with matching agent_id