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

Sourcerer kip as pablo #5

Draft
wants to merge 14 commits into
base: sourcerer_kip_as
Choose a base branch
from
Draft
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
@@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [kibana-plugin-core-public](./kibana-plugin-core-public.md) &gt; [OverlayFlyoutOpenOptions](./kibana-plugin-core-public.overlayflyoutopenoptions.md) &gt; [maskProps](./kibana-plugin-core-public.overlayflyoutopenoptions.maskprops.md)

## OverlayFlyoutOpenOptions.maskProps property

<b>Signature:</b>

```typescript
maskProps?: EuiOverlayMaskProps;
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export interface OverlayFlyoutOpenOptions
| [className](./kibana-plugin-core-public.overlayflyoutopenoptions.classname.md) | <code>string</code> | |
| [closeButtonAriaLabel](./kibana-plugin-core-public.overlayflyoutopenoptions.closebuttonarialabel.md) | <code>string</code> | |
| [hideCloseButton](./kibana-plugin-core-public.overlayflyoutopenoptions.hideclosebutton.md) | <code>boolean</code> | |
| [maskProps](./kibana-plugin-core-public.overlayflyoutopenoptions.maskprops.md) | <code>EuiOverlayMaskProps</code> | |
| [maxWidth](./kibana-plugin-core-public.overlayflyoutopenoptions.maxwidth.md) | <code>boolean &#124; number &#124; string</code> | |
| [onClose](./kibana-plugin-core-public.overlayflyoutopenoptions.onclose.md) | <code>(flyout: OverlayRef) =&gt; void</code> | EuiFlyout onClose handler. If provided the consumer is responsible for calling flyout.close() to close the flyout; |
| [ownFocus](./kibana-plugin-core-public.overlayflyoutopenoptions.ownfocus.md) | <code>boolean</code> | |
Expand Down
3 changes: 2 additions & 1 deletion src/core/public/overlays/flyout/flyout_service.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

/* eslint-disable max-classes-per-file */

import { EuiFlyout, EuiFlyoutSize } from '@elastic/eui';
import { EuiFlyout, EuiFlyoutSize, EuiOverlayMaskProps } from '@elastic/eui';
import React from 'react';
import { render, unmountComponentAtNode } from 'react-dom';
import { Subject } from 'rxjs';
Expand Down Expand Up @@ -86,6 +86,7 @@ export interface OverlayFlyoutOpenOptions {
size?: EuiFlyoutSize;
maxWidth?: boolean | number | string;
hideCloseButton?: boolean;
maskProps?: EuiOverlayMaskProps;
/**
* EuiFlyout onClose handler.
* If provided the consumer is responsible for calling flyout.close() to close the flyout;
Expand Down
3 changes: 3 additions & 0 deletions src/core/public/public.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { EuiButtonEmptyProps } from '@elastic/eui';
import { EuiConfirmModalProps } from '@elastic/eui';
import { EuiFlyoutSize } from '@elastic/eui';
import { EuiGlobalToastListToast } from '@elastic/eui';
import { EuiOverlayMaskProps } from '@elastic/eui';
import { History } from 'history';
import { Href } from 'history';
import { IconType } from '@elastic/eui';
Expand Down Expand Up @@ -1048,6 +1049,8 @@ export interface OverlayFlyoutOpenOptions {
// (undocumented)
hideCloseButton?: boolean;
// (undocumented)
maskProps?: EuiOverlayMaskProps;
// (undocumented)
maxWidth?: boolean | number | string;
onClose?: (flyout: OverlayRef) => void;
// (undocumented)
Expand Down
3 changes: 3 additions & 0 deletions src/plugins/index_pattern_field_editor/public/open_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export const getFieldEditorOpener =
flyout.close();
}
},
maskProps: {
className: 'indexPatternFieldEditorMaskOverlay',
},
}
);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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 { cleanKibana } from '../../tasks/common';

import { loginAndWaitForPage } from '../../tasks/login';
import { openTimelineUsingToggle } from '../../tasks/security_main';
import { openTimelineFieldsBrowser, populateTimeline } from '../../tasks/timeline';

import { HOSTS_URL, ALERTS_URL } from '../../urls/navigation';

import { waitForAlertsIndexToBeCreated, waitForAlertsPanelToBeLoaded } from '../../tasks/alerts';
import { createCustomRuleActivated } from '../../tasks/api_calls/rules';

import { getNewRule } from '../../objects/rule';
import { refreshPage } from '../../tasks/security_header';
import { waitForAlertsToPopulate } from '../../tasks/create_new_rule';
import { openEventsViewerFieldsBrowser } from '../../tasks/hosts/events';

describe('Create DataView runtime field', () => {
before(() => {
cleanKibana();
});

it('adds field to alert table', () => {
const fieldName = 'field.name.alert.page';
loginAndWaitForPage(ALERTS_URL);
waitForAlertsPanelToBeLoaded();
waitForAlertsIndexToBeCreated();
createCustomRuleActivated(getNewRule());
refreshPage();
waitForAlertsToPopulate(500);
openEventsViewerFieldsBrowser();

cy.get('[data-test-subj="create-field"]').click();
cy.get('.indexPatternFieldEditorMaskOverlay').find('[data-test-subj="input"]').type(fieldName);
cy.get('[data-test-subj="fieldSaveButton"]').click();

cy.get(
`[data-test-subj="events-viewer-panel"] [data-test-subj="dataGridHeaderCell-${fieldName}"]`
).should('exist');
});

it('adds field to timeline', () => {
const fieldName = 'field.name.timeline';

loginAndWaitForPage(HOSTS_URL);
openTimelineUsingToggle();
populateTimeline();
openTimelineFieldsBrowser();

cy.get('[data-test-subj="create-field"]').click();
cy.get('.indexPatternFieldEditorMaskOverlay').find('[data-test-subj="input"]').type(fieldName);
cy.get('[data-test-subj="fieldSaveButton"]').click();

cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${fieldName}"]`).should(
'exist'
);
});
});
3 changes: 2 additions & 1 deletion x-pack/plugins/security_solution/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@
"lens",
"lists",
"home",
"telemetry"
"telemetry",
"indexPatternFieldEditor"
],
"server": true,
"ui": true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import React, { useCallback, useMemo, useEffect } from 'react';
import { connect, ConnectedProps, useDispatch } from 'react-redux';
import deepEqual from 'fast-deep-equal';
import styled from 'styled-components';

