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][Detections] - Auto refresh all rules/monitoring tables #82062

Merged
merged 11 commits into from
Nov 6, 2020
6 changes: 6 additions & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export const FILTERS_GLOBAL_HEIGHT = 109; // px
export const FULL_SCREEN_TOGGLED_CLASS_NAME = 'fullScreenToggled';
export const NO_ALERT_INDEX = 'no-alert-index-049FC71A-4C2C-446F-9901-37XMC5024C51';
export const ENDPOINT_METADATA_INDEX = 'metrics-endpoint.metadata-*';
export const DEFAULT_RULE_REFRESH_INTERVAL_ON = true;
export const DEFAULT_RULE_REFRESH_INTERVAL_VALUE = 60000; // ms
export const DEFAULT_RULE_REFRESH_IDLE_VALUE = 2700000; // ms

export enum SecurityPageName {
detections = 'detections',
Expand Down Expand Up @@ -74,6 +77,9 @@ export const DEFAULT_INDEX_PATTERN = [
/** This Kibana Advanced Setting enables the `Security news` feed widget */
export const ENABLE_NEWS_FEED_SETTING = 'securitySolution:enableNewsFeed';

/** This Kibana Advanced Setting sets the auto refresh interval for the detections all rules table */
export const DEFAULT_RULES_TABLE_REFRESH_SETTING = 'securitySolution:rulesTableRefresh';

/** This Kibana Advanced Setting specifies the URL of the News feed widget */
export const NEWS_FEED_URL_SETTING = 'securitySolution:newsFeedUrl';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
RULE_SWITCH,
SECOND_RULE,
SEVENTH_RULE,
RULE_AUTO_REFRESH_IDLE_MODAL,
} from '../screens/alerts_detection_rules';

import {
Expand All @@ -19,12 +20,17 @@ import {
} from '../tasks/alerts';
import {
activateRule,
checkAllRulesIdleModal,
checkAutoRefresh,
dismissAllRulesIdleModal,
resetAllRulesIdleModalTimeout,
sortByActivatedRules,
waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded,
waitForRuleToBeActivated,
} from '../tasks/alerts_detection_rules';
import { esArchiverLoad, esArchiverUnload } from '../tasks/es_archiver';
import { loginAndWaitForPageWithoutDateRange } from '../tasks/login';
import { DEFAULT_RULE_REFRESH_INTERVAL_VALUE } from '../../common/constants';

import { DETECTIONS_URL } from '../urls/navigation';

Expand All @@ -35,6 +41,7 @@ describe('Alerts detection rules', () => {

after(() => {
esArchiverUnload('prebuilt_rules_loaded');
cy.clock().invoke('restore');
});

it('Sorts by activated rules', () => {
Expand Down Expand Up @@ -75,4 +82,34 @@ describe('Alerts detection rules', () => {
});
});
});

it('Auto refreshes rules', () => {
cy.clock(Date.now());

loginAndWaitForPageWithoutDateRange(DETECTIONS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
goToManageAlertsDetectionRules();
waitForLoadElasticPrebuiltDetectionRulesTableToBeLoaded();

// mock 1 minute passing to make sure refresh
// is conducted
checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible');

// mock 45 minutes passing to check that idle modal shows
// and refreshing is paused
checkAllRulesIdleModal('be.visible');
checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'not.be.visible');

// clicking on modal to continue, should resume refreshing
dismissAllRulesIdleModal();
checkAutoRefresh(DEFAULT_RULE_REFRESH_INTERVAL_VALUE, 'be.visible');

// if mouse movement detected, idle modal should not
// show after 45 min
resetAllRulesIdleModalTimeout();
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.exist');

cy.clock().invoke('restore');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,15 @@ export const CREATE_NEW_RULE_BTN = '[data-test-subj="create-new-rule"]';

export const COLLAPSED_ACTION_BTN = '[data-test-subj="euiCollapsedItemActionsButton"]';

export const CUSTOM_RULES_BTN = '[data-test-subj="show-custom-rules-filter-button"]';
export const CUSTOM_RULES_BTN = '[data-test-subj="showCustomRulesFilterButton"]';

export const DELETE_RULE_ACTION_BTN = '[data-test-subj="deleteRuleAction"]';

export const EDIT_RULE_ACTION_BTN = '[data-test-subj="editRuleAction"]';

export const DELETE_RULE_BULK_BTN = '[data-test-subj="deleteRuleBulk"]';

export const ELASTIC_RULES_BTN = '[data-test-subj="show-elastic-rules-filter-button"]';
export const ELASTIC_RULES_BTN = '[data-test-subj="showElasticRulesFilterButton"]';

export const EXPORT_ACTION_BTN = '[data-test-subj="exportRuleAction"]';

Expand All @@ -31,7 +31,7 @@ export const LOAD_PREBUILT_RULES_BTN = '[data-test-subj="load-prebuilt-rules"]';
export const LOADING_INITIAL_PREBUILT_RULES_TABLE =
'[data-test-subj="initialLoadingPanelAllRulesTable"]';

export const LOADING_SPINNER = '[data-test-subj="loading-spinner"]';
export const ASYNC_LOADING_PROGRESS = '[data-test-subj="loadingRulesInfoProgress"]';

export const NEXT_BTN = '[data-test-subj="pagination-button-next"]';

Expand Down Expand Up @@ -64,3 +64,7 @@ export const SHOWING_RULES_TEXT = '[data-test-subj="showingRules"]';
export const SORT_RULES_BTN = '[data-test-subj="tableHeaderSortButton"]';

export const THREE_HUNDRED_ROWS = '[data-test-subj="tablePagination-300-rows"]';

export const RULE_AUTO_REFRESH_IDLE_MODAL = '[data-test-subj="allRulesIdleModal"]';

export const RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE = '[data-test-subj="allRulesIdleModal"] button';
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import {
DELETE_RULE_BULK_BTN,
LOAD_PREBUILT_RULES_BTN,
LOADING_INITIAL_PREBUILT_RULES_TABLE,
LOADING_SPINNER,
PAGINATION_POPOVER_BTN,
RELOAD_PREBUILT_RULES_BTN,
RULE_CHECKBOX,
Expand All @@ -26,6 +25,9 @@ import {
EXPORT_ACTION_BTN,
EDIT_RULE_ACTION_BTN,
NEXT_BTN,
ASYNC_LOADING_PROGRESS,
RULE_AUTO_REFRESH_IDLE_MODAL,
RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE,
} from '../screens/alerts_detection_rules';
import { ALL_ACTIONS, DELETE_RULE } from '../screens/rule_details';

Expand Down Expand Up @@ -66,8 +68,8 @@ export const exportFirstRule = () => {

export const filterByCustomRules = () => {
cy.get(CUSTOM_RULES_BTN).click({ force: true });
cy.get(LOADING_SPINNER).should('exist');
cy.get(LOADING_SPINNER).should('not.exist');
cy.get(ASYNC_LOADING_PROGRESS).should('exist');
cy.get(ASYNC_LOADING_PROGRESS).should('not.exist');
};

export const goToCreateNewRule = () => {
Expand Down Expand Up @@ -119,6 +121,32 @@ export const waitForRuleToBeActivated = () => {
};

export const waitForRulesToBeLoaded = () => {
cy.get(LOADING_SPINNER).should('exist');
cy.get(LOADING_SPINNER).should('not.exist');
cy.get(ASYNC_LOADING_PROGRESS).should('exist');
cy.get(ASYNC_LOADING_PROGRESS).should('not.exist');
};

// when using, ensure you've called cy.clock prior in test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment still valid? Didn't see a cy.clock anywhere?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Deleted in followup #83023

export const checkAutoRefresh = (ms: number, condition: string) => {
cy.get(ASYNC_LOADING_PROGRESS).should('not.be.visible');
cy.tick(ms);
cy.get(ASYNC_LOADING_PROGRESS).should(condition);
};

export const dismissAllRulesIdleModal = () => {
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL_CONTINUE)
.eq(1)
.should('exist')
.click({ force: true, multiple: true });
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should('not.be.visible');
};

export const checkAllRulesIdleModal = (condition: string) => {
cy.tick(2700000);
cy.get(RULE_AUTO_REFRESH_IDLE_MODAL).should(condition);
};

export const resetAllRulesIdleModalTimeout = () => {
cy.tick(2000000);
cy.window().trigger('mousemove', { force: true });
cy.tick(700000);
};

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 @@ -45,6 +45,7 @@ export interface HeaderSectionProps extends HeaderProps {
title: string | React.ReactNode;
titleSize?: EuiTitleSize;
tooltip?: string;
growLeftSplit?: boolean;
}

const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
Expand All @@ -57,10 +58,11 @@ const HeaderSectionComponent: React.FC<HeaderSectionProps> = ({
title,
titleSize = 'm',
tooltip,
growLeftSplit = true,
}) => (
<Header data-test-subj="header-section" border={border} height={height}>
<EuiFlexGroup alignItems="center">
<EuiFlexItem>
<EuiFlexItem grow={growLeftSplit}>
<EuiFlexGroup alignItems="center" responsive={false}>
<EuiFlexItem>
<EuiTitle size={titleSize}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The files under components/last_updated were just moved from under timeline. Not new code.

* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/
import React from 'react';
import { mount } from 'enzyme';
import { I18nProvider } from '@kbn/i18n/react';

import { LastUpdatedAt } from './';

describe('LastUpdatedAt', () => {
beforeEach(() => {
Date.now = jest.fn().mockReturnValue(1603995369774);
});

test('it renders correct relative time', () => {
const wrapper = mount(
<I18nProvider>
<LastUpdatedAt updatedAt={1603995240115} />
</I18nProvider>
);

expect(wrapper.text()).toEqual(' Updated 2 minutes ago');
});

test('it only renders icon if "compact" is true', () => {
const wrapper = mount(
<I18nProvider>
<LastUpdatedAt compact updatedAt={1603995240115} />
</I18nProvider>
);

expect(wrapper.text()).toEqual('');
expect(wrapper.find('[data-test-subj="last-updated-at-clock-icon"]').exists()).toBeTruthy();
});

test('it renders updating text if "showUpdating" is true', () => {
const wrapper = mount(
<I18nProvider>
<LastUpdatedAt updatedAt={1603995240115} showUpdating />
</I18nProvider>
);

expect(wrapper.text()).toEqual(' Updating...');
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiIcon, EuiText, EuiToolTip } from '@elastic/eui';
import { FormattedRelative } from '@kbn/i18n/react';
import React, { useEffect, useMemo, useState } from 'react';

import * as i18n from './translations';

interface LastUpdatedAtProps {
compact?: boolean;
updatedAt: number;
showUpdating?: boolean;
}

export const Updated = React.memo<{ date: number; prefix: string; updatedAt: number }>(
({ date, prefix, updatedAt }) => (
<>
{prefix}
{
<FormattedRelative
data-test-subj="last-updated-at-date"
key={`formatedRelative-${date}`}
value={new Date(updatedAt)}
/>
}
</>
)
);

Updated.displayName = 'Updated';

const prefix = ` ${i18n.UPDATED} `;

export const LastUpdatedAt = React.memo<LastUpdatedAtProps>(
({ compact = false, updatedAt, showUpdating = false }) => {
const [date, setDate] = useState(Date.now());

function tick() {
setDate(Date.now());
}

useEffect(() => {
const timerID = setInterval(() => tick(), 10000);
return () => {
clearInterval(timerID);
};
}, []);

const updateText = useMemo(() => {
if (showUpdating) {
return <span> {i18n.UPDATING}</span>;
}

if (!compact) {
return <Updated date={date} prefix={prefix} updatedAt={updatedAt} />;
}

return null;
}, [compact, date, showUpdating, updatedAt]);

return (
<EuiToolTip
data-test-subj="timeline-stream-tool-tip"
content={
<>
<Updated date={date} prefix={prefix} updatedAt={updatedAt} />
</>
}
>
<EuiText size="s">
<EuiIcon data-test-subj="last-updated-at-clock-icon" type="clock" />
{updateText}
</EuiText>
</EuiToolTip>
);
}
);

LastUpdatedAt.displayName = 'LastUpdatedAt';
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { i18n } from '@kbn/i18n';

export const UPDATING = i18n.translate('xpack.securitySolution.lastUpdated.updating', {
defaultMessage: 'Updating...',
});

export const UPDATED = i18n.translate('xpack.securitySolution.lastUpdated.updated', {
defaultMessage: 'Updated',
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ import {
DEFAULT_REFRESH_RATE_INTERVAL,
DEFAULT_TIME_RANGE,
DEFAULT_TO,
DEFAULT_RULES_TABLE_REFRESH_SETTING,
DEFAULT_RULE_REFRESH_INTERVAL_ON,
DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
DEFAULT_RULE_REFRESH_IDLE_VALUE,
} from '../../../../common/constants';
import { StartServices } from '../../../types';
import { createSecuritySolutionStorageMock } from '../../mock/mock_local_storage';
Expand All @@ -48,6 +52,11 @@ const mockUiSettings: Record<string, unknown> = {
[DEFAULT_DATE_FORMAT_TZ]: 'UTC',
[DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS',
[DEFAULT_DARK_MODE]: false,
[DEFAULT_RULES_TABLE_REFRESH_SETTING]: {
on: DEFAULT_RULE_REFRESH_INTERVAL_ON,
value: DEFAULT_RULE_REFRESH_INTERVAL_VALUE,
idleTimeout: DEFAULT_RULE_REFRESH_IDLE_VALUE,
},
};

export const createUseUiSettingMock = () => (key: string, defaultValue?: unknown): unknown => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ export const getColumns = ({
getEmptyTagValue()
) : (
<LocalizedDateTooltip fieldName={i18n.COLUMN_LAST_UPDATE} date={new Date(value)}>
<FormattedRelative value={value} />
<FormattedDate value={value} fieldName={'last rule update date'} />
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed this here because all the relative times (updated at, last run, last update, etc) felt overwhelming. @marrasherrier what do you think?

</LocalizedDateTooltip>
);
},
Expand Down
Loading