Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main' into insight-filter-builder
Browse files Browse the repository at this point in the history
  • Loading branch information
kqualters-elastic committed Feb 6, 2023
2 parents 2fe5cb3 + 01a18df commit 68caa3e
Show file tree
Hide file tree
Showing 12 changed files with 103 additions and 140 deletions.
2 changes: 1 addition & 1 deletion docs/api/spaces-management/post.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ experimental[] Create a {kib} space.
==== Request body

`id`::
(Required, string) The space ID that is part of the Kibana URL when inside the space. You are unable to change the ID with the update operation.
(Required, string) The space ID that is part of the Kibana URL when inside the space. Space IDs are limited to lowercase alphanumeric, underscore, and hyphen characters (a-z, 0-9, '_', and '-'). You are unable to change the ID with the update operation.

`name`::
(Required, string) The display name for the space.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,15 @@
*/
import { useKibana } from '../../../common/lib/kibana';
import { useIsFieldInIndexPattern } from '.';
import { renderHook } from '@testing-library/react-hooks';
import { getRequiredMapsFields } from '../../network/components/embeddables/map_config';

jest.mock('../../../common/lib/kibana');
jest.mock('../../network/components/embeddables/map_config');
const mockUseKibana = useKibana as jest.Mock;
describe('useIsFieldInIndexPattern', () => {
beforeAll(() => {
mockUseKibana.mockReturnValue({
services: {
data: {
dataViews: {
getFieldsForWildcard: () => [],
},
},
},
});
});
beforeEach(() => {
jest.clearAllMocks();
});
it('returns false when no fields in field list exist in the index pattern', async () => {
const isFieldInIndexPattern = useIsFieldInIndexPattern();
const res = await isFieldInIndexPattern('index-pattern-*', ['fields.list']);
expect(res).toEqual(false);
});
it('returns false when some but not all fields in field list exist in the index pattern', async () => {
mockUseKibana.mockReturnValue({
services: {
http: {},
Expand All @@ -40,23 +25,37 @@ describe('useIsFieldInIndexPattern', () => {
},
},
});
const isFieldInIndexPattern = useIsFieldInIndexPattern();
const res = await isFieldInIndexPattern('index-pattern-*', ['fields.list', 'another']);
expect(res).toEqual(false);
(getRequiredMapsFields as jest.Mock).mockReturnValue(['fields.list']);
});
it('returns true when all fields in field list exist in the index pattern', async () => {
it('returns false when no fields in field list exist in the index pattern', async () => {
mockUseKibana.mockReturnValue({
services: {
http: {},
data: {
dataViews: {
getFieldsForWildcard: () => [{ name: 'fields.list' }],
getFieldsForWildcard: () => [],
},
},
},
});
const isFieldInIndexPattern = useIsFieldInIndexPattern();
const res = await isFieldInIndexPattern('index-pattern-*', ['fields.list']);
const {
result: { current: isFieldInIndexPattern },
} = renderHook(useIsFieldInIndexPattern);
const res = await isFieldInIndexPattern('index-pattern-*');
expect(res).toEqual(false);
});
it('returns false when some but not all fields in field list exist in the index pattern', async () => {
(getRequiredMapsFields as jest.Mock).mockReturnValue(['fields.list', 'another']);
const {
result: { current: isFieldInIndexPattern },
} = renderHook(useIsFieldInIndexPattern);
const res = await isFieldInIndexPattern('index-pattern-*');
expect(res).toEqual(false);
});
it('returns true when all fields in field list exist in the index pattern', async () => {
const {
result: { current: isFieldInIndexPattern },
} = renderHook(useIsFieldInIndexPattern);
const res = await isFieldInIndexPattern('index-pattern-*');
expect(res).toEqual(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,30 @@
* 2.0.
*/

import { useMemo } from 'react';
import memoizeOne from 'memoize-one';
import { getRequiredMapsFields } from '../../network/components/embeddables/map_config';
import { useKibana } from '../../../common/lib/kibana';

type FieldValidationCheck = (pattern: string, fieldsList: string[]) => Promise<boolean>;
type FieldValidationCheck = (pattern: string) => Promise<boolean>;

export const useIsFieldInIndexPattern = (): FieldValidationCheck => {
const { dataViews } = useKibana().services.data;
return async (pattern: string, fieldsList: string[]) => {
const fields = await dataViews.getFieldsForWildcard({
pattern,
fields: fieldsList,
});
const fieldNames = fields.map((f) => f.name);
return fieldsList.every((field) => fieldNames.includes(field));
};

return useMemo(
() =>
memoizeOne(
async (pattern: string) => {
const fieldsList = getRequiredMapsFields(pattern);
const fields = await dataViews.getFieldsForWildcard({
pattern,
fields: fieldsList,
});
const fieldNames = fields.map((f) => f.name);
return fieldsList.every((field) => fieldNames.includes(field));
},
(newArgs, lastArgs) => newArgs[0] === lastArgs[0]
),
[dataViews]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,9 +58,9 @@ const mockGetStorage = jest.fn();
const mockSetStorage = jest.fn();
const setQuery: jest.Mock = jest.fn();
const filebeatDataView = { id: '6f1eeb50-023d-11eb-bcb6-6ba0578012a9', title: 'filebeat-*' };
const auditbeatDataView = { id: '28995490-023d-11eb-bcb6-6ba0578012a9', title: 'auditbeat-*' };
const packetbeatDataView = { id: '28995490-023d-11eb-bcb6-6ba0578012a9', title: 'packetbeat-*' };
const mockSelector = {
kibanaDataViews: [filebeatDataView, auditbeatDataView],
kibanaDataViews: [filebeatDataView, packetbeatDataView],
};
const embeddableValue = {
destroyed: false,
Expand Down Expand Up @@ -102,9 +102,9 @@ describe('EmbeddedMapComponent', () => {
setQuery.mockClear();
mockGetStorage.mockReturnValue(true);
jest.spyOn(redux, 'useSelector').mockReturnValue(mockSelector);
mockUseSourcererDataView.mockReturnValue({ selectedPatterns: ['filebeat-*', 'packetbeat-*'] });
mockUseSourcererDataView.mockReturnValue({ selectedPatterns: ['filebeat-*', 'auditbeat-*'] });
mockCreateEmbeddable.mockResolvedValue(embeddableValue);
mockUseIsFieldInIndexPattern.mockReturnValue(() => [true, true]);
mockUseIsFieldInIndexPattern.mockReturnValue(() => true);
});

afterEach(() => {
Expand Down Expand Up @@ -215,9 +215,9 @@ describe('EmbeddedMapComponent', () => {
});

test('On rerender with new selected patterns, selects existing Kibana data views that match any selected index pattern', async () => {
mockUseSourcererDataView
.mockReturnValueOnce({ selectedPatterns: ['filebeat-*', 'packetbeat-*'] })
.mockReturnValue({ selectedPatterns: ['filebeat-*', 'auditbeat-*'] });
mockUseSourcererDataView.mockReturnValue({
selectedPatterns: ['filebeat-*', 'auditbeat-*'],
});
const { rerender } = render(
<TestProviders>
<EmbeddedMapComponent {...testProps} />
Expand All @@ -227,6 +227,9 @@ describe('EmbeddedMapComponent', () => {
const dataViewArg = (getLayerList as jest.Mock).mock.calls[0][0];
expect(dataViewArg).toEqual([filebeatDataView]);
});
mockUseSourcererDataView.mockReturnValue({
selectedPatterns: ['filebeat-*', 'packetbeat-*'],
});
rerender(
<TestProviders>
<EmbeddedMapComponent {...testProps} />
Expand All @@ -235,7 +238,7 @@ describe('EmbeddedMapComponent', () => {
await waitFor(() => {
// data view is updated with the returned embeddable.setLayerList callback, which is passesd getLayerList(dataViews)
const dataViewArg = (getLayerList as jest.Mock).mock.calls[1][0];
expect(dataViewArg).toEqual([filebeatDataView, auditbeatDataView]);
expect(dataViewArg).toEqual([filebeatDataView, packetbeatDataView]);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,17 @@
* 2.0.
*/

// embedded map v2

import { EuiAccordion, EuiLink, EuiText } from '@elastic/eui';
import React, { useCallback, useEffect, useState, useMemo } from 'react';
import { createHtmlPortalNode, InPortal } from 'react-reverse-portal';
import styled, { css } from 'styled-components';

import type { Filter, Query } from '@kbn/es-query';
import type { ErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import { isErrorEmbeddable } from '@kbn/embeddable-plugin/public';
import type { MapEmbeddable } from '@kbn/maps-plugin/public/embeddable';
import { isEqual } from 'lodash/fp';
import { useAppToasts } from '../../../../common/hooks/use_app_toasts';
import { useIsFieldInIndexPattern } from '../../../containers/fields';
import { Loader } from '../../../../common/components/loader';
Expand All @@ -24,7 +26,7 @@ import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt';
import { MapToolTip } from './map_tool_tip/map_tool_tip';
import * as i18n from './translations';
import { useKibana } from '../../../../common/lib/kibana';
import { getLayerList, getRequiredMapsFields } from './map_config';
import { getLayerList } from './map_config';
import { sourcererSelectors } from '../../../../common/store/sourcerer';
import type { SourcererDataView } from '../../../../common/store/sourcerer/model';
import { SourcererScopeName } from '../../../../common/store/sourcerer/model';
Expand Down Expand Up @@ -123,26 +125,15 @@ export const EmbeddedMapComponent = ({
const isFieldInIndexPattern = useIsFieldInIndexPattern();

const [mapDataViews, setMapDataViews] = useState<SourcererDataView[]>([]);

const availableDataViews = useMemo(() => {
const dataViews = kibanaDataViews.filter((dataView) =>
selectedPatterns.includes(dataView.title)
);
if (selectedPatterns.length > 0 && dataViews.length === 0) {
setIsIndexError(true);
}
return dataViews;
}, [kibanaDataViews, selectedPatterns]);
const [availableDataViews, setAvailableDataViews] = useState<SourcererDataView[]>([]);

useEffect(() => {
let canceled = false;

const fetchData = async () => {
try {
const apiResponse = await Promise.all(
availableDataViews.map(async ({ title }) =>
isFieldInIndexPattern(title, getRequiredMapsFields(title))
)
availableDataViews.map(async ({ title }) => isFieldInIndexPattern(title))
);
// ensures only index patterns with maps fields are passed
const goodDataViews = availableDataViews.filter((_, i) => apiResponse[i] ?? false);
Expand All @@ -165,6 +156,16 @@ export const EmbeddedMapComponent = ({
};
}, [addError, availableDataViews, isFieldInIndexPattern]);

useEffect(() => {
const dataViews = kibanaDataViews.filter((dataView) =>
selectedPatterns.includes(dataView.title)
);
if (selectedPatterns.length > 0 && dataViews.length === 0) {
setIsIndexError(true);
}
setAvailableDataViews((prevViews) => (isEqual(prevViews, dataViews) ? prevViews : dataViews));
}, [kibanaDataViews, selectedPatterns]);

// This portalNode provided by react-reverse-portal allows us re-parent the MapToolTip within our
// own component tree instead of the embeddables (default). This is necessary to have access to
// the Redux store, theme provider, etc, which is required to register and un-register the draggable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,7 @@ import { ActionTypeForm } from './action_type_form';
import { AddConnectorInline } from './connector_add_inline';
import { actionTypeCompare } from '../../lib/action_type_compare';
import { checkActionFormActionTypeEnabled } from '../../lib/check_action_type_enabled';
import {
DEFAULT_FREQUENCY_WITH_SUMMARY,
DEFAULT_FREQUENCY_WITHOUT_SUMMARY,
VIEW_LICENSE_OPTIONS_LINK,
} from '../../../common/constants';
import { DEFAULT_FREQUENCY, VIEW_LICENSE_OPTIONS_LINK } from '../../../common/constants';
import { useKibana } from '../../../common/lib/kibana';
import { ConnectorAddModal } from '.';
import { suspendedComponentWithProps } from '../../lib/suspended_component_with_props';
Expand Down Expand Up @@ -224,7 +220,7 @@ export const ActionForm = ({
actionTypeId: actionTypeModel.id,
group: defaultActionGroupId,
params: {},
frequency: hasSummary ? DEFAULT_FREQUENCY_WITH_SUMMARY : DEFAULT_FREQUENCY_WITHOUT_SUMMARY,
frequency: DEFAULT_FREQUENCY,
});
setActionIdByIndex(actionTypeConnectors[0].id, actions.length - 1);
}
Expand All @@ -236,7 +232,7 @@ export const ActionForm = ({
actionTypeId: actionTypeModel.id,
group: defaultActionGroupId,
params: {},
frequency: hasSummary ? DEFAULT_FREQUENCY_WITH_SUMMARY : DEFAULT_FREQUENCY_WITHOUT_SUMMARY,
frequency: DEFAULT_FREQUENCY,
});
setActionIdByIndex(actions.length.toString(), actions.length - 1);
setEmptyActionsIds([...emptyActionsIds, actions.length.toString()]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,11 @@ import { act } from 'react-dom/test-utils';
import { RuleAction } from '../../../types';
import { ActionNotifyWhen } from './action_notify_when';
import { RuleNotifyWhen } from '@kbn/alerting-plugin/common';
import {
DEFAULT_FREQUENCY_WITHOUT_SUMMARY,
DEFAULT_FREQUENCY_WITH_SUMMARY,
} from '../../../common/constants';
import { DEFAULT_FREQUENCY } from '../../../common/constants';

describe('action_notify_when', () => {
async function setup(
frequency: RuleAction['frequency'] = DEFAULT_FREQUENCY_WITH_SUMMARY,
frequency: RuleAction['frequency'] = DEFAULT_FREQUENCY,
hasSummary: boolean = true
) {
const wrapper = mountWithIntl(
Expand Down Expand Up @@ -50,15 +47,15 @@ describe('action_notify_when', () => {
'[data-test-subj="summaryOrPerRuleSelect"]'
);
expect(summaryOrPerRuleSelect.exists()).toBeTruthy();
expect(summaryOrPerRuleSelect.first().props()['aria-label']).toEqual('Summary of alerts');
expect(summaryOrPerRuleSelect.first().props()['aria-label']).toEqual('For each alert');

const notifyWhenSelect = wrapperDefault.find('[data-test-subj="notifyWhenSelect"]');
expect(notifyWhenSelect.exists()).toBeTruthy();
expect((notifyWhenSelect.first().props() as EuiSuperSelectProps<''>).valueOfSelected).toEqual(
RuleNotifyWhen.ACTIVE
RuleNotifyWhen.CHANGE
);
}
const wrapperForEach = await setup(DEFAULT_FREQUENCY_WITHOUT_SUMMARY);
const wrapperForEach = await setup(DEFAULT_FREQUENCY);
{
const summaryOrPerRuleSelect = wrapperForEach.find(
'[data-test-subj="summaryOrPerRuleSelect"]'
Expand All @@ -73,7 +70,7 @@ describe('action_notify_when', () => {
);
}
const wrapperSummaryThrottle = await setup({
...DEFAULT_FREQUENCY_WITH_SUMMARY,
...DEFAULT_FREQUENCY,
throttle: '5h',
notifyWhen: RuleNotifyWhen.THROTTLE,
});
Expand All @@ -82,7 +79,7 @@ describe('action_notify_when', () => {
'[data-test-subj="summaryOrPerRuleSelect"]'
);
expect(summaryOrPerRuleSelect.exists()).toBeTruthy();
expect(summaryOrPerRuleSelect.first().props()['aria-label']).toEqual('Summary of alerts');
expect(summaryOrPerRuleSelect.first().props()['aria-label']).toEqual('For each alert');

const notifyWhenSelect = wrapperSummaryThrottle.find('[data-test-subj="notifyWhenSelect"]');
expect(notifyWhenSelect.exists()).toBeTruthy();
Expand All @@ -99,7 +96,7 @@ describe('action_notify_when', () => {
});

it('hides the summary selector when hasSummary is false', async () => {
const wrapper = await setup(DEFAULT_FREQUENCY_WITHOUT_SUMMARY, false);
const wrapper = await setup(DEFAULT_FREQUENCY, false);
const summaryOrPerRuleSelect = wrapper.find('[data-test-subj="summaryOrPerRuleSelect"]');
expect(summaryOrPerRuleSelect.exists()).toBeFalsy();
});
Expand Down
Loading

0 comments on commit 68caa3e

Please sign in to comment.