import { isEmpty } from 'lodash/fp';
import { inputsModel, inputsSelectors, State } from '../../store';
import { inputsActions } from '../../store/actions';
Expand All @@ -32,6 +31,7 @@ import { defaultControlColumn } from '../../../timelines/components/timeline/bod
import { EventsViewer } from './events_viewer';
import * as i18n from './translations';
import { GraphOverlay } from '../../../timelines/components/graph_overlay';
import { useCreateFieldButton } from '../../../timelines/components/create_field_button';

const EMPTY_CONTROL_COLUMNS: ControlColumnProps[] = [];
const leadingControlColumns: ControlColumnProps[] = [
Expand Down Expand Up @@ -174,6 +174,8 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
}, [id, timelineQuery, globalQuery]);
const bulkActions = useMemo(() => ({ onAlertStatusActionSuccess }), [onAlertStatusActionSuccess]);

const createFieldComponent = useCreateFieldButton(scopeId, id);

return (
<>
<FullScreenContainer $isFullScreen={globalFullScreen}>
Expand Down Expand Up @@ -217,6 +219,7 @@ const StatefulEventsViewerComponent: React.FC<Props> = ({
trailingControlColumns,
type: 'embedded',
unit,
createFieldComponent,
})
) : (
<EventsViewer
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jest.mock('react-redux', () => {
useDispatch: () => mockDispatch,
};
});
jest.mock('../../lib/kibana'); // , () => ({
jest.mock('../../lib/kibana');

