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

[CTI] Threat Intel Card on Overview page needs to accommodate Fleet TI integrations #115940

Merged
merged 37 commits into from
Dec 3, 2021
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
30777b7
Add support integrations
nkhristinin Oct 21, 2021
25e8cf4
Fix types
nkhristinin Oct 21, 2021
5beebc7
fix unit tests
nkhristinin Oct 21, 2021
703068c
Fix tests and types
nkhristinin Oct 24, 2021
81f6275
fix eslint
nkhristinin Oct 24, 2021
9a99da3
fix file case
nkhristinin Oct 25, 2021
2a987b6
add cy tests
nkhristinin Oct 25, 2021
084564b
Revert test
nkhristinin Oct 25, 2021
602e19d
Add tests
nkhristinin Oct 25, 2021
5e30a63
Add support of installed integrations
nkhristinin Oct 26, 2021
5521224
Fix types
nkhristinin Oct 26, 2021
fe6aa52
Add isntalled ingtegration case for cypress tests
nkhristinin Oct 26, 2021
55b4d40
Fix cypress tests
nkhristinin Oct 26, 2021
52a2d42
Fix comments
nkhristinin Oct 27, 2021
6006c59
Fix capital naming
nkhristinin Oct 27, 2021
7043195
Fix again capital naming
nkhristinin Oct 27, 2021
bbdbd01
Add dynamic dashboard for a new integrations packages
nkhristinin Nov 15, 2021
639ed73
intermidiate changes, to keep it remote
nkhristinin Nov 19, 2021
9cdd203
Big refactoring
nkhristinin Nov 22, 2021
45c0c5e
Tests and refactoring
nkhristinin Nov 23, 2021
55d0100
Remove unused constanrs
nkhristinin Nov 23, 2021
f2cbd0f
Fix e2e tests
nkhristinin Nov 24, 2021
d4eda79
PR comments fix
nkhristinin Nov 24, 2021
6c710ef
fix ts
nkhristinin Nov 24, 2021
3b93684
Fix translations
nkhristinin Nov 25, 2021
53bb048
Merge branch 'main' into cti-card-ecs
kibanamachine Nov 29, 2021
8c8fbc5
Merge branch 'main' into cti-card-ecs
kibanamachine Nov 30, 2021
a62193e
Remove stubs
nkhristinin Nov 30, 2021
1d2bb14
Rename isSomeIntegrationsDisabled -> allIntegrationsInstalled
nkhristinin Nov 30, 2021
f563313
Add buildQuery tests
nkhristinin Nov 30, 2021
b30e143
Fix type
nkhristinin Nov 30, 2021
2522dc8
Add tests for Enable Source button
nkhristinin Nov 30, 2021
b618acf
Remove copied file
nkhristinin Dec 1, 2021
b61b368
Move api call to api.ts
nkhristinin Dec 1, 2021
a3a2e7f
Rename fetchFleetIntegrations
nkhristinin Dec 2, 2021
3c16a96
Remove __mocks__
nkhristinin Dec 2, 2021
17ec4ce
Merge branch 'main' into cti-card-ecs
kibanamachine Dec 2, 2021
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 @@ -47,7 +47,6 @@ export const isValidEventField = (field: string): field is EventField =>
export interface CtiDataSourceRequestOptions extends IEsSearchRequest {
defaultIndex: string[];
factoryQueryType?: FactoryQueryTypes;
hostName?: string;
timerange?: TimerangeInput;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,6 @@ describe('CTI Link Panel', () => {
});

it('renders dashboard module as expected when there are events in the selected time period', () => {
cy.intercept('GET', '/api/fleet/epm/packages*', {
response: [
{
name: 'ti_abusech',
title: 'AbuseCH',
id: 'ti_abusech',
status: 'installed',
},
{
name: 'ti_anomali',
title: 'Anomali',
id: 'ti_anomali',
status: 'not_installed',
},
],
});

loginAndWaitForPage(OVERVIEW_URL);
cy.get(`${OVERVIEW_CTI_LINKS} ${OVERVIEW_CTI_LINKS_INFO_INNER_PANEL}`).should('exist');
cy.get(`${OVERVIEW_CTI_LINKS} ${OVERVIEW_CTI_ENABLE_INTEGRATIONS_BUTTON}`).should('exist');
Expand All @@ -77,31 +60,4 @@ describe('CTI Link Panel', () => {
cy.get(`${OVERVIEW_CTI_TOTAL_EVENT_COUNT}`).should('have.text', 'Showing: 1 indicator');
});
});

describe('all integrations installed', () => {
before(() => {
esArchiverLoad('threat_indicator');
cy.intercept('GET', '/api/fleet/epm/packages*', {
response: [
{
name: 'ti_abusech',
title: 'AbuseCH',
id: 'ti_abusech',
status: 'installed',
},
],
});
});

after(() => {
esArchiverUnload('threat_indicator');
});

it('render cti dashboard without enable integrations button', () => {
loginAndWaitForPage(OVERVIEW_URL);
cy.get(`${OVERVIEW_CTI_LINKS} ${OVERVIEW_CTI_ENABLE_INTEGRATIONS_BUTTON}`).should(
'not.exist'
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,5 @@ export interface LinkPanelViewProps {
listItems: LinkPanelListItem[];
splitPanel?: JSX.Element;
totalCount?: number;
isSomeIntegrationsDisabled?: boolean;
allIntegrationsInstalled?: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('CtiEnabledModule', () => {
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<CtiEnabledModule {...mockProps} isSomeIntegrationsDisabled={true} />
<CtiEnabledModule {...mockProps} allIntegrationsInstalled={true} />
</ThemeProvider>
</I18nProvider>
</Provider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { useCtiDashboardLinks } from '../../containers/overview_cti_links';
import { ThreatIntelPanelView } from './threat_intel_panel_view';

export const CtiEnabledModuleComponent: React.FC<ThreatIntelLinkPanelProps> = (props) => {
const { to, from, isSomeIntegrationsDisabled, allTiDataSources, setQuery, deleteQuery } = props;
const { to, from, allIntegrationsInstalled, allTiDataSources, setQuery, deleteQuery } = props;
const { tiDataSources, totalCount } = useTiDataSources({
to,
from,
Expand All @@ -26,7 +26,7 @@ export const CtiEnabledModuleComponent: React.FC<ThreatIntelLinkPanelProps> = (p
<ThreatIntelPanelView
listItems={listItems}
totalCount={totalCount}
isSomeIntegrationsDisabled={isSomeIntegrationsDisabled}
allIntegrationsInstalled={allIntegrationsInstalled}
/>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,27 @@ describe('ThreatIntelLinkPanel', () => {
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<ThreatIntelLinkPanel {...mockProps} isSomeIntegrationsDisabled={true} />
<ThreatIntelLinkPanel {...mockProps} allIntegrationsInstalled={true} />
</ThemeProvider>
</I18nProvider>
</Provider>
);

expect(wrapper.find('[data-test-subj="cti-enabled-module"]').length).toEqual(1);
expect(wrapper.find('[data-test-subj="cti-enable-integrations-button"]').length).toEqual(0);
});

it('renders Enable source buttons when not all integrations installed', () => {
const wrapper = mount(
<Provider store={store}>
<I18nProvider>
<ThemeProvider theme={mockTheme}>
<ThreatIntelLinkPanel {...mockProps} allIntegrationsInstalled={false} />
</ThemeProvider>
</I18nProvider>
</Provider>
);
expect(wrapper.find('[data-test-subj="cti-enable-integrations-button"]').length).not.toBe(0);
});

it('renders CtiDisabledModule when Threat Intel module is disabled', () => {
Expand All @@ -66,7 +80,7 @@ describe('ThreatIntelLinkPanel', () => {
<ThreatIntelLinkPanel
{...mockProps}
allTiDataSources={[]}
isSomeIntegrationsDisabled={true}
allIntegrationsInstalled={true}
/>
</ThemeProvider>
</I18nProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,19 @@ export type ThreatIntelLinkPanelProps = Pick<
GlobalTimeArgs,
'from' | 'to' | 'deleteQuery' | 'setQuery'
> & {
isSomeIntegrationsDisabled: boolean | undefined;
allIntegrationsInstalled: boolean | undefined;
allTiDataSources: TiDataSources[];
};

const ThreatIntelLinkPanelComponent: React.FC<ThreatIntelLinkPanelProps> = (props) => {
const { isSomeIntegrationsDisabled, allTiDataSources } = props;
const { allIntegrationsInstalled, allTiDataSources } = props;
const isThreatIntelModuleEnabled = allTiDataSources.length > 0;
return isThreatIntelModuleEnabled ? (
Copy link
Contributor

Choose a reason for hiding this comment

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

this could create a flash in the UI where the disabled module view shows up until the enabled module view is loaded. to prevent the flash we could consider returning null, or a loading view.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, thank you right. I fixed it by providing isInitialyLoaded state.
But I do in on x-pack/plugins/security_solution/public/overview/pages/overview.tsx
We are also wait there for fleet packages API

<div data-test-subj="cti-enabled-module">
<CtiEnabledModule
{...props}
allTiDataSources={allTiDataSources}
isSomeIntegrationsDisabled={Boolean(isSomeIntegrationsDisabled)}
allIntegrationsInstalled={Boolean(allIntegrationsInstalled)}
/>
</div>
) : (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const mockProps = {
from: '2020-01-21T20:49:57.080Z',
setQuery: jest.fn(),
deleteQuery: jest.fn(),
isSomeIntegrationsDisabled: true,
allIntegrationsInstalled: true,
allTiDataSources: [
{ dataset: 'ti_abusech', name: 'AbuseCH', count: 5, path: '/dashboard_path_abuseurl' },
],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export const ThreatIntelPanelView: React.FC<LinkPanelViewProps> = ({
listItems,
splitPanel,
totalCount = 0,
isSomeIntegrationsDisabled,
allIntegrationsInstalled,
}) => {
const integrationsLink = useIntegrationsPageLink();

Expand All @@ -55,7 +55,7 @@ export const ThreatIntelPanelView: React.FC<LinkPanelViewProps> = ({
infoPanel: useMemo(
() => (
<>
{isSomeIntegrationsDisabled ? (
{allIntegrationsInstalled === false ? (
<InnerLinkPanel
dataTestSubj="cti-inner-panel-info"
color={'warning'}
Expand All @@ -75,7 +75,7 @@ export const ThreatIntelPanelView: React.FC<LinkPanelViewProps> = ({
) : null}
</>
),
[isSomeIntegrationsDisabled, integrationsLink]
[allIntegrationsInstalled, integrationsLink]
),
inspectQueryId: isInspectEnabled ? CTIEventCountQueryId : undefined,
listItems,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { KibanaServices } from '../../../common/lib/kibana';
import { EPM_API_ROUTES } from '../../../../../fleet/common';

export interface IntegrationResponse {
id: string;
status: string;
savedObject?: {
attributes?: {
installed_kibana: Array<{
type: string;
id: string;
}>;
};
};
}

export const fetchIntegrations = () =>
nkhristinin marked this conversation as resolved.
Show resolved Hide resolved
KibanaServices.get().http.fetch<{
response: IntegrationResponse[];
}>(EPM_API_ROUTES.LIST_PATTERN, {
method: 'GET',
});
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export const useAllTiDataSources = () => {
[]
);

const { tiDataSources, isInitialyLoaded } = useTiDataSources({ to, from });
const { tiDataSources, isInitiallyLoaded } = useTiDataSources({ to, from });

return { tiDataSources, isInitialyLoaded };
return { tiDataSources, isInitiallyLoaded };
};
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ export const useTiDataSources = ({
deleteQuery,
}: TiDataSourcesProps) => {
const [tiDataSources, setTiDataSources] = useState<TiDataSources[]>([]);
const [isInitialyLoaded, setIsInitialyLoaded] = useState(false);
const [isInitiallyLoaded, setIsInitiallyLoaded] = useState(false);
const { data, uiSettings } = useKibana().services;
const defaultThreatIndices = uiSettings.get<string[]>(DEFAULT_THREAT_INDEX_KEY);
const { result, start, loading } = useTiDataSourcesComplete();
Expand Down Expand Up @@ -117,10 +117,10 @@ export const useTiDataSources = ({
}, [deleteQuery]);

useEffect(() => {
if (result && !isInitialyLoaded) {
setIsInitialyLoaded(true);
if (result && !isInitiallyLoaded) {
setIsInitiallyLoaded(true);
}
}, [isInitialyLoaded, result]);
}, [isInitiallyLoaded, result]);

useEffect(() => {
if (!loading && result) {
Expand Down Expand Up @@ -170,5 +170,5 @@ export const useTiDataSources = ({

const totalCount = tiDataSources.reduce((acc, val) => acc + val.count, 0);

return { tiDataSources, totalCount, isInitialyLoaded };
return { tiDataSources, totalCount, isInitiallyLoaded };
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,18 @@
*/

import { useEffect, useState } from 'react';
import { KibanaServices } from '../../../common/lib/kibana';
import { EPM_API_ROUTES, installationStatuses } from '../../../../../fleet/common';
import { TI_INTEGRATION_PREFIX } from '../../../../common/cti/constants';

interface IntegrationResponse {
id: string;
status: string;
savedObject?: {
attributes?: {
installed_kibana: Array<{
type: string;
id: string;
}>;
};
};
}
import { installationStatuses } from '../../../../../fleet/common';
import { TI_INTEGRATION_PREFIX } from '../../../../common/cti/constants';
import { fetchIntegrations, IntegrationResponse } from './api';

export interface Integration {
id: string;
dashboardIds: string[];
}

interface TiIntegrationStatus {
isSomeIntegrationsDisabled: boolean;
allIntegrationsInstalled: boolean;
}

export const useTiIntegrations = () => {
Expand All @@ -40,26 +28,22 @@ export const useTiIntegrations = () => {
useEffect(() => {
const getPackages = async () => {
try {
const { response: integrations } = await KibanaServices.get().http.fetch<{
response: IntegrationResponse[];
}>(EPM_API_ROUTES.LIST_PATTERN, {
method: 'GET',
});
const { response: integrations } = await fetchIntegrations();
const tiIntegrations = integrations.filter((integration: IntegrationResponse) =>
integration.id.startsWith(TI_INTEGRATION_PREFIX)
);

const isSomeIntegrationsDisabled = tiIntegrations.some(
const allIntegrationsInstalled = tiIntegrations.every(
(integration: IntegrationResponse) =>
integration.status !== installationStatuses.Installed
integration.status === installationStatuses.Installed
);

setTiIntegrationsStatus({
isSomeIntegrationsDisabled,
allIntegrationsInstalled,
});
} catch (e) {
setTiIntegrationsStatus({
isSomeIntegrationsDisabled: true,
allIntegrationsInstalled: false,
});
}
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const OverviewComponent = () => {
endpointPrivileges: { canAccessFleet },
} = useUserPrivileges();
const { hasIndexRead, hasKibanaREAD } = useAlertsPrivileges();
const { tiDataSources: allTiDataSources, isInitialyLoaded: allTiDataSourcesLoaded } =
const { tiDataSources: allTiDataSources, isInitiallyLoaded: allTiDataSourcesLoaded } =
useAllTiDataSources();
const tiIntegrationStatus = useTiIntegrations();
const isTiLoaded = tiIntegrationStatus && allTiDataSourcesLoaded;
Expand Down Expand Up @@ -156,9 +156,7 @@ const OverviewComponent = () => {
<EuiFlexItem grow={1}>
{isTiLoaded && (
<ThreatIntelLinkPanel
isSomeIntegrationsDisabled={
tiIntegrationStatus?.isSomeIntegrationsDisabled
}
allIntegrationsInstalled={tiIntegrationStatus?.allIntegrationsInstalled}
allTiDataSources={allTiDataSources}
deleteQuery={deleteQuery}
from={from}
Expand Down
Loading