+
@@ -62,14 +61,14 @@
-
+
diff --git a/src/plugins/timelion/public/partials/save_sheet.html b/src/plugins/timelion/public/partials/save_sheet.html
index a0e0727f3ec82..7773a9d25df71 100644
--- a/src/plugins/timelion/public/partials/save_sheet.html
+++ b/src/plugins/timelion/public/partials/save_sheet.html
@@ -19,7 +19,7 @@
+
+
- some text
-
- some node
-
+ primary
+
+
+ secondary
+
+
+
-
-
-
- primary
-
-
-
-
- secondary
-
-
-
-
- danger
-
-
-
+ danger
+
-
+
-
+
`;
@@ -130,66 +124,61 @@ exports[`item_details_card ItemDetailsCard should render correctly with no actio
paddingSize="none"
>
-
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
+ some text
+
+ some node
+
+
+
+
-
- some text
-
- some node
-
-
-
-
-
-
-
+ gutterSize="s"
+ justifyContent="flexEnd"
+ />
+
-
+
`;
diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx
index e9d1825658bee..74f31a623969c 100644
--- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.stories.tsx
@@ -21,7 +21,7 @@ storiesOf('Components/ItemDetailsCard', module).add('default', () => {
- {'content text'}
+ {'content text '}
{'content node'}
diff --git a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx
index c41c5f89c0068..37003961d67d0 100644
--- a/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/item_details_card/index.tsx
@@ -81,11 +81,17 @@ ItemDetailsPropertySummary.displayName = 'ItemPropertySummary';
export const ItemDetailsAction: FC> = memo(
({ children, ...rest }) => (
-
-
- {children}
-
-
+ <>
+
+ {children}
+
+ >
)
);
@@ -99,32 +105,30 @@ export const ItemDetailsCard: FC = memo(({ children }) => {
return (
-
-
-
-
-
- {childElements.get(ItemDetailsPropertySummary)}
-
-
-
-
- {childElements.get(OTHER_NODES)}
- {childElements.has(ItemDetailsAction) && (
-
-
- {childElements.get(ItemDetailsAction)?.map((action, index) => (
-
- {action}
-
- ))}
-
-
- )}
-
-
+
+
+
+ {childElements.get(ItemDetailsPropertySummary)}
+
+
+
+
+
+ {childElements.get(OTHER_NODES)}
+
+ {childElements.has(ItemDetailsAction) && (
+
+
+ {childElements.get(ItemDetailsAction)?.map((action, index) => (
+
+ {action}
+
+ ))}
+
+
+ )}
-
+
);
diff --git a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
index 93f7ff95dfb00..18aa4e65a03cf 100644
--- a/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
+++ b/x-pack/plugins/security_solution/public/common/store/sourcerer/model.ts
@@ -56,7 +56,7 @@ export const initSourcererScope = {
errorMessage: null,
indexPattern: EMPTY_INDEX_PATTERN,
indicesExist: true,
- loading: true,
+ loading: false,
selectedPatterns: [],
};
diff --git a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
index 47da1e93cf004..bfc104b105236 100644
--- a/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/alerts_table/actions.test.tsx
@@ -21,6 +21,7 @@ import { CreateTimeline, UpdateTimelineLoading } from './types';
import { Ecs } from '../../../../common/ecs';
import { TimelineId, TimelineType, TimelineStatus } from '../../../../common/types/timeline';
import { ISearchStart } from '../../../../../../../src/plugins/data/public';
+import { dataPluginMock } from '../../../../../../../src/plugins/data/public/mocks';
jest.mock('apollo-client');
@@ -29,7 +30,7 @@ describe('alert actions', () => {
const unix = moment(anchor).valueOf();
let createTimeline: CreateTimeline;
let updateTimelineIsLoading: UpdateTimelineLoading;
- let searchStrategyClient: ISearchStart;
+ let searchStrategyClient: jest.Mocked;
let clock: sinon.SinonFakeTimers;
beforeEach(() => {
@@ -42,11 +43,13 @@ describe('alert actions', () => {
createTimeline = jest.fn() as jest.Mocked;
updateTimelineIsLoading = jest.fn() as jest.Mocked;
+
searchStrategyClient = {
aggs: {} as ISearchStart['aggs'],
showError: jest.fn(),
search: jest.fn().mockResolvedValue({ data: mockTimelineDetails }),
searchSource: {} as ISearchStart['searchSource'],
+ session: dataPluginMock.createStartContract().search.session,
};
jest.spyOn(apolloClient, 'query').mockImplementation((obj) => {
diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx
index 1a2deb059ad4f..293ed4d488c7d 100644
--- a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/index.tsx
@@ -7,20 +7,11 @@
import React from 'react';
import { HeaderPage, HeaderPageProps } from '../../../common/components/header_page';
-import * as i18n from './translations';
const DetectionEngineHeaderPageComponent: React.FC = (props) => (
);
-DetectionEngineHeaderPageComponent.defaultProps = {
- badgeOptions: {
- beta: true,
- text: i18n.PAGE_BADGE_LABEL,
- tooltip: i18n.PAGE_BADGE_TOOLTIP,
- },
-};
-
export const DetectionEngineHeaderPage = React.memo(DetectionEngineHeaderPageComponent);
DetectionEngineHeaderPage.displayName = 'DetectionEngineHeaderPage';
diff --git a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/translations.ts b/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/translations.ts
deleted file mode 100644
index f59be16923805..0000000000000
--- a/x-pack/plugins/security_solution/public/detections/components/detection_engine_header_page/translations.ts
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * 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 PAGE_BADGE_LABEL = i18n.translate(
- 'xpack.securitySolution.detectionEngine.headerPage.pageBadgeLabel',
- {
- defaultMessage: 'Beta',
- }
-);
-
-export const PAGE_BADGE_TOOLTIP = i18n.translate(
- 'xpack.securitySolution.detectionEngine.headerPage.pageBadgeTooltip',
- {
- defaultMessage:
- 'Alerts is still in beta. Please help us improve by reporting issues or bugs in the Kibana repo.',
- }
-);
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
index a41da908085bc..75ab1524c5c06 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.test.tsx
@@ -5,10 +5,12 @@
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { waitFor } from '@testing-library/react';
+import { shallow, mount, ReactWrapper } from 'enzyme';
import '../../../../common/mock/match_media';
import { PrePackagedRulesPrompt } from './load_empty_prompt';
+import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api';
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
@@ -23,16 +25,94 @@ jest.mock('react-router-dom', () => {
jest.mock('../../../../common/components/link_to');
+jest.mock('../../../containers/detection_engine/rules/api', () => ({
+ getPrePackagedRulesStatus: jest.fn().mockResolvedValue({
+ rules_not_installed: 0,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 0,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ }),
+ createPrepackagedRules: jest.fn(),
+}));
+
+const props = {
+ createPrePackagedRules: jest.fn(),
+ loading: false,
+ userHasNoPermissions: false,
+ 'data-test-subj': 'load-prebuilt-rules',
+};
+
describe('PrePackagedRulesPrompt', () => {
it('renders correctly', () => {
- const wrapper = shallow(
-
- );
+ const wrapper = shallow();
expect(wrapper.find('EmptyPrompt')).toHaveLength(1);
});
});
+
+describe('LoadPrebuiltRulesAndTemplatesButton', () => {
+ it('renders correct button with correct text - Load Elastic prebuilt rules and timeline templates', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 3,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 3,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount();
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual(
+ 'Load Elastic prebuilt rules and timeline templates'
+ );
+ });
+ });
+
+ it('renders correct button with correct text - Load Elastic prebuilt rules', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 3,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 0,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount();
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual(
+ 'Load Elastic prebuilt rules'
+ );
+ });
+ });
+
+ it('renders correct button with correct text - Load Elastic prebuilt timeline templates', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 0,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 3,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount();
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="load-prebuilt-rules"]').last().text()).toEqual(
+ 'Load Elastic prebuilt timeline templates'
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx
index 99968cd4d9fe8..64b3cfa3aa991 100644
--- a/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx
+++ b/x-pack/plugins/security_solution/public/detections/components/rules/pre_packaged_rules/load_empty_prompt.tsx
@@ -4,8 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui';
-import React, { memo, useCallback } from 'react';
+import { EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import React, { memo, useCallback, useMemo } from 'react';
import styled from 'styled-components';
import { useHistory } from 'react-router-dom';
@@ -14,6 +14,8 @@ import * as i18n from './translations';
import { LinkButton } from '../../../../common/components/links';
import { SecurityPageName } from '../../../../app/types';
import { useFormatUrl } from '../../../../common/components/link_to';
+import { usePrePackagedRules } from '../../../containers/detection_engine/rules';
+import { useUserData } from '../../user_info';
const EmptyPrompt = styled(EuiEmptyPrompt)`
align-self: center; /* Corrects horizontal centering in IE11 */
@@ -46,24 +48,36 @@ const PrePackagedRulesPromptComponent: React.FC = (
[history]
);
+ const [
+ { isSignalIndexExists, isAuthenticated, hasEncryptionKey, canUserCRUD, hasIndexWrite },
+ ] = useUserData();
+
+ const { getLoadPrebuiltRulesAndTemplatesButton } = usePrePackagedRules({
+ canUserCRUD,
+ hasIndexWrite,
+ isSignalIndexExists,
+ isAuthenticated,
+ hasEncryptionKey,
+ });
+
+ const loadPrebuiltRulesAndTemplatesButton = useMemo(
+ () =>
+ getLoadPrebuiltRulesAndTemplatesButton({
+ isDisabled: userHasNoPermissions,
+ onClick: handlePreBuiltCreation,
+ fill: true,
+ 'data-test-subj': 'load-prebuilt-rules',
+ }),
+ [getLoadPrebuiltRulesAndTemplatesButton, handlePreBuiltCreation, userHasNoPermissions]
+ );
+
return (
{i18n.PRE_BUILT_TITLE}}
body={{i18n.PRE_BUILT_MSG}
}
actions={
-
-
- {i18n.PRE_BUILT_ACTION}
-
-
+ {loadPrebuiltRulesAndTemplatesButton}
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton',
+ {
+ values: { missingRules },
+ defaultMessage:
+ 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ',
+ }
+ );
+
+export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) =>
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton',
+ {
+ values: { missingTimelines },
+ defaultMessage:
+ 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ',
+ }
+ );
+
+export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = (
+ missingRules: number,
+ missingTimelines: number
+) =>
+ i18n.translate(
+ 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton',
+ {
+ values: { missingRules, missingTimelines },
+ defaultMessage:
+ 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ',
+ }
+ );
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx
index 92d46a785b034..7f74e92584494 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.test.tsx
@@ -32,6 +32,10 @@ describe('usePrePackagedRules', () => {
await waitForNextUpdate();
expect(result.current).toEqual({
+ getLoadPrebuiltRulesAndTemplatesButton:
+ result.current.getLoadPrebuiltRulesAndTemplatesButton,
+ getReloadPrebuiltRulesAndTemplatesButton:
+ result.current.getReloadPrebuiltRulesAndTemplatesButton,
createPrePackagedRules: null,
loading: true,
loadingCreatePrePackagedRules: false,
@@ -63,6 +67,10 @@ describe('usePrePackagedRules', () => {
await waitForNextUpdate();
expect(result.current).toEqual({
+ getLoadPrebuiltRulesAndTemplatesButton:
+ result.current.getLoadPrebuiltRulesAndTemplatesButton,
+ getReloadPrebuiltRulesAndTemplatesButton:
+ result.current.getReloadPrebuiltRulesAndTemplatesButton,
createPrePackagedRules: result.current.createPrePackagedRules,
loading: false,
loadingCreatePrePackagedRules: false,
@@ -100,6 +108,10 @@ describe('usePrePackagedRules', () => {
expect(resp).toEqual(true);
expect(spyOnCreatePrepackagedRules).toHaveBeenCalled();
expect(result.current).toEqual({
+ getLoadPrebuiltRulesAndTemplatesButton:
+ result.current.getLoadPrebuiltRulesAndTemplatesButton,
+ getReloadPrebuiltRulesAndTemplatesButton:
+ result.current.getReloadPrebuiltRulesAndTemplatesButton,
createPrePackagedRules: result.current.createPrePackagedRules,
loading: false,
loadingCreatePrePackagedRules: false,
diff --git a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx
index d82d97883a3d0..4d19f44bcfc84 100644
--- a/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx
+++ b/x-pack/plugins/security_solution/public/detections/containers/detection_engine/rules/use_pre_packaged_rules.tsx
@@ -4,7 +4,8 @@
* you may not use this file except in compliance with the Elastic License.
*/
-import { useEffect, useState } from 'react';
+import React, { useCallback, useMemo, useState, useEffect } from 'react';
+import { EuiButton } from '@elastic/eui';
import {
errorToToaster,
@@ -14,6 +15,11 @@ import {
import { getPrePackagedRulesStatus, createPrepackagedRules } from './api';
import * as i18n from './translations';
+import {
+ getPrePackagedRuleStatus,
+ getPrePackagedTimelineStatus,
+} from '../../../pages/detection_engine/rules/helpers';
+
type Func = () => void;
export type CreatePreBuiltRules = () => Promise;
@@ -23,6 +29,23 @@ interface ReturnPrePackagedTimelines {
timelinesNotUpdated: number | null;
}
+type GetLoadPrebuiltRulesAndTemplatesButton = (args: {
+ isDisabled: boolean;
+ onClick: () => void;
+ fill?: boolean;
+ 'data-test-subj'?: string;
+}) => React.ReactNode | null;
+
+type GetReloadPrebuiltRulesAndTemplatesButton = ({
+ isDisabled,
+ onClick,
+ fill,
+}: {
+ isDisabled: boolean;
+ onClick: () => void;
+ fill?: boolean;
+}) => React.ReactNode | null;
+
interface ReturnPrePackagedRules {
createPrePackagedRules: null | CreatePreBuiltRules;
loading: boolean;
@@ -32,6 +55,8 @@ interface ReturnPrePackagedRules {
rulesInstalled: number | null;
rulesNotInstalled: number | null;
rulesNotUpdated: number | null;
+ getLoadPrebuiltRulesAndTemplatesButton: GetLoadPrebuiltRulesAndTemplatesButton;
+ getReloadPrebuiltRulesAndTemplatesButton: GetReloadPrebuiltRulesAndTemplatesButton;
}
export type ReturnPrePackagedRulesAndTimelines = ReturnPrePackagedRules &
@@ -89,7 +114,6 @@ export const usePrePackagedRules = ({
const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false);
const [loading, setLoading] = useState(true);
const [, dispatchToaster] = useStateToaster();
-
useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();
@@ -100,7 +124,6 @@ export const usePrePackagedRules = ({
const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({
signal: abortCtrl.signal,
});
-
if (isSubscribed) {
setPrepackagedDataStatus({
createPrePackagedRules: createElasticRules,
@@ -225,9 +248,108 @@ export const usePrePackagedRules = ({
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [canUserCRUD, hasIndexWrite, isAuthenticated, hasEncryptionKey, isSignalIndexExists]);
+ const prePackagedRuleStatus = useMemo(
+ () =>
+ getPrePackagedRuleStatus(
+ prepackagedDataStatus.rulesInstalled,
+ prepackagedDataStatus.rulesNotInstalled,
+ prepackagedDataStatus.rulesNotUpdated
+ ),
+ [
+ prepackagedDataStatus.rulesInstalled,
+ prepackagedDataStatus.rulesNotInstalled,
+ prepackagedDataStatus.rulesNotUpdated,
+ ]
+ );
+
+ const prePackagedTimelineStatus = useMemo(
+ () =>
+ getPrePackagedTimelineStatus(
+ prepackagedDataStatus.timelinesInstalled,
+ prepackagedDataStatus.timelinesNotInstalled,
+ prepackagedDataStatus.timelinesNotUpdated
+ ),
+ [
+ prepackagedDataStatus.timelinesInstalled,
+ prepackagedDataStatus.timelinesNotInstalled,
+ prepackagedDataStatus.timelinesNotUpdated,
+ ]
+ );
+ const getLoadPrebuiltRulesAndTemplatesButton = useCallback(
+ ({ isDisabled, onClick, fill, 'data-test-subj': dataTestSubj = 'loadPrebuiltRulesBtn' }) => {
+ return prePackagedRuleStatus === 'ruleNotInstalled' ||
+ prePackagedTimelineStatus === 'timelinesNotInstalled' ? (
+
+ {prePackagedRuleStatus === 'ruleNotInstalled' &&
+ prePackagedTimelineStatus === 'timelinesNotInstalled' &&
+ i18n.LOAD_PREPACKAGED_RULES_AND_TEMPLATES}
+
+ {prePackagedRuleStatus === 'ruleNotInstalled' &&
+ prePackagedTimelineStatus !== 'timelinesNotInstalled' &&
+ i18n.LOAD_PREPACKAGED_RULES}
+
+ {prePackagedRuleStatus !== 'ruleNotInstalled' &&
+ prePackagedTimelineStatus === 'timelinesNotInstalled' &&
+ i18n.LOAD_PREPACKAGED_TIMELINE_TEMPLATES}
+
+ ) : null;
+ },
+ [loadingCreatePrePackagedRules, prePackagedRuleStatus, prePackagedTimelineStatus]
+ );
+
+ const getMissingRulesOrTimelinesButtonTitle = useCallback(
+ (missingRules: number, missingTimelines: number) => {
+ if (missingRules > 0 && missingTimelines === 0)
+ return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules);
+ else if (missingRules === 0 && missingTimelines > 0)
+ return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines);
+ else if (missingRules > 0 && missingTimelines > 0)
+ return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines);
+ },
+ []
+ );
+
+ const getReloadPrebuiltRulesAndTemplatesButton = useCallback(
+ ({ isDisabled, onClick, fill = false }) => {
+ return prePackagedRuleStatus === 'someRuleUninstall' ||
+ prePackagedTimelineStatus === 'someTimelineUninstall' ? (
+
+ {getMissingRulesOrTimelinesButtonTitle(
+ prepackagedDataStatus.rulesNotInstalled ?? 0,
+ prepackagedDataStatus.timelinesNotInstalled ?? 0
+ )}
+
+ ) : null;
+ },
+ [
+ getMissingRulesOrTimelinesButtonTitle,
+ loadingCreatePrePackagedRules,
+ prePackagedRuleStatus,
+ prePackagedTimelineStatus,
+ prepackagedDataStatus.rulesNotInstalled,
+ prepackagedDataStatus.timelinesNotInstalled,
+ ]
+ );
+
return {
loading,
loadingCreatePrePackagedRules,
...prepackagedDataStatus,
+ getLoadPrebuiltRulesAndTemplatesButton,
+ getReloadPrebuiltRulesAndTemplatesButton,
};
};
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx
index 886a24dd7cbe8..58e61c5b47486 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.test.tsx
@@ -5,13 +5,14 @@
*/
import React from 'react';
-import { shallow } from 'enzyme';
+import { shallow, mount, ReactWrapper } from 'enzyme';
import '../../../../common/mock/match_media';
import { RulesPage } from './index';
import { useUserData } from '../../../components/user_info';
-import { usePrePackagedRules } from '../../../containers/detection_engine/rules';
-
+import { waitFor } from '@testing-library/react';
+import { TestProviders } from '../../../../common/mock';
+import { getPrePackagedRulesStatus } from '../../../containers/detection_engine/rules/api';
jest.mock('react-router-dom', () => {
const original = jest.requireActual('react-router-dom');
@@ -26,16 +27,164 @@ jest.mock('react-router-dom', () => {
jest.mock('../../../containers/detection_engine/lists/use_lists_config');
jest.mock('../../../../common/components/link_to');
jest.mock('../../../components/user_info');
-jest.mock('../../../containers/detection_engine/rules');
+jest.mock('../../../../common/components/toasters', () => {
+ const actual = jest.requireActual('../../../../common/components/toasters');
+ return {
+ ...actual,
+ errorToToaster: jest.fn(),
+ useStateToaster: jest.fn().mockReturnValue([jest.fn(), jest.fn()]),
+ displaySuccessToast: jest.fn(),
+ };
+});
+
+jest.mock('../../../containers/detection_engine/rules/api', () => ({
+ getPrePackagedRulesStatus: jest.fn().mockResolvedValue({
+ rules_not_installed: 0,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 0,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ }),
+ createPrepackagedRules: jest.fn(),
+}));
+
+jest.mock('../../../../common/lib/kibana', () => {
+ return {
+ useToast: jest.fn(),
+ useHttp: jest.fn(),
+ };
+});
+
+jest.mock('../../../components/value_lists_management_modal', () => {
+ return {
+ ValueListsModal: jest.fn().mockReturnValue(),
+ };
+});
+
+jest.mock('./all', () => {
+ return {
+ AllRules: jest.fn().mockReturnValue(),
+ };
+});
+
+jest.mock('../../../../common/utils/route/spy_routes', () => {
+ return {
+ SpyRoute: jest.fn().mockReturnValue(),
+ };
+});
+
+jest.mock('../../../components/rules/pre_packaged_rules/update_callout', () => {
+ return {
+ UpdatePrePackagedRulesCallOut: jest.fn().mockReturnValue(),
+ };
+});
describe('RulesPage', () => {
beforeAll(() => {
(useUserData as jest.Mock).mockReturnValue([{}]);
- (usePrePackagedRules as jest.Mock).mockReturnValue({});
});
- it('renders correctly', () => {
+
+ it('renders AllRules', () => {
const wrapper = shallow();
+ expect(wrapper.find('[data-test-subj="all-rules"]').exists()).toEqual(true);
+ });
+
+ it('renders correct button with correct text - Load Elastic prebuilt rules and timeline templates', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 3,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 3,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount(
+
+
+
+ );
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual(
+ 'Load Elastic prebuilt rules and timeline templates'
+ );
+ });
+ });
+
+ it('renders correct button with correct text - Load Elastic prebuilt rules', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 3,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 0,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount(
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual(
+ 'Load Elastic prebuilt rules'
+ );
+ });
+ });
+
+ it('renders correct button with correct text - Load Elastic prebuilt timeline templates', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 0,
+ rules_installed: 0,
+ rules_not_updated: 0,
+ timelines_not_installed: 3,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount(
+
+
+
+ );
+
+ await waitFor(() => {
+ wrapper.update();
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').exists()).toEqual(true);
+ expect(wrapper.find('[data-test-subj="loadPrebuiltRulesBtn"]').last().text()).toEqual(
+ 'Load Elastic prebuilt timeline templates'
+ );
+ });
+ });
+
+ it('renders a callout - Update Elastic prebuilt rules', async () => {
+ (getPrePackagedRulesStatus as jest.Mock).mockResolvedValue({
+ rules_not_installed: 2,
+ rules_installed: 1,
+ rules_not_updated: 1,
+ timelines_not_installed: 0,
+ timelines_installed: 0,
+ timelines_not_updated: 0,
+ });
+
+ const wrapper: ReactWrapper = mount(
+
+
+
+ );
- expect(wrapper.find('AllRules')).toHaveLength(1);
+ await waitFor(() => {
+ wrapper.update();
+ expect(wrapper.find('[data-test-subj="update-callout-button"]').exists()).toEqual(true);
+ });
});
});
diff --git a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
index 53c82569f94ae..8c7cb6a0d9284 100644
--- a/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
+++ b/x-pack/plugins/security_solution/public/detections/pages/detection_engine/rules/index.tsx
@@ -5,7 +5,7 @@
*/
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui';
-import React, { useCallback, useRef, useState } from 'react';
+import React, { useCallback, useMemo, useRef, useState } from 'react';
import { useHistory } from 'react-router-dom';
import { usePrePackagedRules, importRules } from '../../../containers/detection_engine/rules';
@@ -70,6 +70,8 @@ const RulesPageComponent: React.FC = () => {
timelinesInstalled,
timelinesNotInstalled,
timelinesNotUpdated,
+ getLoadPrebuiltRulesAndTemplatesButton,
+ getReloadPrebuiltRulesAndTemplatesButton,
} = usePrePackagedRules({
canUserCRUD,
hasIndexWrite,
@@ -113,18 +115,6 @@ const RulesPageComponent: React.FC = () => {
refreshRulesData.current = refreshRule;
}, []);
- const getMissingRulesOrTimelinesButtonTitle = useCallback(
- (missingRules: number, missingTimelines: number) => {
- if (missingRules > 0 && missingTimelines === 0)
- return i18n.RELOAD_MISSING_PREPACKAGED_RULES(missingRules);
- else if (missingRules === 0 && missingTimelines > 0)
- return i18n.RELOAD_MISSING_PREPACKAGED_TIMELINES(missingTimelines);
- else if (missingRules > 0 && missingTimelines > 0)
- return i18n.RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES(missingRules, missingTimelines);
- },
- []
- );
-
const goToNewRule = useCallback(
(ev) => {
ev.preventDefault();
@@ -133,6 +123,24 @@ const RulesPageComponent: React.FC = () => {
[history]
);
+ const loadPrebuiltRulesAndTemplatesButton = useMemo(
+ () =>
+ getLoadPrebuiltRulesAndTemplatesButton({
+ isDisabled: userHasNoPermissions(canUserCRUD) || loading,
+ onClick: handleCreatePrePackagedRules,
+ }),
+ [canUserCRUD, getLoadPrebuiltRulesAndTemplatesButton, handleCreatePrePackagedRules, loading]
+ );
+
+ const reloadPrebuiltRulesAndTemplatesButton = useMemo(
+ () =>
+ getReloadPrebuiltRulesAndTemplatesButton({
+ isDisabled: userHasNoPermissions(canUserCRUD) || loading,
+ onClick: handleCreatePrePackagedRules,
+ }),
+ [canUserCRUD, getReloadPrebuiltRulesAndTemplatesButton, handleCreatePrePackagedRules, loading]
+ );
+
if (
redirectToDetections(
isSignalIndexExists,
@@ -177,35 +185,11 @@ const RulesPageComponent: React.FC = () => {
title={i18n.PAGE_TITLE}
>
- {(prePackagedRuleStatus === 'ruleNotInstalled' ||
- prePackagedTimelineStatus === 'timelinesNotInstalled') && (
-
-
- {i18n.LOAD_PREPACKAGED_RULES}
-
-
+ {loadPrebuiltRulesAndTemplatesButton && (
+ {loadPrebuiltRulesAndTemplatesButton}
)}
- {(prePackagedRuleStatus === 'someRuleUninstall' ||
- prePackagedTimelineStatus === 'someTimelineUninstall') && (
-
-
- {getMissingRulesOrTimelinesButtonTitle(
- rulesNotInstalled ?? 0,
- timelinesNotInstalled ?? 0
- )}
-
-
+ {reloadPrebuiltRulesAndTemplatesButton && (
+ {reloadPrebuiltRulesAndTemplatesButton}
)}
@@ -247,6 +231,7 @@ const RulesPageComponent: React.FC = () => {
{(prePackagedRuleStatus === 'ruleNeedUpdate' ||
prePackagedTimelineStatus === 'timelineNeedUpdate') && (
{
)}
- i18n.translate(
- 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesButton',
- {
- values: { missingRules },
- defaultMessage:
- 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} ',
- }
- );
-
-export const RELOAD_MISSING_PREPACKAGED_TIMELINES = (missingTimelines: number) =>
- i18n.translate(
- 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedTimelinesButton',
- {
- values: { missingTimelines },
- defaultMessage:
- 'Install {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ',
- }
- );
-
-export const RELOAD_MISSING_PREPACKAGED_RULES_AND_TIMELINES = (
- missingRules: number,
- missingTimelines: number
-) =>
- i18n.translate(
- 'xpack.securitySolution.detectionEngine.rules.reloadMissingPrePackagedRulesAndTimelinesButton',
- {
- values: { missingRules, missingTimelines },
- defaultMessage:
- 'Install {missingRules} Elastic prebuilt {missingRules, plural, =1 {rule} other {rules}} and {missingTimelines} Elastic prebuilt {missingTimelines, plural, =1 {timeline} other {timelines}} ',
- }
- );
-
export const IMPORT_RULE_BTN_TITLE = i18n.translate(
'xpack.securitySolution.detectionEngine.components.importRuleModal.importRuleTitle',
{
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
index 12a76ae0772a3..d785e3b3a131a 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.test.tsx
@@ -702,10 +702,10 @@ describe('when on the list page', () => {
});
it('should not show any numbered badges if all actions are successful', () => {
- const policyResponse = docGenerator.generatePolicyResponse(
- new Date().getTime(),
- HostPolicyResponseActionStatus.success
- );
+ const policyResponse = docGenerator.generatePolicyResponse({
+ ts: new Date().getTime(),
+ allStatus: HostPolicyResponseActionStatus.success,
+ });
reactTestingLibrary.act(() => {
store.dispatch({
type: 'serverReturnedEndpointPolicyResponse',
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
index 36c5b0d1037e5..c5d3c3c25313d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/index.tsx
@@ -561,7 +561,7 @@ export const EndpointList = () => {
return (
{
)}
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 0
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 0
-
-
-
-
-
-
+ trusted app 0
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 0
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -388,255 +375,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 1
-
-
-
- -
- OS
-
- -
-
-
- Mac OS
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 1
-
-
-
-
-
-
+ trusted app 1
+
+
+
+
-
+
+
+
+ Mac OS
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 1
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -652,255 +626,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 2
-
-
-
- -
- OS
-
- -
-
-
- Linux
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 2
-
-
-
-
-
-
+ trusted app 2
+
+
+
+
-
+
+
+
+ Linux
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 2
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -916,255 +877,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 3
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 3
-
-
-
-
-
-
+ trusted app 3
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 3
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -1180,255 +1128,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 4
-
-
-
- -
- OS
-
- -
-
-
- Mac OS
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 4
-
-
-
-
-
-
+ trusted app 4
+
+
+
+
-
+
+
+
+ Mac OS
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 4
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -1444,255 +1379,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 5
-
-
-
- -
- OS
-
- -
-
-
- Linux
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 5
-
-
-
-
-
-
+ trusted app 5
+
+
+
+
-
+
+
+
+ Linux
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 5
+
+
+
+
+
+
+
+
+
-
-
-
+
+
+
+
+
+ Remove
+
+
+
@@ -1708,255 +1630,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
+ Name
+
+
-
- -
- Name
-
- -
-
-
- trusted app 6
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 6
-
-
-
-
-
-
+ trusted app 6
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 6
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -1972,255 +1881,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
+ Name
+
+
-
- -
- Name
-
- -
-
-
- trusted app 7
-
-
-
- -
- OS
-
- -
-
-
- Mac OS
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 7
-
-
-
-
-
-
+ trusted app 7
+
+
+
+
-
+
+
+
+ Mac OS
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 7
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -2236,255 +2132,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
+ Name
+
+
-
- -
- Name
-
- -
-
-
- trusted app 8
-
-
-
- -
- OS
-
- -
-
-
- Linux
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 8
-
-
-
-
-
-
+ trusted app 8
+
+
+
+
-
+
+
+
+ Linux
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 8
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -2500,255 +2383,242 @@ exports[`TrustedAppsGrid renders correctly when loaded data 1`] = `
class="euiPanel"
>
-
-
+ Name
+
+
-
- -
- Name
-
- -
-
-
- trusted app 9
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 9
-
-
-
-
-
-
+ trusted app 9
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 9
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+
+
+
@@ -3054,255 +2924,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 0
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 0
-
-
-
-
-
-
+ trusted app 0
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 0
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -3318,255 +3175,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 1
-
-
-
- -
- OS
-
- -
-
-
- Mac OS
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 1
-
-
-
-
-
-
+ trusted app 1
+
+
+
+
-
+
+
+
+ Mac OS
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 1
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -3582,255 +3426,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 2
-
-
-
- -
- OS
-
- -
-
-
- Linux
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 2
-
-
-
-
-
-
+ trusted app 2
+
+
+
+
-
+
+
+
+ Linux
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 2
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -3846,255 +3677,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 3
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 3
-
-
-
-
-
-
+ trusted app 3
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 3
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -4110,255 +3928,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 4
-
-
-
- -
- OS
-
- -
-
-
- Mac OS
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 4
-
-
-
-
-
-
+ trusted app 4
+
+
+
+
-
+
+
+
+ Mac OS
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 4
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -4374,255 +4179,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 5
-
-
-
- -
- OS
-
- -
-
-
- Linux
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 5
-
-
-
-
-
-
+ trusted app 5
+
+
+
+
-
+
+
+
+ Linux
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 5
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
- Remove
-
-
-
-
-
-
-
+ Remove
+
+
+
@@ -4638,255 +4430,242 @@ exports[`TrustedAppsGrid renders correctly when loading data for the second time
class="euiPanel"
>
-
-
-
+ -
+
-
-
- Name
-
- -
-
-
- trusted app 6
-
-
-
- -
- OS
-
- -
-
-
- Windows
-
-
-
- -
- Date Created
-
- -
- 1 minute ago
-
- -
- Created By
-
- -
-
-
- someone
-
-
-
- -
- Description
-
- -
-
-
- Trusted App 6
-
-
-
-
-
-
+ trusted app 6
+
+
+
+
-
+
+
+
+ Windows
+
+
+
+
+ Date Created
+
+
+ 1 minute ago
+
+
+ Created By
+
+
+
+
+ someone
+
+
+
+
+ Description
+
+
+
+
+ Trusted App 6
+
+
+
+
+
+