describe('source/index.tsx', () => {
describe('getBrowserFields', () => {
Expand All @@ -39,11 +39,11 @@ describe('source/index.tsx', () => {
expect(fields).toEqual({});
});

test('it returns the same input with the same title', () => {
getBrowserFields('title 1', []);
// Since it is memoized it will return the same output which is empty object given 'title 1' a second time
const fields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
expect(fields).toEqual({});
test('it returns the same input given the same title and same fields length', () => {
const oldFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
const newFields = getBrowserFields('title 1', mocksSource.indexFields as IndexField[]);
// Since it is memoized it will return the same object instance
expect(newFields).toBe(oldFields);
});

test('it transforms input into output as expected', () => {
Expand Down Expand Up @@ -282,5 +282,44 @@ describe('source/index.tsx', () => {
);
});
});

it('doesnt set source when `autoCall` is false', async () => {
await act(async () => {
const { rerender, waitForNextUpdate } = renderHook<string, void>(
() => useIndexFields(SourcererScopeName.default, false),
{
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
}
);
await waitForNextUpdate();
rerender();
expect(mockDispatch).not.toBeCalled();
});
});

it('sets source when `autoCall` is false and when indexFieldsSearch is called', async () => {
await act(async () => {
const { rerender, waitForNextUpdate, result } = renderHook<
string,
ReturnType<typeof useIndexFields>
>(() => useIndexFields(SourcererScopeName.default, false), {
wrapper: ({ children }) => <Provider store={store}>{children}</Provider>,
});
await waitForNextUpdate();
rerender();

result.current.indexFieldsSearch(sourcererState.defaultDataView.id);

expect(mockDispatch).toHaveBeenCalledTimes(2);
expect(mockDispatch.mock.calls[0][0]).toHaveProperty(
'type',
'x-pack/security_solution/local/sourcerer/SET_SOURCERER_SCOPE_LOADING'
);
expect(mockDispatch.mock.calls[1][0]).toHaveProperty(
'type',
'x-pack/security_solution/local/sourcerer/SET_SOURCE'
);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,7 @@ export const getBrowserFields = memoizeOne(
return accumulator;
}, {});
},
// Update the value only if _title has changed
(newArgs, lastArgs) => newArgs[0] === lastArgs[0]
(newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length
);

export const getDocValueFields = memoizeOne(
Expand All @@ -103,8 +102,7 @@ export const getDocValueFields = memoizeOne(
return accumulator;
}, [])
: [],
// Update the value only if _title has changed
(newArgs, lastArgs) => newArgs[0] === lastArgs[0]
(newArgs, lastArgs) => newArgs[0] === lastArgs[0] && newArgs[1].length === lastArgs[1].length
);

export const indicesExistOrDataTemporarilyUnavailable = (
Expand Down Expand Up @@ -215,9 +213,12 @@ export const useFetchIndex = (
/**
* Sourcerer specific index fields hook/request
* sets redux state, returns nothing
*
* @param autoCall - When false it doesn't call `indexFieldsSearch` automacally.
*/
export const useIndexFields = (
sourcererScopeName: SourcererScopeName
sourcererScopeName: SourcererScopeName,
autoCall = true
): { indexFieldsSearch: (selectedDataViewId: string, newSignalsIndex?: string) => void } => {
const { data } = useKibana().services;
const abortCtrl = useRef(new AbortController());
Expand Down Expand Up @@ -357,18 +358,19 @@ export const useIndexFields = (

useEffect(() => {
if (
(dataViewId != null &&
autoCall &&
((dataViewId != null &&
// remove this when https://github.com/elastic/kibana/pull/114907 is merged
sourcererScopeName !== refSourcererScopeName.current) ||
(dataViewId !== refDataViewId.current && selectedPatterns.length > 0) ||
(selectedPatterns.length > 0 && refSelectedPatterns.current.length === 0)
(dataViewId !== refDataViewId.current && selectedPatterns.length > 0) ||
(selectedPatterns.length > 0 && refSelectedPatterns.current.length === 0))
) {
indexFieldsSearch(dataViewId);
}
refSourcererScopeName.current = sourcererScopeName;
refSelectedPatterns.current = selectedPatterns;
refDataViewId.current = dataViewId;
}, [dataViewId, indexFieldsSearch, selectedPatterns, sourcererScopeName]);
}, [dataViewId, indexFieldsSearch, selectedPatterns, sourcererScopeName, autoCall]);

useEffect(() => {
return () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* 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, fireEvent, act, screen } from '@testing-library/react';
import React from 'react';
import { CreateFieldButton } from './index';
import {
indexPatternFieldEditorPluginMock,
Start,
} from '../../../../../../../src/plugins/index_pattern_field_editor/public/mocks';

import { TestProviders } from '../../../common/mock';
import { useKibana } from '../../../common/lib/kibana';
import { DataView } from '../../../../../../../src/plugins/data/common';
import { SourcererScopeName } from '../../../common/store/sourcerer/model';
import { TimelineId } from '../../../../common';

const useKibanaMock = useKibana as jest.Mocked<typeof useKibana>;

let mockIndexPatternFieldEditor: Start;
jest.mock('../../../common/lib/kibana');

const runAllPromises = () => new Promise(setImmediate);

describe('CreateFieldButton', () => {
beforeEach(() => {
mockIndexPatternFieldEditor = indexPatternFieldEditorPluginMock.createStartContract();
useKibanaMock().services.indexPatternFieldEditor = mockIndexPatternFieldEditor;
useKibanaMock().services.data.dataViews.get = () => new Promise(() => undefined);
});

it('displays the button when user has permissions', () => {
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => true;

render(
<CreateFieldButton
selectedDataViewId={'dataViewId'}
onClick={() => undefined}
sourcererScope={SourcererScopeName.timeline}
timelineId={TimelineId.detectionsPage}
/>,
{
wrapper: TestProviders,
}
);

expect(screen.getByRole('button')).toBeInTheDocument();
});

it("doesn't display the button when user doesn't have permissions", () => {
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => false;
render(
<CreateFieldButton
selectedDataViewId={'dataViewId'}
onClick={() => undefined}
sourcererScope={SourcererScopeName.timeline}
timelineId={TimelineId.detectionsPage}
/>,
{
wrapper: TestProviders,
}
);

expect(screen.queryByRole('button')).not.toBeInTheDocument();
});

it("calls 'onClick' param when the button is clicked", async () => {
mockIndexPatternFieldEditor.userPermissions.editIndexPattern = () => true;
useKibanaMock().services.data.dataViews.get = () => Promise.resolve({} as DataView);

const onClickParam = jest.fn();
await act(async () => {
render(
<CreateFieldButton
selectedDataViewId={'dataViewId'}
onClick={onClickParam}
sourcererScope={SourcererScopeName.timeline}
timelineId={TimelineId.detectionsPage}
/>,
{
wrapper: TestProviders,
}
);
await runAllPromises();
});

fireEvent.click(screen.getByRole('button'));
expect(onClickParam).toHaveBeenCalled();
});
});
Loading