Skip to content

Commit

Permalink
Merge branch 'master' of github.com:elastic/kibana into rule-data-plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
dgieselaar committed May 12, 2021
2 parents 3eac1ab + 0b4c957 commit 6e84ed5
Show file tree
Hide file tree
Showing 11 changed files with 205 additions and 14 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

import { i18n } from '@kbn/i18n';
import moment from 'moment';
import { showOpenSearchPanel } from './show_open_search_panel';
import { getSharingData, showPublicUrlSwitch } from '../../helpers/get_sharing_data';
import { unhashUrl } from '../../../../../kibana_utils/public';
Expand Down Expand Up @@ -122,7 +123,13 @@ export const getTopNavLinks = ({
objectType: 'search',
sharingData: {
...sharingData,
title: savedSearch.title,
// CSV reports can be generated without a saved search so we provide a fallback title
title:
savedSearch.title ||
i18n.translate('discover.localMenu.fallbackReportTitle', {
defaultMessage: 'Discover search [{date}]',
values: { date: moment().toISOString(true) },
}),
},
isDirty: !savedSearch.id || state.isAppStateDirty(),
showPublicUrlSwitch,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* 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 { mountWithIntl } from '@kbn/test/jest';
import { notificationServiceMock } from 'src/core/public/mocks';

import { ReportingPanelContent, Props } from './reporting_panel_content';

describe('ReportingPanelContent', () => {
const mountComponent = (props: Partial<Props>) =>
mountWithIntl(
<ReportingPanelContent
requiresSavedState
// We have unsaved changes
isDirty={true}
reportType="test"
layoutId="test"
getJobParams={jest.fn().mockReturnValue({})}
objectId={'my-object-id'}
apiClient={{ getReportingJobPath: () => 'test' } as any}
toasts={notificationServiceMock.createSetupContract().toasts}
{...props}
/>
);
describe('saved state', () => {
it('prevents generating reports when saving is required and we have unsaved changes', () => {
const wrapper = mountComponent({
requiresSavedState: true,
isDirty: true,
objectId: undefined,
});
wrapper.update();
expect(wrapper.find('[data-test-subj="generateReportButton"]').last().props().disabled).toBe(
true
);
});

it('allows generating reports when saving is not required', () => {
const wrapper = mountComponent({
requiresSavedState: false,
isDirty: true,
objectId: undefined,
});
wrapper.update();
expect(wrapper.find('[data-test-subj="generateReportButton"]').last().props().disabled).toBe(
false
);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@ export interface Props {
apiClient: ReportingAPIClient;
toasts: ToastsSetup;
reportType: string;

/**
* Whether the report to be generated requires saved state that is not captured in the URL submitted to the report generator.
*/
requiresSavedState: boolean;
layoutId: string | undefined;
objectId?: string;
getJobParams: () => BaseParams;
Expand Down Expand Up @@ -85,7 +90,10 @@ class ReportingPanelContentUi extends Component<Props, State> {
}

public render() {
if (this.isNotSaved() || this.props.isDirty || this.state.isStale) {
if (
this.props.requiresSavedState &&
(this.isNotSaved() || this.props.isDirty || this.state.isStale)
) {
return (
<EuiForm className="kbnShareContextMenu__finalPanel" data-test-subj="shareReportingForm">
<EuiFormRow
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export class ScreenCapturePanelContent extends Component<Props, State> {
public render() {
return (
<ReportingPanelContent
requiresSavedState
apiClient={this.props.apiClient}
toasts={this.props.toasts}
reportType={this.props.reportType}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export const ReportingCsvShareProvider = ({
title: panelTitle,
content: (
<ReportingPanelContent
requiresSavedState={false}
apiClient={apiClient}
toasts={toasts}
reportType={CSV_JOB_TYPE}
Expand Down
8 changes: 4 additions & 4 deletions x-pack/plugins/reporting/server/lib/layouts/print.css
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,12 @@ discover-app .discover-table-footer {
* Dashboard tweaks
*/

.dshAppContainer .embPanel__header {
.dashboardViewport .embPanel__header {
/* hide the panel heading with the controls and title */
display: none !important
display: none !important;
}

.dshAppContainer .euiPanel {
.dashboardViewport .euiPanel {
/* Remove the border from the eui panel */
border: none !important;
}
Expand All @@ -114,7 +114,7 @@ discover-app .discover-table-footer {
* 2. React grid item's transform affects the visualizations, even when they are using fixed positioning. Chrome seems
* to handle this fine, but firefox moves the visualizations around.
*/
.dshAppContainer .react-grid-item {
.dashboardViewport .react-grid-item {
height: 0 !important; /* 1. */
width: 0 !important; /* 1. */
transform: none !important; /* 2. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ import {
ALERT_RULE_VERSION,
NUMBER_OF_ALERTS,
} from '../../screens/alerts';
import { JSON_LINES } from '../../screens/alerts_details';
import {
JSON_LINES,
TABLE_CELL,
TABLE_ROWS,
THREAT_CONTENT,
THREAT_DETAILS_VIEW,
THREAT_INTEL_TAB,
THREAT_SUMMARY_VIEW,
TITLE,
} from '../../screens/alerts_details';
import {
CUSTOM_RULES_BTN,
RISK_SCORE,
Expand Down Expand Up @@ -63,7 +72,11 @@ import {
waitForAlertsIndexToBeCreated,
waitForAlertsPanelToBeLoaded,
} from '../../tasks/alerts';
import { openJsonView, scrollJsonViewToBottom } from '../../tasks/alerts_details';
import {
openJsonView,
openThreatIndicatorDetails,
scrollJsonViewToBottom,
} from '../../tasks/alerts_details';
import {
changeRowsPerPageTo300,
duplicateFirstRule,
Expand Down Expand Up @@ -585,6 +598,90 @@ describe('indicator match', () => {
});
});
});

it('Displays threat summary data on alerts details', () => {
const expectedThreatSummary = [
{ field: 'matched.field', value: 'myhash.mysha256' },
{ field: 'matched.type', value: 'file' },
{ field: 'first_seen', value: '2021-03-10T08:02:14.000Z' },
];

expandFirstAlert();

cy.get(THREAT_SUMMARY_VIEW).within(() => {
cy.get(TABLE_ROWS).should('have.length', expectedThreatSummary.length);
expectedThreatSummary.forEach((row, index) => {
cy.get(TABLE_ROWS)
.eq(index)
.within(() => {
cy.get(TITLE).should('have.text', row.field);
cy.get(THREAT_CONTENT).should('have.text', row.value);
});
});
});
});

it('Displays threat indicator data on the threat intel tab', () => {
const expectedThreatIndicatorData = [
{ field: 'first_seen', value: '2021-03-10T08:02:14.000Z' },
{ field: 'file.size', value: '80280' },
{ field: 'file.type', value: 'elf' },
{
field: 'file.hash.sha256',
value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
},
{
field: 'file.hash.tlsh',
value: '6D7312E017B517CC1371A8353BED205E9128223972AE35302E97528DF957703BAB2DBE',
},
{
field: 'file.hash.ssdeep',
value:
'1536:87vbq1lGAXSEYQjbChaAU2yU23M51DjZgSQAvcYkFtZTjzBht5:8D+CAXFYQChaAUk5ljnQssL',
},
{ field: 'file.hash.md5', value: '9b6c3518a91d23ed77504b5416bfb5b3' },
{ field: 'type', value: 'file' },
{
field: 'event.reference',
value:
'https://urlhaus-api.abuse.ch/v1/download/a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3/(opens in a new tab or window)',
},
{ field: 'event.ingested', value: '2021-03-10T14:51:09.809069Z' },
{ field: 'event.created', value: '2021-03-10T14:51:07.663Z' },
{ field: 'event.kind', value: 'enrichment' },
{ field: 'event.module', value: 'threatintel' },
{ field: 'event.category', value: 'threat' },
{ field: 'event.type', value: 'indicator' },
{ field: 'event.dataset', value: 'threatintel.abusemalware' },
{
field: 'matched.atomic',
value: 'a04ac6d98ad989312783d4fe3456c53730b212c79a426fb215708b6c6daa3de3',
},
{ field: 'matched.field', value: 'myhash.mysha256' },
{
field: 'matched.id',
value: '84cf452c1e0375c3d4412cb550bd1783358468a3b3b777da4829d72c7d6fb74f',
},
{ field: 'matched.index', value: 'filebeat-7.12.0-2021.03.10-000001' },
{ field: 'matched.type', value: 'file' },
];

expandFirstAlert();
openThreatIndicatorDetails();

cy.get(THREAT_INTEL_TAB).should('have.text', 'Threat Intel (1)');
cy.get(THREAT_DETAILS_VIEW).within(() => {
cy.get(TABLE_ROWS).should('have.length', expectedThreatIndicatorData.length);
expectedThreatIndicatorData.forEach((row, index) => {
cy.get(TABLE_ROWS)
.eq(index)
.within(() => {
cy.get(TABLE_CELL).eq(0).should('have.text', row.field);
cy.get(TABLE_CELL).eq(1).should('have.text', row.value);
});
});
});
});
});

describe('Duplicates the indicator rule', () => {
Expand Down
12 changes: 12 additions & 0 deletions x-pack/plugins/security_solution/cypress/screens/alerts_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,18 @@ export const JSON_LINES = '.ace_line';

export const JSON_VIEW_TAB = '[data-test-subj="jsonViewTab"]';

export const TABLE_CELL = '.euiTableRowCell';

export const TABLE_TAB = '[data-test-subj="tableTab"]';

export const TABLE_ROWS = '.euiTableRow';

export const THREAT_CONTENT = '[data-test-subj^=draggable-content-threat]';

export const THREAT_DETAILS_VIEW = '[data-test-subj="threat-details-view-0"]';

export const THREAT_INTEL_TAB = '[data-test-subj="threatIntelTab"]';

export const THREAT_SUMMARY_VIEW = '[data-test-subj="threat-summary-view"]';

export const TITLE = '.euiTitle';
11 changes: 10 additions & 1 deletion x-pack/plugins/security_solution/cypress/tasks/alerts_details.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@
* 2.0.
*/

import { JSON_CONTENT, JSON_VIEW_TAB, TABLE_TAB } from '../screens/alerts_details';
import {
JSON_CONTENT,
JSON_VIEW_TAB,
TABLE_TAB,
THREAT_INTEL_TAB,
} from '../screens/alerts_details';

export const openJsonView = () => {
cy.get(JSON_VIEW_TAB).click();
Expand All @@ -15,6 +20,10 @@ export const openTable = () => {
cy.get(TABLE_TAB).click();
};

export const openThreatIndicatorDetails = () => {
cy.get(THREAT_INTEL_TAB).click();
};

export const scrollJsonViewToBottom = () => {
cy.get(JSON_CONTENT).click({ force: true });
cy.get(JSON_CONTENT).type('{pagedown}{pagedown}{pagedown}');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ const EventDetailsComponent: React.FC<Props> = ({
isAlert
? {
id: EventsViewType.threatIntelView,
'data-test-subj': 'threatIntelTab',
name: `${i18n.THREAT_INTEL} (${threatCount})`,
content: <ThreatDetailsView threatData={threatData} />,
}
Expand Down
10 changes: 5 additions & 5 deletions x-pack/test/functional/apps/discover/reporting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
describe('Check Available', () => {
beforeEach(() => PageObjects.common.navigateToApp('discover'));

it('is not available if new', async () => {
it('is available if new', async () => {
await PageObjects.reporting.openCsvReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});

it('becomes available when saved', async () => {
Expand All @@ -52,19 +52,19 @@ export default function ({ getService, getPageObjects }: FtrProviderContext) {
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
});

it('becomes available/not available when a saved search is created, changed and saved again', async () => {
it('remains available regardless of the saved search state', async () => {
// create new search, csv export is not available
await PageObjects.discover.clickNewSearchButton();
await PageObjects.reporting.openCsvReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
// save search, csv export is available
await PageObjects.discover.saveSearch('my search - expectEnabledGenerateReportButton 2');
await PageObjects.reporting.openCsvReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
// add filter, csv export is not available
await filterBar.addFilter('currency', 'is', 'EUR');
await PageObjects.reporting.openCsvReportingPanel();
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be('true');
expect(await PageObjects.reporting.isGenerateReportButtonDisabled()).to.be(null);
// save search again, csv export is available
await PageObjects.discover.saveSearch('my search - expectEnabledGenerateReportButton 2');
await PageObjects.reporting.openCsvReportingPanel();
Expand Down

0 comments on commit 6e84ed5

Please sign in to comment.