[TIP] Show loading when integration availability is checked (#139537)

This commit is contained in:
Luke Gmys 2022-08-30 12:26:45 +02:00 committed by GitHub
parent b70775d57e
commit 62092e2e05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 450 additions and 183 deletions

View file

@ -14,13 +14,12 @@ import type { SourcererDataView } from '@kbn/threat-intelligence-plugin/public/t
import { useKibana } from '../common/lib/kibana';
import { FiltersGlobal } from '../common/components/filters_global';
import { SpyRoute } from '../common/utils/route/spy_routes';
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
import { useIsExperimentalFeatureEnabled } from '../common/hooks/use_experimental_features';
import { licenseService } from '../common/hooks/use_license';
import { SecurityPageName } from '../app/types';
import type { SecuritySubPluginRoutes } from '../app/types';
import { useSourcererDataView } from '../common/containers/sourcerer';
import { PluginTemplateWrapper } from '../common/components/plugin_template_wrapper';
import { SecuritySolutionPageWrapper } from '../common/components/page_wrapper';
const ThreatIntelligence = memo(() => {
const { threatIntelligence } = useKibana().services;
@ -35,18 +34,15 @@ const ThreatIntelligence = memo(() => {
const securitySolutionContext: SecuritySolutionPluginContext = {
getFiltersGlobalComponent: () => FiltersGlobal,
getPageWrapper: () => SecuritySolutionPageWrapper,
licenseService,
sourcererDataView: sourcererDataView as unknown as SourcererDataView,
};
return (
<TrackApplicationView viewId="threat_intelligence">
<PluginTemplateWrapper>
<SecuritySolutionPageWrapper noPadding>
<ThreatIntelligencePlugin securitySolutionContext={securitySolutionContext} />
<SpyRoute pageName={SecurityPageName.threatIntelligenceIndicators} />
</SecuritySolutionPageWrapper>
</PluginTemplateWrapper>
<ThreatIntelligencePlugin securitySolutionContext={securitySolutionContext} />
<SpyRoute pageName={SecurityPageName.threatIntelligenceIndicators} />
</TrackApplicationView>
);
});

View file

@ -13,6 +13,10 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
() =>
({ children }) =>
<div>{children}</div>,
getPageWrapper:
() =>
({ children }) =>
<div>{children}</div>,
licenseService: {
isEnterprise() {
return true;
@ -22,5 +26,6 @@ export const getSecuritySolutionContextMock = (): SecuritySolutionPluginContext
browserFields: {},
selectedPatterns: [],
indexPattern: { fields: [], title: '' },
loading: false,
},
});

View file

@ -110,6 +110,13 @@ export const mockedServices = {
getFieldBrowser: jest.fn().mockReturnValue(null),
},
timelines: timelinesServiceMock,
securityLayout: {
getPluginWrapper:
() =>
({ children }: any) => {
return <>{children}</>;
},
},
};
export const TestProvidersComponent: FC = ({ children }) => (

View file

@ -9,11 +9,12 @@ import { render, screen } from '@testing-library/react';
import React from 'react';
import { DefaultPageLayout, TITLE_TEST_ID } from './layout';
import '@testing-library/jest-dom';
import { TestProvidersComponent } from '../../common/mocks/test_providers';
describe('<Layout />', () => {
describe('when pageTitle is not specified', () => {
beforeEach(() => {
render(<DefaultPageLayout />);
render(<DefaultPageLayout />, { wrapper: TestProvidersComponent });
});
it('should not render secondary heading', () => {
@ -23,7 +24,9 @@ describe('<Layout />', () => {
describe('when pageTitle is passed, it should be rendered as secondary heading', () => {
beforeEach(() => {
render(<DefaultPageLayout pageTitle="Stranger Threats" />);
render(<DefaultPageLayout pageTitle="Stranger Threats" />, {
wrapper: TestProvidersComponent,
});
});
it('should render secondary heading', () => {

View file

@ -7,6 +7,7 @@
import { EuiPageHeader, EuiPageHeaderSection, EuiSpacer, EuiText } from '@elastic/eui';
import React, { FC } from 'react';
import { SecuritySolutionPageWrapper } from '../../containers/security_solution_page_wrapper';
export interface LayoutProps {
pageTitle?: string;
@ -17,7 +18,7 @@ export const TITLE_TEST_ID = 'tiDefaultPageLayoutTitle';
export const DefaultPageLayout: FC<LayoutProps> = ({ children, pageTitle, border = true }) => {
return (
<>
<SecuritySolutionPageWrapper>
<EuiPageHeader alignItems="center" bottomBorder={border}>
<EuiPageHeaderSection>
{pageTitle && (
@ -29,6 +30,6 @@ export const DefaultPageLayout: FC<LayoutProps> = ({ children, pageTitle, border
</EuiPageHeader>
<EuiSpacer size="l" />
{children}
</>
</SecuritySolutionPageWrapper>
);
};

View file

@ -10,6 +10,7 @@ import { FC } from 'react';
import { Paywall } from '../../components/paywall';
import { useKibana } from '../../hooks/use_kibana';
import { useSecurityContext } from '../../hooks/use_security_context';
import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper';
export const EnterpriseGuard: FC = ({ children }) => {
const { licenseService } = useSecurityContext();
@ -22,8 +23,10 @@ export const EnterpriseGuard: FC = ({ children }) => {
}
return (
<Paywall
licenseManagementHref={http.basePath.prepend('/app/management/stack/license_management')}
/>
<SecuritySolutionPluginTemplateWrapper template="noData">
<Paywall
licenseManagementHref={http.basePath.prepend('/app/management/stack/license_management')}
/>
</SecuritySolutionPluginTemplateWrapper>
);
};

View file

@ -0,0 +1,125 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`checking if the page should be visible (based on indicator count) when indicator count is being loaded should render nothing at all: loading 1`] = `
<DocumentFragment>
<span
aria-label="Loading"
class="euiLoadingLogo emotion-euiLoadingLogo-xl"
role="progressbar"
>
<span
class="emotion-euiLoadingLogo__icon"
>
<span
data-euiicon-type="logoSecurity"
/>
</span>
</span>
</DocumentFragment>
`;
exports[`checking if the page should be visible (based on indicator count) when indicator count is loaded and there are no indicators should render empty page when no indicators are found: no indicators 1`] = `
<DocumentFragment>
<div
class="euiPanel euiPanel--transparent euiEmptyPrompt euiEmptyPrompt--horizontal euiEmptyPrompt--paddingLarge emotion-euiPanel-m-transparent"
data-test-subj="tiEmptyPage"
>
<div
class="euiEmptyPrompt__main"
>
<div
class="euiEmptyPrompt__icon"
>
<figure
class="euiImageWrapper emotion-euiImageWrapper-fullWidth"
>
<img
alt="Enable Threat Intelligence Integrations"
class="euiImage emotion-euiImage-fullWidth"
src="test-file-stub"
/>
</figure>
</div>
<div
class="euiEmptyPrompt__content"
>
<div
class="euiEmptyPrompt__contentInner"
>
<h3
class="euiTitle emotion-euiTitle-s"
>
Get started with Elastic Threat Intelligence
</h3>
<div
class="euiSpacer euiSpacer--m emotion-euiSpacer-m"
/>
<div
class="euiText emotion-euiText-m-euiTextColor-subdued"
>
<p>
Elastic Threat Intelligence makes it easy to analyze and investigate potential security threats by aggregating data from multiple sources in one place.
</p>
<p>
Youll be able to view data from all activated threat intelligence feeds and take action from this page.
</p>
<p>
To get started with Elastic Threat Intelligence, enable one or more Threat Intelligence Integrations from the Integrations page or ingest data using filebeat. For more information, view the
<a
class="euiLink emotion-euiLink-primary"
data-test-subj="tiEmptyPageDocsLink"
href=""
rel="noopener noreferrer"
target="_blank"
>
Security app documentation
<span
class="emotion-euiLink__externalIcon"
data-euiicon-type="popout"
>
External link
</span>
<span
class="emotion-euiScreenReaderOnly-euiLink__screenReaderText"
>
(opens in a new tab or window)
</span>
</a>
.
</p>
</div>
<div
class="euiSpacer euiSpacer--l emotion-euiSpacer-l"
/>
<button
class="euiButton euiButton--primary euiButton--fill"
data-test-subj="tiEmptyPageIntegrationsPageLink"
type="button"
>
<span
class="euiButtonContent euiButton__content"
>
<span
class="euiButtonContent__icon"
color="inherit"
data-euiicon-type="plusInCircle"
/>
<span
class="euiButton__text"
>
Add Integrations
</span>
</span>
</button>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`checking if the page should be visible (based on indicator count) when loading is done and we have some indicators should render indicators table: indicators are present 1`] = `
<DocumentFragment>
should be restricted
</DocumentFragment>
`;

View file

@ -0,0 +1,8 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export * from './integrations_guard';

View file

@ -0,0 +1,81 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { render } from '@testing-library/react';
import React from 'react';
import { IntegrationsGuard } from '.';
import { TestProvidersComponent } from '../../common/mocks/test_providers';
import { useTIDocumentationLink } from '../../hooks/use_documentation_link';
import { useIntegrationsPageLink } from '../../hooks/use_integrations_page_link';
import { useIndicatorsTotalCount } from '../../modules/indicators/hooks/use_indicators_total_count';
jest.mock('../../modules/indicators/hooks/use_indicators_total_count');
jest.mock('../../hooks/use_integrations_page_link');
jest.mock('../../hooks/use_documentation_link');
describe('checking if the page should be visible (based on indicator count)', () => {
describe('when indicator count is being loaded', () => {
it('should render nothing at all', () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 0,
isLoading: true,
});
(
useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink>
).mockReturnValue('');
(
useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>
).mockReturnValue('');
const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, {
wrapper: TestProvidersComponent,
});
expect(asFragment()).toMatchSnapshot('loading');
});
});
describe('when indicator count is loaded and there are no indicators', () => {
it('should render empty page when no indicators are found', async () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 0,
isLoading: false,
});
(
useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink>
).mockReturnValue('');
(
useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>
).mockReturnValue('');
const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, {
wrapper: TestProvidersComponent,
});
expect(asFragment()).toMatchSnapshot('no indicators');
});
});
describe('when loading is done and we have some indicators', () => {
it('should render indicators table', async () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 7,
isLoading: false,
});
const { asFragment } = render(<IntegrationsGuard>should be restricted</IntegrationsGuard>, {
wrapper: TestProvidersComponent,
});
expect(asFragment()).toMatchSnapshot('indicators are present');
});
});
});

View file

@ -0,0 +1,33 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { EuiLoadingLogo } from '@elastic/eui';
import React from 'react';
import { FC } from 'react';
import { EmptyPage } from '../../modules/empty_page';
import { useIndicatorsTotalCount } from '../../modules/indicators/hooks/use_indicators_total_count';
import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper';
/**
* Renders children only if TI integrations are enabled
*/
export const IntegrationsGuard: FC = ({ children }) => {
const { count: indicatorsTotalCount, isLoading: isIndicatorsTotalCountLoading } =
useIndicatorsTotalCount();
if (isIndicatorsTotalCountLoading) {
return (
<SecuritySolutionPluginTemplateWrapper template="noData">
<EuiLoadingLogo logo="logoSecurity" size="xl" />
</SecuritySolutionPluginTemplateWrapper>
);
}
const showEmptyPage = indicatorsTotalCount === 0;
return showEmptyPage ? <EmptyPage /> : <>{children}</>;
};

View file

@ -0,0 +1,21 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import { FC } from 'react';
import { useSecurityContext } from '../hooks/use_security_context';
/**
* Security solution page wrapper, with some extra styling etc.
*/
export const SecuritySolutionPageWrapper: FC = ({ children }) => {
const contextValue = useSecurityContext();
const Component = contextValue.getPageWrapper();
return <Component>{children}</Component>;
};

View file

@ -0,0 +1,37 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import React from 'react';
import type { FC } from 'react';
import type { KibanaPageTemplateProps } from '@kbn/shared-ux-page-kibana-template';
import { useKibana } from '../hooks/use_kibana';
interface SecuritySolutionPluginTemplateWrapperProps {
/**
* Accepts all the values from KibanaPageTemplate, as well as `noData` which centers the page contents.
*/
template?: KibanaPageTemplateProps['template'] | 'noData';
}
/**
* Uses securityLayout service to retrieve shared plugin wrapper component and renders plugin routes / children inside of it.
*
* The `template` prop can be used to alter the page layout for a given plugin route / all routes within a plugin - depending on the nesting.
*/
export const SecuritySolutionPluginTemplateWrapper: FC<
SecuritySolutionPluginTemplateWrapperProps
> = ({ children, template }) => {
const {
services: {
securityLayout: { getPluginWrapper },
},
} = useKibana();
const Wrapper = getPluginWrapper();
return <Wrapper template={template}>{children}</Wrapper>;
};

View file

@ -13,6 +13,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { useTIDocumentationLink } from '../../hooks/use_documentation_link';
import { useIntegrationsPageLink } from '../../hooks/use_integrations_page_link';
import illustration from './integrations_light.svg';
import { SecuritySolutionPluginTemplateWrapper } from '../../containers/security_solution_plugin_template_wrapper';
export const DOCS_LINK_TEST_ID = 'tiEmptyPageDocsLink';
export const EMPTY_PROMPT_TEST_ID = 'tiEmptyPage';
@ -23,82 +24,84 @@ export const EmptyPage: VFC = () => {
const documentationLink = useTIDocumentationLink();
return (
<EuiEmptyPrompt
icon={
<EuiImage
size="fullWidth"
alt={i18n.translate('xpack.threatIntelligence.common.emptyPage.imgAlt', {
defaultMessage: 'Enable Threat Intelligence Integrations',
})}
src={illustration}
/>
}
title={
<h3>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.title"
defaultMessage="Get started with Elastic Threat Intelligence"
<SecuritySolutionPluginTemplateWrapper template="noData">
<EuiEmptyPrompt
icon={
<EuiImage
size="fullWidth"
alt={i18n.translate('xpack.threatIntelligence.common.emptyPage.imgAlt', {
defaultMessage: 'Enable Threat Intelligence Integrations',
})}
src={illustration}
/>
</h3>
}
titleSize="s"
layout="horizontal"
color="transparent"
body={
<>
<p>
}
title={
<h3>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body1"
defaultMessage="Elastic Threat Intelligence makes it easy to analyze and investigate potential security
id="xpack.threatIntelligence.common.emptyPage.title"
defaultMessage="Get started with Elastic Threat Intelligence"
/>
</h3>
}
titleSize="s"
layout="horizontal"
color="transparent"
body={
<>
<p>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body1"
defaultMessage="Elastic Threat Intelligence makes it easy to analyze and investigate potential security
threats by aggregating data from multiple sources in one place."
/>
</p>
<p>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body2"
defaultMessage="Youll be able to view data from all activated threat intelligence feeds and take action
/>
</p>
<p>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body2"
defaultMessage="Youll be able to view data from all activated threat intelligence feeds and take action
from this page."
/>
</p>
<p>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body3"
defaultMessage="To get started with Elastic Threat Intelligence, enable one or more Threat Intelligence
/>
</p>
<p>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.body3"
defaultMessage="To get started with Elastic Threat Intelligence, enable one or more Threat Intelligence
Integrations from the Integrations page or ingest data using filebeat. For more
information, view the {docsLink}."
values={{
docsLink: (
<EuiLink
href={documentationLink}
target="_blank"
data-test-subj={DOCS_LINK_TEST_ID}
>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.docsLinkText"
defaultMessage="Security app documentation"
/>
</EuiLink>
),
}}
values={{
docsLink: (
<EuiLink
href={documentationLink}
target="_blank"
data-test-subj={DOCS_LINK_TEST_ID}
>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.docsLinkText"
defaultMessage="Security app documentation"
/>
</EuiLink>
),
}}
/>
</p>
</>
}
actions={
<EuiButton
data-test-subj={INTEGRATION_LINK_ID}
href={integrationsPageLink}
color="primary"
iconType="plusInCircle"
fill
>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.buttonText"
defaultMessage="Add Integrations"
/>
</p>
</>
}
actions={
<EuiButton
data-test-subj={INTEGRATION_LINK_ID}
href={integrationsPageLink}
color="primary"
iconType="plusInCircle"
fill
>
<FormattedMessage
id="xpack.threatIntelligence.common.emptyPage.buttonText"
defaultMessage="Add Integrations"
/>
</EuiButton>
}
data-test-subj={EMPTY_PROMPT_TEST_ID}
/>
</EuiButton>
}
data-test-subj={EMPTY_PROMPT_TEST_ID}
/>
</SecuritySolutionPluginTemplateWrapper>
);
};

View file

@ -12,7 +12,7 @@ import {
isCompleteResponse,
} from '@kbn/data-plugin/common';
import { useKibana } from '../../../hooks/use_kibana';
import { RawIndicatorsResponse } from './use_indicators';
import type { RawIndicatorsResponse } from './use_indicators';
import { useSourcererDataView } from './use_sourcerer_data_view';
export const useIndicatorsTotalCount = () => {
@ -24,7 +24,7 @@ export const useIndicatorsTotalCount = () => {
const [count, setCount] = useState<number>(0);
const [isLoading, setIsLoading] = useState<boolean>(true);
const { selectedPatterns } = useSourcererDataView();
const { selectedPatterns, loading: loadingDataView } = useSourcererDataView();
useEffect(() => {
const query = {
@ -71,5 +71,5 @@ export const useIndicatorsTotalCount = () => {
});
}, [searchService, selectedPatterns]);
return { count, isLoading };
return { count, isLoading: isLoading || loadingDataView };
};

View file

@ -7,28 +7,16 @@
import React from 'react';
import { render } from '@testing-library/react';
import { TestProvidersComponent } from '../../common/mocks/test_providers';
import { IndicatorsPage } from './indicators_page';
import { useIndicators } from './hooks/use_indicators';
import { useIndicatorsTotalCount } from './hooks/use_indicators_total_count';
import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
import {
TABLE_TEST_ID as INDICATORS_TABLE_TEST_ID,
TABLE_TEST_ID,
} from './components/indicators_table/indicators_table';
import { EMPTY_PROMPT_TEST_ID } from '../empty_page';
import { useIntegrationsPageLink } from '../../hooks/use_integrations_page_link';
import { useTIDocumentationLink } from '../../hooks/use_documentation_link';
import { useFilters } from './hooks/use_filters';
import moment from 'moment';
import { TestProvidersComponent } from '../../common/mocks/test_providers';
import { TABLE_TEST_ID } from './components/indicators_table';
jest.mock('./hooks/use_indicators');
jest.mock('./hooks/use_indicators_total_count');
jest.mock('./hooks/use_filters');
jest.mock('../../hooks/use_integrations_page_link');
jest.mock('../../hooks/use_documentation_link');
jest.mock('./hooks/use_aggregated_indicators');
const stub = () => {};
@ -64,65 +52,19 @@ describe('<IndicatorsPage />', () => {
});
});
describe('checking if the page should be visible (based on indicator count)', () => {
describe('when indicator count is being loaded', () => {
it('should render nothing at all', () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 0,
isLoading: true,
});
(
useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink>
).mockReturnValue('');
(
useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>
).mockReturnValue('');
it('should render the table', () => {
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
expect(queryByTestId(EMPTY_PROMPT_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(TABLE_TEST_ID)).not.toBeInTheDocument();
});
});
describe('when indicator count is loaded and there are no indicators', () => {
it('should render empty page when no indicators are found', async () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 0,
isLoading: false,
});
(
useIntegrationsPageLink as jest.MockedFunction<typeof useIntegrationsPageLink>
).mockReturnValue('');
(
useTIDocumentationLink as jest.MockedFunction<typeof useTIDocumentationLink>
).mockReturnValue('');
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
expect(queryByTestId(TABLE_TEST_ID)).not.toBeInTheDocument();
expect(queryByTestId(EMPTY_PROMPT_TEST_ID)).toBeInTheDocument();
});
});
expect(queryByTestId(TABLE_TEST_ID)).toBeInTheDocument();
});
describe('when loading is done and we have some indicators', () => {
it('should render indicators table', async () => {
(
useIndicatorsTotalCount as jest.MockedFunction<typeof useIndicatorsTotalCount>
).mockReturnValue({
count: 7,
isLoading: false,
});
it('should render the query input', () => {
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
expect(queryByTestId('iocListPageQueryInput')).toBeInTheDocument();
});
const { queryByTestId } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
expect(queryByTestId(INDICATORS_TABLE_TEST_ID)).toBeInTheDocument();
expect(queryByTestId(EMPTY_PROMPT_TEST_ID)).not.toBeInTheDocument();
});
it('should render stack by selector', () => {
const { queryByText } = render(<IndicatorsPage />, { wrapper: TestProvidersComponent });
expect(queryByText('Stack by')).toBeInTheDocument();
});
});

View file

@ -9,8 +9,6 @@ import React, { VFC } from 'react';
import { IndicatorsBarChartWrapper } from './components/indicators_barchart_wrapper/indicators_barchart_wrapper';
import { IndicatorsTable } from './components/indicators_table/indicators_table';
import { useIndicators } from './hooks/use_indicators';
import { EmptyPage } from '../empty_page';
import { useIndicatorsTotalCount } from './hooks/use_indicators_total_count';
import { DefaultPageLayout } from '../../components/layout';
import { useFilters } from './hooks/use_filters';
import { FiltersGlobal } from '../../containers/filters_global';
@ -18,9 +16,6 @@ import QueryBar from './components/query_bar';
import { useSourcererDataView } from './hooks/use_sourcerer_data_view';
export const IndicatorsPage: VFC = () => {
const { count: indicatorsTotalCount, isLoading: isIndicatorsTotalCountLoading } =
useIndicatorsTotalCount();
const { browserFields, indexPattern } = useSourcererDataView();
const {
@ -40,18 +35,7 @@ export const IndicatorsPage: VFC = () => {
timeRange,
});
// This prevents indicators table flash when total count is loading.
// TODO: Improve this with custom loader component. It would require changes to security solutions' template wrapper - to allow
// 'template' overrides.
if (isIndicatorsTotalCountLoading) {
return null;
}
const showEmptyPage = indicatorsTotalCount === 0;
return showEmptyPage ? (
<EmptyPage />
) : (
return (
<DefaultPageLayout pageTitle="Indicators">
<FiltersGlobal>
<QueryBar

View file

@ -7,7 +7,7 @@
import { CoreStart, Plugin } from '@kbn/core/public';
import { Storage } from '@kbn/kibana-utils-plugin/public';
import React, { Suspense } from 'react';
import React, { Suspense, VFC } from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from './hooks/use_kibana';
import {
@ -19,6 +19,8 @@ import {
} from './types';
import { SecuritySolutionContext } from './containers/security_solution_context';
import { EnterpriseGuard } from './containers/enterprise_guard';
import { SecuritySolutionPluginTemplateWrapper } from './containers/security_solution_plugin_template_wrapper';
import { IntegrationsGuard } from './containers/integrations_guard';
interface AppProps {
securitySolutionContext: SecuritySolutionPluginContext;
@ -26,6 +28,14 @@ interface AppProps {
const LazyIndicatorsPage = React.lazy(() => import('./modules/indicators/indicators_page'));
const IndicatorsPage: VFC = () => (
<SecuritySolutionPluginTemplateWrapper>
<Suspense fallback={<div />}>
<LazyIndicatorsPage />
</Suspense>
</SecuritySolutionPluginTemplateWrapper>
);
/**
* This is used here:
* x-pack/plugins/security_solution/public/threat_intelligence/pages/threat_intelligence.tsx
@ -37,13 +47,13 @@ export const createApp =
(
<IntlProvider>
<SecuritySolutionContext.Provider value={securitySolutionContext}>
<EnterpriseGuard>
<KibanaContextProvider services={services}>
<Suspense fallback={<div />}>
<LazyIndicatorsPage />
</Suspense>
</KibanaContextProvider>
</EnterpriseGuard>
<KibanaContextProvider services={services}>
<EnterpriseGuard>
<IntegrationsGuard>
<IndicatorsPage />
</IntegrationsGuard>
</EnterpriseGuard>
</KibanaContextProvider>
</SecuritySolutionContext.Provider>
</IntlProvider>
);

View file

@ -42,6 +42,7 @@ export type Services = {
dataViews: DataViewsPublicPluginStart;
triggersActionsUi: TriggersActionsStart;
timelines: TimelinesUIStart;
securityLayout: any;
} & CoreStart;
export interface LicenseAware {
@ -54,6 +55,7 @@ export interface SourcererDataView {
indexPattern: SecuritySolutionDataViewBase;
browserFields: BrowserFields;
selectedPatterns: string[];
loading: boolean;
}
/**
@ -64,6 +66,12 @@ export interface SecuritySolutionPluginContext {
* Gets the `FiltersGlobal` component for embedding a filter bar in the security solution application.
* */
getFiltersGlobalComponent: () => ComponentType<{ children: ReactNode }>;
/**
* Gets the `PageWrapper` component for embedding a filter bar in the security solution application.
* */
getPageWrapper: () => ComponentType<{ children: ReactNode }>;
/**
* Get the user's license to drive the Threat Intelligence plugin's visibility.
*/