= {
run_ml_inference: { type: 'boolean' },
},
},
+ preferences: {
+ properties: {
+ extract_full_html: { type: 'boolean' },
+ },
+ },
scheduling: {
properties: {
enabled: { type: 'boolean' },
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts
index e6584c0a8b205..c3b52f53857d6 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.test.ts
@@ -154,6 +154,7 @@ describe('addConnector lib function', () => {
reduce_whitespace: true,
run_ml_inference: false,
},
+ preferences: {},
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
service_type: null,
status: ConnectorStatus.CREATED,
@@ -339,6 +340,7 @@ describe('addConnector lib function', () => {
reduce_whitespace: true,
run_ml_inference: false,
},
+ preferences: {},
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
service_type: null,
status: ConnectorStatus.CREATED,
@@ -446,6 +448,7 @@ describe('addConnector lib function', () => {
reduce_whitespace: true,
run_ml_inference: false,
},
+ preferences: {},
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
service_type: null,
status: ConnectorStatus.CREATED,
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts
index 507ec3fd0bb4f..abe52caf453d2 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/add_connector.ts
@@ -164,6 +164,7 @@ export const addConnector = async (
run_ml_inference: connectorsPipelineMeta.default_run_ml_inference,
}
: null,
+ preferences: {},
scheduling: { enabled: false, interval: '0 0 0 * * ?' },
service_type: input.service_type || null,
status: ConnectorStatus.CREATED,
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
index 25956b271245a..2ea8a875bd3b4 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/start_sync.test.ts
@@ -42,6 +42,7 @@ describe('addConnector lib function', () => {
last_sync_error: null,
last_sync_status: null,
last_synced: null,
+ preferences: {},
scheduling: { enabled: true, interval: '1 2 3 4 5' },
service_type: null,
status: 'not connected',
@@ -67,6 +68,7 @@ describe('addConnector lib function', () => {
last_sync_error: null,
last_sync_status: null,
last_synced: null,
+ preferences: {},
scheduling: { enabled: true, interval: '1 2 3 4 5' },
service_type: null,
status: 'not connected',
diff --git a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts
index 2fa4c0954b245..89b936a41a889 100644
--- a/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts
+++ b/x-pack/plugins/enterprise_search/server/lib/connectors/update_connector_scheduling.test.ts
@@ -41,6 +41,7 @@ describe('addConnector lib function', () => {
last_sync_error: null,
last_sync_status: null,
last_synced: null,
+ preferences: {},
scheduling: { enabled: false, interval: '* * * * *' },
service_type: null,
status: 'not connected',
@@ -69,6 +70,7 @@ describe('addConnector lib function', () => {
last_sync_error: null,
last_sync_status: null,
last_synced: null,
+ preferences: {},
scheduling: { enabled: true, interval: '1 2 3 4 5' },
service_type: null,
status: 'not connected',
diff --git a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
index 382b67d7a32f3..8861722205ea0 100644
--- a/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
+++ b/x-pack/plugins/enterprise_search/server/routes/enterprise_search/connectors.ts
@@ -159,7 +159,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
connectorId: schema.string(),
}),
query: schema.object({
- page: schema.number({ defaultValue: 0, min: 0 }),
+ from: schema.number({ defaultValue: 0, min: 0 }),
size: schema.number({ defaultValue: 10, min: 0 }),
}),
},
@@ -169,7 +169,7 @@ export function registerConnectorRoutes({ router, log }: RouteDependencies) {
const result = await fetchSyncJobsByConnectorId(
client,
request.params.connectorId,
- request.query.page,
+ request.query.from,
request.query.size
);
return response.ok({ body: result });
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.test.tsx
deleted file mode 100644
index daf8177b9e73e..0000000000000
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.test.tsx
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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 { act } from '@testing-library/react-hooks';
-
-import { createFleetTestRendererMock } from '../../../../../../mock';
-
-import { useHistoryBlock } from '.';
-
-describe('useHistoryBlock', () => {
- describe('without search params', () => {
- it('should not block if not edited', () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.renderHook(() => useHistoryBlock(false));
-
- act(() => renderer.mountHistory.push('/test'));
-
- const { location } = renderer.mountHistory;
- expect(location.pathname).toBe('/test');
- expect(location.search).toBe('');
- expect(renderer.startServices.overlays.openConfirm).not.toBeCalled();
- });
-
- it('should block if edited', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(true);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test',
- expect.anything()
- );
- });
-
- it('should block if edited and not navigate on cancel', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(false);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).not.toBeCalled();
- });
- });
- describe('with search params', () => {
- it('should not block if not edited', () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.renderHook(() => useHistoryBlock(false));
-
- act(() => renderer.mountHistory.push('/test?param=test'));
-
- const { location } = renderer.mountHistory;
- expect(location.pathname).toBe('/test');
- expect(location.search).toBe('?param=test');
- expect(renderer.startServices.overlays.openConfirm).not.toBeCalled();
- });
-
- it('should block if edited and navigate on confirm', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(true);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test?param=test'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test?param=test',
- expect.anything()
- );
- });
-
- it('should block if edited and not navigate on cancel', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(false);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test?param=test'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).not.toBeCalled();
- });
- });
-
- describe('with hash params', () => {
- it('should not block if not edited', () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.renderHook(() => useHistoryBlock(false));
-
- act(() => renderer.mountHistory.push('/test#/hash'));
-
- const { location } = renderer.mountHistory;
- expect(location.pathname).toBe('/test');
- expect(location.hash).toBe('#/hash');
- expect(renderer.startServices.overlays.openConfirm).not.toBeCalled();
- });
-
- it('should block if edited and navigate on confirm', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(true);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test#/hash'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test#/hash',
- expect.anything()
- );
- });
-
- it('should block if edited and not navigate on cancel', async () => {
- const renderer = createFleetTestRendererMock();
-
- renderer.startServices.overlays.openConfirm.mockResolvedValue(false);
- renderer.renderHook(() => useHistoryBlock(true));
-
- act(() => renderer.mountHistory.push('/test#/hash'));
- // needed because we have an async useEffect
- await act(() => new Promise((resolve) => resolve()));
-
- expect(renderer.startServices.overlays.openConfirm).toBeCalled();
- expect(renderer.startServices.application.navigateToUrl).not.toBeCalled();
- });
- });
-});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.tsx
index edf04f8733ad8..7b295c9f53d53 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/index.tsx
@@ -5,46 +5,5 @@
* 2.0.
*/
-import { useEffect } from 'react';
-import { useHistory } from 'react-router-dom';
-import { i18n } from '@kbn/i18n';
-
-import { useStartServices } from '../../../../hooks';
-
-export function useHistoryBlock(isEdited: boolean) {
- const history = useHistory();
- const { overlays, application } = useStartServices();
-
- useEffect(() => {
- if (!isEdited) {
- return;
- }
-
- const unblock = history.block((state) => {
- async function confirmAsync() {
- const confirmRes = await overlays.openConfirm(
- i18n.translate('xpack.fleet.editPackagePolicy.historyBlockDescription', {
- defaultMessage: `Unsaved changes will be discarded. Are you sure you would like to continue?`,
- }),
- {
- title: i18n.translate('xpack.fleet.editPackagePolicy.historyBlockTitle', {
- defaultMessage: 'Discard Changes?',
- }),
- }
- );
-
- if (confirmRes) {
- unblock();
-
- application.navigateToUrl(state.pathname + state.hash + state.search, {
- state: state.state,
- });
- }
- }
- confirmAsync();
- return false;
- });
-
- return unblock;
- }, [history, isEdited, overlays, application]);
-}
+export { useHistoryBlock } from './use_history_block';
+export { usePackagePolicyWithRelatedData } from './use_package_policy';
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx
index 91a4afbda62e2..491c5f6276941 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.test.tsx
@@ -11,6 +11,8 @@ import { createFleetTestRendererMock } from '../../../../../../mock';
import { useHistoryBlock } from './use_history_block';
+// our test mountHistory prepends the basePath to URLs, however useHistory state doesnt have the basePath
+// in production, so we have to prepend it to the state.pathname, this results in /mock/mock in the assertions
describe('useHistoryBlock', () => {
describe('without search params', () => {
it('should not block if not edited', () => {
@@ -38,7 +40,7 @@ describe('useHistoryBlock', () => {
expect(renderer.startServices.overlays.openConfirm).toBeCalled();
expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test',
+ '/mock/mock/test',
expect.anything()
);
});
@@ -83,7 +85,7 @@ describe('useHistoryBlock', () => {
expect(renderer.startServices.overlays.openConfirm).toBeCalled();
expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test?param=test',
+ '/mock/mock/test?param=test',
expect.anything()
);
});
@@ -129,7 +131,7 @@ describe('useHistoryBlock', () => {
expect(renderer.startServices.overlays.openConfirm).toBeCalled();
expect(renderer.startServices.application.navigateToUrl).toBeCalledWith(
- '/mock/test#/hash',
+ '/mock/mock/test#/hash',
expect.anything()
);
});
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.tsx
index edf04f8733ad8..abdd287c75777 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/hooks/use_history_block.tsx
@@ -13,7 +13,7 @@ import { useStartServices } from '../../../../hooks';
export function useHistoryBlock(isEdited: boolean) {
const history = useHistory();
- const { overlays, application } = useStartServices();
+ const { overlays, application, http } = useStartServices();
useEffect(() => {
if (!isEdited) {
@@ -32,11 +32,10 @@ export function useHistoryBlock(isEdited: boolean) {
}),
}
);
-
if (confirmRes) {
+ const url = http.basePath.prepend(state.pathname) + state.hash + state.search;
unblock();
-
- application.navigateToUrl(state.pathname + state.hash + state.search, {
+ application.navigateToUrl(url, {
state: state.state,
});
}
@@ -46,5 +45,5 @@ export function useHistoryBlock(isEdited: boolean) {
});
return unblock;
- }, [history, isEdited, overlays, application]);
+ }, [history, isEdited, overlays, application, http.basePath]);
}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
index b066457b0aa2e..8b87f64d7b6c0 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/edit_package_policy_page/index.tsx
@@ -53,9 +53,8 @@ import type { PackagePolicyEditExtensionComponentProps } from '../../../types';
import { ExperimentalFeaturesService, pkgKeyFromPackageInfo } from '../../../services';
import { generateUpdatePackagePolicyDevToolsRequest } from '../services';
-import { useHistoryBlock } from './hooks';
import { UpgradeStatusCallout } from './components';
-import { usePackagePolicyWithRelatedData } from './hooks/use_package_policy';
+import { usePackagePolicyWithRelatedData, useHistoryBlock } from './hooks';
export const EditPackagePolicyPage = memo(() => {
const {
@@ -163,7 +162,6 @@ export const EditPackagePolicyForm = memo<{
}
return '/';
}, [from, getHref, packageInfo, policyId]);
-
const successRedirectPath = useMemo(() => {
if (packageInfo && policyId) {
return from === 'package-edit' || from === 'upgrade-from-integrations-policy-list'
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx
index f13fd592daccf..b8d54b45759de 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/home/hooks/use_available_packages.tsx
@@ -182,22 +182,23 @@ export const useAvailablePackages = () => {
);
const {
- data: eprCategories,
+ data: eprCategoriesRes,
isLoading: isLoadingCategories,
error: eprCategoryLoadingError,
} = useCategories(prereleaseIntegrationsEnabled);
+ const eprCategories = useMemo(() => eprCategoriesRes?.items || [], [eprCategoriesRes]);
// Subcategories
const subCategories = useMemo(() => {
- return eprCategories?.items.filter((item) => item.parent_id !== undefined);
- }, [eprCategories?.items]);
+ return eprCategories?.filter((item) => item.parent_id !== undefined);
+ }, [eprCategories]);
const allCategories: CategoryFacet[] = useMemo(() => {
const eprAndCustomCategories: CategoryFacet[] = isLoadingCategories
? []
: mergeCategoriesAndCount(
eprCategories
- ? (eprCategories.items as Array<{ id: string; title: string; count: number }>)
+ ? (eprCategories as Array<{ id: string; title: string; count: number }>)
: [],
cards
);
diff --git a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts
index db37f85f56701..500cf141fed26 100644
--- a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.test.ts
@@ -9,10 +9,15 @@ import { elasticsearchServiceMock } from '@kbn/core-elasticsearch-server-mocks';
import { savedObjectsClientMock } from '@kbn/core-saved-objects-api-server-mocks';
import type { NewPackagePolicy, PackagePolicy } from '../../types';
+import { updateCurrentWriteIndices } from '../epm/elasticsearch/template/template';
import { getInstallation } from '../epm/packages';
import { handleExperimentalDatastreamFeatureOptIn } from './experimental_datastream_features';
+const mockedUpdateCurrentWriteIndices = updateCurrentWriteIndices as jest.MockedFunction<
+ typeof updateCurrentWriteIndices
+>;
+
jest.mock('../epm/packages', () => {
return {
getInstallation: jest.fn(),
@@ -27,6 +32,9 @@ jest.mock('../epm/packages', () => {
};
});
+jest.mock('../app_context');
+jest.mock('../epm/elasticsearch/template/template');
+
const mockGetInstallation = getInstallation as jest.Mock;
jest.mock('../epm/elasticsearch/template/install', () => {
@@ -140,6 +148,7 @@ function getExistingTestPackagePolicy({
describe('experimental_datastream_features', () => {
beforeEach(() => {
soClient.get.mockClear();
+ mockedUpdateCurrentWriteIndices.mockReset();
esClient.cluster.getComponentTemplate.mockClear();
esClient.cluster.putComponentTemplate.mockClear();
@@ -173,6 +182,24 @@ describe('experimental_datastream_features', () => {
},
],
});
+
+ esClient.indices.getIndexTemplate.mockResolvedValueOnce({
+ index_templates: [
+ {
+ name: 'metrics-test.test',
+ index_template: {
+ template: {
+ settings: {},
+ mappings: {},
+ },
+ composed_of: [],
+ index_patterns: '',
+ },
+ },
+ ],
+ });
+
+ esClient.indices.getIndexTemplate.mockClear();
});
const soClient = savedObjectsClientMock.create();
@@ -310,22 +337,6 @@ describe('experimental_datastream_features', () => {
isDocValueOnlyOther: false,
});
- esClient.indices.getIndexTemplate.mockResolvedValueOnce({
- index_templates: [
- {
- name: 'metrics-test.test',
- index_template: {
- template: {
- settings: {},
- mappings: {},
- },
- composed_of: [],
- index_patterns: '',
- },
- },
- ],
- });
-
await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy });
expect(esClient.indices.getIndexTemplate).toHaveBeenCalled();
@@ -372,6 +383,33 @@ describe('experimental_datastream_features', () => {
expect(esClient.cluster.getComponentTemplate).not.toHaveBeenCalled();
expect(esClient.cluster.putComponentTemplate).not.toHaveBeenCalled();
});
+
+ it('does not update write indices', async () => {
+ const packagePolicy = getExistingTestPackagePolicy({
+ isSyntheticSourceEnabled: true,
+ isTSDBEnabled: false,
+ isDocValueOnlyNumeric: false,
+ isDocValueOnlyOther: false,
+ });
+
+ mockGetInstallation.mockResolvedValueOnce({
+ experimental_data_stream_features: [
+ {
+ data_stream: 'metrics-test.test',
+ features: {
+ synthetic_source: true,
+ tsdb: false,
+ doc_value_only_numeric: false,
+ doc_value_only_other: false,
+ },
+ },
+ ],
+ });
+
+ await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy });
+
+ expect(mockedUpdateCurrentWriteIndices).not.toHaveBeenCalled();
+ });
});
describe('when opt in status is changed', () => {
@@ -509,6 +547,38 @@ describe('experimental_datastream_features', () => {
})
);
});
+
+ it('should update existing write indices', async () => {
+ const packagePolicy = getExistingTestPackagePolicy({
+ isSyntheticSourceEnabled: false,
+ isTSDBEnabled: true,
+ isDocValueOnlyNumeric: false,
+ isDocValueOnlyOther: false,
+ });
+
+ esClient.indices.getIndexTemplate.mockResolvedValueOnce({
+ index_templates: [
+ {
+ name: 'metrics-test.test',
+ index_template: {
+ template: {
+ settings: {},
+ mappings: {},
+ },
+ composed_of: [],
+ index_patterns: '',
+ },
+ },
+ ],
+ });
+
+ await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy });
+
+ expect(mockedUpdateCurrentWriteIndices).toHaveBeenCalledTimes(1);
+ expect(
+ mockedUpdateCurrentWriteIndices.mock.calls[0][2].map(({ templateName }) => templateName)
+ ).toEqual(['metrics-test.test']);
+ });
});
});
});
diff --git a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
index 88bde17e2aa39..e98f45a5671c0 100644
--- a/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
+++ b/x-pack/plugins/fleet/server/services/package_policies/experimental_datastream_features.ts
@@ -13,8 +13,15 @@ import { merge } from 'lodash';
import { getRegistryDataStreamAssetBaseName } from '../../../common/services';
import type { ExperimentalIndexingFeature } from '../../../common/types';
-import type { NewPackagePolicy, PackagePolicy } from '../../types';
+import type {
+ NewPackagePolicy,
+ PackagePolicy,
+ IndexTemplate,
+ IndexTemplateEntry,
+} from '../../types';
+import { appContextService } from '../app_context';
import { prepareTemplate } from '../epm/elasticsearch/template/install';
+import { updateCurrentWriteIndices } from '../epm/elasticsearch/template/template';
import { getInstallation, getPackageInfo } from '../epm/packages';
import { updateDatastreamExperimentalFeatures } from '../epm/packages/update';
import {
@@ -71,6 +78,8 @@ export async function handleExperimentalDatastreamFeatureOptIn({
});
}
+ const updatedIndexTemplates: IndexTemplateEntry[] = [];
+
for (const featureMapEntry of packagePolicy.package.experimental_data_stream_features) {
const existingOptIn = installation?.experimental_data_stream_features?.find(
(optIn) => optIn.data_stream === featureMapEntry.data_stream
@@ -126,6 +135,10 @@ export async function handleExperimentalDatastreamFeatureOptIn({
let sourceModeSettings = {};
+ const indexTemplateRes = await esClient.indices.getIndexTemplate({
+ name: featureMapEntry.data_stream,
+ });
+
if (isSyntheticSourceOptInChanged) {
sourceModeSettings = {
_source: {
@@ -152,12 +165,10 @@ export async function handleExperimentalDatastreamFeatureOptIn({
});
}
- if (isTSDBOptInChanged) {
- const indexTemplateRes = await esClient.indices.getIndexTemplate({
- name: featureMapEntry.data_stream,
- });
- const indexTemplate = indexTemplateRes.index_templates[0].index_template;
+ const indexTemplate = indexTemplateRes.index_templates[0].index_template;
+ let updatedIndexTemplate = indexTemplate as IndexTemplate;
+ if (isTSDBOptInChanged) {
const indexTemplateBody = {
...indexTemplate,
template: {
@@ -171,12 +182,24 @@ export async function handleExperimentalDatastreamFeatureOptIn({
},
};
+ updatedIndexTemplate = indexTemplateBody as IndexTemplate;
+
await esClient.indices.putIndexTemplate({
name: featureMapEntry.data_stream,
// @ts-expect-error
body: indexTemplateBody,
});
}
+
+ updatedIndexTemplates.push({
+ templateName: featureMapEntry.data_stream,
+ indexTemplate: updatedIndexTemplate,
+ });
+ }
+
+ // Trigger rollover for updated datastreams
+ if (updatedIndexTemplates.length > 0) {
+ await updateCurrentWriteIndices(esClient, appContextService.getLogger(), updatedIndexTemplates);
}
// Update the installation object to persist the experimental feature map
diff --git a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx
index 44b37a0f9cc2a..4ddfa550f5d09 100644
--- a/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx
+++ b/x-pack/plugins/index_management/public/application/components/mappings_editor/components/configuration_form/source_field_section/source_field_section.tsx
@@ -44,7 +44,11 @@ export const SourceFieldSection = () => {
-
+
{
+import {
+ useHostsUrlState,
+ INITIAL_DATE_RANGE,
+ HostsState,
+ StringDateRangeTimestamp,
+} from './use_unified_search_url_state';
+
+const buildQuerySubmittedPayload = (
+ hostState: HostsState & { dateRangeTimestamp: StringDateRangeTimestamp }
+) => {
const { panelFilters, filters, dateRangeTimestamp, query: queryObj } = hostState;
return {
@@ -77,8 +84,11 @@ export const useUnifiedSearch = () => {
// Track telemetry event on query/filter/date changes
useEffect(() => {
- telemetry.reportHostsViewQuerySubmitted(buildQuerySubmittedPayload(state));
- }, [state, telemetry]);
+ const dateRangeTimestamp = getDateRangeAsTimestamp();
+ telemetry.reportHostsViewQuerySubmitted(
+ buildQuerySubmittedPayload({ ...state, dateRangeTimestamp })
+ );
+ }, [getDateRangeAsTimestamp, state, telemetry]);
const onSubmit = useCallback(
(data?: {
diff --git a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
index 1a19f21626d82..41e476dbf12c5 100644
--- a/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
+++ b/x-pack/plugins/infra/public/pages/metrics/hosts/hooks/use_unified_search_url_state.ts
@@ -143,6 +143,11 @@ const HostsStateRT = rt.type({
export type HostsState = rt.TypeOf;
+export interface StringDateRangeTimestamp {
+ from: number;
+ to: number;
+}
+
const SetQueryType = rt.partial(HostsStateRT.props);
const encodeUrlState = HostsStateRT.encode;
diff --git a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
index 92fbc186dce5f..85059a2ee6233 100644
--- a/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
+++ b/x-pack/plugins/infra/server/lib/alerting/metric_threshold/metric_threshold_executor.test.ts
@@ -33,6 +33,7 @@ import {
} from './metric_threshold_executor';
import { Evaluation } from './lib/evaluate_rule';
import type { LogMeta, Logger } from '@kbn/logging';
+import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common';
jest.mock('./lib/evaluate_rule', () => ({ evaluateRule: jest.fn() }));
@@ -116,6 +117,7 @@ const mockOptions = {
ruleTypeName: '',
},
logger,
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
};
const setEvaluationResults = (response: Array>) => {
diff --git a/x-pack/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
index aa815e833a6d8..73afa216e62d4 100644
--- a/x-pack/plugins/lens/public/app_plugin/app.test.tsx
+++ b/x-pack/plugins/lens/public/app_plugin/app.test.tsx
@@ -1607,7 +1607,7 @@ describe('Lens App', () => {
},
},
});
- expect(services.spaces.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({
+ expect(services.spaces?.ui.components.getLegacyUrlConflict).toHaveBeenCalledWith({
currentObjectId: '1234',
objectNoun: 'Lens visualization',
otherObjectId: '2',
diff --git a/x-pack/plugins/lens/public/app_plugin/types.ts b/x-pack/plugins/lens/public/app_plugin/types.ts
index 314bc3a2e52f5..193ba130e02ef 100644
--- a/x-pack/plugins/lens/public/app_plugin/types.ts
+++ b/x-pack/plugins/lens/public/app_plugin/types.ts
@@ -158,7 +158,7 @@ export interface LensAppServices {
savedObjectsTagging?: SavedObjectTaggingPluginStart;
getOriginatingAppName: () => string | undefined;
presentationUtil: PresentationUtilPluginStart;
- spaces: SpacesApi;
+ spaces?: SpacesApi;
charts: ChartsPluginSetup;
share?: SharePluginStart;
unifiedSearch: UnifiedSearchPublicPluginStart;
diff --git a/x-pack/plugins/lens/public/plugin.ts b/x-pack/plugins/lens/public/plugin.ts
index 6db5d4abb90e8..2e269f37e7f8c 100644
--- a/x-pack/plugins/lens/public/plugin.ts
+++ b/x-pack/plugins/lens/public/plugin.ts
@@ -146,7 +146,7 @@ export interface LensPluginStartDependencies {
dataViewFieldEditor: IndexPatternFieldEditorStart;
dataViewEditor: DataViewEditorStart;
inspector: InspectorStartContract;
- spaces: SpacesPluginStart;
+ spaces?: SpacesPluginStart;
usageCollection?: UsageCollectionStart;
docLinks: DocLinksStart;
share?: SharePluginStart;
diff --git a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx
index 2d8ce9405f12f..ecf519382ff6c 100644
--- a/x-pack/plugins/lens/public/state_management/load_initial.test.tsx
+++ b/x-pack/plugins/lens/public/state_management/load_initial.test.tsx
@@ -309,7 +309,7 @@ describe('Initializing the store', () => {
expect(deps.lensServices.attributeService.unwrapAttributes).toHaveBeenCalledWith({
savedObjectId: defaultSavedObjectId,
});
- expect(deps.lensServices.spaces.ui.redirectLegacyUrl).toHaveBeenCalledWith({
+ expect(deps.lensServices.spaces?.ui.redirectLegacyUrl).toHaveBeenCalledWith({
path: '#/edit/id2?search',
aliasPurpose: 'savedObjectConversion',
objectNoun: 'Lens visualization',
diff --git a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
index 6e718e0f0ccd8..9a10dc2b782c9 100644
--- a/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
+++ b/x-pack/plugins/ml/public/application/components/scatterplot_matrix/scatterplot_matrix.tsx
@@ -532,7 +532,7 @@ export const ScatterplotMatrix: FC = ({
openInNewTab: false,
});
}}
- data-test-subj="mlSplomoExploreInCustomVisualizationLink"
+ data-test-subj="mlSplomExploreInCustomVisualizationLink"
>
{
try {
delete jobConfig.dest;
delete jobConfig.model_memory_limit;
+ delete jobConfig.analyzed_fields;
const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics(
jobConfig
);
diff --git a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts
index a1660f126bbb8..846e27288491c 100644
--- a/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts
+++ b/x-pack/plugins/ml/public/application/data_frame_analytics/pages/job_map/use_fetch_analytics_map_data.ts
@@ -5,8 +5,9 @@
* 2.0.
*/
-import { useState } from 'react';
+import { useState, useRef } from 'react';
import { i18n } from '@kbn/i18n';
+import { asyncForEach } from '@kbn/std';
import { uniqWith, isEqual } from 'lodash';
import cytoscape from 'cytoscape';
import { ml } from '../../../services/ml_api_service';
@@ -23,11 +24,11 @@ interface GetDataObjectParameter {
export const useFetchAnalyticsMapData = () => {
const [isLoading, setIsLoading] = useState(false);
const [elements, setElements] = useState([]);
- const [nodeDetails, setNodeDetails] = useState>({});
const [error, setError] = useState();
const [message, setMessage] = useState();
// Keeps track of which nodes have been used as root so we can refetch related nodes on refresh
const [usedAsRoot, setUsedAsRoot] = useState>({});
+ const nodeDetails = useRef>({});
const fetchAndSetElements = async (idToUse: string, treatAsRoot: boolean, type?: string) => {
setIsLoading(true);
@@ -57,11 +58,11 @@ export const useFetchAnalyticsMapData = () => {
if (nodeElements?.length > 0) {
if (treatAsRoot === false) {
setElements(nodeElements);
- setNodeDetails(details);
+ nodeDetails.current = details;
} else {
const uniqueElements = uniqWith([...nodeElements, ...elements], isEqual);
setElements(uniqueElements);
- setNodeDetails({ ...details, ...nodeDetails });
+ nodeDetails.current = { ...details, ...nodeDetails.current };
}
}
setIsLoading(false);
@@ -88,11 +89,9 @@ export const useFetchAnalyticsMapData = () => {
// If related nodes had been fetched from any node then refetch
if (Object.keys(usedAsRoot).length) {
- for (const nodeId in usedAsRoot) {
- if (usedAsRoot.hasOwnProperty(nodeId)) {
- await fetchAndSetElements(nodeId, true, usedAsRoot[nodeId]);
- }
- }
+ await asyncForEach(Object.keys(usedAsRoot), async (nodeId) => {
+ await fetchAndSetElements(nodeId, true, usedAsRoot[nodeId]);
+ });
}
};
@@ -102,7 +101,7 @@ export const useFetchAnalyticsMapData = () => {
fetchAndSetElementsWrapper,
isLoading,
message,
- nodeDetails,
+ nodeDetails: nodeDetails.current,
setElements,
setError,
};
diff --git a/x-pack/plugins/observability/public/application/application.test.tsx b/x-pack/plugins/observability/public/application/application.test.tsx
index 5c5c6a24a03d1..3320022c12b80 100644
--- a/x-pack/plugins/observability/public/application/application.test.tsx
+++ b/x-pack/plugins/observability/public/application/application.test.tsx
@@ -91,6 +91,7 @@ describe('renderApp', () => {
},
reportUiCounter: jest.fn(),
},
+ kibanaVersion: '8.7.0',
});
unmount();
}).not.toThrowError();
diff --git a/x-pack/plugins/observability/public/application/index.tsx b/x-pack/plugins/observability/public/application/index.tsx
index 283cc195089a7..7edbb74b80c82 100644
--- a/x-pack/plugins/observability/public/application/index.tsx
+++ b/x-pack/plugins/observability/public/application/index.tsx
@@ -53,6 +53,7 @@ export const renderApp = ({
ObservabilityPageTemplate,
usageCollection,
isDev,
+ kibanaVersion,
}: {
core: CoreStart;
config: ConfigSchema;
@@ -62,6 +63,7 @@ export const renderApp = ({
ObservabilityPageTemplate: React.ComponentType;
usageCollection: UsageCollectionSetup;
isDev?: boolean;
+ kibanaVersion: string;
}) => {
const { element, history, theme$ } = appMountParameters;
const i18nCore = core.i18n;
@@ -83,7 +85,13 @@ export const renderApp = ({
().services;
const { ObservabilityPageTemplate } = usePluginContext();
@@ -66,7 +67,10 @@ export function OverviewPage() {
},
]);
- const { data: newsFeed } = useFetcher(() => getNewsFeed({ http }), [http]);
+ const { data: newsFeed } = useFetcher(
+ () => getNewsFeed({ http, kibanaVersion }),
+ [http, kibanaVersion]
+ );
const { hasAnyData, isAllRequestsComplete } = useHasData();
const { trackMetric } = useOverviewMetrics({ hasAnyData });
diff --git a/x-pack/plugins/observability/public/plugin.ts b/x-pack/plugins/observability/public/plugin.ts
index 17a21f41301b6..332e481f0ec50 100644
--- a/x-pack/plugins/observability/public/plugin.ts
+++ b/x-pack/plugins/observability/public/plugin.ts
@@ -187,6 +187,7 @@ export class Plugin
const category = DEFAULT_APP_CATEGORIES.observability;
const euiIconType = 'logoObservability';
const config = this.initContext.config.get();
+ const kibanaVersion = this.initContext.env.packageInfo.version;
createCallObservabilityApi(coreSetup.http);
@@ -211,6 +212,7 @@ export class Plugin
ObservabilityPageTemplate: navigation.PageTemplate,
usageCollection: pluginsSetup.usageCollection,
isDev: this.initContext.env.mode.dev,
+ kibanaVersion,
});
};
diff --git a/x-pack/plugins/observability/public/services/get_news_feed.test.ts b/x-pack/plugins/observability/public/services/get_news_feed.test.ts
index 3a35e87166720..59178bd841be2 100644
--- a/x-pack/plugins/observability/public/services/get_news_feed.test.ts
+++ b/x-pack/plugins/observability/public/services/get_news_feed.test.ts
@@ -25,7 +25,7 @@ describe('getNewsFeed', () => {
},
} as unknown as HttpSetup;
- const newsFeed = await getNewsFeed({ http });
+ const newsFeed = await getNewsFeed({ http, kibanaVersion: '8.7.0' });
expect(newsFeed.items).toEqual([]);
});
it('Returns array with the news feed', async () => {
@@ -92,7 +92,7 @@ describe('getNewsFeed', () => {
},
} as unknown as HttpSetup;
- const newsFeed = await getNewsFeed({ http });
+ const newsFeed = await getNewsFeed({ http, kibanaVersion: '8.7.0' });
expect(newsFeed.items.length).toEqual(3);
});
});
diff --git a/x-pack/plugins/observability/public/services/get_news_feed.ts b/x-pack/plugins/observability/public/services/get_news_feed.ts
index 31c7c6cd30ea8..8916cffbd1493 100644
--- a/x-pack/plugins/observability/public/services/get_news_feed.ts
+++ b/x-pack/plugins/observability/public/services/get_news_feed.ts
@@ -6,6 +6,7 @@
*/
import type { HttpSetup } from '@kbn/core/public';
+import semverCoerce from 'semver/functions/coerce';
export interface NewsItem {
title: { en: string };
@@ -17,10 +18,27 @@ export interface NewsItem {
interface NewsFeed {
items: NewsItem[];
}
+/**
+ * Removes the suffix that is sometimes appended to the Kibana version,
+ * (e.g. `8.0.0-SNAPSHOT-rc1`), which is typically only seen in non-production
+ * environments
+ */
+const removeSuffixFromVersion = (kibanaVersion?: string) =>
+ semverCoerce(kibanaVersion)?.version ?? kibanaVersion;
-export async function getNewsFeed({ http }: { http: HttpSetup }): Promise {
+export async function getNewsFeed({
+ http,
+ kibanaVersion,
+}: {
+ http: HttpSetup;
+ kibanaVersion: string;
+}): Promise {
try {
- return await http.get('https://feeds.elastic.co/observability-solution/v8.0.0.json');
+ return await http.get(
+ `https://feeds.elastic.co/observability-solution/v${removeSuffixFromVersion(
+ kibanaVersion
+ )}.json`
+ );
} catch (e) {
console.error('Error while fetching news feed', e);
return { items: [] };
diff --git a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts
index 2004fbb8428c0..97ffcbd1a7227 100644
--- a/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts
+++ b/x-pack/plugins/observability/server/lib/rules/slo_burn_rate/executor.test.ts
@@ -19,6 +19,7 @@ import { ISearchStartSearchSource } from '@kbn/data-plugin/public';
import { MockedLogger } from '@kbn/logging-mocks';
import { SanitizedRuleConfig } from '@kbn/alerting-plugin/common';
import { Alert, RuleExecutorServices } from '@kbn/alerting-plugin/server';
+import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
import {
ALERT_EVALUATION_THRESHOLD,
ALERT_EVALUATION_VALUE,
@@ -118,6 +119,7 @@ describe('BurnRateRuleExecutor', () => {
rule: {} as SanitizedRuleConfig,
spaceId: 'irrelevant',
state: {},
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
expect(alertWithLifecycleMock).not.toBeCalled();
@@ -142,6 +144,7 @@ describe('BurnRateRuleExecutor', () => {
rule: {} as SanitizedRuleConfig,
spaceId: 'irrelevant',
state: {},
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
expect(alertWithLifecycleMock).not.toBeCalled();
@@ -166,6 +169,7 @@ describe('BurnRateRuleExecutor', () => {
rule: {} as SanitizedRuleConfig,
spaceId: 'irrelevant',
state: {},
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
expect(alertWithLifecycleMock).not.toBeCalled();
@@ -195,6 +199,7 @@ describe('BurnRateRuleExecutor', () => {
rule: {} as SanitizedRuleConfig,
spaceId: 'irrelevant',
state: {},
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
expect(alertWithLifecycleMock).toBeCalledWith({
@@ -242,6 +247,7 @@ describe('BurnRateRuleExecutor', () => {
rule: {} as SanitizedRuleConfig,
spaceId: 'irrelevant',
state: {},
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
expect(alertWithLifecycleMock).not.toBeCalled();
diff --git a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts
index d098c2bc57fb2..290aae563c37b 100644
--- a/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts
+++ b/x-pack/plugins/osquery/cypress/e2e/all/alerts.cy.ts
@@ -200,7 +200,7 @@ describe('Alert Event Details', () => {
it('should be able to add investigation guides to response actions', () => {
const investigationGuideNote =
- 'It seems that you have suggested queries in investigation guide, would you like to add them as response actions?';
+ 'You have queries in the investigation guide. Add them as response actions?';
cy.visit('/app/security/rules');
cy.contains(RULE_NAME).click();
cy.contains('Edit rule settings').click();
diff --git a/x-pack/plugins/osquery/public/results/results_table.tsx b/x-pack/plugins/osquery/public/results/results_table.tsx
index 53ce6f8ee5ad2..3f1ca20f38eaf 100644
--- a/x-pack/plugins/osquery/public/results/results_table.tsx
+++ b/x-pack/plugins/osquery/public/results/results_table.tsx
@@ -49,7 +49,10 @@ const DataContext = createContext([]);
const StyledEuiDataGrid = styled(EuiDataGrid)`
:not(.euiDataGrid--fullScreen) {
- max-height: 500px;
+ .euiDataGrid__virtualized {
+ height: 100% !important;
+ max-height: 500px;
+ }
}
`;
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
index e3678e1455527..7f22b218ec0fd 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_executor.ts
@@ -162,6 +162,7 @@ export const createLifecycleExecutor =
const {
services: { alertFactory, shouldWriteAlerts },
state: previousState,
+ flappingSettings,
} = options;
const ruleDataClientWriter = await ruleDataClient.getWriter();
@@ -266,6 +267,7 @@ export const createLifecycleExecutor =
const isActive = !isRecovered;
const flappingHistory = getUpdatedFlappingHistory(
+ flappingSettings,
alertId,
state,
isNew,
@@ -290,7 +292,7 @@ export const createLifecycleExecutor =
pendingRecoveredCount: 0,
};
- const flapping = isFlapping(flappingHistory, isCurrentlyFlapping);
+ const flapping = isFlapping(flappingSettings, flappingHistory, isCurrentlyFlapping);
const event: ParsedTechnicalFields & ParsedExperimentalFields = {
...alertData?.fields,
@@ -329,7 +331,7 @@ export const createLifecycleExecutor =
const newEventsToIndex = makeEventsDataMapFor(newAlertIds);
const trackedRecoveredEventsToIndex = makeEventsDataMapFor(trackedAlertRecoveredIds);
const allEventsToIndex = [
- ...getAlertsForNotification(trackedEventsToIndex),
+ ...getAlertsForNotification(flappingSettings, trackedEventsToIndex),
...newEventsToIndex,
];
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
index de164dd07c0b3..92cbdb35240b5 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type.test.ts
@@ -22,6 +22,7 @@ import { createLifecycleRuleTypeFactory } from './create_lifecycle_rule_type_fac
import { ISearchStartSearchSource } from '@kbn/data-plugin/common';
import { SharePluginStart } from '@kbn/share-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
+import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
type RuleTestHelpers = ReturnType;
@@ -138,6 +139,7 @@ function createRule(shouldWriteAlerts: boolean = true) {
spaceId: 'spaceId',
startedAt,
state,
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
})) ?? {}) as Record);
previousStartedAt = startedAt;
diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts
index 08a6c90eda022..b3047303bcb08 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.test.ts
@@ -5,7 +5,12 @@
* 2.0.
*/
+import {
+ DEFAULT_FLAPPING_SETTINGS,
+ DISABLE_FLAPPING_SETTINGS,
+} from '@kbn/alerting-plugin/common/rules_settings';
import { ALERT_STATUS_ACTIVE, ALERT_STATUS_RECOVERED } from '@kbn/rule-data-utils';
+import { cloneDeep } from 'lodash';
import { getAlertsForNotification } from './get_alerts_for_notification';
describe('getAlertsForNotification', () => {
@@ -38,7 +43,8 @@ describe('getAlertsForNotification', () => {
test('should set pendingRecoveredCount to zero for all active alerts', () => {
const trackedEvents = [alert4];
- expect(getAlertsForNotification(trackedEvents)).toMatchInlineSnapshot(`
+ expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents))
+ .toMatchInlineSnapshot(`
Array [
Object {
"event": Object {
@@ -55,8 +61,9 @@ describe('getAlertsForNotification', () => {
});
test('should not remove alerts if the num of recovered alerts is not at the limit', () => {
- const trackedEvents = [alert1, alert2, alert3];
- expect(getAlertsForNotification(trackedEvents)).toMatchInlineSnapshot(`
+ const trackedEvents = cloneDeep([alert1, alert2, alert3]);
+ expect(getAlertsForNotification(DEFAULT_FLAPPING_SETTINGS, trackedEvents))
+ .toMatchInlineSnapshot(`
Array [
Object {
"event": Object {
@@ -82,4 +89,34 @@ describe('getAlertsForNotification', () => {
]
`);
});
+
+ test('should reset counts and not modify alerts if flapping is disabled', () => {
+ const trackedEvents = cloneDeep([alert1, alert2, alert3]);
+ expect(getAlertsForNotification(DISABLE_FLAPPING_SETTINGS, trackedEvents))
+ .toMatchInlineSnapshot(`
+ Array [
+ Object {
+ "event": Object {
+ "kibana.alert.status": "recovered",
+ },
+ "flapping": true,
+ "pendingRecoveredCount": 0,
+ },
+ Object {
+ "event": Object {
+ "kibana.alert.status": "recovered",
+ },
+ "flapping": false,
+ "pendingRecoveredCount": 0,
+ },
+ Object {
+ "event": Object {
+ "kibana.alert.status": "recovered",
+ },
+ "flapping": true,
+ "pendingRecoveredCount": 0,
+ },
+ ]
+ `);
+ });
});
diff --git a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts
index 75d07642c5e53..878db2a918022 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_alerts_for_notification.ts
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { MAX_FLAP_COUNT } from '@kbn/alerting-plugin/server/lib/flapping_utils';
+import { RulesSettingsFlappingProperties } from '@kbn/alerting-plugin/common/rules_settings';
import {
ALERT_END,
ALERT_STATUS,
@@ -14,15 +14,21 @@ import {
EVENT_ACTION,
} from '@kbn/rule-data-utils';
-export function getAlertsForNotification(trackedEventsToIndex: any[]) {
+export function getAlertsForNotification(
+ flappingSettings: RulesSettingsFlappingProperties,
+ trackedEventsToIndex: any[]
+) {
return trackedEventsToIndex.map((trackedEvent) => {
- if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) {
+ if (!flappingSettings.enabled || trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_ACTIVE) {
trackedEvent.pendingRecoveredCount = 0;
- } else if (trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED) {
+ } else if (
+ flappingSettings.enabled &&
+ trackedEvent.event[ALERT_STATUS] === ALERT_STATUS_RECOVERED
+ ) {
if (trackedEvent.flapping) {
const count = trackedEvent.pendingRecoveredCount || 0;
trackedEvent.pendingRecoveredCount = count + 1;
- if (trackedEvent.pendingRecoveredCount < MAX_FLAP_COUNT) {
+ if (trackedEvent.pendingRecoveredCount < flappingSettings.statusChangeThreshold) {
trackedEvent.event[ALERT_STATUS] = ALERT_STATUS_ACTIVE;
trackedEvent.event[EVENT_ACTION] = 'active';
delete trackedEvent.event[ALERT_END];
diff --git a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts
index 2194d37360f14..52467f168e641 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.test.ts
@@ -5,6 +5,10 @@
* 2.0.
*/
+import {
+ DEFAULT_FLAPPING_SETTINGS,
+ DISABLE_FLAPPING_SETTINGS,
+} from '@kbn/alerting-plugin/common/rules_settings';
import { getUpdatedFlappingHistory } from './get_updated_flapping_history';
describe('getUpdatedFlappingHistory', () => {
@@ -17,8 +21,17 @@ describe('getUpdatedFlappingHistory', () => {
test('sets flapping state to true if the alert is new', () => {
const state = { wrapped: initialRuleState, trackedAlerts: {}, trackedAlertsRecovered: {} };
- expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, true, false, false, []))
- .toMatchInlineSnapshot(`
+ expect(
+ getUpdatedFlappingHistory(
+ DEFAULT_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ true,
+ false,
+ false,
+ []
+ )
+ ).toMatchInlineSnapshot(`
Array [
true,
]
@@ -40,8 +53,17 @@ describe('getUpdatedFlappingHistory', () => {
},
trackedAlertsRecovered: {},
};
- expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, false, true, []))
- .toMatchInlineSnapshot(`
+ expect(
+ getUpdatedFlappingHistory(
+ DEFAULT_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ false,
+ false,
+ true,
+ []
+ )
+ ).toMatchInlineSnapshot(`
Array [
false,
]
@@ -64,8 +86,17 @@ describe('getUpdatedFlappingHistory', () => {
trackedAlerts: {},
};
const recoveredIds = ['TEST_ALERT_0'];
- expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, true, false, true, recoveredIds))
- .toMatchInlineSnapshot(`
+ expect(
+ getUpdatedFlappingHistory(
+ DEFAULT_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ true,
+ false,
+ true,
+ recoveredIds
+ )
+ ).toMatchInlineSnapshot(`
Array [
true,
]
@@ -89,8 +120,17 @@ describe('getUpdatedFlappingHistory', () => {
trackedAlertsRecovered: {},
};
const recoveredIds = ['TEST_ALERT_0'];
- expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, true, false, recoveredIds))
- .toMatchInlineSnapshot(`
+ expect(
+ getUpdatedFlappingHistory(
+ DEFAULT_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ false,
+ true,
+ false,
+ recoveredIds
+ )
+ ).toMatchInlineSnapshot(`
Array [
true,
]
@@ -98,7 +138,7 @@ describe('getUpdatedFlappingHistory', () => {
expect(recoveredIds).toEqual(['TEST_ALERT_0']);
});
- test('sets flapping state to true on an alert that is still recovered', () => {
+ test('sets flapping state to false on an alert that is still recovered', () => {
const state = {
wrapped: initialRuleState,
trackedAlerts: {},
@@ -114,12 +154,49 @@ describe('getUpdatedFlappingHistory', () => {
},
};
const recoveredIds = ['TEST_ALERT_0'];
- expect(getUpdatedFlappingHistory('TEST_ALERT_0', state, false, true, false, recoveredIds))
- .toMatchInlineSnapshot(`
+ expect(
+ getUpdatedFlappingHistory(
+ DEFAULT_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ false,
+ true,
+ false,
+ recoveredIds
+ )
+ ).toMatchInlineSnapshot(`
Array [
false,
]
`);
expect(recoveredIds).toEqual(['TEST_ALERT_0']);
});
+
+ test('does not set flapping state if flapping is not enabled', () => {
+ const state = {
+ wrapped: initialRuleState,
+ trackedAlerts: {},
+ trackedAlertsRecovered: {
+ TEST_ALERT_0: {
+ alertId: 'TEST_ALERT_0',
+ alertUuid: 'TEST_ALERT_0_UUID',
+ started: '2020-01-01T12:00:00.000Z',
+ flappingHistory: [],
+ flapping: false,
+ pendingRecoveredCount: 0,
+ },
+ },
+ };
+ expect(
+ getUpdatedFlappingHistory(
+ DISABLE_FLAPPING_SETTINGS,
+ 'TEST_ALERT_0',
+ state,
+ false,
+ true,
+ false,
+ ['TEST_ALERT_0']
+ )
+ ).toMatchInlineSnapshot(`Array []`);
+ });
});
diff --git a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.ts b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.ts
index 0f64e5778f929..854f919722330 100644
--- a/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.ts
+++ b/x-pack/plugins/rule_registry/server/utils/get_updated_flapping_history.ts
@@ -6,11 +6,13 @@
*/
import { RuleTypeState } from '@kbn/alerting-plugin/common';
+import { RulesSettingsFlappingProperties } from '@kbn/alerting-plugin/common/rules_settings';
import { updateFlappingHistory } from '@kbn/alerting-plugin/server/lib';
import { remove } from 'lodash';
import { WrappedLifecycleRuleState } from './create_lifecycle_executor';
export function getUpdatedFlappingHistory(
+ flappingSettings: RulesSettingsFlappingProperties,
alertId: string,
state: WrappedLifecycleRuleState,
isNew: boolean,
@@ -20,31 +22,43 @@ export function getUpdatedFlappingHistory(
) {
// duplicating this logic to determine flapping at this level
let flappingHistory: boolean[] = [];
- if (isRecovered) {
- if (state.trackedAlerts[alertId]) {
- // this alert has flapped from active to recovered
- flappingHistory = updateFlappingHistory(state.trackedAlerts[alertId].flappingHistory, true);
- } else if (state.trackedAlertsRecovered[alertId]) {
- // this alert is still recovered
+ if (flappingSettings.enabled) {
+ if (isRecovered) {
+ if (state.trackedAlerts[alertId]) {
+ // this alert has flapped from active to recovered
+ flappingHistory = updateFlappingHistory(
+ flappingSettings,
+ state.trackedAlerts[alertId].flappingHistory,
+ true
+ );
+ } else if (state.trackedAlertsRecovered[alertId]) {
+ // this alert is still recovered
+ flappingHistory = updateFlappingHistory(
+ flappingSettings,
+ state.trackedAlertsRecovered[alertId].flappingHistory,
+ false
+ );
+ }
+ } else if (isNew) {
+ if (state.trackedAlertsRecovered[alertId]) {
+ // this alert has flapped from recovered to active
+ flappingHistory = updateFlappingHistory(
+ flappingSettings,
+ state.trackedAlertsRecovered[alertId].flappingHistory,
+ true
+ );
+ remove(recoveredIds, (id) => id === alertId);
+ } else {
+ flappingHistory = updateFlappingHistory(flappingSettings, [], true);
+ }
+ } else if (isActive) {
+ // this alert is still active
flappingHistory = updateFlappingHistory(
- state.trackedAlertsRecovered[alertId].flappingHistory,
+ flappingSettings,
+ state.trackedAlerts[alertId].flappingHistory,
false
);
}
- } else if (isNew) {
- if (state.trackedAlertsRecovered[alertId]) {
- // this alert has flapped from recovered to active
- flappingHistory = updateFlappingHistory(
- state.trackedAlertsRecovered[alertId].flappingHistory,
- true
- );
- remove(recoveredIds, (id) => id === alertId);
- } else {
- flappingHistory = updateFlappingHistory([], true);
- }
- } else if (isActive) {
- // this alert is still active
- flappingHistory = updateFlappingHistory(state.trackedAlerts[alertId].flappingHistory, false);
}
return flappingHistory;
}
diff --git a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts
index 9e75fa159190f..f2416b0cba677 100644
--- a/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts
+++ b/x-pack/plugins/rule_registry/server/utils/rule_executor.test_helpers.ts
@@ -21,6 +21,7 @@ import { searchSourceCommonMock } from '@kbn/data-plugin/common/search/search_so
import { Logger } from '@kbn/logging';
import { SharePluginStart } from '@kbn/share-plugin/server';
import { dataViewPluginMocks } from '@kbn/data-views-plugin/public/mocks';
+import { DEFAULT_FLAPPING_SETTINGS } from '@kbn/alerting-plugin/common/rules_settings';
export const createDefaultAlertExecutorOptions = <
Params extends RuleTypeParams = never,
@@ -87,4 +88,5 @@ export const createDefaultAlertExecutorOptions = <
namespace: undefined,
executionId: 'b33f65d7-6e8b-4aae-8d20-c93613deb33f',
logger,
+ flappingSettings: DEFAULT_FLAPPING_SETTINGS,
});
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts
index c7ccc728525d5..1c2326832402e 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_loaders/index_fleet_endpoint_policy.ts
@@ -33,7 +33,8 @@ export interface IndexedFleetEndpointPolicyResponse {
export const indexFleetEndpointPolicy = async (
kbnClient: KbnClient,
policyName: string,
- endpointPackageVersion: string = '8.0.0'
+ endpointPackageVersion: string = '8.0.0',
+ agentPolicyName?: string
): Promise => {
const response: IndexedFleetEndpointPolicyResponse = {
integrationPolicies: [],
@@ -42,7 +43,8 @@ export const indexFleetEndpointPolicy = async (
// Create Agent Policy first
const newAgentPolicyData: CreateAgentPolicyRequest['body'] = {
- name: `Policy for ${policyName} (${Math.random().toString(36).substr(2, 5)})`,
+ name:
+ agentPolicyName || `Policy for ${policyName} (${Math.random().toString(36).substr(2, 5)})`,
description: `Policy created with endpoint data generator (${policyName})`,
namespace: 'default',
};
diff --git a/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx b/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx
index 4edbc9fe85a32..6c702bb20fd90 100644
--- a/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx
+++ b/x-pack/plugins/security_solution/public/actions/show_top_n/show_top_n_component.test.tsx
@@ -20,6 +20,7 @@ jest.mock('react-router-dom', () => {
useLocation: jest.fn().mockReturnValue({ pathname: '/test' }),
};
});
+jest.mock('../../common/components/visualization_actions');
const casesService = {
ui: { getCasesContext: () => mockCasesContext },
diff --git a/x-pack/plugins/security_solution/public/app/index.tsx b/x-pack/plugins/security_solution/public/app/index.tsx
index 98b82a8d5b8fa..29abcc5c475ea 100644
--- a/x-pack/plugins/security_solution/public/app/index.tsx
+++ b/x-pack/plugins/security_solution/public/app/index.tsx
@@ -49,5 +49,8 @@ export const renderApp = ({
,
element
);
- return () => unmountComponentAtNode(element);
+ return () => {
+ services.data.search.session.clear();
+ unmountComponentAtNode(element);
+ };
};
diff --git a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx
index 1358739742b6e..8b968077f3dcb 100644
--- a/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/charts/donutchart.tsx
@@ -46,7 +46,6 @@ export interface DonutChartProps {
data: DonutChartData[] | null | undefined;
fillColor: FillColor;
height?: number;
- isChartEmbeddablesEnabled?: boolean;
label: React.ReactElement | string;
legendItems?: LegendItem[] | null | undefined;
onElementClick?: ElementClickListener;
@@ -67,10 +66,10 @@ export interface DonutChartWrapperProps {
/* Make this position absolute in order to overlap the text onto the donut */
export const DonutTextWrapper = styled(EuiFlexGroup)<
EuiFlexGroupProps & {
- $isChartEmbeddablesEnabled?: boolean;
$dataExists?: boolean;
+ $donutTextWrapperStyles?: FlattenSimpleInterpolation;
+ $isChartEmbeddablesEnabled?: boolean;
className?: string;
- donutTextWrapperStyles?: FlattenSimpleInterpolation;
}
>`
top: ${({ $isChartEmbeddablesEnabled, $dataExists }) =>
@@ -80,8 +79,8 @@ export const DonutTextWrapper = styled(EuiFlexGroup)<
position: absolute;
z-index: 1;
- ${({ className, donutTextWrapperStyles }) =>
- className && donutTextWrapperStyles ? `&.${className} {${donutTextWrapperStyles}}` : ''}
+ ${({ className, $donutTextWrapperStyles }) =>
+ className && $donutTextWrapperStyles ? `&.${className} {${$donutTextWrapperStyles}}` : ''}
`;
export const StyledEuiFlexItem = styled(EuiFlexItem)`
@@ -117,11 +116,11 @@ const DonutChartWrapperComponent: React.FC = ({
@@ -151,7 +150,6 @@ export const DonutChart = ({
data,
fillColor,
height = 90,
- isChartEmbeddablesEnabled,
label,
legendItems,
onElementClick,
@@ -165,7 +163,7 @@ export const DonutChart = ({
dataExists={data != null && data.length > 0}
label={label}
title={title}
- isChartEmbeddablesEnabled={isChartEmbeddablesEnabled}
+ isChartEmbeddablesEnabled={false}
>
<>
{data == null || totalCount == null || totalCount === 0 ? (
diff --git a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx
index c06390b4f9a16..1387784d82860 100644
--- a/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/events_tab/events_query_tab_body.test.tsx
@@ -47,6 +47,9 @@ jest.mock('../../lib/kibana', () => {
};
});
+jest.mock('../visualization_actions');
+jest.mock('../visualization_actions/lens_embeddable');
+
jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useHistory: () => mockHistory,
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx
new file mode 100644
index 0000000000000..65d3774af1eef
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/chart_content.tsx
@@ -0,0 +1,29 @@
+/*
+ * 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 { BarChartComponentProps } from '../charts/barchart';
+import { BarChart } from '../charts/barchart';
+import { MatrixLoader } from './matrix_loader';
+
+const MatrixHistogramChartContentComponent = ({
+ isInitialLoading,
+ barChart,
+ configs,
+ stackByField,
+ scopeId,
+}: BarChartComponentProps & { isInitialLoading: boolean }) => {
+ return isInitialLoading ? (
+
+ ) : (
+
+ );
+};
+
+export const MatrixHistogramChartContent = React.memo(MatrixHistogramChartContentComponent);
+
+MatrixHistogramChartContentComponent.displayName = 'MatrixHistogramChartContentComponent';
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
index ac5a43d23a28b..ec4a25039e912 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.test.tsx
@@ -176,22 +176,7 @@ describe('Matrix Histogram Component', () => {
});
describe('Inspect button', () => {
- test("it doesn't render Inspect button by default on Host page", () => {
- mockLocation.mockReturnValue({ pathname: '/hosts' });
-
- const testProps = {
- ...mockMatrixOverTimeHistogramProps,
- lensAttributes: dnsTopDomainsLensAttributes,
- };
- wrapper = mount( , {
- wrappingComponent: TestProviders,
- });
- expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false);
- });
-
- test("it doesn't render Inspect button by default on Network page", () => {
- mockLocation.mockReturnValue({ pathname: '/network' });
-
+ test("it doesn't render Inspect button by default", () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
@@ -201,41 +186,10 @@ describe('Matrix Histogram Component', () => {
});
expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(false);
});
-
- test('it render Inspect button by default on other pages', () => {
- mockLocation.mockReturnValue({ pathname: '/overview' });
-
- const testProps = {
- ...mockMatrixOverTimeHistogramProps,
- lensAttributes: dnsTopDomainsLensAttributes,
- };
- wrapper = mount( , {
- wrappingComponent: TestProviders,
- });
- expect(wrapper.find('[data-test-subj="inspect-icon-button"]').exists()).toBe(true);
- });
});
describe('VisualizationActions', () => {
- test('it renders VisualizationActions on Host page if lensAttributes is provided', () => {
- mockLocation.mockReturnValue({ pathname: '/hosts' });
-
- const testProps = {
- ...mockMatrixOverTimeHistogramProps,
- lensAttributes: dnsTopDomainsLensAttributes,
- };
- wrapper = mount( , {
- wrappingComponent: TestProviders,
- });
- expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(true);
- expect(wrapper.find('[data-test-subj="mock-viz-actions"]').prop('className')).toEqual(
- 'histogram-viz-actions'
- );
- });
-
- test('it renders VisualizationActions on Network page if lensAttributes is provided', () => {
- mockLocation.mockReturnValue({ pathname: '/network' });
-
+ test('it renders VisualizationActions if lensAttributes is provided', () => {
const testProps = {
...mockMatrixOverTimeHistogramProps,
lensAttributes: dnsTopDomainsLensAttributes,
@@ -248,20 +202,6 @@ describe('Matrix Histogram Component', () => {
'histogram-viz-actions'
);
});
-
- test("it doesn't renders VisualizationActions except Host / Network pages", () => {
- const testProps = {
- ...mockMatrixOverTimeHistogramProps,
- lensAttributes: dnsTopDomainsLensAttributes,
- };
-
- mockLocation.mockReturnValue({ pathname: '/overview' });
-
- wrapper = mount( , {
- wrappingComponent: TestProviders,
- });
- expect(wrapper.find('[data-test-subj="mock-viz-actions"]').exists()).toBe(false);
- });
});
describe('toggle query', () => {
diff --git a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
index 7b44b9295218c..48e812ff2afa3 100644
--- a/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/matrix_histogram/index.tsx
@@ -11,11 +11,8 @@ import styled from 'styled-components';
import { EuiFlexGroup, EuiFlexItem, EuiProgress, EuiSelect, EuiSpacer } from '@elastic/eui';
import { useDispatch } from 'react-redux';
-import { useLocation } from 'react-router-dom';
import * as i18n from './translations';
-import { BarChart } from '../charts/barchart';
import { HeaderSection } from '../header_section';
-import { MatrixLoader } from './matrix_loader';
import { Panel } from '../panel';
import { getBarchartConfigs, getCustomChartData } from './utils';
import { useMatrixHistogramCombined } from '../../containers/matrix_histogram';
@@ -35,8 +32,10 @@ import { HoverVisibilityContainer } from '../hover_visibility_container';
import { VisualizationActions } from '../visualization_actions';
import type { GetLensAttributes, LensAttributes } from '../visualization_actions/types';
import { useQueryToggle } from '../../containers/query_toggle';
+import { useIsExperimentalFeatureEnabled } from '../../hooks/use_experimental_features';
import { VISUALIZATION_ACTIONS_BUTTON_CLASS } from '../visualization_actions/utils';
-import { isExplorePage } from '../../../helpers';
+import { VisualizationEmbeddable } from '../visualization_actions/visualization_embeddable';
+import { MatrixHistogramChartContent } from './chart_content';
export type MatrixHistogramComponentProps = MatrixHistogramProps &
Omit & {
@@ -71,6 +70,8 @@ const HistogramPanel = styled(Panel)<{ height?: number }>`
${({ height }) => (height != null ? `min-height: ${height}px;` : '')}
`;
+const CHART_HEIGHT = '150px';
+
export const MatrixHistogramComponent: React.FC = ({
chartHeight,
defaultStackByOption,
@@ -107,7 +108,6 @@ export const MatrixHistogramComponent: React.FC =
hideQueryToggle = false,
}) => {
const dispatch = useDispatch();
- const { pathname } = useLocation();
const handleBrushEnd = useCallback(
({ x }) => {
@@ -169,6 +169,8 @@ export const MatrixHistogramComponent: React.FC =
[setQuerySkip, setToggleStatus]
);
+ const isChartEmbeddablesEnabled = useIsExperimentalFeatureEnabled('chartEmbeddablesEnabled');
+
const matrixHistogramRequest = {
endDate,
errorMessage,
@@ -180,11 +182,10 @@ export const MatrixHistogramComponent: React.FC =
stackByField: selectedStackByOption.value,
runtimeMappings,
isPtrIncluded,
- skip: querySkip,
+ skip: querySkip || isChartEmbeddablesEnabled,
};
const [loading, { data, inspect, totalCount, refetch }] =
useMatrixHistogramCombined(matrixHistogramRequest);
- const onExplorePage = isExplorePage(pathname);
const titleWithStackByField = useMemo(
() => (title != null && typeof title === 'function' ? title(selectedStackByOption) : title),
@@ -209,22 +210,28 @@ export const MatrixHistogramComponent: React.FC =
useEffect(() => {
if (!loading && !isInitialLoading) {
- setQuery({ id, inspect, loading, refetch });
+ setQuery({
+ id,
+ inspect,
+ loading,
+ refetch,
+ });
}
if (isInitialLoading && !!barChartData && data) {
setIsInitialLoading(false);
}
}, [
- setQuery,
+ barChartData,
+ data,
id,
inspect,
+ isChartEmbeddablesEnabled,
+ isInitialLoading,
loading,
refetch,
- isInitialLoading,
- barChartData,
- data,
setIsInitialLoading,
+ setQuery,
]);
const timerange = useMemo(() => ({ from: startDate, to: endDate }), [startDate, endDate]);
@@ -261,11 +268,11 @@ export const MatrixHistogramComponent: React.FC =
toggleQuery={hideQueryToggle ? undefined : toggleQuery}
subtitle={subtitleWithCounts}
inspectMultiple
- showInspectButton={showInspectButton || !onExplorePage}
+ showInspectButton={showInspectButton && !isChartEmbeddablesEnabled}
isInspectDisabled={filterQuery === undefined}
>
- {onExplorePage && (getLensAttributes || lensAttributes) && timerange && (
+ {(getLensAttributes || lensAttributes) && timerange && (
=
{toggleStatus ? (
- isInitialLoading ? (
-
+ isChartEmbeddablesEnabled ? (
+
) : (
- ;
refetchByRestartingSession: Refetch;
+ refetchByDeletingSession: Refetch;
} => {
const dispatch = useDispatch();
const { data } = useKibana().services;
@@ -44,6 +45,7 @@ export const useRefetchByRestartingSession = ({
);
const refetchByRestartingSession = useCallback(() => {
+ const searchSessionId = session.current.start();
dispatch(
inputsActions.setInspectionParameter({
id: queryId,
@@ -54,13 +56,21 @@ export const useRefetchByRestartingSession = ({
* like most of our components, it refetches when receiving a new search
* session ID.
**/
- searchSessionId: skip ? undefined : session.current.start(),
+ searchSessionId: skip ? undefined : searchSessionId,
})
);
}, [dispatch, queryId, selectedInspectIndex, skip]);
+ /**
+ * This is for refetching alert index when the first rule just created
+ */
+ const refetchByDeletingSession = useCallback(() => {
+ dispatch(inputsActions.deleteOneQuery({ inputId: InputsModelId.global, id: queryId }));
+ }, [dispatch, queryId]);
+
return {
session,
refetchByRestartingSession,
+ refetchByDeletingSession,
};
};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap
index 84d29e3e7fd9d..a00e6517914da 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/authentication.test.ts.snap
@@ -251,7 +251,7 @@ Object {
],
"legend": Object {
"isVisible": true,
- "position": "right",
+ "position": "left",
},
"preferredSeriesType": "bar_stacked",
"title": "Empty XY chart",
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap
index 93bdbe2a0dca5..ab1eed774e8aa 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/event.test.ts.snap
@@ -175,7 +175,8 @@ Object {
],
"legend": Object {
"isVisible": true,
- "position": "right",
+ "legendSize": "xlarge",
+ "position": "left",
},
"preferredSeriesType": "bar_stacked",
"title": "Empty XY chart",
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap
index d53499bd57c8a..7d06e63032e95 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/__snapshots__/external_alert.test.ts.snap
@@ -206,7 +206,7 @@ Object {
],
"legend": Object {
"isVisible": true,
- "position": "right",
+ "position": "left",
},
"preferredSeriesType": "bar_stacked",
"title": "Empty XY chart",
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_status_donut.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_status_donut.test.ts.snap
new file mode 100644
index 0000000000000..12de83c43fc33
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_by_status_donut.test.ts.snap
@@ -0,0 +1,166 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getAlertsByStatusAttributes should render without extra options 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-b9b43606-7ff7-46ae-a47c-85bed80fab9a",
+ "type": "index-pattern",
+ },
+ Object {
+ "id": "security-solution-my-test",
+ "name": "a1aaa83b-5026-444e-9465-50e0afade01c",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "b9b43606-7ff7-46ae-a47c-85bed80fab9a": Object {
+ "columnOrder": Array [
+ "a9b43606-7ff7-46ae-a47c-85bed80fab9a",
+ "21cc4a49-3780-4b1a-be28-f02fa5303d24",
+ ],
+ "columns": Object {
+ "21cc4a49-3780-4b1a-be28-f02fa5303d24": Object {
+ "dataType": "number",
+ "filter": Object {
+ "language": "kuery",
+ "query": "",
+ },
+ "isBucketed": false,
+ "label": "Count of records",
+ "operationType": "count",
+ "params": Object {
+ "emptyAsNull": true,
+ },
+ "scale": "ratio",
+ "sourceField": "___records___",
+ },
+ "a9b43606-7ff7-46ae-a47c-85bed80fab9a": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Filters",
+ "operationType": "filters",
+ "params": Object {
+ "filters": Array [
+ Object {
+ "input": Object {
+ "language": "kuery",
+ "query": "kibana.alert.severity: \\"critical\\"",
+ },
+ "label": "Critical",
+ },
+ Object {
+ "input": Object {
+ "language": "kuery",
+ "query": "kibana.alert.severity : \\"high\\" ",
+ },
+ "label": "High",
+ },
+ Object {
+ "input": Object {
+ "language": "kuery",
+ "query": "kibana.alert.severity: \\"medium\\"",
+ },
+ "label": "Medium",
+ },
+ Object {
+ "input": Object {
+ "language": "kuery",
+ "query": "kibana.alert.severity : \\"low\\" ",
+ },
+ "label": "Low",
+ },
+ ],
+ },
+ "scale": "ordinal",
+ },
+ },
+ "incompleteColumns": Object {},
+ "sampling": 1,
+ },
+ },
+ },
+ "textBased": Object {
+ "layers": Object {},
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "layers": Array [
+ Object {
+ "categoryDisplay": "hide",
+ "emptySizeRatio": 0.85,
+ "layerId": "b9b43606-7ff7-46ae-a47c-85bed80fab9a",
+ "layerType": "data",
+ "legendDisplay": "hide",
+ "metrics": Array [
+ "21cc4a49-3780-4b1a-be28-f02fa5303d24",
+ ],
+ "nestedLegend": true,
+ "numberDisplay": "value",
+ "percentDecimals": 2,
+ "primaryGroups": Array [
+ "a9b43606-7ff7-46ae-a47c-85bed80fab9a",
+ ],
+ },
+ ],
+ "shape": "donut",
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsPie",
+}
+`;
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap
new file mode 100644
index 0000000000000..d0b6f7a79ce34
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_histogram.test.ts.snap
@@ -0,0 +1,343 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getAlertsHistogramLensAttributes should render with extra options - filters 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object {
+ "columnOrder": Array [
+ "34919782-4546-43a5-b668-06ac934d3acd",
+ "aac9d7d0-13a3-480a-892b-08207a787926",
+ "e09e0380-0740-4105-becc-0a4ca12e3944",
+ ],
+ "columns": Object {
+ "34919782-4546-43a5-b668-06ac934d3acd": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "secondaryFields": Array [],
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "aac9d7d0-13a3-480a-892b-08207a787926": Object {
+ "dataType": "date",
+ "isBucketed": true,
+ "label": "@timestamp",
+ "operationType": "date_histogram",
+ "params": Object {
+ "interval": "auto",
+ },
+ "scale": "interval",
+ "sourceField": "@timestamp",
+ },
+ "e09e0380-0740-4105-becc-0a4ca12e3944": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of records",
+ "operationType": "count",
+ "scale": "ratio",
+ "sourceField": "___records___",
+ },
+ },
+ "incompleteColumns": Object {},
+ },
+ },
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ ".alerts-security.alerts-default",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": ".alerts-security.alerts-default",
+ },
+ },
+ ],
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "axisTitlesVisibilitySettings": Object {
+ "x": false,
+ "yLeft": false,
+ "yRight": true,
+ },
+ "layers": Array [
+ Object {
+ "accessors": Array [
+ "e09e0380-0740-4105-becc-0a4ca12e3944",
+ ],
+ "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75",
+ "layerType": "data",
+ "position": "top",
+ "seriesType": "bar_stacked",
+ "showGridlines": false,
+ "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd",
+ "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926",
+ },
+ ],
+ "legend": Object {
+ "isVisible": true,
+ "legendSize": "xlarge",
+ "position": "left",
+ },
+ "preferredSeriesType": "bar_stacked",
+ "title": "Empty XY chart",
+ "valueLabels": "hide",
+ "valuesInLegend": true,
+ "yLeftExtent": Object {
+ "mode": "full",
+ },
+ "yRightExtent": Object {
+ "mode": "full",
+ },
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsXY",
+}
+`;
+
+exports[`getAlertsHistogramLensAttributes should render without extra options 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "0039eb0c-9a1a-4687-ae54-0f4e239bec75": Object {
+ "columnOrder": Array [
+ "34919782-4546-43a5-b668-06ac934d3acd",
+ "aac9d7d0-13a3-480a-892b-08207a787926",
+ "e09e0380-0740-4105-becc-0a4ca12e3944",
+ ],
+ "columns": Object {
+ "34919782-4546-43a5-b668-06ac934d3acd": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "e09e0380-0740-4105-becc-0a4ca12e3944",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "secondaryFields": Array [],
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "aac9d7d0-13a3-480a-892b-08207a787926": Object {
+ "dataType": "date",
+ "isBucketed": true,
+ "label": "@timestamp",
+ "operationType": "date_histogram",
+ "params": Object {
+ "interval": "auto",
+ },
+ "scale": "interval",
+ "sourceField": "@timestamp",
+ },
+ "e09e0380-0740-4105-becc-0a4ca12e3944": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of records",
+ "operationType": "count",
+ "scale": "ratio",
+ "sourceField": "___records___",
+ },
+ },
+ "incompleteColumns": Object {},
+ },
+ },
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "axisTitlesVisibilitySettings": Object {
+ "x": false,
+ "yLeft": false,
+ "yRight": true,
+ },
+ "layers": Array [
+ Object {
+ "accessors": Array [
+ "e09e0380-0740-4105-becc-0a4ca12e3944",
+ ],
+ "layerId": "0039eb0c-9a1a-4687-ae54-0f4e239bec75",
+ "layerType": "data",
+ "position": "top",
+ "seriesType": "bar_stacked",
+ "showGridlines": false,
+ "splitAccessor": "34919782-4546-43a5-b668-06ac934d3acd",
+ "xAccessor": "aac9d7d0-13a3-480a-892b-08207a787926",
+ },
+ ],
+ "legend": Object {
+ "isVisible": true,
+ "legendSize": "xlarge",
+ "position": "left",
+ },
+ "preferredSeriesType": "bar_stacked",
+ "title": "Empty XY chart",
+ "valueLabels": "hide",
+ "valuesInLegend": true,
+ "yLeftExtent": Object {
+ "mode": "full",
+ },
+ "yRightExtent": Object {
+ "mode": "full",
+ },
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsXY",
+}
+`;
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap
new file mode 100644
index 0000000000000..58ecf5d44d015
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/alerts_table.test.ts.snap
@@ -0,0 +1,523 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getAlertsTableLensAttributes should render with extra options - breakdownField 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
+ "columnOrder": Array [
+ "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "f04a71a3-399f-4d32-9efc-8a005e989991",
+ ],
+ "columns": Object {
+ "2881fedd-54b7-42ba-8c97-5175dec86166": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of agent.type",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "agent.type",
+ },
+ "f04a71a3-399f-4d32-9efc-8a005e989991": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of agent.type",
+ "operationType": "count",
+ "params": Object {
+ "emptyAsNull": true,
+ },
+ "scale": "ratio",
+ "sourceField": "agent.type",
+ },
+ },
+ "incompleteColumns": Object {},
+ "sampling": 1,
+ },
+ },
+ },
+ "textBased": Object {
+ "layers": Object {},
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "columns": Array [
+ Object {
+ "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "isTransposed": false,
+ "width": 362,
+ },
+ Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "isTransposed": false,
+ },
+ Object {
+ "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "isTransposed": false,
+ },
+ ],
+ "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "layerType": "data",
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsDatatable",
+}
+`;
+
+exports[`getAlertsTableLensAttributes should render with extra options - filters 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
+ "columnOrder": Array [
+ "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "f04a71a3-399f-4d32-9efc-8a005e989991",
+ ],
+ "columns": Object {
+ "2881fedd-54b7-42ba-8c97-5175dec86166": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of undefined",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": undefined,
+ },
+ "f04a71a3-399f-4d32-9efc-8a005e989991": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of undefined",
+ "operationType": "count",
+ "params": Object {
+ "emptyAsNull": true,
+ },
+ "scale": "ratio",
+ "sourceField": undefined,
+ },
+ },
+ "incompleteColumns": Object {},
+ "sampling": 1,
+ },
+ },
+ },
+ "textBased": Object {
+ "layers": Object {},
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ ".alerts-security.alerts-default",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": ".alerts-security.alerts-default",
+ },
+ },
+ ],
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "columns": Array [
+ Object {
+ "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "isTransposed": false,
+ "width": 362,
+ },
+ Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "isTransposed": false,
+ },
+ Object {
+ "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "isTransposed": false,
+ },
+ ],
+ "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "layerType": "data",
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsDatatable",
+}
+`;
+
+exports[`getAlertsTableLensAttributes should render without extra options 1`] = `
+Object {
+ "description": "",
+ "references": Array [
+ Object {
+ "id": "security-solution-my-test",
+ "name": "indexpattern-datasource-layer-4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "type": "index-pattern",
+ },
+ ],
+ "state": Object {
+ "adHocDataViews": Object {},
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b": Object {
+ "columnOrder": Array [
+ "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "f04a71a3-399f-4d32-9efc-8a005e989991",
+ ],
+ "columns": Object {
+ "2881fedd-54b7-42ba-8c97-5175dec86166": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "75ce269b-ee9c-4c7d-a14e-9226ba0fe059": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top values of undefined",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 1000,
+ },
+ "scale": "ordinal",
+ "sourceField": undefined,
+ },
+ "f04a71a3-399f-4d32-9efc-8a005e989991": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of undefined",
+ "operationType": "count",
+ "params": Object {
+ "emptyAsNull": true,
+ },
+ "scale": "ratio",
+ "sourceField": undefined,
+ },
+ },
+ "incompleteColumns": Object {},
+ "sampling": 1,
+ },
+ },
+ },
+ "textBased": Object {
+ "layers": Object {},
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "_index",
+ "negate": false,
+ "params": Array [
+ "signal-index",
+ ],
+ "type": "phrases",
+ },
+ "query": Object {
+ "bool": Object {
+ "minimum_should_match": 1,
+ "should": Array [
+ Object {
+ "match_phrase": Object {
+ "_index": "signal-index",
+ },
+ },
+ ],
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "columns": Array [
+ Object {
+ "columnId": "2881fedd-54b7-42ba-8c97-5175dec86166",
+ "isTransposed": false,
+ "width": 362,
+ },
+ Object {
+ "columnId": "f04a71a3-399f-4d32-9efc-8a005e989991",
+ "isTransposed": false,
+ },
+ Object {
+ "columnId": "75ce269b-ee9c-4c7d-a14e-9226ba0fe059",
+ "isTransposed": false,
+ },
+ ],
+ "layerId": "4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b",
+ "layerType": "data",
+ },
+ },
+ "title": "Alerts",
+ "visualizationType": "lnsDatatable",
+}
+`;
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/rule_preview.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/rule_preview.test.ts.snap
new file mode 100644
index 0000000000000..5a841f2bc942a
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/__snapshots__/rule_preview.test.ts.snap
@@ -0,0 +1,173 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`getRulePreviewLensAttributes should render without extra options 1`] = `
+Object {
+ "description": "",
+ "references": Array [],
+ "state": Object {
+ "adHocDataViews": Object {
+ "mockInternalReferenceId": Object {
+ "allowNoIndex": false,
+ "fieldAttrs": Object {},
+ "fieldFormats": Object {},
+ "id": "mockInternalReferenceId",
+ "name": ".preview.alerts-security.alerts-undefined",
+ "runtimeFieldMap": Object {},
+ "sourceFilters": Array [],
+ "timeFieldName": "@timestamp",
+ "title": ".preview.alerts-security.alerts-undefined",
+ },
+ },
+ "datasourceStates": Object {
+ "formBased": Object {
+ "layers": Object {
+ "mockLayerId": Object {
+ "columnOrder": Array [
+ "e92c8920-0449-4564-81f4-8945517817a4",
+ "eba07b4d-766d-49d7-8435-d40367d3d055",
+ "9c89324b-0c59-4403-9698-d989a09dc5a8",
+ ],
+ "columns": Object {
+ "9c89324b-0c59-4403-9698-d989a09dc5a8": Object {
+ "dataType": "number",
+ "isBucketed": false,
+ "label": "Count of records",
+ "operationType": "count",
+ "params": Object {
+ "emptyAsNull": true,
+ },
+ "scale": "ratio",
+ "sourceField": "___records___",
+ },
+ "e92c8920-0449-4564-81f4-8945517817a4": Object {
+ "dataType": "string",
+ "isBucketed": true,
+ "label": "Top 10 values of event.category",
+ "operationType": "terms",
+ "params": Object {
+ "exclude": Array [],
+ "excludeIsRegex": false,
+ "include": Array [],
+ "includeIsRegex": false,
+ "missingBucket": false,
+ "orderBy": Object {
+ "columnId": "9c89324b-0c59-4403-9698-d989a09dc5a8",
+ "type": "column",
+ },
+ "orderDirection": "desc",
+ "otherBucket": true,
+ "parentFormat": Object {
+ "id": "terms",
+ },
+ "size": 10,
+ },
+ "scale": "ordinal",
+ "sourceField": "event.category",
+ },
+ "eba07b4d-766d-49d7-8435-d40367d3d055": Object {
+ "dataType": "date",
+ "isBucketed": true,
+ "label": "@timestamp",
+ "operationType": "date_histogram",
+ "params": Object {
+ "dropPartials": false,
+ "includeEmptyRows": true,
+ "interval": "auto",
+ },
+ "scale": "interval",
+ "sourceField": "@timestamp",
+ },
+ },
+ "incompleteColumns": Object {},
+ "sampling": 1,
+ },
+ },
+ },
+ "textBased": Object {
+ "layers": Object {},
+ },
+ },
+ "filters": Array [
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "field": "kibana.alert.rule.uuid",
+ "index": "mockInternalReferenceId",
+ "key": "kibana.alert.rule.uuid",
+ "negate": false,
+ "params": Object {
+ "query": undefined,
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "kibana.alert.rule.uuid": undefined,
+ },
+ },
+ },
+ Object {
+ "meta": Object {
+ "alias": null,
+ "disabled": false,
+ "key": "host.id",
+ "negate": false,
+ "params": Object {
+ "query": "123",
+ },
+ "type": "phrase",
+ },
+ "query": Object {
+ "match_phrase": Object {
+ "host.id": "123",
+ },
+ },
+ },
+ ],
+ "internalReferences": Array [
+ Object {
+ "id": "mockInternalReferenceId",
+ "name": "indexpattern-datasource-layer-mockLayerId",
+ "type": "index-pattern",
+ },
+ ],
+ "query": Object {
+ "language": "kql",
+ "query": "host.name: *",
+ },
+ "visualization": Object {
+ "axisTitlesVisibilitySettings": Object {
+ "x": false,
+ "yLeft": false,
+ "yRight": true,
+ },
+ "layers": Array [
+ Object {
+ "accessors": Array [
+ "9c89324b-0c59-4403-9698-d989a09dc5a8",
+ ],
+ "layerId": "mockLayerId",
+ "layerType": "data",
+ "position": "top",
+ "seriesType": "bar_stacked",
+ "showGridlines": false,
+ "splitAccessor": "e92c8920-0449-4564-81f4-8945517817a4",
+ "xAccessor": "eba07b4d-766d-49d7-8435-d40367d3d055",
+ },
+ ],
+ "legend": Object {
+ "isVisible": false,
+ "position": "left",
+ },
+ "preferredSeriesType": "bar_stacked",
+ "title": "Empty XY chart",
+ "valueLabels": "hide",
+ "valuesInLegend": true,
+ "yTitle": "",
+ },
+ },
+ "title": "Rule preview",
+ "visualizationType": "lnsXY",
+}
+`;
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts
new file mode 100644
index 0000000000000..109bf7da68bee
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.test.ts
@@ -0,0 +1,63 @@
+/*
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { mockExtraFilter, wrapper } from '../../../mocks';
+
+import { useLensAttributes } from '../../../use_lens_attributes';
+
+import { getAlertsByStatusAttributes } from './alerts_by_status_donut';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('b9b43606-7ff7-46ae-a47c-85bed80fab9a'),
+}));
+
+jest.mock('../../../../../containers/sourcerer', () => ({
+ useSourcererDataView: jest.fn().mockReturnValue({
+ dataViewId: 'security-solution-my-test',
+ indicesExist: true,
+ selectedPatterns: ['signal-index'],
+ }),
+}));
+
+jest.mock('../../../../../utils/route/use_route_spy', () => ({
+ useRouteSpy: jest.fn().mockReturnValue([
+ {
+ pageName: 'alerts',
+ },
+ ]),
+}));
+
+describe('getAlertsByStatusAttributes', () => {
+ it('should render without extra options', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getAlertsByStatusAttributes,
+ stackByField: 'kibana.alert.workflow_status',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+
+ it('should render with extra options - filters', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ extraOptions: {
+ filters: mockExtraFilter,
+ },
+ getLensAttributes: getAlertsByStatusAttributes,
+ stackByField: 'kibana.alert.workflow_status',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current?.state.filters).toEqual(expect.arrayContaining(mockExtraFilter));
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts
index 6875a2c7f6b55..33bc6827fa020 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_by_status_donut.ts
@@ -4,14 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-
-import type { GetLensAttributes, LensAttributes } from '../../../types';
+import { v4 as uuidv4 } from 'uuid';
+import type { GetLensAttributes } from '../../../types';
+const layerId = uuidv4();
export const getAlertsByStatusAttributes: GetLensAttributes = (
stackByField = 'kibana.alert.workflow_status',
extraOptions
-) =>
- ({
+) => {
+ return {
title: 'Alerts',
description: '',
visualizationType: 'lnsPie',
@@ -20,7 +21,7 @@ export const getAlertsByStatusAttributes: GetLensAttributes = (
shape: 'donut',
layers: [
{
- layerId: '51ed355e-6e23-4038-a417-f653a1160370',
+ layerId,
primaryGroups: ['a9b43606-7ff7-46ae-a47c-85bed80fab9a'],
metrics: ['21cc4a49-3780-4b1a-be28-f02fa5303d24'],
numberDisplay: 'value',
@@ -38,30 +39,35 @@ export const getAlertsByStatusAttributes: GetLensAttributes = (
language: 'kuery',
},
filters: [
- {
- meta: {
- disabled: false,
- negate: false,
- alias: null,
- index: 'a1aaa83b-5026-444e-9465-50e0afade01c',
- key: stackByField,
- field: stackByField,
- params: {
- query: extraOptions?.status,
- },
- type: 'phrase',
- },
- query: {
- match_phrase: {
- [stackByField]: extraOptions?.status,
- },
- },
- },
+ ...(extraOptions?.status && stackByField
+ ? [
+ {
+ meta: {
+ disabled: false,
+ negate: false,
+ alias: null,
+ index: 'a1aaa83b-5026-444e-9465-50e0afade01c',
+ key: stackByField,
+ field: stackByField,
+ params: {
+ query: extraOptions?.status,
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ [stackByField]: extraOptions?.status,
+ },
+ },
+ },
+ ]
+ : []),
+ ...(extraOptions?.filters ? extraOptions.filters : []),
],
datasourceStates: {
formBased: {
layers: {
- '51ed355e-6e23-4038-a417-f653a1160370': {
+ [layerId]: {
columns: {
'a9b43606-7ff7-46ae-a47c-85bed80fab9a': {
label: 'Filters',
@@ -138,7 +144,7 @@ export const getAlertsByStatusAttributes: GetLensAttributes = (
{
type: 'index-pattern',
id: '{dataViewId}',
- name: 'indexpattern-datasource-layer-51ed355e-6e23-4038-a417-f653a1160370',
+ name: `indexpattern-datasource-layer-${layerId}`,
},
{
type: 'index-pattern',
@@ -146,4 +152,5 @@ export const getAlertsByStatusAttributes: GetLensAttributes = (
id: '{dataViewId}',
},
],
- } as LensAttributes);
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts
new file mode 100644
index 0000000000000..bf84e4999faa5
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.test.ts
@@ -0,0 +1,88 @@
+/*
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { wrapper } from '../../../mocks';
+
+import { useLensAttributes } from '../../../use_lens_attributes';
+
+import { getAlertsHistogramLensAttributes } from './alerts_histogram';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('0039eb0c-9a1a-4687-ae54-0f4e239bec75'),
+}));
+
+jest.mock('../../../../../containers/sourcerer', () => ({
+ useSourcererDataView: jest.fn().mockReturnValue({
+ dataViewId: 'security-solution-my-test',
+ indicesExist: true,
+ selectedPatterns: ['signal-index'],
+ }),
+}));
+
+jest.mock('../../../../../utils/route/use_route_spy', () => ({
+ useRouteSpy: jest.fn().mockReturnValue([
+ {
+ detailName: 'mockRule',
+ pageName: 'rules',
+ tabName: 'alerts',
+ },
+ ]),
+}));
+
+describe('getAlertsHistogramLensAttributes', () => {
+ it('should render without extra options', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getAlertsHistogramLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+
+ it('should render with extra options - filters', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ extraOptions: {
+ filters: [
+ {
+ meta: {
+ type: 'phrases',
+ key: '_index',
+ params: ['.alerts-security.alerts-default'],
+ alias: null,
+ negate: false,
+ disabled: false,
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ match_phrase: {
+ _index: '.alerts-security.alerts-default',
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ },
+ ],
+ },
+ getLensAttributes: getAlertsHistogramLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts
new file mode 100644
index 0000000000000..78b4a134a7620
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_histogram.ts
@@ -0,0 +1,127 @@
+/*
+ * 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 { v4 as uuidv4 } from 'uuid';
+import type { GetLensAttributes } from '../../../types';
+const layerId = uuidv4();
+
+export const getAlertsHistogramLensAttributes: GetLensAttributes = (
+ stackByField = 'kibana.alert.rule.name',
+ extraOptions
+) => {
+ return {
+ title: 'Alerts',
+ description: '',
+ visualizationType: 'lnsXY',
+ state: {
+ visualization: {
+ title: 'Empty XY chart',
+ legend: {
+ isVisible: true,
+ position: 'left',
+ legendSize: 'xlarge',
+ },
+ valueLabels: 'hide',
+ preferredSeriesType: 'bar_stacked',
+ layers: [
+ {
+ layerId,
+ accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'],
+ position: 'top',
+ seriesType: 'bar_stacked',
+ showGridlines: false,
+ layerType: 'data',
+ xAccessor: 'aac9d7d0-13a3-480a-892b-08207a787926',
+ splitAccessor: '34919782-4546-43a5-b668-06ac934d3acd',
+ },
+ ],
+ yRightExtent: {
+ mode: 'full',
+ },
+ yLeftExtent: {
+ mode: 'full',
+ },
+ axisTitlesVisibilitySettings: {
+ x: false,
+ yLeft: false,
+ yRight: true,
+ },
+ valuesInLegend: true,
+ },
+ query: {
+ query: '',
+ language: 'kuery',
+ },
+ filters: extraOptions?.filters ? extraOptions.filters : [],
+ datasourceStates: {
+ formBased: {
+ layers: {
+ [layerId]: {
+ columns: {
+ 'aac9d7d0-13a3-480a-892b-08207a787926': {
+ label: '@timestamp',
+ dataType: 'date',
+ operationType: 'date_histogram',
+ sourceField: '@timestamp',
+ isBucketed: true,
+ scale: 'interval',
+ params: {
+ interval: 'auto',
+ },
+ },
+ 'e09e0380-0740-4105-becc-0a4ca12e3944': {
+ label: 'Count of records',
+ dataType: 'number',
+ operationType: 'count',
+ isBucketed: false,
+ scale: 'ratio',
+ sourceField: '___records___',
+ },
+ '34919782-4546-43a5-b668-06ac934d3acd': {
+ label: `Top values of ${stackByField}`,
+ dataType: 'string',
+ operationType: 'terms',
+ scale: 'ordinal',
+ sourceField: stackByField,
+ isBucketed: true,
+ params: {
+ size: 1000,
+ orderBy: {
+ type: 'column',
+ columnId: 'e09e0380-0740-4105-becc-0a4ca12e3944',
+ },
+ orderDirection: 'desc',
+ otherBucket: true,
+ missingBucket: false,
+ parentFormat: {
+ id: 'terms',
+ },
+ secondaryFields: [],
+ },
+ },
+ },
+ columnOrder: [
+ '34919782-4546-43a5-b668-06ac934d3acd',
+ 'aac9d7d0-13a3-480a-892b-08207a787926',
+ 'e09e0380-0740-4105-becc-0a4ca12e3944',
+ ],
+ incompleteColumns: {},
+ },
+ },
+ },
+ },
+ internalReferences: [],
+ adHocDataViews: {},
+ },
+ references: [
+ {
+ type: 'index-pattern',
+ id: '{dataViewId}',
+ name: `indexpattern-datasource-layer-${layerId}`,
+ },
+ ],
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts
new file mode 100644
index 0000000000000..e8457e8dfb533
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.test.ts
@@ -0,0 +1,100 @@
+/*
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { wrapper } from '../../../mocks';
+
+import { useLensAttributes } from '../../../use_lens_attributes';
+
+import { getAlertsTableLensAttributes } from './alerts_table';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('4aa7cf71-cf20-4e62-8ca6-ca6be6b0988b'),
+}));
+
+jest.mock('../../../../../containers/sourcerer', () => ({
+ useSourcererDataView: jest.fn().mockReturnValue({
+ dataViewId: 'security-solution-my-test',
+ indicesExist: true,
+ selectedPatterns: ['signal-index'],
+ }),
+}));
+
+jest.mock('../../../../../utils/route/use_route_spy', () => ({
+ useRouteSpy: jest.fn().mockReturnValue([
+ {
+ pageName: 'alerts',
+ },
+ ]),
+}));
+
+describe('getAlertsTableLensAttributes', () => {
+ it('should render without extra options', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getAlertsTableLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+
+ it('should render with extra options - filters', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ extraOptions: {
+ filters: [
+ {
+ meta: {
+ type: 'phrases',
+ key: '_index',
+ params: ['.alerts-security.alerts-default'],
+ alias: null,
+ negate: false,
+ disabled: false,
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ match_phrase: {
+ _index: '.alerts-security.alerts-default',
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ },
+ ],
+ },
+ getLensAttributes: getAlertsTableLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+
+ it('should render with extra options - breakdownField', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ extraOptions: { breakdownField: 'agent.type' },
+ getLensAttributes: getAlertsTableLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts
new file mode 100644
index 0000000000000..678179855557c
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/alerts_table.ts
@@ -0,0 +1,137 @@
+/*
+ * 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 { v4 as uuidv4 } from 'uuid';
+import type { GetLensAttributes } from '../../../types';
+
+const layerId = uuidv4();
+
+export const getAlertsTableLensAttributes: GetLensAttributes = (
+ stackByField = 'kibana.alert.rule.name',
+ extraOptions
+) => {
+ return {
+ title: 'Alerts',
+ description: '',
+ visualizationType: 'lnsDatatable',
+ state: {
+ visualization: {
+ columns: [
+ {
+ columnId: '2881fedd-54b7-42ba-8c97-5175dec86166',
+ isTransposed: false,
+ width: 362,
+ },
+ {
+ columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
+ isTransposed: false,
+ },
+ {
+ columnId: '75ce269b-ee9c-4c7d-a14e-9226ba0fe059',
+ isTransposed: false,
+ },
+ ],
+ layerId,
+ layerType: 'data',
+ },
+ query: {
+ query: '',
+ language: 'kuery',
+ },
+ filters: extraOptions?.filters ? extraOptions.filters : [],
+ datasourceStates: {
+ formBased: {
+ layers: {
+ [layerId]: {
+ columns: {
+ '2881fedd-54b7-42ba-8c97-5175dec86166': {
+ label: `Top values of ${stackByField}`,
+ dataType: 'string',
+ operationType: 'terms',
+ scale: 'ordinal',
+ sourceField: stackByField,
+ isBucketed: true,
+ params: {
+ size: 1000,
+ orderBy: {
+ type: 'column',
+ columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
+ },
+ orderDirection: 'desc',
+ otherBucket: true,
+ missingBucket: false,
+ parentFormat: {
+ id: 'terms',
+ },
+ include: [],
+ exclude: [],
+ includeIsRegex: false,
+ excludeIsRegex: false,
+ },
+ },
+ 'f04a71a3-399f-4d32-9efc-8a005e989991': {
+ label: `Count of ${extraOptions?.breakdownField}`,
+ dataType: 'number',
+ operationType: 'count',
+ isBucketed: false,
+ scale: 'ratio',
+ sourceField: extraOptions?.breakdownField,
+ params: {
+ emptyAsNull: true,
+ },
+ },
+ '75ce269b-ee9c-4c7d-a14e-9226ba0fe059': {
+ label: `Top values of ${extraOptions?.breakdownField}`,
+ dataType: 'string',
+ operationType: 'terms',
+ scale: 'ordinal',
+ sourceField: extraOptions?.breakdownField,
+ isBucketed: true,
+ params: {
+ size: 1000,
+ orderBy: {
+ type: 'column',
+ columnId: 'f04a71a3-399f-4d32-9efc-8a005e989991',
+ },
+ orderDirection: 'desc',
+ otherBucket: true,
+ missingBucket: false,
+ parentFormat: {
+ id: 'terms',
+ },
+ include: [],
+ exclude: [],
+ includeIsRegex: false,
+ excludeIsRegex: false,
+ },
+ },
+ },
+ columnOrder: [
+ '2881fedd-54b7-42ba-8c97-5175dec86166',
+ '75ce269b-ee9c-4c7d-a14e-9226ba0fe059',
+ 'f04a71a3-399f-4d32-9efc-8a005e989991',
+ ],
+ sampling: 1,
+ incompleteColumns: {},
+ },
+ },
+ },
+ textBased: {
+ layers: {},
+ },
+ },
+ internalReferences: [],
+ adHocDataViews: {},
+ },
+ references: [
+ {
+ type: 'index-pattern',
+ id: '{dataViewId}',
+ name: `indexpattern-datasource-layer-${layerId}`,
+ },
+ ],
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts
new file mode 100644
index 0000000000000..85b4a11bbc7f9
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.test.ts
@@ -0,0 +1,70 @@
+/*
+ * 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 { renderHook } from '@testing-library/react-hooks';
+import { mockRulePreviewFilter, wrapper } from '../../../mocks';
+
+import { useLensAttributes } from '../../../use_lens_attributes';
+
+import { getRulePreviewLensAttributes } from './rule_preview';
+const mockInternalReferenceId = 'mockInternalReferenceId';
+const mockRuleId = 'mockRuleId';
+
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValueOnce('mockLayerId').mockReturnValueOnce('mockInternalReferenceId'),
+}));
+
+jest.mock('../../../../../containers/sourcerer', () => ({
+ useSourcererDataView: jest.fn().mockReturnValue({
+ dataViewId: 'security-solution-my-test',
+ indicesExist: true,
+ selectedPatterns: ['signal-index'],
+ }),
+}));
+
+jest.mock('../../../../../utils/route/use_route_spy', () => ({
+ useRouteSpy: jest.fn().mockReturnValue([
+ {
+ pageName: 'alerts',
+ },
+ ]),
+}));
+
+describe('getRulePreviewLensAttributes', () => {
+ beforeEach(() => {
+ jest.clearAllMocks();
+ });
+ it('should render without extra options', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getRulePreviewLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toMatchSnapshot();
+ });
+
+ it('should render with extra options - filters', () => {
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ extraOptions: {
+ ruleId: mockRuleId,
+ },
+ getLensAttributes: getRulePreviewLensAttributes,
+ stackByField: 'event.category',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current?.state.filters).toEqual(
+ expect.arrayContaining(mockRulePreviewFilter(mockInternalReferenceId, mockRuleId))
+ );
+ });
+});
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts
new file mode 100644
index 0000000000000..33d59c358ea5f
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/alerts/rule_preview.ts
@@ -0,0 +1,167 @@
+/*
+ * 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 { v4 as uuidv4 } from 'uuid';
+import type { GetLensAttributes } from '../../../types';
+
+const layerId = uuidv4();
+const internalReferenceId = uuidv4();
+
+export const getRulePreviewLensAttributes: GetLensAttributes = (
+ stackByField = 'event.category',
+ extraOptions
+) => {
+ return {
+ title: 'Rule preview',
+ description: '',
+ visualizationType: 'lnsXY',
+ state: {
+ visualization: {
+ title: 'Empty XY chart',
+ legend: {
+ isVisible: false,
+ position: 'left',
+ },
+ valueLabels: 'hide',
+ preferredSeriesType: 'bar_stacked',
+ layers: [
+ {
+ layerId,
+ accessors: ['9c89324b-0c59-4403-9698-d989a09dc5a8'],
+ position: 'top',
+ seriesType: 'bar_stacked',
+ showGridlines: false,
+ layerType: 'data',
+ xAccessor: 'eba07b4d-766d-49d7-8435-d40367d3d055',
+ splitAccessor: 'e92c8920-0449-4564-81f4-8945517817a4',
+ },
+ ],
+ valuesInLegend: true,
+ yTitle: '',
+ axisTitlesVisibilitySettings: {
+ x: false,
+ yLeft: false,
+ yRight: true,
+ },
+ },
+ query: {
+ query: '',
+ language: 'kuery',
+ },
+ filters: [
+ {
+ meta: {
+ disabled: false,
+ negate: false,
+ alias: null,
+ index: internalReferenceId,
+ key: 'kibana.alert.rule.uuid',
+ field: 'kibana.alert.rule.uuid',
+ params: {
+ query: extraOptions?.ruleId,
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ 'kibana.alert.rule.uuid': extraOptions?.ruleId,
+ },
+ },
+ },
+ ],
+ datasourceStates: {
+ formBased: {
+ layers: {
+ [layerId]: {
+ columns: {
+ '9c89324b-0c59-4403-9698-d989a09dc5a8': {
+ label: 'Count of records',
+ dataType: 'number',
+ operationType: 'count',
+ isBucketed: false,
+ scale: 'ratio',
+ sourceField: '___records___',
+ params: {
+ emptyAsNull: true,
+ },
+ },
+ 'eba07b4d-766d-49d7-8435-d40367d3d055': {
+ label: '@timestamp',
+ dataType: 'date',
+ operationType: 'date_histogram',
+ sourceField: '@timestamp',
+ isBucketed: true,
+ scale: 'interval',
+ params: {
+ interval: 'auto',
+ includeEmptyRows: true,
+ dropPartials: false,
+ },
+ },
+ 'e92c8920-0449-4564-81f4-8945517817a4': {
+ label: `Top 10 values of ${stackByField}`,
+ dataType: 'string',
+ operationType: 'terms',
+ scale: 'ordinal',
+ sourceField: stackByField,
+ isBucketed: true,
+ params: {
+ size: 10,
+ orderBy: {
+ type: 'column',
+ columnId: '9c89324b-0c59-4403-9698-d989a09dc5a8',
+ },
+ orderDirection: 'desc',
+ otherBucket: true,
+ missingBucket: false,
+ parentFormat: {
+ id: 'terms',
+ },
+ include: [],
+ exclude: [],
+ includeIsRegex: false,
+ excludeIsRegex: false,
+ },
+ },
+ },
+ columnOrder: [
+ 'e92c8920-0449-4564-81f4-8945517817a4',
+ 'eba07b4d-766d-49d7-8435-d40367d3d055',
+ '9c89324b-0c59-4403-9698-d989a09dc5a8',
+ ],
+ sampling: 1,
+ incompleteColumns: {},
+ },
+ },
+ },
+ textBased: {
+ layers: {},
+ },
+ },
+ internalReferences: [
+ {
+ type: 'index-pattern',
+ id: internalReferenceId,
+ name: `indexpattern-datasource-layer-${layerId}`,
+ },
+ ],
+ adHocDataViews: {
+ [internalReferenceId]: {
+ id: internalReferenceId,
+ title: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`,
+ timeFieldName: '@timestamp',
+ sourceFilters: [],
+ fieldFormats: {},
+ runtimeFieldMap: {},
+ fieldAttrs: {},
+ allowNoIndex: false,
+ name: `.preview.alerts-security.alerts-${extraOptions?.spaceId}`,
+ },
+ },
+ },
+ references: [],
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts
index 4e69bac6287ec..4378b74400aa2 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/authentication.ts
@@ -20,7 +20,7 @@ export const authenticationLensAttributes: LensAttributes = {
title: 'Empty XY chart',
legend: {
isVisible: true,
- position: 'right',
+ position: 'left',
},
valueLabels: 'hide',
preferredSeriesType: 'bar_stacked',
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts
index 87d246fc2350b..a9a1c3951de5a 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/event.test.ts
@@ -12,6 +12,10 @@ import { useLensAttributes } from '../../use_lens_attributes';
import { getEventsHistogramLensAttributes } from './events';
+jest.mock('uuid', () => ({
+ v4: jest.fn().mockReturnValue('0039eb0c-9a1a-4687-ae54-0f4e239bec75'),
+}));
+
jest.mock('../../../../containers/sourcerer', () => ({
useSourcererDataView: jest.fn().mockReturnValue({
selectedPatterns: ['auditbeat-mytest-*'],
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts
index e48f6aa6c1a87..61e9bac0cb3ac 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/events.ts
@@ -4,13 +4,15 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
+import { v4 as uuidv4 } from 'uuid';
+import type { GetLensAttributes } from '../../types';
-import type { GetLensAttributes, LensAttributes } from '../../types';
+const layerId = uuidv4();
export const getEventsHistogramLensAttributes: GetLensAttributes = (
stackByField = 'event.action'
-) =>
- ({
+) => {
+ return {
title: 'Events',
description: '',
visualizationType: 'lnsXY',
@@ -19,13 +21,14 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = (
title: 'Empty XY chart',
legend: {
isVisible: true,
- position: 'right',
+ position: 'left',
+ legendSize: 'xlarge',
},
valueLabels: 'hide',
preferredSeriesType: 'bar_stacked',
layers: [
{
- layerId: '0039eb0c-9a1a-4687-ae54-0f4e239bec75',
+ layerId,
accessors: ['e09e0380-0740-4105-becc-0a4ca12e3944'],
position: 'top',
seriesType: 'bar_stacked',
@@ -55,7 +58,7 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = (
datasourceStates: {
formBased: {
layers: {
- '0039eb0c-9a1a-4687-ae54-0f4e239bec75': {
+ [layerId]: {
columns: {
'aac9d7d0-13a3-480a-892b-08207a787926': {
label: '@timestamp',
@@ -113,7 +116,8 @@ export const getEventsHistogramLensAttributes: GetLensAttributes = (
{
type: 'index-pattern',
id: '{dataViewId}',
- name: 'indexpattern-datasource-layer-0039eb0c-9a1a-4687-ae54-0f4e239bec75',
+ name: `indexpattern-datasource-layer-${layerId}`,
},
],
- } as LensAttributes);
+ };
+};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts
index f5a664b98161b..44aa790332ba0 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/common/external_alert.ts
@@ -20,7 +20,7 @@ export const getExternalAlertLensAttributes: GetLensAttributes = (
title: 'Empty XY chart',
legend: {
isVisible: true,
- position: 'right',
+ position: 'left',
},
valueLabels: 'hide',
preferredSeriesType: 'bar_stacked',
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap
index 38ee4c908fce0..a261abe99ffcf 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/__snapshots__/dns_top_domains.test.ts.snap
@@ -231,7 +231,7 @@ Object {
],
"legend": Object {
"isVisible": true,
- "position": "right",
+ "position": "left",
},
"preferredSeriesType": "bar",
"tickLabelsVisibilitySettings": Object {
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts
index 0f195bdeaa8d4..2e9ff92261518 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_attributes/network/dns_top_domains.ts
@@ -17,7 +17,7 @@ export const dnsTopDomainsLensAttributes: LensAttributes = {
visualization: {
legend: {
isVisible: true,
- position: 'right',
+ position: 'left',
},
valueLabels: 'hide',
fittingFunction: 'None',
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx
index 985a9881d330b..19805b8ce96f3 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/lens_embeddable.tsx
@@ -31,7 +31,7 @@ const LensComponentWrapper = styled.div<{ height?: string; width?: string }>`
background-color: transparent;
}
.expExpressionRenderer__expression {
- padding: 0 !important;
+ padding: 2px 0 0 0 !important;
}
.legacyMtrVis__container {
padding: 0;
@@ -48,8 +48,6 @@ const initVisualizationData: {
isLoading: true,
};
-const style = { height: '100%', minWidth: '100px' };
-
const LensEmbeddableComponent: React.FC = ({
applyGlobalQueriesAndFilters = true,
extraActions,
@@ -65,7 +63,16 @@ const LensEmbeddableComponent: React.FC = ({
stackByField,
timerange,
width: wrapperWidth,
+ withActions = true,
}) => {
+ const style = useMemo(
+ () => ({
+ height: wrapperHeight ?? '100%',
+ minWidth: '100px',
+ width: wrapperWidth ?? '100%',
+ }),
+ [wrapperHeight, wrapperWidth]
+ );
const { lens } = useKibana().services;
const dispatch = useDispatch();
const [isShowingModal, setIsShowingModal] = useState(false);
@@ -81,7 +88,6 @@ const LensEmbeddableComponent: React.FC = ({
stackByField,
title: '',
});
-
const LensComponent = lens.EmbeddableComponent;
const inspectActionProps = useMemo(
() => ({
@@ -98,7 +104,7 @@ const LensEmbeddableComponent: React.FC = ({
extraActions,
inspectActionProps,
timeRange: timerange,
- withActions: true,
+ withActions,
});
const handleCloseModal = useCallback(() => {
@@ -165,6 +171,10 @@ const LensEmbeddableComponent: React.FC = ({
[attributes?.state?.adHocDataViews]
);
+ if (!searchSessionId) {
+ return null;
+ }
+
if (
!attributes ||
(visualizationData?.responses != null && visualizationData?.responses?.length === 0)
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx
index e0f6a5ab56d6d..77fa5b02cede0 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/mocks.tsx
@@ -129,3 +129,50 @@ export const mockAttributes: LensAttributes = {
},
],
};
+
+export const mockExtraFilter = [
+ {
+ meta: {
+ type: 'phrases',
+ key: '_index',
+ params: ['.alerts-security.alerts-default'],
+ alias: null,
+ negate: false,
+ disabled: false,
+ },
+ query: {
+ bool: {
+ should: [
+ {
+ match_phrase: {
+ _index: '.alerts-security.alerts-default',
+ },
+ },
+ ],
+ minimum_should_match: 1,
+ },
+ },
+ },
+];
+
+export const mockRulePreviewFilter = (internalReferenceId: string, ruleId: string) => [
+ {
+ meta: {
+ disabled: false,
+ negate: false,
+ alias: null,
+ index: internalReferenceId,
+ key: 'kibana.alert.rule.uuid',
+ field: 'kibana.alert.rule.uuid',
+ params: {
+ query: ruleId,
+ },
+ type: 'phrase',
+ },
+ query: {
+ match_phrase: {
+ 'kibana.alert.rule.uuid': ruleId,
+ },
+ },
+ },
+];
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts
index 5ef9b3eda38b4..b761aba812100 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/types.ts
@@ -57,6 +57,7 @@ export interface LensEmbeddableComponentProps {
stackByField?: string;
timerange: { from: string; to: string };
width?: string;
+ withActions?: boolean;
}
export enum RequestStatus {
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts
index 9c8ccdfc51cfb..0de49b52d66ff 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.test.ts
@@ -66,11 +66,11 @@ describe(`useActions`, () => {
expect(result.current[0].id).toEqual('inspect');
expect(result.current[0].order).toEqual(4);
- expect(result.current[1].id).toEqual('openInLens');
+ expect(result.current[1].id).toEqual('addToNewCase');
expect(result.current[1].order).toEqual(3);
- expect(result.current[2].id).toEqual('addToNewCase');
+ expect(result.current[2].id).toEqual('addToExistingCase');
expect(result.current[2].order).toEqual(2);
- expect(result.current[3].id).toEqual('addToExistingCase');
+ expect(result.current[3].id).toEqual('openInLens');
expect(result.current[3].order).toEqual(1);
});
@@ -110,11 +110,11 @@ describe(`useActions`, () => {
expect(result.current[0].id).toEqual('inspect');
expect(result.current[0].order).toEqual(4);
- expect(result.current[1].id).toEqual('openInLens');
+ expect(result.current[1].id).toEqual('addToNewCase');
expect(result.current[1].order).toEqual(3);
- expect(result.current[2].id).toEqual('addToNewCase');
+ expect(result.current[2].id).toEqual('addToExistingCase');
expect(result.current[2].order).toEqual(2);
- expect(result.current[3].id).toEqual('addToExistingCase');
+ expect(result.current[3].id).toEqual('openInLens');
expect(result.current[3].order).toEqual(1);
expect(result.current[4].id).toEqual('mockExtraAction');
expect(result.current[4].order).toEqual(0);
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts
index eb8097ee77ade..504f30511cafc 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_actions.ts
@@ -34,9 +34,9 @@ export const useActions = ({
const { navigateToPrefilledEditor } = lens;
const [defaultActions, setDefaultActions] = useState([
'inspect',
- 'openInLens',
'addToNewCase',
'addToExistingCase',
+ 'openInLens',
]);
useEffect(() => {
@@ -74,7 +74,7 @@ export const useActions = ({
const actions = useMemo(
() =>
- defaultActions.reduce((acc, action) => {
+ defaultActions?.reduce((acc, action) => {
if (action === 'inspect' && inspectActionProps != null) {
return [
...acc,
@@ -141,7 +141,7 @@ const getOpenInLensAction = ({ callback }: { callback: () => void }): Action =>
async execute(context: ActionExecutionContext): Promise {
callback();
},
- order: 3,
+ order: 1,
};
};
@@ -168,7 +168,7 @@ const getAddToNewCaseAction = ({
callback();
},
disabled,
- order: 2,
+ order: 3,
};
};
@@ -222,6 +222,6 @@ const getAddToExistingCaseAction = ({
callback();
},
disabled,
- order: 1,
+ order: 2,
};
};
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx
index 7451209120e06..6fd82d4b0e1e9 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.test.tsx
@@ -205,6 +205,45 @@ describe('useLensAttributes', () => {
expect(result?.current).toBeNull();
});
+ it('should return null if stackByField is an empty string', () => {
+ (useSourcererDataView as jest.Mock).mockReturnValue({
+ dataViewId: 'security-solution-default',
+ indicesExist: false,
+ selectedPatterns: ['auditbeat-*'],
+ });
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getExternalAlertLensAttributes,
+ stackByField: '',
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toBeNull();
+ });
+
+ it('should return null if extraOptions.breakDownField is an empty string', () => {
+ (useSourcererDataView as jest.Mock).mockReturnValue({
+ dataViewId: 'security-solution-default',
+ indicesExist: false,
+ selectedPatterns: ['auditbeat-*'],
+ });
+ const { result } = renderHook(
+ () =>
+ useLensAttributes({
+ getLensAttributes: getExternalAlertLensAttributes,
+ stackByField: 'kibana.alert.rule.name',
+ extraOptions: {
+ breakdownField: '',
+ },
+ }),
+ { wrapper }
+ );
+
+ expect(result?.current).toBeNull();
+ });
+
it('should return Lens attributes if adHocDataViews exist', () => {
(useSourcererDataView as jest.Mock).mockReturnValue({
dataViewId: 'security-solution-default',
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx
index 1976a743e5fa1..9b5ef16dddd22 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/use_lens_attributes.tsx
@@ -89,7 +89,13 @@ export const useLensAttributes = ({
const hasAdHocDataViews = Object.values(attrs?.state?.adHocDataViews ?? {}).length > 0;
const lensAttrsWithInjectedData = useMemo(() => {
- if (lensAttributes == null && (getLensAttributes == null || stackByField == null)) {
+ if (
+ lensAttributes == null &&
+ (getLensAttributes == null ||
+ stackByField == null ||
+ stackByField?.length === 0 ||
+ (extraOptions?.breakdownField != null && extraOptions?.breakdownField.length === 0))
+ ) {
return null;
}
@@ -117,6 +123,7 @@ export const useLensAttributes = ({
applyGlobalQueriesAndFilters,
attrs,
dataViewId,
+ extraOptions?.breakdownField,
filters,
getLensAttributes,
hasAdHocDataViews,
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx
index c53835c0f86b9..ee77776e60a91 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import type { RenderResult } from '@testing-library/react';
-import { render } from '@testing-library/react';
+import { render, waitFor } from '@testing-library/react';
import { kpiHostMetricLensAttributes } from './lens_attributes/hosts/kpi_host_metric';
import { VisualizationEmbeddable } from './visualization_embeddable';
import * as inputActions from '../../store/inputs/actions';
@@ -29,11 +29,12 @@ jest.mock('./lens_embeddable');
jest.mock('../page/use_refetch_by_session', () => ({
useRefetchByRestartingSession: jest.fn(),
}));
-
+jest.useFakeTimers();
let res: RenderResult;
const mockSearchSessionId = 'mockSearchSessionId';
const mockSearchSessionIdDefault = 'mockSearchSessionIdDefault';
const mockRefetchByRestartingSession = jest.fn();
+const mockRefetchByDeletingSession = jest.fn();
const mockSetQuery = jest.spyOn(inputActions, 'setQuery');
const mockDeleteQuery = jest.spyOn(inputActions, 'deleteOneQuery');
const state: State = {
@@ -41,6 +42,7 @@ const state: State = {
};
const { storage } = createSecuritySolutionStorageMock();
const store = createStore(state, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
+
describe('VisualizationEmbeddable', () => {
describe('when isDonut = false', () => {
beforeEach(() => {
@@ -55,6 +57,7 @@ describe('VisualizationEmbeddable', () => {
},
},
refetchByRestartingSession: mockRefetchByRestartingSession,
+ refetchByDeletingSession: mockRefetchByDeletingSession,
});
res = render(
@@ -71,15 +74,16 @@ describe('VisualizationEmbeddable', () => {
expect(res.getByTestId('lens-embeddable')).toBeInTheDocument();
});
- it('should set query', () => {
- expect(mockSetQuery).toHaveBeenCalledTimes(1);
- expect(mockSetQuery).toHaveBeenCalledWith({
- inputId: InputsModelId.global,
- id: 'testId',
- searchSessionId: mockSearchSessionId,
- refetch: mockRefetchByRestartingSession,
- loading: false,
- inspect: null,
+ it('should refetch by delete session when no data exists', async () => {
+ await waitFor(() => {
+ expect(mockSetQuery).toHaveBeenCalledWith({
+ inputId: InputsModelId.global,
+ id: 'testId',
+ searchSessionId: mockSearchSessionId,
+ refetch: mockRefetchByDeletingSession,
+ loading: false,
+ inspect: null,
+ });
});
});
@@ -92,6 +96,73 @@ describe('VisualizationEmbeddable', () => {
});
});
+ describe('when data exists', () => {
+ const mockState = {
+ ...mockGlobalState,
+ inputs: {
+ ...mockGlobalState.inputs,
+ global: {
+ ...mockGlobalState.inputs.global,
+ queries: [
+ {
+ id: 'testId',
+ inspect: {
+ dsl: [],
+ response: [
+ '{\n "took": 4,\n "timed_out": false,\n "_shards": {\n "total": 3,\n "successful": 3,\n "skipped": 2,\n "failed": 0\n },\n "hits": {\n "total": 21300,\n "max_score": null,\n "hits": []\n },\n "aggregations": {\n "0": {\n "buckets": {\n "Critical": {\n "doc_count": 0\n },\n "High": {\n "doc_count": 0\n },\n "Low": {\n "doc_count": 21300\n },\n "Medium": {\n "doc_count": 0\n }\n }\n }\n }\n}',
+ ],
+ },
+ isInspected: false,
+ loading: false,
+ selectedInspectIndex: 0,
+ searchSessionId: undefined,
+ refetch: jest.fn(),
+ },
+ ],
+ },
+ },
+ };
+ const mockStore = createStore(mockState, SUB_PLUGINS_REDUCER, kibanaObservable, storage);
+
+ beforeEach(() => {
+ jest.clearAllMocks();
+ (useRefetchByRestartingSession as jest.Mock).mockReturnValue({
+ session: {
+ current: {
+ start: jest
+ .fn()
+ .mockReturnValueOnce(mockSearchSessionId)
+ .mockReturnValue(mockSearchSessionIdDefault),
+ },
+ },
+ refetchByRestartingSession: mockRefetchByRestartingSession,
+ refetchByDeletingSession: mockRefetchByDeletingSession,
+ });
+ res = render(
+
+
+
+ );
+ });
+
+ it('should refetch by restart session', async () => {
+ await waitFor(() => {
+ expect(mockSetQuery).toHaveBeenCalledWith({
+ inputId: InputsModelId.global,
+ id: 'testId',
+ searchSessionId: mockSearchSessionId,
+ refetch: mockRefetchByRestartingSession,
+ loading: false,
+ inspect: null,
+ });
+ });
+ });
+ });
+
describe('when isDonut = true', () => {
beforeEach(() => {
jest.clearAllMocks();
diff --git a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx
index 7ec7c9ee168ac..9bf8d14d15336 100644
--- a/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/visualization_actions/visualization_embeddable.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { useCallback, useEffect } from 'react';
+import React, { useCallback, useEffect, useRef } from 'react';
import { useDispatch } from 'react-redux';
import { css } from 'styled-components';
import { ChartLabel } from '../../../overview/components/detection_response/alerts_by_status/chart_label';
@@ -18,6 +18,7 @@ import { InputsModelId } from '../../store/inputs/constants';
import { useRefetchByRestartingSession } from '../page/use_refetch_by_session';
import { LensEmbeddable } from './lens_embeddable';
import type { EmbeddableData, VisualizationEmbeddableProps } from './types';
+import { useSourcererDataView } from '../../containers/sourcerer';
const VisualizationEmbeddableComponent: React.FC = (props) => {
const dispatch = useDispatch();
@@ -28,18 +29,22 @@ const VisualizationEmbeddableComponent: React.FC =
label,
donutTextWrapperClassName,
onLoad,
- ...lensPorps
+ ...lensProps
} = props;
- const { session, refetchByRestartingSession } = useRefetchByRestartingSession({
- inputId,
- queryId: id,
- });
+ const { session, refetchByRestartingSession, refetchByDeletingSession } =
+ useRefetchByRestartingSession({
+ inputId,
+ queryId: id,
+ });
+ const { indicesExist } = useSourcererDataView(lensProps.scopeId);
+
+ const memorizedTimerange = useRef(lensProps.timerange);
const getGlobalQuery = inputsSelectors.globalQueryByIdSelector();
- const { inspect } = useDeepEqualSelector((state) => getGlobalQuery(state, id));
+ const { inspect, searchSessionId } = useDeepEqualSelector((state) => getGlobalQuery(state, id));
const visualizationData = inspect?.response
? parseVisualizationData(inspect?.response)
: null;
- const dataExists = visualizationData != null && visualizationData[0]?.hits.total !== 0;
+ const dataExists = visualizationData != null && visualizationData[0]?.hits?.total !== 0;
const donutTextWrapperStyles = dataExists
? css`
top: 40%;
@@ -70,17 +75,42 @@ const VisualizationEmbeddableComponent: React.FC =
);
useEffect(() => {
- dispatch(
- inputsActions.setQuery({
- inputId,
- id,
- searchSessionId: session.current.start(),
- refetch: refetchByRestartingSession,
- loading: false,
- inspect: null,
- })
- );
- }, [dispatch, inputId, id, refetchByRestartingSession, session]);
+ // This handles timerange update when (alert) indices not found
+ if (
+ (!indicesExist && memorizedTimerange.current?.from !== lensProps.timerange.from) ||
+ memorizedTimerange.current?.to !== lensProps.timerange.to
+ ) {
+ memorizedTimerange.current = lensProps.timerange;
+ dispatch(inputsActions.deleteOneQuery({ inputId, id }));
+ }
+ }, [dispatch, id, indicesExist, inputId, lensProps.timerange]);
+
+ useEffect(() => {
+ // This handles initial mount and refetch when (alert) indices not found
+ if (!searchSessionId) {
+ setTimeout(() => {
+ dispatch(
+ inputsActions.setQuery({
+ inputId,
+ id,
+ searchSessionId: session.current.start(),
+ refetch: dataExists ? refetchByRestartingSession : refetchByDeletingSession,
+ loading: false,
+ inspect: null,
+ })
+ );
+ }, 200);
+ }
+ }, [
+ dispatch,
+ inputId,
+ id,
+ session,
+ dataExists,
+ refetchByRestartingSession,
+ searchSessionId,
+ refetchByDeletingSession,
+ ]);
useEffect(() => {
return () => {
@@ -88,22 +118,26 @@ const VisualizationEmbeddableComponent: React.FC =
};
}, [dispatch, id, inputId]);
+ if ((!lensProps.getLensAttributes && !lensProps.lensAttributes) || !lensProps.timerange) {
+ return null;
+ }
+
if (isDonut) {
return (
: null}
+ title={dataExists ? : null}
donutTextWrapperClassName={donutTextWrapperClassName}
donutTextWrapperStyles={donutTextWrapperStyles}
>
-
+
);
}
- return ;
+ return ;
};
export const VisualizationEmbeddable = React.memo(VisualizationEmbeddableComponent);
diff --git a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
index 85512855580c3..f75385fdd4955 100644
--- a/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
+++ b/x-pack/plugins/security_solution/public/common/containers/matrix_histogram/index.ts
@@ -249,7 +249,10 @@ export const useMatrixHistogramCombined = (
const [missingDataLoading, missingDataResponse] = useMatrixHistogram({
...matrixHistogramQueryProps,
includeMissingData: false,
- skip: skipMissingData || matrixHistogramQueryProps.filterQuery === undefined,
+ skip:
+ skipMissingData ||
+ matrixHistogramQueryProps.filterQuery === undefined ||
+ matrixHistogramQueryProps.skip,
});
const combinedLoading = useMemo(
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_investigation_guide_panel.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_investigation_guide_panel.tsx
index 697605dbc5e5a..77146ec2fc5a3 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_investigation_guide_panel.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/osquery/osquery_investigation_guide_panel.tsx
@@ -11,6 +11,7 @@ import React, { useCallback, useState } from 'react';
interface OsqueryInvestigationGuidePanelProps {
onClick: () => void;
+ queriesLength: number;
}
const panelCss = {
@@ -19,7 +20,7 @@ const panelCss = {
const flexGroupCss = { padding: `0 24px` };
export const OsqueryInvestigationGuidePanel = React.memo(
- ({ onClick }) => {
+ ({ onClick, queriesLength }) => {
const [hideInvestigationGuideSuggestion, setHideInvestigationGuideSuggestion] = useState(false);
const handleClick = useCallback(() => {
@@ -37,7 +38,10 @@ export const OsqueryInvestigationGuidePanel = React.memo
diff --git a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_list.tsx b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_list.tsx
index 0f0409c5deb6e..44c49529b439f 100644
--- a/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_list.tsx
+++ b/x-pack/plugins/security_solution/public/detection_engine/rule_response_actions/response_actions_list.tsx
@@ -54,7 +54,10 @@ export const ResponseActionsList = React.memo(({ items
})}
{osqueryNoteQueries.length ? (
-
+
) : null}