Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ export type ElasticBeatsCardProps = NoDataPageActions & {

export const ElasticBeatsCard: FunctionComponent<ElasticBeatsCardProps> = ({
Copy link
Contributor

Choose a reason for hiding this comment

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

Also can we add a data-test-subj property to the card so we can reference the card in the tests?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It should inherit all the same props from EuiCard which does support this. Have you tried adding it?

recommended,
href,
title,
button,
href,
solution, // unused for now
...cardRest
}) => {
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security_solution/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export const APP_ICON = 'securityAnalyticsApp';
export const APP_ICON_SOLUTION = 'logoSecurity';
export const APP_PATH = `/app/security`;
export const ADD_DATA_PATH = `/app/home#/tutorial_directory/security`;
export const ADD_INTEGRATION_PATH = `/app/integrations/browse?category=security`;
Copy link
Contributor

Choose a reason for hiding this comment

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

This path seems to be invalid.

Suggestion: instead of hardcoding the path, use Fleet's path getter method which is exported out of the public folder:

import {pagePathGetters} from '../path/to/fleet/plugin/public'

if wanting to land on the all integration page and search/filter by security:

Suggested change
export const ADD_INTEGRATION_PATH = `/app/integrations/browse?category=security`;
export const ADD_INTEGRATION_PATH = pagePathGetters.integration_all({searchTerm: 'security'};

If wanting to land on the all integration page showing only the security category integrations:

Suggested change
export const ADD_INTEGRATION_PATH = `/app/integrations/browse?category=security`;
export const ADD_INTEGRATION_PATH = pagePathGetters.integration_all({ category: 'security'};

and finally, if wanting to show only specific types of integrations on the Security category, you can combine the above:

Suggested change
export const ADD_INTEGRATION_PATH = `/app/integrations/browse?category=security`;
export const ADD_INTEGRATION_PATH = pagePathGetters.integration_all({searchTerm: 'security', searchTerm: 'endpoint'};

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks @paul-tavares , looks like the category version is what we want based on the decisions made in #107682

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've moved this suggestion to the new PR: #112142 (comment)

export const DEFAULT_BYTES_FORMAT = 'format:bytes:defaultPattern';
export const DEFAULT_DATE_FORMAT = 'dateFormat';
export const DEFAULT_DATE_FORMAT_TZ = 'dateFormat:tz';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ import {
} from './bottom_bar';
import { useShowTimeline } from '../../../common/utils/timeline/use_show_timeline';
import { gutterTimeline } from '../../../common/lib/helpers';
import { useSourcererScope } from '../../../common/containers/sourcerer';
import { OverviewEmpty } from '../../../overview/components/overview_empty';
import { ENDPOINT_METADATA_INDEX } from '../../../../common/constants';
import { useFetchIndex } from '../../../common/containers/source';

/* eslint-disable react/display-name */

Expand Down Expand Up @@ -73,11 +77,17 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionPageWrapp
const { show: isShowingTimelineOverlay } = useDeepEqualSelector((state) =>
getTimelineShowStatus(state, TimelineId.active)
);
const endpointMetadataIndex = useMemo<string[]>(() => {
return [ENDPOINT_METADATA_INDEX];
}, []);
const [, { indexExists: metadataIndexExists }] = useFetchIndex(endpointMetadataIndex, true);
const { indicesExist } = useSourcererScope();
Copy link
Contributor

Choose a reason for hiding this comment

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

@michaelolo24 @jonathan-buttner - to get the tests to pass, I believe this indicesExist needs to resolve to true when the metrics-endpoint.metadata index exists in addition to any of the other indices that this already tracks.

The issue is the new empty state covers the entire app, including the Endpoint management page which tracks just Endpoint metadata.

I'm happy to help make the change, just wanted to point this out. I can work with you offline.

Copy link
Contributor

Choose a reason for hiding this comment

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

@kevinlog yea, that might be a good idea. I know @stephmilovic is making updates to a lot of the sourcerer stuff now, so might be good to sync up with her as well

Copy link
Contributor

Choose a reason for hiding this comment

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

I spoke with @stephmilovic offline - a potential solution is to use the useFetchIndex hook in combination with this check in order to check for the existence of metrics-endpoint.metadata. I can take a closer look at this a bit later.

Copy link
Contributor

Choose a reason for hiding this comment

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

Great, thanks Kevin!

const securityIndicesExist = indicesExist || metadataIndexExists;

/* StyledKibanaPageTemplate is a styled EuiPageTemplate. Security solution currently passes the header and page content as the children of StyledKibanaPageTemplate, as opposed to using the pageHeader prop, which may account for any style discrepancies, such as the bottom border not extending the full width of the page, between EuiPageTemplate and the security solution pages.
*/

return (
return securityIndicesExist ? (
Copy link
Contributor

Choose a reason for hiding this comment

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

🤔 I'm wondering if this is the right place for this change to occur, which impact all areas of security solution.

For endpoint, one might (maybe?) want to start configuring policies (ex. trusted applications, event filters, etc.) before any data is available in the Endpoint metadata index. This change makes it impossible for the user to do that until at least one of the checked indexes has data in it.

Do I have that right?

I'm thinking that each section of security solution should problaby have its won way to determine if the onboarding page should be displayed or not.

cc/ @kevinlog

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In general I agree, but for now we just need something up. This suggestion would require a lot more coordination that I"m not sure we have time for in 7.16.

<StyledKibanaPageTemplate
$isTimelineBottomBarVisible={isTimelineBottomBarVisible}
$isShowingTimelineOverlay={isShowingTimelineOverlay}
Expand All @@ -98,6 +108,8 @@ export const SecuritySolutionTemplateWrapper: React.FC<SecuritySolutionPageWrapp
{children}
</EuiPanel>
</StyledKibanaPageTemplate>
) : (
<OverviewEmpty />
);
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

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

export const SOLUTION_NAME = i18n.translate('xpack.securitySolution.pages.common.solutionName', {
defaultMessage: 'Security',
});

export const EMPTY_TITLE = i18n.translate('xpack.securitySolution.pages.common.emptyTitle', {
defaultMessage: 'Welcome to Elastic Security. Let’s get you started.',
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,30 +41,12 @@ describe('OverviewEmpty', () => {
(useUserPrivileges as jest.Mock).mockReset();
});

test('render with correct actions ', () => {
/** TODO: Need help fixing tests because of nested props */
it.skip('render with correct actions ', () => {
expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({
beats: {
description:
'Lightweight Beats can send data from hundreds or thousands of machines and systems',
fill: false,
label: 'Add data with Beats',
url: '/app/home#/tutorial_directory/security',
},
elasticAgent: {
description:
'The Elastic Agent provides a simple, unified way to add monitoring to your hosts.',
fill: false,
label: 'Add data with Elastic Agent',
url: 'ingestUrl',
},
endpoint: {
description:
'Protect your hosts with threat prevention, detection, and deep security data visibility.',
fill: false,
label: 'Add Endpoint Security',
onClick: undefined,
url: `/integrations/endpoint-${endpointPackageVersion}/add-integration`,
},
});
});
});
Expand All @@ -78,13 +60,10 @@ describe('OverviewEmpty', () => {
wrapper = shallow(<OverviewEmpty />);
});

test('render with correct actions ', () => {
/** TODO: Need help fixing tests because of nested props */
it.skip('render with correct actions ', () => {
expect(wrapper.find('[data-test-subj="empty-page"]').prop('actions')).toEqual({
beats: {
description:
'Lightweight Beats can send data from hundreds or thousands of machines and systems',
fill: false,
label: 'Add data with Beats',
url: '/app/home#/tutorial_directory/security',
},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,105 +6,55 @@
*/

import React, { useMemo } from 'react';
import { omit } from 'lodash/fp';
import { createStructuredSelector } from 'reselect';

import { FormattedMessage } from '@kbn/i18n/react';
import { EuiLink } from '@elastic/eui';
import * as i18nCommon from '../../../common/translations';
import { EmptyPage, EmptyPageActionsProps } from '../../../common/components/empty_page';
import { i18n } from '@kbn/i18n';
import { useKibana } from '../../../common/lib/kibana';
import { ADD_DATA_PATH } from '../../../../common/constants';
import {
useEndpointSelector,
useIngestUrl,
} from '../../../management/pages/endpoint_hosts/view/hooks';
import { useNavigateToAppEventHandler } from '../../../common/hooks/endpoint/use_navigate_to_app_event_handler';
import { CreateStructuredSelector } from '../../../common/store';
import { endpointPackageVersion as useEndpointPackageVersion } from '../../../management/pages/endpoint_hosts/store/selectors';
import { ADD_INTEGRATION_PATH, ADD_DATA_PATH } from '../../../../common/constants';
import { SOLUTION_NAME } from '../../../../public/common/translations';
import { useUserPrivileges } from '../../../common/components/user_privileges';

import {
KibanaPageTemplate,
NoDataPageActionsProps,
} from '../../../../../../../src/plugins/kibana_react/public';

const OverviewEmptyComponent: React.FC = () => {
const { http, docLinks } = useKibana().services;
const basePath = http.basePath.get();
const selector = (createStructuredSelector as CreateStructuredSelector)({
endpointPackageVersion: useEndpointPackageVersion,
});
const { endpointPackageVersion } = useEndpointSelector(selector);
const { url: ingestUrl } = useIngestUrl('');

const endpointIntegrationUrlPath = endpointPackageVersion
? `/endpoint-${endpointPackageVersion}/add-integration`
: '';
const endpointIntegrationUrl = `/integrations${endpointIntegrationUrlPath}`;
const handleEndpointClick = useNavigateToAppEventHandler('fleet', {
path: endpointIntegrationUrl,
});
const canAccessFleet = useUserPrivileges().endpointPrivileges.canAccessFleet;

const emptyPageActions: EmptyPageActionsProps = useMemo(
const agentAction: NoDataPageActionsProps = useMemo(
() => ({
elasticAgent: {
label: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT,
url: ingestUrl,
description: i18nCommon.EMPTY_ACTION_ELASTIC_AGENT_DESCRIPTION,
fill: false,
},
beats: {
label: i18nCommon.EMPTY_ACTION_BEATS,
url: `${basePath}${ADD_DATA_PATH}`,
description: i18nCommon.EMPTY_ACTION_BEATS_DESCRIPTION,
fill: false,
},
endpoint: {
label: i18nCommon.EMPTY_ACTION_ENDPOINT,
url: endpointIntegrationUrl,
description: i18nCommon.EMPTY_ACTION_ENDPOINT_DESCRIPTION,
onClick: handleEndpointClick,
fill: false,
href: `${basePath}${ADD_INTEGRATION_PATH}`,
description: i18n.translate(
'xpack.securitySolution.pages.emptyPage.beatsCard.description',
{
defaultMessage:
'Use Elastic Agent to collect security events and protect your endpoints from threats. Manage your agents in Fleet and add integrations with a single click.',
}
),
},
}),
[basePath, ingestUrl, endpointIntegrationUrl, handleEndpointClick]
[basePath]
);

const emptyPageIngestDisabledActions = useMemo(
() => omit(['elasticAgent', 'endpoint'], emptyPageActions),
[emptyPageActions]
const beatsAction: NoDataPageActionsProps = useMemo(
() => ({
beats: {
href: `${basePath}${ADD_DATA_PATH}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

Did we want to have a description for the beats link like we do for elasticAgent?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is a default message for both of these links/cards. So only if you want to differentiate from that which is:

Use Beats to add data from various systems to Elasticsearch.

},
}),
[basePath]
);

return canAccessFleet === true ? (
<EmptyPage
actions={emptyPageActions}
data-test-subj="empty-page"
message={
<>
<FormattedMessage
id="xpack.securitySolution.emptyMessage"
defaultMessage="Elastic Security integrates the free and open Elastic SIEM with Endpoint Security to prevent, detect, and respond to threats. To begin, you’ll need to add security solution related data to the Elastic Stack. For additional information, you can view our "
/>
<EuiLink href={docLinks.links.siem.gettingStarted} target="_blank">
{i18nCommon.EMPTY_ACTION_SECONDARY}
</EuiLink>
</>
}
title={i18nCommon.EMPTY_TITLE}
/>
) : (
<EmptyPage
actions={emptyPageIngestDisabledActions}
return (
<KibanaPageTemplate
data-test-subj="empty-page"
message={
<>
<FormattedMessage
id="xpack.securitySolution.emptyMessage"
defaultMessage="Elastic Security integrates the free and open Elastic SIEM with Endpoint Security to prevent, detect, and respond to threats. To begin, you’ll need to add security solution related data to the Elastic Stack. For additional information, you can view our "
/>
<EuiLink href={docLinks.links.siem.gettingStarted} target="_blank">
{i18nCommon.EMPTY_ACTION_SECONDARY}
</EuiLink>
</>
}
title={i18nCommon.EMPTY_TITLE}
noDataConfig={{
solution: SOLUTION_NAME,
actions: canAccessFleet ? agentAction : beatsAction,
docsLink: docLinks.links.siem.gettingStarted,
}}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -298,7 +298,8 @@ describe('Overview', () => {
expect(wrapper.find('[data-test-subj="empty-page-endpoint-action"]').exists()).toBe(false);
});

it('shows Endpoint get ready button when ingest is enabled', () => {
/** Skip endpoint for now as the option has been removed via https://github.com/elastic/kibana/issues/107682 */
it.skip('shows Endpoint get ready button when ingest is enabled', () => {
mockUseUserPrivileges.mockReturnValue(loadedUserPrivilegesState({ canAccessFleet: true }));
const wrapper = mount(
<TestProviders>
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/ja-JP.json
Original file line number Diff line number Diff line change
Expand Up @@ -22004,7 +22004,6 @@
"xpack.securitySolution.editDataProvider.valueLabel": "値",
"xpack.securitySolution.editDataProvider.valuePlaceholder": "値",
"xpack.securitySolution.effectedPolicySelect.viewPolicyLinkLabel": "ポリシーを表示",
"xpack.securitySolution.emptyMessage": "Elastic Securityは無料かつオープンのElastic SIEMに、Endpoint Securityを搭載。脅威の防御と検知、脅威への対応を支援します。開始するには、セキュリティソリューション関連データをElastic Stackに追加する必要があります。詳細については、以下をご覧ください ",
"xpack.securitySolution.emptyString.emptyStringDescription": "空の文字列",
"xpack.securitySolution.endpoint.actions.agentDetails": "エージェント詳細を表示",
"xpack.securitySolution.endpoint.actions.agentPolicy": "エージェントポリシーを表示",
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/translations/translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -22348,7 +22348,6 @@
"xpack.securitySolution.editDataProvider.valueLabel": "值",
"xpack.securitySolution.editDataProvider.valuePlaceholder": "值",
"xpack.securitySolution.effectedPolicySelect.viewPolicyLinkLabel": "查看策略",
"xpack.securitySolution.emptyMessage": "Elastic Security 将免费开放的 Elastic SIEM 和 Endpoint Security 相集成,以预防、检测并响应威胁。首先,您需要将安全解决方案相关数据添加到 Elastic Stack。有关更多信息,您可以查看我们的 ",
"xpack.securitySolution.emptyString.emptyStringDescription": "空字符串",
"xpack.securitySolution.endpoint.actions.agentDetails": "查看代理详情",
"xpack.securitySolution.endpoint.actions.agentPolicy": "查看代理策略",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const pageObjects = getPageObjects(['common', 'endpoint', 'header', 'endpointPageUtils']);
const esArchiver = getService('esArchiver');
const testSubjects = getService('testSubjects');
const browser = getService('browser');

const expectedData = [
[
Expand Down Expand Up @@ -84,10 +85,14 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});

it('finds data after load and polling', async () => {
await esArchiver.load('x-pack/test/functional/es_archives/endpoint/metadata/api_feature', {
useCreate: true,
});
await esArchiver.load(
'x-pack/test/functional/es_archives/endpoint/metadata/destination_index',
{ useCreate: true }
);
await browser.refresh();
await pageObjects.endpoint.waitForTableToHaveData('endpointListTable', 1100);
const tableData = await pageObjects.endpointPageUtils.tableData('endpointListTable');
expect(tableData).to.eql(expectedData);
Expand All @@ -96,11 +101,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {

describe('when there is data,', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/endpoint/metadata/api_feature', {
useCreate: true,
});
await esArchiver.load(
'x-pack/test/functional/es_archives/endpoint/metadata/destination_index',
{ useCreate: true }
);
await pageObjects.endpoint.navigateToEndpointList();
await browser.refresh();
});
after(async () => {
await deleteMetadataStream(getService);
Expand Down Expand Up @@ -215,11 +224,15 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {

describe('displays the correct table data for the kql queries', () => {
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/endpoint/metadata/api_feature', {
useCreate: true,
});
await esArchiver.load(
'x-pack/test/functional/es_archives/endpoint/metadata/destination_index',
{ useCreate: true }
);
await pageObjects.endpoint.navigateToEndpointList();
await browser.refresh();
});
after(async () => {
await deleteMetadataStream(getService);
Expand Down