diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx
new file mode 100644
index 00000000000000..9c7e6803e44500
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.stories.tsx
@@ -0,0 +1,57 @@
+/*
+ * 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 type { Story, StoryContext } from '@storybook/react';
+import React, { ComponentType } from 'react';
+import { CoreStart } from '../../../../../../../../src/core/public';
+import { createKibanaReactContext } from '../../../../../../../../src/plugins/kibana_react/public';
+import { APMServiceContext } from '../../../../context/apm_service/apm_service_context';
+import { MockUrlParamsContextProvider } from '../../../../context/url_params_context/mock_url_params_context_provider';
+import { AnalyzeDataButton } from './analyze_data_button';
+
+const KibanaContext = createKibanaReactContext(({
+ http: { basePath: { get: () => '' } },
+} as unknown) as Partial);
+
+interface Args {
+ agentName: string;
+ environment?: string;
+ serviceName: string;
+}
+
+export default {
+ title: 'routing/templates/ApmServiceTemplate/AnalyzeDataButton',
+ component: AnalyzeDataButton,
+ decorators: [
+ (StoryComponent: ComponentType, { args }: StoryContext) => {
+ const { agentName, environment, serviceName } = args;
+
+ return (
+
+
+
+
+
+
+
+ );
+ },
+ ],
+};
+
+export const Example: Story = () => {
+ return ;
+};
+Example.args = {
+ agentName: 'iOS/swift',
+ environment: 'testEnvironment',
+ serviceName: 'testServiceName',
+};
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx
new file mode 100644
index 00000000000000..fdd28fdb378b4c
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.test.tsx
@@ -0,0 +1,77 @@
+/*
+ * 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 { composeStories } from '@storybook/testing-react';
+import { render, screen } from '@testing-library/react';
+import React from 'react';
+import {
+ ENVIRONMENT_ALL,
+ ENVIRONMENT_NOT_DEFINED,
+} from '../../../../../common/environment_filter_values';
+import * as stories from './analyze_data_button.stories';
+
+const { Example } = composeStories(stories);
+
+describe('AnalyzeDataButton', () => {
+ describe('with a non-RUM and non-mobile agent', () => {
+ it('renders nothing', () => {
+ render();
+
+ expect(screen.queryByRole('link')).not.toBeInTheDocument();
+ });
+ });
+
+ describe('with a RUM agent', () => {
+ it('uses a ux dataType', () => {
+ render();
+
+ expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:ux,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
+ );
+ });
+ });
+
+ describe('with a mobile agent', () => {
+ it('uses a mobile dataType', () => {
+ render();
+
+ expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(testEnvironment),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
+ );
+ });
+ });
+
+ describe('with no environment', () => {
+ it('does not include the environment', () => {
+ render();
+
+ expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
+ );
+ });
+ });
+
+ describe('with environment not defined', () => {
+ it('does not include the environment', () => {
+ render();
+
+ expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
+ );
+ });
+ });
+
+ describe('with environment all', () => {
+ it('uses ALL_VALUES', () => {
+ render();
+
+ expect((screen.getByRole('link') as HTMLAnchorElement).href).toEqual(
+ 'http://localhost/app/observability/exploratory-view#?sr=(apm-series:(dt:mobile,isNew:!t,op:average,rdf:(service.environment:!(ALL_VALUES),service.name:!(testServiceName)),rt:kpi-over-time,time:(from:now-15m,to:now)))'
+ );
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx
new file mode 100644
index 00000000000000..e963349364442e
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/analyze_data_button.tsx
@@ -0,0 +1,87 @@
+/*
+ * 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 { EuiButtonEmpty, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public';
+import {
+ createExploratoryViewUrl,
+ SeriesUrl,
+} from '../../../../../../observability/public';
+import { ALL_VALUES_SELECTED } from '../../../../../../observability/public';
+import {
+ isIosAgentName,
+ isRumAgentName,
+} from '../../../../../common/agent_name';
+import {
+ SERVICE_ENVIRONMENT,
+ SERVICE_NAME,
+} from '../../../../../common/elasticsearch_fieldnames';
+import {
+ ENVIRONMENT_ALL,
+ ENVIRONMENT_NOT_DEFINED,
+} from '../../../../../common/environment_filter_values';
+import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
+import { useUrlParams } from '../../../../context/url_params_context/use_url_params';
+
+function getEnvironmentDefinition(environment?: string) {
+ switch (environment) {
+ case ENVIRONMENT_ALL.value:
+ return { [SERVICE_ENVIRONMENT]: [ALL_VALUES_SELECTED] };
+ case ENVIRONMENT_NOT_DEFINED.value:
+ case undefined:
+ return {};
+ default:
+ return { [SERVICE_ENVIRONMENT]: [environment] };
+ }
+}
+
+export function AnalyzeDataButton() {
+ const { agentName, serviceName } = useApmServiceContext();
+ const { services } = useKibana();
+ const { urlParams } = useUrlParams();
+ const { rangeTo, rangeFrom, environment } = urlParams;
+ const basepath = services.http?.basePath.get();
+
+ if (isRumAgentName(agentName) || isIosAgentName(agentName)) {
+ const href = createExploratoryViewUrl(
+ {
+ 'apm-series': {
+ dataType: isRumAgentName(agentName) ? 'ux' : 'mobile',
+ time: { from: rangeFrom, to: rangeTo },
+ reportType: 'kpi-over-time',
+ reportDefinitions: {
+ [SERVICE_NAME]: [serviceName],
+ ...getEnvironmentDefinition(environment),
+ },
+ operationType: 'average',
+ isNew: true,
+ } as SeriesUrl,
+ },
+ basepath
+ );
+
+ return (
+
+
+ {i18n.translate('xpack.apm.analyzeDataButton.label', {
+ defaultMessage: 'Analyze data',
+ })}
+
+
+ );
+ }
+
+ return null;
+}
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
similarity index 66%
rename from x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx
rename to x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
index 2e10c853f54295..591ccae32f6127 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
@@ -5,45 +5,33 @@
* 2.0.
*/
-import React from 'react';
-import { i18n } from '@kbn/i18n';
import {
+ EuiBetaBadge,
EuiFlexGroup,
EuiFlexItem,
EuiPageHeaderProps,
EuiTitle,
- EuiBetaBadge,
- EuiToolTip,
- EuiButtonEmpty,
} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
import { omit } from 'lodash';
-import { ApmMainTemplate } from './apm_main_template';
-import { useKibana } from '../../../../../../../src/plugins/kibana_react/public';
-import { ApmServiceContextProvider } from '../../../context/apm_service/apm_service_context';
-import { enableServiceOverview } from '../../../../common/ui_settings_keys';
+import React from 'react';
import {
+ isIosAgentName,
isJavaAgentName,
isRumAgentName,
- isIosAgentName,
-} from '../../../../common/agent_name';
-import { ServiceIcons } from '../../shared/service_icons';
-import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
-import { useApmServiceContext } from '../../../context/apm_service/use_apm_service_context';
-import { useUrlParams } from '../../../context/url_params_context/use_url_params';
-import { ENVIRONMENT_NOT_DEFINED } from '../../../../common/environment_filter_values';
-import {
- SERVICE_NAME,
- SERVICE_ENVIRONMENT,
-} from '../../../../common/elasticsearch_fieldnames';
-import { Correlations } from '../../app/correlations';
-import { SearchBar } from '../../shared/search_bar';
-import {
- createExploratoryViewUrl,
- SeriesUrl,
-} from '../../../../../observability/public';
-import { useApmParams } from '../../../hooks/use_apm_params';
-import { useBreadcrumb } from '../../../context/breadcrumbs/use_breadcrumb';
-import { useApmRouter } from '../../../hooks/use_apm_router';
+} from '../../../../../common/agent_name';
+import { enableServiceOverview } from '../../../../../common/ui_settings_keys';
+import { useApmPluginContext } from '../../../../context/apm_plugin/use_apm_plugin_context';
+import { ApmServiceContextProvider } from '../../../../context/apm_service/apm_service_context';
+import { useApmServiceContext } from '../../../../context/apm_service/use_apm_service_context';
+import { useBreadcrumb } from '../../../../context/breadcrumbs/use_breadcrumb';
+import { useApmParams } from '../../../../hooks/use_apm_params';
+import { useApmRouter } from '../../../../hooks/use_apm_router';
+import { Correlations } from '../../../app/correlations';
+import { SearchBar } from '../../../shared/search_bar';
+import { ServiceIcons } from '../../../shared/service_icons';
+import { ApmMainTemplate } from '../apm_main_template';
+import { AnalyzeDataButton } from './analyze_data_button';
type Tab = NonNullable[0] & {
key:
@@ -105,7 +93,7 @@ function TemplateWithContext({
-
{serviceName}
+ <>{serviceName}>
@@ -115,7 +103,7 @@ function TemplateWithContext({
-
+
@@ -132,53 +120,6 @@ function TemplateWithContext({
);
}
-function AnalyzeDataButton({ serviceName }: { serviceName: string }) {
- const { agentName } = useApmServiceContext();
- const { services } = useKibana();
- const { urlParams } = useUrlParams();
- const { rangeTo, rangeFrom, environment } = urlParams;
- const basepath = services.http?.basePath.get();
-
- if (isRumAgentName(agentName) || isIosAgentName(agentName)) {
- const href = createExploratoryViewUrl(
- {
- 'apm-series': {
- dataType: isRumAgentName(agentName) ? 'ux' : 'mobile',
- time: { from: rangeFrom, to: rangeTo },
- reportType: 'kpi-over-time',
- reportDefinitions: {
- [SERVICE_NAME]: [serviceName],
- ...(!!environment && ENVIRONMENT_NOT_DEFINED.value !== environment
- ? { [SERVICE_ENVIRONMENT]: [environment] }
- : {}),
- },
- operationType: 'average',
- isNew: true,
- } as SeriesUrl,
- },
- basepath
- );
-
- return (
-
-
- {i18n.translate('xpack.apm.analyzeDataButton.label', {
- defaultMessage: 'Analyze data',
- })}
-
-
- );
- }
-
- return null;
-}
-
function useTabs({ selectedTab }: { selectedTab: Tab['key'] }) {
const { agentName } = useApmServiceContext();
const { core, config } = useApmPluginContext();
diff --git a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
index 28e674bc4150b8..cc1e79c9687f5a 100644
--- a/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/EnvironmentFilter/index.tsx
@@ -64,12 +64,15 @@ function getOptions(environments: string[]) {
export function EnvironmentFilter() {
const history = useHistory();
const location = useLocation();
- const { path } = useApmParams('/*');
+ const apmParams = useApmParams('/*', true);
const { urlParams } = useUrlParams();
const { environment, start, end } = urlParams;
const { environments, status = 'loading' } = useEnvironmentsFetcher({
- serviceName: 'serviceName' in path ? path.serviceName : undefined,
+ serviceName:
+ apmParams && 'serviceName' in apmParams.path
+ ? apmParams.path.serviceName
+ : undefined,
start,
end,
});
diff --git a/x-pack/plugins/apm/public/hooks/use_apm_params.ts b/x-pack/plugins/apm/public/hooks/use_apm_params.ts
index d7661dbcf4d21c..93285554efd287 100644
--- a/x-pack/plugins/apm/public/hooks/use_apm_params.ts
+++ b/x-pack/plugins/apm/public/hooks/use_apm_params.ts
@@ -8,8 +8,18 @@
import { OutputOf, PathsOf, useParams } from '@kbn/typed-react-router-config';
import { ApmRoutes } from '../components/routing/apm_route_config';
+export function useApmParams>(
+ path: TPath,
+ optional: true
+): OutputOf | undefined;
+
export function useApmParams>(
path: TPath
-): OutputOf {
- return useParams(path as never);
+): OutputOf;
+
+export function useApmParams(
+ path: string,
+ optional?: true
+): OutputOf> | undefined {
+ return useParams(path, optional);
}
diff --git a/x-pack/plugins/apm/server/lib/fleet/source_maps.ts b/x-pack/plugins/apm/server/lib/fleet/source_maps.ts
index 6d608f7751f3ba..10514ddcbdf620 100644
--- a/x-pack/plugins/apm/server/lib/fleet/source_maps.ts
+++ b/x-pack/plugins/apm/server/lib/fleet/source_maps.ts
@@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import * as t from 'io-ts';
import {
CoreSetup,
CoreStart,
@@ -14,7 +13,7 @@ import {
import { promisify } from 'util';
import { unzip } from 'zlib';
import { Artifact } from '../../../../fleet/server';
-import { sourceMapRt } from '../../routes/source_maps';
+import { SourceMap } from '../../routes/source_maps';
import { APMPluginStartDependencies } from '../../types';
import { getApmPackgePolicies } from './get_apm_package_policies';
import { APM_SERVER, PackagePolicy } from './register_fleet_policy_callbacks';
@@ -23,7 +22,7 @@ export interface ApmArtifactBody {
serviceName: string;
serviceVersion: string;
bundleFilepath: string;
- sourceMap: t.TypeOf;
+ sourceMap: SourceMap;
}
export type ArtifactSourceMap = Omit & {
body: ApmArtifactBody;
diff --git a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
index f939a9c39c63c8..aab8025a7679e4 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
+++ b/x-pack/plugins/apm/server/lib/rum_client/__snapshots__/queries.test.ts.snap
@@ -64,13 +64,7 @@ Object {
},
},
],
- "must_not": Array [
- Object {
- "term": Object {
- "service.environment": "staging",
- },
- },
- ],
+ "must_not": Array [],
},
},
"size": 0,
@@ -512,13 +506,7 @@ Object {
},
},
],
- "must_not": Array [
- Object {
- "term": Object {
- "service.environment": "staging",
- },
- },
- ],
+ "must_not": Array [],
},
},
"size": 0,
@@ -566,13 +554,7 @@ Object {
},
},
],
- "must_not": Array [
- Object {
- "term": Object {
- "service.environment": "staging",
- },
- },
- ],
+ "must_not": Array [],
},
},
"size": 0,
diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.test.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.test.ts
new file mode 100644
index 00000000000000..ba5e318a1901bc
--- /dev/null
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.test.ts
@@ -0,0 +1,31 @@
+/*
+ * 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 { getEsFilter } from './get_es_filter';
+
+describe('getEfFilters', function () {
+ it('should return environment in include filters', function () {
+ const result = getEsFilter({
+ browser: ['Chrome'],
+ environment: 'production',
+ });
+
+ expect(result).toEqual([
+ { terms: { 'user_agent.name': ['Chrome'] } },
+ { term: { 'service.environment': 'production' } },
+ ]);
+ });
+
+ it('should not return environment in exclude filters', function () {
+ const result = getEsFilter(
+ { browserExcluded: ['Chrome'], environment: 'production' },
+ true
+ );
+
+ expect(result).toEqual([{ terms: { 'user_agent.name': ['Chrome'] } }]);
+ });
+});
diff --git a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
index 5f587f82e979d9..76ef9fb95089ff 100644
--- a/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
+++ b/x-pack/plugins/apm/server/lib/rum_client/ui_filters/get_es_filter.ts
@@ -34,5 +34,8 @@ export function getEsFilter(uiFilters: UxUIFilters, exclude?: boolean) {
};
}) as ESFilter[];
- return [...mappedFilters, ...environmentQuery(uiFilters.environment)];
+ return [
+ ...mappedFilters,
+ ...(exclude ? [] : environmentQuery(uiFilters.environment)),
+ ];
}
diff --git a/x-pack/plugins/apm/server/routes/fleet.ts b/x-pack/plugins/apm/server/routes/fleet.ts
index a01c9dd1579b1f..afe18b33c48223 100644
--- a/x-pack/plugins/apm/server/routes/fleet.ts
+++ b/x-pack/plugins/apm/server/routes/fleet.ts
@@ -138,10 +138,12 @@ const getMigrationCheckRoute = createApmServerRoute({
const fleetPluginStart = await plugins.fleet.start();
const securityPluginStart = await plugins.security.start();
const hasRequiredRole = isSuperuser({ securityPluginStart, request });
- const cloudAgentPolicy = await getCloudAgentPolicy({
- savedObjectsClient,
- fleetPluginStart,
- });
+ const cloudAgentPolicy = hasRequiredRole
+ ? await getCloudAgentPolicy({
+ savedObjectsClient,
+ fleetPluginStart,
+ })
+ : undefined;
return {
has_cloud_agent_policy: !!cloudAgentPolicy,
has_cloud_apm_package_policy: !!getApmPackagePolicy(cloudAgentPolicy),
diff --git a/x-pack/plugins/apm/server/routes/source_maps.ts b/x-pack/plugins/apm/server/routes/source_maps.ts
index f6d160e68a76af..d92bad31cd8d8f 100644
--- a/x-pack/plugins/apm/server/routes/source_maps.ts
+++ b/x-pack/plugins/apm/server/routes/source_maps.ts
@@ -5,9 +5,9 @@
* 2.0.
*/
import Boom from '@hapi/boom';
-import { jsonRt } from '@kbn/io-ts-utils';
import * as t from 'io-ts';
import { SavedObjectsClientContract } from 'kibana/server';
+import { jsonRt } from '@kbn/io-ts-utils';
import {
createApmArtifact,
deleteApmArtifact,
@@ -17,6 +17,7 @@ import {
import { getInternalSavedObjectsClient } from '../lib/helpers/get_internal_saved_objects_client';
import { createApmServerRoute } from './create_apm_server_route';
import { createApmServerRouteRepository } from './create_apm_server_route_repository';
+import { stringFromBufferRt } from '../utils/string_from_buffer_rt';
export const sourceMapRt = t.intersection([
t.type({
@@ -32,6 +33,8 @@ export const sourceMapRt = t.intersection([
}),
]);
+export type SourceMap = t.TypeOf;
+
const listSourceMapRoute = createApmServerRoute({
endpoint: 'GET /api/apm/sourcemaps',
options: { tags: ['access:apm'] },
@@ -62,7 +65,10 @@ const uploadSourceMapRoute = createApmServerRoute({
service_name: t.string,
service_version: t.string,
bundle_filepath: t.string,
- sourcemap: jsonRt.pipe(sourceMapRt),
+ sourcemap: t
+ .union([t.string, stringFromBufferRt])
+ .pipe(jsonRt)
+ .pipe(sourceMapRt),
}),
}),
handler: async ({ params, plugins, core }) => {
diff --git a/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts
new file mode 100644
index 00000000000000..4e21215cac8b5d
--- /dev/null
+++ b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.test.ts
@@ -0,0 +1,51 @@
+/*
+ * 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 { isRight } from 'fp-ts/lib/Either';
+import { stringFromBufferRt } from './string_from_buffer_rt';
+
+const sourceMap = {
+ version: 3,
+ file: 'static/js/main.chunk.js',
+ sources: [
+ '/foo/src/index.css',
+ '/foo/src/App.js',
+ 'webpack:///./src/index.css?bb0a',
+ '/foo/src/index.js',
+ '/foo/src/reportWebVitals.js',
+ ],
+ sourcesContent: [
+ "// Imports\nimport ___CSS_LOADER_API_IMPORT___ from \"../node_modules/css-loader/dist/runtime/api.js\";\nvar ___CSS_LOADER_EXPORT___ = ___CSS_LOADER_API_IMPORT___(true);\n// Module\n___CSS_LOADER_EXPORT___.push([module.id, \"body {\\n margin: 0;\\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\\n sans-serif;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n\\ncode {\\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\\n monospace;\\n}\\n\", \"\",{\"version\":3,\"sources\":[\"webpack://src/index.css\"],\"names\":[],\"mappings\":\"AAAA;EACE,SAAS;EACT;;cAEY;EACZ,mCAAmC;EACnC,kCAAkC;AACpC;;AAEA;EACE;aACW;AACb\",\"sourcesContent\":[\"body {\\n margin: 0;\\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',\\n 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',\\n sans-serif;\\n -webkit-font-smoothing: antialiased;\\n -moz-osx-font-smoothing: grayscale;\\n}\\n\\ncode {\\n font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',\\n monospace;\\n}\\n\"],\"sourceRoot\":\"\"}]);\n// Exports\nexport default ___CSS_LOADER_EXPORT___;\n",
+ 'import React from "react";\nimport {\n BrowserRouter as Router,\n Switch,\n Route,\n Link\n} from "react-router-dom";\n\n// This site has 3 pages, all of which are rendered\n// dynamically in the browser (not server rendered).\n//\n// Although the page does not ever refresh, notice how\n// React Router keeps the URL up to date as you navigate\n// through the site. This preserves the browser history,\n// making sure things like the back button and bookmarks\n// work properly.\n\nexport default function App() {\n return (\n \n
\n
\n
\n Home\n
\n
\n About\n
\n
\n Dashboard\n
\n
\n Error\n
\n
\n\n \n\n {/*\n A looks through all its children \n elements and renders the first one whose path\n matches the current URL. Use a any time\n you have multiple routes, but you want only one\n of them to render at a time\n */}\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
\n \n );\n}\n\n// You can think of these components as "pages"\n// in your app.\n\nfunction Home() {\n return (\n
\n
HOME
\n
\n );\n}\n\nfunction About() {\n return (\n
\n
about
\n
\n );\n}\n\nfunction Dashboard() {\n return (\n
\n
Dashboard
\n
\n );\n}\n\nfunction ErrorPage() {\n throw new Error(\'Boomm\')\n return (\n
\n
error
\n
\n );\n}\n',
+ "var api = require(\"!../node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js\");\n var content = require(\"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.id, content, '']];\n }\n\nvar options = {};\n\noptions.insert = \"head\";\noptions.singleton = false;\n\nvar update = api(content, options);\n\n\nif (module.hot) {\n if (!content.locals || module.hot.invalidate) {\n var isEqualLocals = function isEqualLocals(a, b, isNamedExport) {\n if (!a && b || a && !b) {\n return false;\n }\n\n var p;\n\n for (p in a) {\n if (isNamedExport && p === 'default') {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (a[p] !== b[p]) {\n return false;\n }\n }\n\n for (p in b) {\n if (isNamedExport && p === 'default') {\n // eslint-disable-next-line no-continue\n continue;\n }\n\n if (!a[p]) {\n return false;\n }\n }\n\n return true;\n};\n var oldLocals = content.locals;\n\n module.hot.accept(\n \"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\",\n function () {\n content = require(\"!!../node_modules/css-loader/dist/cjs.js??ref--5-oneOf-4-1!../node_modules/postcss-loader/src/index.js??postcss!./index.css\");\n\n content = content.__esModule ? content.default : content;\n\n if (typeof content === 'string') {\n content = [[module.id, content, '']];\n }\n\n if (!isEqualLocals(oldLocals, content.locals)) {\n module.hot.invalidate();\n\n return;\n }\n\n oldLocals = content.locals;\n\n update(content);\n }\n )\n }\n\n module.hot.dispose(function() {\n update();\n });\n}\n\nmodule.exports = content.locals || {};",
+ "/*eslint-disable import/first */\nimport { init as initApm } from '@elastic/apm-rum'\ninitApm({\n serviceName: 'fleet-source-map-client',\n serverUrl: 'http://localhost:8200',\n // serverUrl: 'https://776d64ec093b47ff86c752f62baa8f51.apm.us-west1.gcp.cloud.es.io:443',\n serviceVersion: '1.0.0',\n environment: 'production'\n})\nimport React from 'react';\nimport ReactDOM from 'react-dom';\nimport './index.css';\nimport App from './App';\nimport reportWebVitals from './reportWebVitals';\n\nReactDOM.render(\n \n \n ,\n document.getElementById('root')\n);\n\n// If you want to start measuring performance in your app, pass a function\n// to log results (for example: reportWebVitals(console.log))\n// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals\nreportWebVitals();\n",
+ "const reportWebVitals = onPerfEntry => {\n if (onPerfEntry && onPerfEntry instanceof Function) {\n import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {\n getCLS(onPerfEntry);\n getFID(onPerfEntry);\n getFCP(onPerfEntry);\n getLCP(onPerfEntry);\n getTTFB(onPerfEntry);\n });\n }\n};\n\nexport default reportWebVitals;\n",
+ ],
+ mappings:
+ ';;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;ACNA;AACA;AAQA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAVA;AAAA;AAAA;AAAA;AAAA;AAeA;AAAA;AAAA;AAAA;AASA;AACA;AAAA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAGA;AAAA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAVA;AAAA;AAAA;AAAA;AAAA;AAzBA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AA2CA;AAGA;AACA;AAjDA;AACA;AAiDA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AAPA;AACA;AAOA;AACA;AACA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAIA;AACA;AARA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5FA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AALA;AAOA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AAAA;AAAA;AAAA;AADA;AAAA;AAAA;AAAA;AAAA;AAOA;AACA;AACA;AAAA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC1BA;AACA;AACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;A',
+ sourceRoot: '',
+};
+
+describe('stringFromBufferRt', () => {
+ describe('decode', () => {
+ it('converts from buffer to string', () => {
+ const sourceMapBuffer = Buffer.from(JSON.stringify(sourceMap));
+ const decoded = stringFromBufferRt.decode(sourceMapBuffer);
+ if (isRight(decoded)) {
+ expect(decoded.right).toEqual(JSON.stringify(sourceMap));
+ } else {
+ expect(true).toBeFalsy();
+ }
+ });
+ });
+ describe('encode', () => {
+ it('converts from string to buffer', () => {
+ const encoded = stringFromBufferRt.encode(JSON.stringify(sourceMap));
+ expect(encoded).toEqual(Buffer.from(JSON.stringify(sourceMap)));
+ });
+ });
+});
diff --git a/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts
new file mode 100644
index 00000000000000..3e9304361c8636
--- /dev/null
+++ b/x-pack/plugins/apm/server/utils/string_from_buffer_rt.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 * as t from 'io-ts';
+
+export const stringFromBufferRt = new t.Type(
+ 'stringFromBufferRt',
+ t.string.is,
+ (input, context) => {
+ return Buffer.isBuffer(input)
+ ? t.success(input.toString('utf-8'))
+ : t.failure(input, context, 'Input is not a Buffer');
+ },
+ (str) => {
+ return Buffer.from(str);
+ }
+);
diff --git a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
index 284749340e440a..a35b83bad3e7e3 100644
--- a/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
+++ b/x-pack/plugins/canvas/public/components/workpad_header/view_menu/custom_interval.tsx
@@ -75,12 +75,7 @@ export const CustomInterval = ({ gutterSize, buttonSize, onSubmit, defaultValue
-
+
{strings.getButtonLabel()}
diff --git a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
index 075c0cd386759b..988aba48d976f5 100644
--- a/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
+++ b/x-pack/plugins/canvas/shareable_runtime/components/footer/settings/__snapshots__/settings.test.tsx.snap
@@ -324,6 +324,7 @@ exports[` can navigate Autoplay Settings 2`] = `
);
-export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverActionItemsProps) => {
+export const PopoverActionsMenu = ({
+ api,
+ onActionComplete,
+ session,
+ core,
+}: PopoverActionItemsProps) => {
const [isPopoverOpen, setPopover] = useState(false);
const onPopoverClick = () => {
@@ -64,6 +71,10 @@ export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverAc
setPopover(false);
};
+ const onActionClick = () => {
+ closePopover();
+ };
+
const renderPopoverButton = () => (
{
- const actionDef = getAction(api, actionType, session, onActionComplete);
+ const actionDef = getAction(api, actionType, session, core, onActionClick, onActionComplete);
if (actionDef) {
const { label, textColor, iconType } = actionDef;
@@ -115,16 +126,20 @@ export const PopoverActionsMenu = ({ api, onActionComplete, session }: PopoverAc
const panels: EuiContextMenuPanelDescriptor[] = [{ id: 0, items }];
- return actions.length ? (
-
-
-
- ) : null;
+ return (
+ <>
+ {actions.length ? (
+
+
+
+ ) : null}
+ >
+ );
};
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx
index 870de78ea06caf..6fa0d0d443d71b 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/rename_button.tsx
@@ -20,18 +20,25 @@ import {
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import React, { useState } from 'react';
+import { CoreStart } from 'kibana/public';
import { SearchSessionsMgmtAPI } from '../../lib/api';
import { TableText } from '../';
-import { OnActionComplete } from './types';
+import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public';
+import { OnActionClick, OnActionComplete, OnActionDismiss } from './types';
interface RenameButtonProps {
id: string;
name: string;
api: SearchSessionsMgmtAPI;
+ overlays: CoreStart['overlays'];
onActionComplete: OnActionComplete;
+ onActionClick: OnActionClick;
}
-const RenameDialog = ({ onDismiss, ...props }: RenameButtonProps & { onDismiss: () => void }) => {
+const RenameDialog = ({
+ onActionDismiss,
+ ...props
+}: RenameButtonProps & { onActionDismiss: OnActionDismiss }) => {
const { id, name: originalName, api, onActionComplete } = props;
const [isLoading, setIsLoading] = useState(false);
const [newName, setNewName] = useState(originalName);
@@ -56,7 +63,7 @@ const RenameDialog = ({ onDismiss, ...props }: RenameButtonProps & { onDismiss:
const isNewNameValid = newName && originalName !== newName;
return (
-
+ {title}
@@ -75,7 +82,7 @@ const RenameDialog = ({ onDismiss, ...props }: RenameButtonProps & { onDismiss:
- {cancel}
+ {cancel} {
- const [showRenameDialog, setShowRenameDialog] = useState(false);
+ const { overlays, onActionClick } = props;
const onClick = () => {
- setShowRenameDialog(true);
- };
-
- const onDismiss = () => {
- setShowRenameDialog(false);
+ onActionClick();
+ const ref = overlays.openModal(
+ toMountPoint( ref?.close()} {...props} />)
+ );
};
return (
@@ -116,7 +122,6 @@ export const RenameButton = (props: RenameButtonProps) => {
defaultMessage="Edit name"
/>
- {showRenameDialog ? : null}
>
);
};
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts
index 407666adbfab45..ec0c281d0a4fe1 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/components/actions/types.ts
@@ -6,6 +6,8 @@
*/
export type OnActionComplete = () => void;
+export type OnActionClick = () => void;
+export type OnActionDismiss = () => void;
export enum ACTION {
INSPECT = 'inspect',
diff --git a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
index 7dd4966124e96f..94033a2536a875 100644
--- a/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
+++ b/x-pack/plugins/data_enhanced/public/search/sessions_mgmt/lib/get_columns.tsx
@@ -243,6 +243,7 @@ export const getColumns = (
api={api}
key={`popkey-${session.id}`}
session={session}
+ core={core}
onActionComplete={onActionComplete}
/>
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx
index c29bbc3b3a731c..bd4dbfe8fa061d 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/constants.tsx
@@ -36,34 +36,34 @@ export const DOCUMENT_CREATION_WARNINGS = {
// This is indented the way it is to work with ApiCodeExample.
// Use dedent() when calling this alone
export const DOCUMENTS_API_JSON_EXAMPLE = `[
- {
- "id": "park_rocky-mountain",
- "title": "Rocky Mountain",
- "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
- "nps_link": "https://www.nps.gov/romo/index.htm",
- "states": [
- "Colorado"
- ],
- "visitors": 4517585,
- "world_heritage_site": false,
- "location": "40.4,-105.58",
- "acres": 265795.2,
- "square_km": 1075.6,
- "date_established": "1915-01-26T06:00:00Z"
- },
- {
- "id": "park_saguaro",
- "title": "Saguaro",
- "description": "Split into the separate Rincon Mountain and Tucson Mountain districts, this park is evidence that the dry Sonoran Desert is still home to a great variety of life spanning six biotic communities. Beyond the namesake giant saguaro cacti, there are barrel cacti, chollas, and prickly pears, as well as lesser long-nosed bats, spotted owls, and javelinas.",
- "nps_link": "https://www.nps.gov/sagu/index.htm",
- "states": [
- "Arizona"
- ],
- "visitors": 820426,
- "world_heritage_site": false,
- "location": "32.25,-110.5",
- "acres": 91715.72,
- "square_km": 371.2,
- "date_established": "1994-10-14T05:00:00Z"
- }
- ]`;
+ {
+ "id": "park_rocky-mountain",
+ "title": "Rocky Mountain",
+ "description": "Bisected north to south by the Continental Divide, this portion of the Rockies has ecosystems varying from over 150 riparian lakes to montane and subalpine forests to treeless alpine tundra. Wildlife including mule deer, bighorn sheep, black bears, and cougars inhabit its igneous mountains and glacial valleys. Longs Peak, a classic Colorado fourteener, and the scenic Bear Lake are popular destinations, as well as the historic Trail Ridge Road, which reaches an elevation of more than 12,000 feet (3,700 m).",
+ "nps_link": "https://www.nps.gov/romo/index.htm",
+ "states": [
+ "Colorado"
+ ],
+ "visitors": 4517585,
+ "world_heritage_site": false,
+ "location": "40.4,-105.58",
+ "acres": 265795.2,
+ "square_km": 1075.6,
+ "date_established": "1915-01-26T06:00:00Z"
+ },
+ {
+ "id": "park_saguaro",
+ "title": "Saguaro",
+ "description": "Split into the separate Rincon Mountain and Tucson Mountain districts, this park is evidence that the dry Sonoran Desert is still home to a great variety of life spanning six biotic communities. Beyond the namesake giant saguaro cacti, there are barrel cacti, chollas, and prickly pears, as well as lesser long-nosed bats, spotted owls, and javelinas.",
+ "nps_link": "https://www.nps.gov/sagu/index.htm",
+ "states": [
+ "Arizona"
+ ],
+ "visitors": 820426,
+ "world_heritage_site": false,
+ "location": "32.25,-110.5",
+ "acres": 91715.72,
+ "square_km": 371.2,
+ "date_established": "1994-10-14T05:00:00Z"
+ }
+ ]`;
diff --git a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx
index 079fd20e25af30..e5f295f0368fb9 100644
--- a/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx
+++ b/x-pack/plugins/enterprise_search/public/applications/app_search/components/document_creation/creation_mode_components/api_code_example.tsx
@@ -7,8 +7,6 @@
import React from 'react';
-import dedent from 'dedent';
-
import { useValues, useActions } from 'kea';
import {
@@ -107,23 +105,22 @@ export const FlyoutBody: React.FC = () => {
- {dedent(`
- curl -X POST '${documentsApiUrl}'
- -H 'Content-Type: application/json'
- -H 'Authorization: Bearer ${apiKey}'
- -d '${DOCUMENTS_API_JSON_EXAMPLE}'
- # Returns
- # [
- # {
- # "id": "park_rocky-mountain",
- # "errors": []
- # },
- # {
- # "id": "park_saguaro",
- # "errors": []
- # }
- # ]
- `)}
+ {`\
+curl -X POST '${documentsApiUrl}' \\
+ -H 'Content-Type: application/json' \\
+ -H 'Authorization: Bearer ${apiKey}' \\
+ -d '${DOCUMENTS_API_JSON_EXAMPLE}'
+# Returns
+# [
+# {
+# "id": "park_rocky-mountain",
+# "errors": []
+# },
+# {
+# "id": "park_saguaro",
+# "errors": []
+# }
+# ]`}
);
diff --git a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts
index 21051e77547f25..d6ee25da6e2b1b 100644
--- a/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts
+++ b/x-pack/plugins/enterprise_search/public/applications/workplace_search/views/content_sources/components/display_settings/constants.ts
@@ -7,12 +7,7 @@
import { i18n } from '@kbn/i18n';
-export const LEAVE_UNASSIGNED_FIELD = i18n.translate(
- 'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.leaveUnassigned.field',
- {
- defaultMessage: 'Leave unassigned',
- }
-);
+export const LEAVE_UNASSIGNED_FIELD = '';
export const SUCCESS_MESSAGE = i18n.translate(
'xpack.enterpriseSearch.workplaceSearch.contentSources.displaySettings.success.message',
diff --git a/x-pack/plugins/fleet/common/openapi/bundled.json b/x-pack/plugins/fleet/common/openapi/bundled.json
index 9ec519365ea22f..feeba68644334e 100644
--- a/x-pack/plugins/fleet/common/openapi/bundled.json
+++ b/x-pack/plugins/fleet/common/openapi/bundled.json
@@ -941,6 +941,11 @@
"post": {
"summary": "Agent policy - copy one policy",
"operationId": "agent-policy-copy",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/kbn_xsrf"
+ }
+ ],
"responses": {
"200": {
"description": "OK",
@@ -981,8 +986,7 @@
}
},
"description": ""
- },
- "description": "Copies one agent policy"
+ }
}
},
"/agent_policies/delete": {
diff --git a/x-pack/plugins/fleet/common/openapi/bundled.yaml b/x-pack/plugins/fleet/common/openapi/bundled.yaml
index a5d177e9a0f186..38daf60b33e0db 100644
--- a/x-pack/plugins/fleet/common/openapi/bundled.yaml
+++ b/x-pack/plugins/fleet/common/openapi/bundled.yaml
@@ -579,6 +579,8 @@ paths:
post:
summary: Agent policy - copy one policy
operationId: agent-policy-copy
+ parameters:
+ - $ref: '#/components/parameters/kbn_xsrf'
responses:
'200':
description: OK
@@ -604,7 +606,6 @@ paths:
required:
- name
description: ''
- description: Copies one agent policy
/agent_policies/delete:
post:
summary: Agent policy - Delete
diff --git a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml
index 4b42f8cab0677d..487f6e95f86696 100644
--- a/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml
+++ b/x-pack/plugins/fleet/common/openapi/paths/agent_policies@{agent_policy_id}@copy.yaml
@@ -7,6 +7,8 @@ parameters:
post:
summary: Agent policy - copy one policy
operationId: agent-policy-copy
+ parameters:
+ - $ref: ../components/headers/kbn_xsrf.yaml
responses:
'200':
description: OK
@@ -32,4 +34,4 @@ post:
required:
- name
description: ''
- description: Copies one agent policy
+
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
index a599d726cedefe..f90db1a3a64282 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agents/agent_details_page/components/agent_details/agent_details_integrations.tsx
@@ -107,6 +107,9 @@ export const AgentDetailsIntegration: React.FunctionComponent<{
logQuery: getLogsQueryByInputType(inputType),
})}
iconType="editorAlignLeft"
+ aria-label={i18n.translate('xpack.fleet.agentDetailsIntegrations.viewLogsButton', {
+ defaultMessage: 'View logs',
+ })}
/>
);
diff --git a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
index 63372e435cfa1c..8ec5fd83a12543 100644
--- a/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
+++ b/x-pack/plugins/fleet/public/applications/integrations/sections/epm/screens/detail/policies/package_policies.tsx
@@ -223,6 +223,12 @@ export const PackagePoliciesPage = ({ name, version }: PackagePoliciesPanelProps
iconType="plusInCircle"
onClick={() => setFlyoutOpenForPolicyId(agentPolicy.id)}
data-test-subj="addAgentButton"
+ aria-label={i18n.translate(
+ 'xpack.fleet.epm.packageDetails.integrationList.addAgent',
+ {
+ defaultMessage: 'Add Agent',
+ }
+ )}
/>
)}
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx
index 4677ea4b747b5f..79ffd28c9e7884 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.helpers.tsx
@@ -99,6 +99,13 @@ const createActions = (testBed: TestBed) => {
component.update();
},
+ async setProcessorType(type: string) {
+ await act(async () => {
+ find('processorTypeSelector.input').simulate('change', [{ value: type }]);
+ });
+ component.update();
+ },
+
removeProcessor(processorSelector: string) {
find(`${processorSelector}.moreMenu.button`).simulate('click');
find(`${processorSelector}.moreMenu.deleteButton`).simulate('click');
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx
index fbc46159c4e139..5b02927ab873c3 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/pipeline_processors_editor.test.tsx
@@ -5,6 +5,7 @@
* 2.0.
*/
+import { act } from 'react-dom/test-utils';
import { setup, SetupResult } from './pipeline_processors_editor.helpers';
import { Pipeline } from '../../../../../common/types';
@@ -120,6 +121,37 @@ describe('Pipeline Editor', () => {
});
});
+ it('allows to edit an existing processor and change its type', async () => {
+ const { actions, exists, component, find } = testBed;
+
+ // Open one of the existing processors
+ actions.openProcessorEditor('processors>2');
+ expect(exists('editProcessorForm')).toBeTruthy();
+
+ // Change its type to `append` and set the missing required fields
+ await actions.setProcessorType('append');
+ await act(async () => {
+ find('appendValueField.input').simulate('change', [{ label: 'some_value' }]);
+ });
+ component.update();
+
+ await actions.submitProcessorForm();
+
+ const [onUpdateResult] = onUpdate.mock.calls[onUpdate.mock.calls.length - 1];
+ const {
+ processors: { 2: editedProcessor },
+ } = onUpdateResult.getData();
+
+ expect(editedProcessor.append).toEqual({
+ if: undefined,
+ tag: undefined,
+ description: undefined,
+ ignore_failure: undefined,
+ field: 'test',
+ value: ['some_value'],
+ });
+ });
+
it('removes a processor', () => {
const { actions } = testBed;
// processor>0 denotes the first processor in the top-level processors array.
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx
index c980f84910fa0b..88e5c62a5b1d39 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.helpers.tsx
@@ -102,6 +102,13 @@ const createActions = (testBed: TestBed) => {
component.update();
},
+ async clickProcessorConfigurationTab() {
+ await act(async () => {
+ find('configurationTab').simulate('click');
+ });
+ component.update();
+ },
+
async clickProcessorOutputTab() {
await act(async () => {
find('outputTab').simulate('click');
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx
index 91384e36c83937..607978512e2033 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/__jest__/test_pipeline.test.tsx
@@ -356,6 +356,21 @@ describe('Test pipeline', () => {
expect(statusIconLabel).toEqual('Success');
});
+ describe('Configuration tab', () => {
+ it('should not clear up form when clicking configuration tab', async () => {
+ const { actions, find, exists } = testBed;
+
+ // Click processor to open manage flyout
+ await actions.clickProcessor('processors>0');
+ // Verify flyout opened
+ expect(exists('editProcessorForm')).toBe(true);
+ // Click the "Configuration" tab
+ await actions.clickProcessorConfigurationTab();
+ // Verify type selector has not changed
+ expect(find('processorTypeSelector.input').text()).toBe('Set');
+ });
+ });
+
describe('Output tab', () => {
beforeEach(async () => {
const { actions } = testBed;
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/edit_processor_form.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/edit_processor_form.tsx
index c63842f28f7ea9..c80b9b136339df 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/edit_processor_form.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/components/processor_form/edit_processor_form.tsx
@@ -196,6 +196,11 @@ export const EditProcessorForm: FunctionComponent = ({
{tabs.map((tab) => (
{
+ // No need to do anything if user clicks the already active tab
+ if (tab.id === activeTab) {
+ return;
+ }
+
if (tab.id === 'output') {
await handleSubmit(false);
} else {
diff --git a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
index ddf996de7805c4..6233b220ae7730 100644
--- a/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
+++ b/x-pack/plugins/ingest_pipelines/public/application/components/pipeline_editor/context/processors_context.tsx
@@ -158,18 +158,26 @@ export const PipelineProcessorsContextProvider: FunctionComponent = ({
'internal_networks_field',
];
+ // If the processor type is changed while editing, we need to ignore unkownOptions as they
+ // will contain the fields from the previous processor resulting in the wrong request.
+ const hasProcessorTypeChanged = mode.arg.processor.type !== processorTypeAndOptions.type;
// The processor that we are updating may have options configured the UI does not know about
- const unknownOptions = omit(mode.arg.processor.options, knownOptionNames);
+ const unknownOptions = hasProcessorTypeChanged
+ ? {}
+ : omit(mode.arg.processor.options, knownOptionNames);
// In order to keep the options we don't get back from our UI, we merge the known and unknown options
const updatedProcessorOptions = {
...processorTypeAndOptions.options,
...unknownOptions,
};
+
processorsDispatch({
type: 'updateProcessor',
payload: {
processor: {
...mode.arg.processor,
+ // Always prefer the newly selected processor type, as it might change during editing
+ type: processorTypeAndOptions.type,
options: updatedProcessorOptions,
},
selector: mode.arg.selector,
diff --git a/x-pack/plugins/maps/server/sample_data/flights_saved_objects.js b/x-pack/plugins/maps/server/sample_data/flights_saved_objects.js
index a1fbeacfcbfd1f..4e9915623d7c7d 100644
--- a/x-pack/plugins/maps/server/sample_data/flights_saved_objects.js
+++ b/x-pack/plugins/maps/server/sample_data/flights_saved_objects.js
@@ -24,7 +24,7 @@ const layerList = [
{
id: 'jzppx',
label: 'Flights',
- minZoom: 9,
+ minZoom: 8,
maxZoom: 24,
alpha: 1,
sourceDescriptor: {
@@ -45,26 +45,41 @@ const layerList = [
'AvgTicketPrice',
'FlightDelay',
],
+ applyGlobalQuery: true,
+ scalingType: 'MVT',
+ sortField: 'timestamp',
indexPatternRefName: 'layer_1_source_index_pattern',
},
visible: true,
style: {
type: 'VECTOR',
properties: {
+ icon: {
+ type: 'STATIC',
+ options: {
+ value: 'marker',
+ },
+ },
fillColor: {
type: 'DYNAMIC',
options: {
field: {
- name: 'FlightTimeMin',
+ name: 'FlightDelayMin',
origin: 'source',
},
- color: 'Greens',
+ color: 'Yellow to Red',
+ fieldMetaOptions: {
+ isEnabled: false,
+ sigma: 3,
+ },
+ type: 'ORDINAL',
+ useCustomColorRamp: false,
},
},
lineColor: {
type: 'STATIC',
options: {
- color: '#FFFFFF',
+ color: '#000',
},
},
lineWidth: {
@@ -74,25 +89,61 @@ const layerList = [
},
},
iconSize: {
- type: 'DYNAMIC',
+ type: 'STATIC',
options: {
- field: {
- name: 'DistanceMiles',
- origin: 'source',
- },
- minSize: 1,
- maxSize: 32,
+ size: 6,
+ },
+ },
+ iconOrientation: {
+ type: 'STATIC',
+ options: {
+ orientation: 0,
+ },
+ },
+ labelText: {
+ type: 'STATIC',
+ options: {
+ value: '',
+ },
+ },
+ labelColor: {
+ type: 'STATIC',
+ options: {
+ color: '#000000',
+ },
+ },
+ labelSize: {
+ type: 'STATIC',
+ options: {
+ size: 14,
+ },
+ },
+ labelBorderColor: {
+ type: 'STATIC',
+ options: {
+ color: '#FFFFFF',
+ },
+ },
+ symbolizeAs: {
+ options: {
+ value: 'circle',
+ },
+ },
+ labelBorderSize: {
+ options: {
+ size: 'SMALL',
},
},
},
+ isTimeAware: true,
},
- type: 'VECTOR',
+ type: 'TILED_VECTOR',
},
{
id: 'y4jsz',
label: 'Flight Origin Location',
minZoom: 0,
- maxZoom: 9,
+ maxZoom: 8,
alpha: 1,
sourceDescriptor: {
type: 'ES_GEO_GRID',
@@ -106,25 +157,37 @@ const layerList = [
label: 'flight count',
},
{
- type: 'avg',
- field: 'FlightTimeMin',
- label: 'minimum flight time',
+ type: 'sum',
+ field: 'FlightDelayMin',
},
],
+ applyGlobalQuery: true,
indexPatternRefName: 'layer_2_source_index_pattern',
},
visible: true,
style: {
type: 'VECTOR',
properties: {
+ icon: {
+ type: 'STATIC',
+ options: {
+ value: 'marker',
+ },
+ },
fillColor: {
type: 'DYNAMIC',
options: {
+ color: 'Yellow to Red',
+ fieldMetaOptions: {
+ isEnabled: false,
+ sigma: 3,
+ },
+ type: 'ORDINAL',
+ useCustomColorRamp: false,
field: {
- name: 'doc_count',
+ name: 'sum_of_FlightDelayMin',
origin: 'source',
},
- color: 'Blues',
},
},
lineColor: {
@@ -143,80 +206,59 @@ const layerList = [
type: 'DYNAMIC',
options: {
field: {
- name: 'avg_of_FlightTimeMin',
origin: 'source',
+ name: 'doc_count',
},
- minSize: 1,
+ minSize: 4,
maxSize: 32,
+ fieldMetaOptions: {
+ isEnabled: false,
+ sigma: 3,
+ },
},
},
- },
- },
- type: 'VECTOR',
- },
- {
- id: 'x8xpo',
- label: 'Flight Destination Location',
- minZoom: 0,
- maxZoom: 9,
- alpha: 1,
- sourceDescriptor: {
- type: 'ES_GEO_GRID',
- resolution: 'COARSE',
- id: '60a7346a-8c5f-4c03-b7d1-e8b36e847551',
- geoField: 'DestLocation',
- requestType: 'point',
- metrics: [
- {
- type: 'count',
- label: 'flight count',
+ iconOrientation: {
+ type: 'STATIC',
+ options: {
+ orientation: 0,
+ },
},
- {
- type: 'avg',
- field: 'FlightDelayMin',
- label: 'average delay',
+ labelText: {
+ type: 'STATIC',
+ options: {
+ value: '',
+ },
},
- ],
- indexPatternRefName: 'layer_3_source_index_pattern',
- },
- visible: true,
- style: {
- type: 'VECTOR',
- properties: {
- fillColor: {
- type: 'DYNAMIC',
+ labelColor: {
+ type: 'STATIC',
options: {
- field: {
- name: 'doc_count',
- origin: 'source',
- },
- color: 'Reds',
+ color: '#000000',
},
},
- lineColor: {
+ labelSize: {
type: 'STATIC',
options: {
- color: '#af0303',
+ size: 14,
},
},
- lineWidth: {
+ labelBorderColor: {
type: 'STATIC',
options: {
- size: 1,
+ color: '#FFFFFF',
},
},
- iconSize: {
- type: 'DYNAMIC',
+ symbolizeAs: {
options: {
- field: {
- name: 'avg_of_FlightDelayMin',
- origin: 'source',
- },
- minSize: 1,
- maxSize: 32,
+ value: 'circle',
+ },
+ },
+ labelBorderSize: {
+ options: {
+ size: 'SMALL',
},
},
},
+ isTimeAware: true,
},
type: 'VECTOR',
},
@@ -227,45 +269,34 @@ export const getFlightsSavedObjects = () => {
{
id: '5dd88580-1906-11e9-919b-ffe5949a18d2',
type: 'map',
- updated_at: '2019-01-15T20:44:54.767Z',
- version: 2,
+ updated_at: '2021-07-07T02:20:04.294Z',
+ version: '3',
+ attributes: {
+ title: i18n.translate('xpack.maps.sampleData.flightsSpec.mapsTitle', {
+ defaultMessage: '[Flights] Origin Time Delayed',
+ }),
+ description: '',
+ layerListJSON: JSON.stringify(layerList),
+ mapStateJSON:
+ '{"zoom":4.28,"center":{"lon":-112.44472,"lat":34.65823},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"query":"","language":"kuery"},"filters":[],"settings":{"autoFitToDataBounds":false,"backgroundColor":"#ffffff","disableInteractive":false,"disableTooltipControl":false,"hideToolbarOverlay":false,"hideLayerControl":false,"hideViewControl":false,"initialLocation":"LAST_SAVED_LOCATION","fixedLocation":{"lat":0,"lon":0,"zoom":2},"browserLocation":{"zoom":2},"maxZoom":24,"minZoom":0,"showScaleControl":false,"showSpatialFilters":true,"showTimesliderToggleButton":true,"spatialFiltersAlpa":0.3,"spatialFiltersFillColor":"#DA8B45","spatialFiltersLineColor":"#DA8B45"}}',
+ title: '[Flights] Origin Time Delayed',
+ uiStateJSON: '{"isLayerTOCOpen":true,"openTOCDetails":[]}',
+ },
+ migrationVersion: {
+ map: '7.14.0',
+ },
references: [
{
+ id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
name: 'layer_1_source_index_pattern',
type: 'index-pattern',
- id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
},
{
- name: 'layer_2_source_index_pattern',
- type: 'index-pattern',
id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
- },
- {
- name: 'layer_3_source_index_pattern',
+ name: 'layer_2_source_index_pattern',
type: 'index-pattern',
- id: 'd3d7af60-4c81-11e8-b3d7-01146121b73d',
},
],
- migrationVersion: {
- map: '7.4.0',
- },
- attributes: {
- title: i18n.translate('xpack.maps.sampleData.flightaSpec.mapsTitle', {
- defaultMessage: '[Flights] Origin and Destination Flight Time',
- }),
- description: '',
- mapStateJSON:
- '{"zoom":3.14,"center":{"lon":-89.58746,"lat":38.38637},"timeFilters":{"from":"now-7d","to":"now"},"refreshConfig":{"isPaused":true,"interval":0},"query":{"query":"","language":"kuery"}}',
- layerListJSON: JSON.stringify(layerList),
- uiStateJSON: '{"isDarkMode":false}',
- bounds: {
- type: 'envelope',
- coordinates: [
- [-139.83779, 56.64828],
- [-39.33713, 14.04811],
- ],
- },
- },
},
];
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx
index 02566474512ee0..bb4b83f639beb2 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/metric_selection_summary.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import React, { FC, useContext, useEffect, useState } from 'react';
+import React, { FC, useContext, useEffect, useState, useMemo } from 'react';
import { EuiSpacer } from '@elastic/eui';
import { JobCreatorContext } from '../../../job_creator_context';
import { RareJobCreator } from '../../../../../common/job_creator';
@@ -17,14 +17,15 @@ import { RARE_DETECTOR_TYPE } from './rare_view';
import { DetectorDescription } from './detector_description';
const DTR_IDX = 0;
-interface Props {
- rareDetectorType: RARE_DETECTOR_TYPE;
-}
-export const RareDetectorsSummary: FC = ({ rareDetectorType }) => {
- const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext(
- JobCreatorContext
- );
+export const RareDetectorsSummary: FC = () => {
+ const {
+ jobCreator: jc,
+ chartLoader,
+ resultsLoader,
+ chartInterval,
+ jobCreatorUpdated,
+ } = useContext(JobCreatorContext);
const jobCreator = jc as RareJobCreator;
const [loadingData, setLoadingData] = useState(false);
@@ -32,6 +33,20 @@ export const RareDetectorsSummary: FC = ({ rareDetectorType }) => {
const [eventRateChartData, setEventRateChartData] = useState([]);
const [jobIsRunning, setJobIsRunning] = useState(false);
+ const rareDetectorType = useMemo(() => {
+ if (jobCreator.rareField !== null) {
+ if (jobCreator.populationField === null) {
+ return RARE_DETECTOR_TYPE.RARE;
+ } else {
+ return jobCreator.frequentlyRare
+ ? RARE_DETECTOR_TYPE.FREQ_RARE_POPULATION
+ : RARE_DETECTOR_TYPE.RARE_POPULATION;
+ }
+ } else {
+ return RARE_DETECTOR_TYPE.RARE;
+ }
+ }, [jobCreatorUpdated]);
+
function setResultsWrapper(results: Results) {
const anomalies = results.anomalies[DTR_IDX];
if (anomalies !== undefined) {
@@ -48,6 +63,7 @@ export const RareDetectorsSummary: FC = ({ rareDetectorType }) => {
const resultsSubscription = resultsLoader.subscribeToResults(setResultsWrapper);
jobCreator.subscribeToProgress(watchProgress);
loadChart();
+
return () => {
resultsSubscription.unsubscribe();
};
diff --git a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx
index d67cac8d0fc5c6..4d0ed3b58973d1 100644
--- a/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx
+++ b/x-pack/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/rare_view/rare_view.tsx
@@ -35,7 +35,7 @@ export const RareView: FC = ({ isActive, setCanProceed }) => {
}, [rareFieldValid, settingsValid]);
return isActive === false ? (
-
+
) : (
<>
+
+#### Defined in
+
+[rule_registry/server/alert_data_client/alerts_client.ts:66](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L66)
## Methods
@@ -101,7 +112,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:79](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L79)
+[rule_registry/server/alert_data_client/alerts_client.ts:87](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L87)
___
@@ -121,7 +132,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:115](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L115)
+[rule_registry/server/alert_data_client/alerts_client.ts:134](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L134)
___
@@ -142,7 +153,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:68](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L68)
+[rule_registry/server/alert_data_client/alerts_client.ts:76](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L76)
___
@@ -162,7 +173,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:219](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L219)
+[rule_registry/server/alert_data_client/alerts_client.ts:238](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L238)
___
@@ -188,4 +199,4 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:160](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L160)
+[rule_registry/server/alert_data_client/alerts_client.ts:179](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L179)
diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md
index 051a5affc03799..23c5b94ad0d522 100644
--- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md
+++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/constructoroptions.md
@@ -19,7 +19,7 @@
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:34](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L34)
+[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40)
___
@@ -29,7 +29,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:33](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L33)
+[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39)
___
@@ -39,7 +39,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:35](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L35)
+[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41)
___
@@ -49,4 +49,4 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:32](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L32)
+[rule_registry/server/alert_data_client/alerts_client.ts:38](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L38)
diff --git a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md
index 10e793155c1964..a8bff815c250fd 100644
--- a/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md
+++ b/x-pack/plugins/rule_registry/docs/alerts_client/interfaces/updateoptions.md
@@ -25,7 +25,7 @@
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:41](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L41)
+[rule_registry/server/alert_data_client/alerts_client.ts:47](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L47)
___
@@ -35,7 +35,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:39](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L39)
+[rule_registry/server/alert_data_client/alerts_client.ts:45](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L45)
___
@@ -45,7 +45,7 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:42](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L42)
+[rule_registry/server/alert_data_client/alerts_client.ts:48](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L48)
___
@@ -55,4 +55,4 @@ ___
#### Defined in
-[rule_registry/server/alert_data_client/alerts_client.ts:40](https://github.com/elastic/kibana/blob/f2a94addc85/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L40)
+[rule_registry/server/alert_data_client/alerts_client.ts:46](https://github.com/elastic/kibana/blob/48e1b91d751/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts#L46)
diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts
index 553c5ce4472a6c..e5b89cb86acf0e 100644
--- a/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts
+++ b/x-pack/plugins/rule_registry/server/alert_data_client/alerts_client.ts
@@ -6,6 +6,7 @@
*/
import { PublicMethodsOf } from '@kbn/utility-types';
import { decodeVersion, encodeHitVersion } from '@kbn/securitysolution-es-utils';
+
import { AlertTypeParams } from '../../../alerting/server';
import {
ReadOperations,
@@ -16,7 +17,12 @@ import {
import { Logger, ElasticsearchClient } from '../../../../../src/core/server';
import { alertAuditEvent, AlertAuditAction } from './audit_events';
import { AuditLogger } from '../../../security/server';
-import { ALERT_STATUS, OWNER, RULE_ID } from '../../common/technical_rule_data_field_names';
+import {
+ ALERT_STATUS,
+ OWNER,
+ RULE_ID,
+ SPACE_IDS,
+} from '../../common/technical_rule_data_field_names';
import { ParsedTechnicalFields } from '../../common/parse_technical_fields';
import { mapConsumerToIndexName, validFeatureIds, isValidFeatureId } from '../utils/rbac';
@@ -57,12 +63,14 @@ export class AlertsClient {
private readonly auditLogger?: AuditLogger;
private readonly authorization: PublicMethodsOf;
private readonly esClient: ElasticsearchClient;
+ private readonly spaceId: Promise;
constructor({ auditLogger, authorization, logger, esClient }: ConstructorOptions) {
this.logger = logger;
this.authorization = authorization;
this.esClient = esClient;
this.auditLogger = auditLogger;
+ this.spaceId = this.authorization.getSpaceId();
}
public async getAlertsIndex(
@@ -81,13 +89,24 @@ export class AlertsClient {
index,
}: GetAlertParams): Promise<(AlertType & { _version: string | undefined }) | null | undefined> {
try {
+ const alertSpaceId = await this.spaceId;
+ if (alertSpaceId == null) {
+ this.logger.error('Failed to acquire spaceId from authorization client');
+ return;
+ }
const result = await this.esClient.search({
// Context: Originally thought of always just searching `.alerts-*` but that could
// result in a big performance hit. If the client already knows which index the alert
// belongs to, passing in the index will speed things up
index: index ?? '.alerts-*',
ignore_unavailable: true,
- body: { query: { term: { _id: id } } },
+ body: {
+ query: {
+ bool: {
+ filter: [{ term: { _id: id } }, { term: { [SPACE_IDS]: alertSpaceId } }],
+ },
+ },
+ },
seq_no_primary_term: true,
});
diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts
index 897c17a82b9821..bf5375c55c04b4 100644
--- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts
+++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/get.test.ts
@@ -27,6 +27,7 @@ const alertsClientParams: jest.Mocked = {
beforeEach(() => {
jest.resetAllMocks();
+ alertingAuthMock.getSpaceId.mockImplementation(() => Promise.resolve('test_default_space_id'));
});
describe('get()', () => {
@@ -60,6 +61,7 @@ describe('get()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -72,6 +74,9 @@ describe('get()', () => {
Object {
"_version": "WzM2MiwyXQ==",
"kibana.rac.alert.owner": "apm",
+ "kibana.rac.alert.space_ids": Array [
+ "test_default_space_id",
+ ],
"kibana.rac.alert.status": "open",
"message": "hello world 1",
"rule.id": "apm.error_rate",
@@ -83,8 +88,19 @@ describe('get()', () => {
Object {
"body": Object {
"query": Object {
- "term": Object {
- "_id": "1",
+ "bool": Object {
+ "filter": Array [
+ Object {
+ "term": Object {
+ "_id": "1",
+ },
+ },
+ Object {
+ "term": Object {
+ "kibana.space_ids": "test_default_space_id",
+ },
+ },
+ ],
},
},
},
@@ -126,6 +142,7 @@ describe('get()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -187,6 +204,7 @@ describe('get()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -210,6 +228,9 @@ describe('get()', () => {
Object {
"_version": "WzM2MiwyXQ==",
"kibana.rac.alert.owner": "apm",
+ "kibana.rac.alert.space_ids": Array [
+ "test_default_space_id",
+ ],
"kibana.rac.alert.status": "open",
"message": "hello world 1",
"rule.id": "apm.error_rate",
diff --git a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts
index 6fc387fe54b3b4..fd3e0f00a097bf 100644
--- a/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts
+++ b/x-pack/plugins/rule_registry/server/alert_data_client/tests/update.test.ts
@@ -27,6 +27,7 @@ const alertsClientParams: jest.Mocked = {
beforeEach(() => {
jest.resetAllMocks();
+ alertingAuthMock.getSpaceId.mockImplementation(() => Promise.resolve('test_default_space_id'));
});
describe('update()', () => {
@@ -57,6 +58,7 @@ describe('update()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -142,6 +144,7 @@ describe('update()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -234,6 +237,7 @@ describe('update()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
@@ -293,6 +297,7 @@ describe('update()', () => {
message: 'hello world 1',
'kibana.rac.alert.owner': 'apm',
'kibana.rac.alert.status': 'open',
+ 'kibana.rac.alert.space_ids': ['test_default_space_id'],
},
},
],
diff --git a/x-pack/plugins/rule_registry/server/index.ts b/x-pack/plugins/rule_registry/server/index.ts
index 19ea85b056bedf..6b0765e71cbada 100644
--- a/x-pack/plugins/rule_registry/server/index.ts
+++ b/x-pack/plugins/rule_registry/server/index.ts
@@ -14,13 +14,17 @@ export type { RacRequestHandlerContext, RacApiRequestHandlerContext } from './ty
export { RuleDataClient } from './rule_data_client';
export { IRuleDataClient } from './rule_data_client/types';
export { getRuleExecutorData, RuleExecutorData } from './utils/get_rule_executor_data';
-export { createLifecycleRuleTypeFactory } from './utils/create_lifecycle_rule_type_factory';
+export {
+ createLifecycleRuleTypeFactory,
+ LifecycleAlertService,
+} from './utils/create_lifecycle_rule_type_factory';
export {
LifecycleRuleExecutor,
LifecycleAlertServices,
createLifecycleExecutor,
} from './utils/create_lifecycle_executor';
export { createPersistenceRuleTypeFactory } from './utils/create_persistence_rule_type_factory';
+export type { AlertTypeWithExecutor } from './types';
export const plugin = (initContext: PluginInitializerContext) =>
new RuleRegistryPlugin(initContext);
diff --git a/x-pack/plugins/rule_registry/server/scripts/observer/detections_role.json b/x-pack/plugins/rule_registry/server/scripts/observer/detections_role.json
index dd3d3f96e3a33b..ee4a1d84f0b7e2 100644
--- a/x-pack/plugins/rule_registry/server/scripts/observer/detections_role.json
+++ b/x-pack/plugins/rule_registry/server/scripts/observer/detections_role.json
@@ -6,13 +6,7 @@
"kibana": [
{
"feature": {
- "ml": ["read"],
- "monitoring": ["all"],
- "apm": ["minimal_read", "alerts_all"],
- "ruleRegistry": ["all"],
- "actions": ["read"],
- "builtInAlerts": ["all"],
- "alerting": ["all"]
+ "apm": ["read", "alerts_all"]
},
"spaces": ["*"]
}
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 06c2cc8ff005db..8df343fb16d434 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
@@ -31,6 +31,7 @@ import {
OWNER,
RULE_UUID,
TIMESTAMP,
+ SPACE_IDS,
} from '../../common/technical_rule_data_field_names';
import { RuleDataClient } from '../rule_data_client';
import { AlertExecutorOptionsWithExtraServices } from '../types';
@@ -124,6 +125,7 @@ export const createLifecycleExecutor = (logger: Logger, ruleDataClient: RuleData
rule,
services: { alertInstanceFactory },
state: previousState,
+ spaceId,
} = options;
const ruleExecutorData = getRuleData(options);
@@ -258,6 +260,15 @@ export const createLifecycleExecutor = (logger: Logger, ruleDataClient: RuleData
event[ALERT_START] = started;
event[ALERT_UUID] = alertUuid;
+ // not sure why typescript needs the non-null assertion here
+ // we already assert the value is not undefined with the ternary
+ // still getting an error with the ternary.. strange.
+
+ event[SPACE_IDS] =
+ event[SPACE_IDS] == null
+ ? [spaceId]
+ : [spaceId, ...event[SPACE_IDS]!.filter((sid) => sid !== spaceId)];
+
if (isNew) {
event[EVENT_ACTION] = 'open';
}
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 3e7fbbe5cbc59f..e26e5b00435f8b 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
@@ -198,6 +198,9 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
+ "kibana.space_ids": Array [
+ "spaceId",
+ ],
"rule.category": "ruleTypeName",
"rule.id": "ruleTypeId",
"rule.name": "name",
@@ -217,6 +220,9 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
+ "kibana.space_ids": Array [
+ "spaceId",
+ ],
"rule.category": "ruleTypeName",
"rule.id": "ruleTypeId",
"rule.name": "name",
@@ -236,6 +242,9 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
+ "kibana.space_ids": Array [
+ "spaceId",
+ ],
"rule.category": "ruleTypeName",
"rule.id": "ruleTypeId",
"rule.name": "name",
@@ -255,6 +264,9 @@ describe('createLifecycleRuleTypeFactory', () => {
"kibana.rac.alert.producer": "producer",
"kibana.rac.alert.start": "2021-06-16T09:01:00.000Z",
"kibana.rac.alert.status": "open",
+ "kibana.space_ids": Array [
+ "spaceId",
+ ],
"rule.category": "ruleTypeName",
"rule.id": "ruleTypeId",
"rule.name": "name",
diff --git a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
index cf1be1bd32013b..783077a1f68ab0 100644
--- a/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
+++ b/x-pack/plugins/rule_registry/server/utils/create_lifecycle_rule_type_factory.ts
@@ -16,10 +16,13 @@ import { AlertInstance } from '../../../alerting/server';
import { AlertTypeWithExecutor } from '../types';
import { createLifecycleExecutor } from './create_lifecycle_executor';
-export type LifecycleAlertService> = (alert: {
+export type LifecycleAlertService<
+ TAlertInstanceContext extends Record,
+ TActionGroupIds extends string = string
+> = (alert: {
id: string;
fields: Record;
-}) => AlertInstance;
+}) => AlertInstance;
export const createLifecycleRuleTypeFactory = ({
logger,
diff --git a/x-pack/plugins/rule_registry/server/utils/rbac.ts b/x-pack/plugins/rule_registry/server/utils/rbac.ts
index e07c4394be2f10..172201400606a1 100644
--- a/x-pack/plugins/rule_registry/server/utils/rbac.ts
+++ b/x-pack/plugins/rule_registry/server/utils/rbac.ts
@@ -19,6 +19,7 @@ export const mapConsumerToIndexName = {
infrastructure: '.alerts-observability.metrics',
observability: '.alerts-observability',
siem: ['.alerts-security.alerts', '.siem-signals'],
+ synthetics: '.alerts-observability-synthetics',
};
export type ValidFeatureId = keyof typeof mapConsumerToIndexName;
diff --git a/x-pack/plugins/security/server/config_deprecations.test.ts b/x-pack/plugins/security/server/config_deprecations.test.ts
index d2c75fd2331b99..bd8b21358e3e84 100644
--- a/x-pack/plugins/security/server/config_deprecations.test.ts
+++ b/x-pack/plugins/security/server/config_deprecations.test.ts
@@ -165,6 +165,68 @@ describe('Config Deprecations', () => {
`);
});
+ it('warns when using the legacy audit logger', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ enabled: true,
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender).not.toBeDefined();
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.",
+ ]
+ `);
+ });
+
+ it('does not warn when using the ECS audit logger', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ enabled: true,
+ appender: {
+ type: 'file',
+ fileName: './audit.log',
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated).toEqual(config);
+ expect(messages).toHaveLength(0);
+ });
+
+ it('does not warn about using the legacy logger when using the ECS audit logger, even when using the deprecated ECS appender config', () => {
+ const config = {
+ xpack: {
+ security: {
+ audit: {
+ enabled: true,
+ appender: {
+ type: 'file',
+ path: './audit.log',
+ },
+ },
+ },
+ },
+ };
+ const { messages, migrated } = applyConfigDeprecations(cloneDeep(config));
+ expect(migrated.xpack.security.audit.appender.path).not.toBeDefined();
+ expect(migrated.xpack.security.audit.appender.fileName).toEqual('./audit.log');
+ expect(messages).toMatchInlineSnapshot(`
+ Array [
+ "\\"xpack.security.audit.appender.path\\" is deprecated and has been replaced by \\"xpack.security.audit.appender.fileName\\"",
+ ]
+ `);
+ });
+
it(`warns that 'authorization.legacyFallback.enabled' is unused`, () => {
const config = {
xpack: {
diff --git a/x-pack/plugins/security/server/config_deprecations.ts b/x-pack/plugins/security/server/config_deprecations.ts
index 7b659ec1c350d5..808a192f832069 100644
--- a/x-pack/plugins/security/server/config_deprecations.ts
+++ b/x-pack/plugins/security/server/config_deprecations.ts
@@ -21,6 +21,23 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
unused('authorization.legacyFallback.enabled'),
unused('authc.saml.maxRedirectURLSize'),
+ // Deprecation warning for the legacy audit logger.
+ (settings, fromPath, addDeprecation) => {
+ const auditLoggingEnabled = settings?.xpack?.security?.audit?.enabled ?? false;
+ const legacyAuditLoggerEnabled = !settings?.xpack?.security?.audit?.appender;
+ if (auditLoggingEnabled && legacyAuditLoggerEnabled) {
+ addDeprecation({
+ message: `The legacy audit logger is deprecated in favor of the new ECS-compliant audit logger.`,
+ documentationUrl:
+ 'https://www.elastic.co/guide/en/kibana/current/security-settings-kb.html#audit-logging-settings',
+ correctiveActions: {
+ manualSteps: [
+ `Declare an audit logger "appender" via "xpack.security.audit.appender" to enable the ECS audit logger.`,
+ ],
+ },
+ });
+ }
+ },
// Deprecation warning for the old array-based format of `xpack.security.authc.providers`.
(settings, fromPath, addDeprecation) => {
if (Array.isArray(settings?.xpack?.security?.authc?.providers)) {
diff --git a/x-pack/plugins/security_solution/cypress/README.md b/x-pack/plugins/security_solution/cypress/README.md
index 1b486ca3a5fcdf..1bc4a47d6d9c66 100644
--- a/x-pack/plugins/security_solution/cypress/README.md
+++ b/x-pack/plugins/security_solution/cypress/README.md
@@ -27,6 +27,77 @@ This is the configuration used by CI. It uses the FTR to spawn both a Kibana ins
This configuration runs cypress tests against an arbitrary host.
**WARNING**: When using your own instances you need to take into account that if you already have data on it, the tests may fail, as well as, they can put your instances in an undesired state, since our tests uses es_archive to populate data.
+#### integration-test (CI)
+
+This configuration is driven by [elastic/integration-test](https://github.com/elastic/integration-test) which, as part of a bigger set of tests, provisions one VM with two instances configured in CCS mode and runs the [CCS Cypress test specs](./ccs_integration).
+
+The two clusters are named `admin` and `data` and are reachable as follows:
+
+| | Elasticsearch | Kibana |
+|-------|------------------------|------------------------|
+| admin | https://localhost:9200 | https://localhost:5601 |
+| data | https://localhost:9210 | https://localhost:5602 |
+
+### Working with integration-test
+
+#### Initial setup and prerequisites
+
+The entry point is [integration-test/jenkins_test.sh](https://github.com/elastic/integration-test/blob/master/jenkins_test.sh), it essentially prepares the VMs and there runs tests. Some snapshots (`phase1` and `phase2`) are taken along the way so that it's possible to short cut the VM preparation when iterating over tests for development or debugging.
+
+The VMs are managed with Vagrant using the VirtualBox provider therefore you need to install both these tools. The host OS can be either Windows, Linux or MacOS.
+
+`jenkins_test.sh` assumes that a `kibana` folder is present alongside the `integration-test` where it's executed from. The `kibana` folder is used only for loading the test suites though, the actual packages for the VMs preparation are downloaded from elastic.co according to the `BUILD` environment variable or the branch which `jenkins_test.sh` is invoked from. It's your responsibility to checkout the matching branches in `kibana` and `integration-test` as needed.
+
+Read [integration-test#readme](https://github.com/elastic/integration-test#readme) for further details.
+
+#### Use cases
+
+There is no way to just set up the test environment without also executing tests at least once. On the other hand it's time consuming to go throught the whole CI procedure to just iterate over the tests therefore the following instructions support the two use cases:
+
+* reproduce e2e the CI execution locally, ie. for debugging a CI failure
+* use the CI script to easily setup the environment for tests development/debugging
+
+The missing use case, application TDD, requires a different solution that runs from the checked out repositories instead of the pre-built packages and it's yet to be developed.
+
+#### Run the CI flow
+
+This is the CI flow narrowed down to the execution of CCS Cypress tests:
+
+```shell
+cd integration-test
+VMS=ubuntu16_tar_ccs_cypress ./jenkins_test.sh
+```
+
+It destroys and rebuilds the VM. There installs, provisions and starts the stack according to the configuration in [integration-test/provision/ubuntu16_tar_ccs_cypress.sh](https://github.com/elastic/integration-test/blob/master/provision/ubuntu16_tar_ccs_cypress.sh).
+
+The tests are executed using the FTR runner `SecuritySolutionCypressCcsTestRunner` defined in [x-pack/test/security_solution_cypress/runner.ts](../../../test/security_solution_cypress/runner.ts) as configured in [x-pack/test/security_solution_cypress/ccs_config.ts](../../../test/security_solution_cypress/ccs_config.ts).
+
+#### Re-run the tests
+
+After the first run it's possible to restore the VM at `phase2`, right before tests were executed, and run them again:
+
+```shell
+cd integration-test
+MODE=retest ./jenkins_test.sh
+```
+
+It remembers which VM the first round was executed on, you don't need to specify `VMS` any more.
+
+In case your tests are cleaning after themselves and therefore result idempotent, you can skip the restoration to `phase2` and directly run the Cypress command line. See [CCS Custom Target + Headless](#ccs-custom-target--headless) further below for details but ensure you'll define the `CYPRESS_*` following the correspondence:
+
+| Cypress command line | [integration-test/provision/ubuntu16_tar_ccs_cypress.sh](https://github.com/elastic/integration-test/blob/master/provision/ubuntu16_tar_ccs_cypress.sh) |
+|--------------------------------|----------------------------------|
+| CYPRESS_BASE_URL | TEST_KIBANA_URL |
+| CYPRESS_ELASTICSEARCH_URL | TEST_ES_URL |
+| CYPRESS_CCS_KIBANA_URL | TEST_KIBANA_URLDATA |
+| CYPRESS_CCS_ELASTICSEARCH_URL | TEST_ES_URLDATA |
+| CYPRESS_CCS_REMOTE_NAME | TEST_CCS_REMOTE_NAME |
+| CYPRESS_ELASTICSEARCH_USERNAME | ELASTICSEARCH_USERNAME |
+| CYPRESS_ELASTICSEARCH_PASSWORD | ELASTICSEARCH_PASSWORD |
+| TEST_CA_CERT_PATH | integration-test/certs/ca/ca.crt |
+
+Note: `TEST_CA_CERT_PATH` above is truly without `CYPRESS_` prefix.
+
### Test Execution: Examples
#### FTR + Headless (Chrome)
@@ -149,7 +220,7 @@ Appending `--browser firefox` to the `yarn cypress:run:ccs` command above will r
### ccs_integration/
-Contains the specs that are executed in a Cross Cluster Search configuration, typically during integration tests.
+Contains the specs that are executed in a Cross Cluster Search configuration.
### integration/
diff --git a/x-pack/plugins/security_solution/cypress/tasks/login.ts b/x-pack/plugins/security_solution/cypress/tasks/login.ts
index 243bfd113bfd23..2eba6aba0c5e22 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/login.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/login.ts
@@ -205,6 +205,11 @@ const credentialsProvidedByEnvironment = (): boolean =>
* Kibana's `/internal/security/login` endpoint, bypassing the login page (for speed).
*/
const loginViaEnvironmentCredentials = () => {
+ const providerName =
+ Cypress.env('protocol') === 'http' || Cypress.config().baseUrl!.includes('staging')
+ ? 'basic'
+ : 'cloud-basic';
+
cy.log(
`Authenticating via environment credentials from the \`CYPRESS_${ELASTICSEARCH_USERNAME}\` and \`CYPRESS_${ELASTICSEARCH_PASSWORD}\` environment variables`
);
@@ -213,7 +218,7 @@ const loginViaEnvironmentCredentials = () => {
cy.request({
body: {
providerType: 'basic',
- providerName: 'basic',
+ providerName,
currentURL: '/',
params: {
username: Cypress.env(ELASTICSEARCH_USERNAME),
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx
index 819c666bd7267e..6e077f068a0d5f 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.test.tsx
@@ -16,21 +16,30 @@ jest.mock('../../../lib/kibana');
describe('NoEnrichmentsPanelView', () => {
it('renders a qualified container', () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(true);
});
it('renders nothing when all enrichments are present', () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').exists()).toEqual(false);
});
it('renders expected text when no enrichments are present', () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain(
i18n.NO_ENRICHMENTS_FOUND_TITLE
@@ -39,7 +48,10 @@ describe('NoEnrichmentsPanelView', () => {
it('renders expected text when existing enrichments are absent', () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain(
i18n.NO_INDICATOR_ENRICHMENTS_TITLE
@@ -48,7 +60,10 @@ describe('NoEnrichmentsPanelView', () => {
it('renders expected text when investigation enrichments are absent', () => {
const wrapper = mount(
-
+
);
expect(wrapper.find('[data-test-subj="no-enrichments-panel"]').hostNodes().text()).toContain(
i18n.NO_INVESTIGATION_ENRICHMENTS_TITLE
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx
index a26e74d12f40f9..1c419372aea30c 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/cti_details/no_enrichments_panel.tsx
@@ -35,9 +35,9 @@ const NoEnrichmentsPanelView: React.FC<{
NoEnrichmentsPanelView.displayName = 'NoEnrichmentsPanelView';
export const NoEnrichmentsPanel: React.FC<{
- existingEnrichmentsCount: number;
- investigationEnrichmentsCount: number;
-}> = ({ existingEnrichmentsCount, investigationEnrichmentsCount }) => {
+ isIndicatorMatchesPresent: boolean;
+ isInvestigationTimeEnrichmentsPresent: boolean;
+}> = ({ isIndicatorMatchesPresent, isInvestigationTimeEnrichmentsPresent }) => {
const threatIntelDocsUrl = `${
useKibana().services.docLinks.links.filebeat.base
}/filebeat-module-threatintel.html`;
@@ -50,7 +50,7 @@ export const NoEnrichmentsPanel: React.FC<{
>
);
- if (existingEnrichmentsCount === 0 && investigationEnrichmentsCount === 0) {
+ if (!isIndicatorMatchesPresent && !isInvestigationTimeEnrichmentsPresent) {
return (
{i18n.NO_ENRICHMENTS_FOUND_TITLE}}
@@ -61,7 +61,7 @@ export const NoEnrichmentsPanel: React.FC<{
}
/>
);
- } else if (existingEnrichmentsCount === 0) {
+ } else if (!isIndicatorMatchesPresent) {
return (
<>
@@ -75,7 +75,7 @@ export const NoEnrichmentsPanel: React.FC<{
/>
>
);
- } else if (investigationEnrichmentsCount === 0) {
+ } else if (!isInvestigationTimeEnrichmentsPresent) {
return (
<>
diff --git a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
index 7074212dcdb4c5..668c6ffb723aa0 100644
--- a/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/event_details/event_details.tsx
@@ -113,15 +113,14 @@ const EventDetailsComponent: React.FC = ({
loading: enrichmentsLoading,
result: enrichmentsResponse,
} = useInvestigationTimeEnrichment(eventFields);
- const investigationEnrichments = useMemo(() => enrichmentsResponse?.enrichments ?? [], [
- enrichmentsResponse?.enrichments,
- ]);
+
const allEnrichments = useMemo(() => {
if (enrichmentsLoading || !enrichmentsResponse?.enrichments) {
return existingEnrichments;
}
return filterDuplicateEnrichments([...existingEnrichments, ...enrichmentsResponse.enrichments]);
}, [enrichmentsLoading, enrichmentsResponse, existingEnrichments]);
+
const enrichmentCount = allEnrichments.length;
const summaryTab: EventViewTab | undefined = useMemo(
@@ -184,21 +183,16 @@ const EventDetailsComponent: React.FC = ({
<>
existingEnrichments.length
+ }
+ isIndicatorMatchesPresent={existingEnrichments.length > 0}
/>
>
),
}
: undefined,
- [
- allEnrichments,
- enrichmentCount,
- enrichmentsLoading,
- existingEnrichments.length,
- investigationEnrichments.length,
- isAlert,
- ]
+ [allEnrichments, enrichmentCount, enrichmentsLoading, existingEnrichments.length, isAlert]
);
const tableTab = useMemo(
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
index 32eb4baad50598..bf6a94c53b4771 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.test.tsx
@@ -26,8 +26,9 @@ import {
getProcessCodeSignature,
retrieveAlertOsTypes,
filterIndexPatterns,
+ getCodeSignatureValue,
} from './helpers';
-import { AlertData } from './types';
+import { AlertData, Flattened } from './types';
import {
ListOperatorTypeEnum as OperatorTypeEnum,
EntriesArray,
@@ -41,6 +42,7 @@ import { getCommentsArrayMock } from '../../../../../lists/common/schemas/types/
import { fields } from '../../../../../../../src/plugins/data/common/index_patterns/fields/fields.mocks';
import { ENTRIES, OLD_DATE_RELATIVE_TO_DATE_NOW } from '../../../../../lists/common/constants.mock';
import { IFieldType, IIndexPattern } from 'src/plugins/data/common';
+import { CodeSignature } from '../../../../common/ecs/file';
jest.mock('uuid', () => ({
v4: jest.fn().mockReturnValue('123'),
@@ -340,6 +342,17 @@ describe('Exception helpers', () => {
});
});
+ describe('#getCodeSignatureValue', () => {
+ test('it should return empty string if code_signature nested value are undefined', () => {
+ // Using the unsafe casting because with our types this shouldn't be possible but there have been issues with old data having undefined values in these fields
+ const payload = ([{ trusted: undefined, subject_name: undefined }] as unknown) as Flattened<
+ CodeSignature[]
+ >;
+ const result = getCodeSignatureValue(payload);
+ expect(result).toEqual([{ trusted: '', subjectName: '' }]);
+ });
+ });
+
describe('#entryHasNonEcsType', () => {
const mockEcsIndexPattern = {
title: 'testIndex',
diff --git a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
index 3c8652637a997f..f8260062f69741 100644
--- a/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
+++ b/x-pack/plugins/security_solution/public/common/components/exceptions/helpers.tsx
@@ -325,8 +325,8 @@ export const getCodeSignatureValue = (
if (Array.isArray(codeSignature) && codeSignature.length > 0) {
return codeSignature.map((signature) => {
return {
- subjectName: signature.subject_name ?? '',
- trusted: signature.trusted.toString() ?? '',
+ subjectName: signature?.subject_name ?? '',
+ trusted: signature?.trusted?.toString() ?? '',
};
});
} else {
diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx
new file mode 100644
index 00000000000000..fba9dd73460041
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/actions/show_top_n.tsx
@@ -0,0 +1,85 @@
+/*
+ * 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 { EuiButtonIcon, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { StatefulTopN } from '../../top_n';
+import { TimelineId } from '../../../../../common/types/timeline';
+import { SourcererScopeName } from '../../../store/sourcerer/model';
+import { useSourcererScope } from '../../../containers/sourcerer';
+import { TooltipWithKeyboardShortcut } from '../../accessibility';
+import { getAdditionalScreenReaderOnlyContext } from '../utils';
+import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from '../keyboard_shortcut_constants';
+
+const SHOW_TOP = (fieldName: string) =>
+ i18n.translate('xpack.securitySolution.hoverActions.showTopTooltip', {
+ values: { fieldName },
+ defaultMessage: `Show top {fieldName}`,
+ });
+
+interface Props {
+ field: string;
+ onClick: () => void;
+ onFilterAdded?: () => void;
+ ownFocus: boolean;
+ showTopN: boolean;
+ timelineId?: string | null;
+ value?: string[] | string | null;
+}
+
+export const ShowTopNButton: React.FC = React.memo(
+ ({ field, onClick, onFilterAdded, ownFocus, showTopN, timelineId, value }) => {
+ const activeScope: SourcererScopeName =
+ timelineId === TimelineId.active
+ ? SourcererScopeName.timeline
+ : timelineId != null &&
+ [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
+ timelineId as TimelineId
+ )
+ ? SourcererScopeName.detections
+ : SourcererScopeName.default;
+ const { browserFields, indexPattern } = useSourcererScope(activeScope);
+
+ return showTopN ? (
+
+ ) : (
+
+ }
+ >
+
+
+ );
+ }
+);
+
+ShowTopNButton.displayName = 'ShowTopNButton';
diff --git a/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx
new file mode 100644
index 00000000000000..bd5d78fd4e85fc
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/components/hover_actions/index.tsx
@@ -0,0 +1,349 @@
+/*
+ * 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 { EuiFocusTrap, EuiScreenReaderOnly } from '@elastic/eui';
+import React, { useCallback, useEffect, useRef, useMemo } from 'react';
+import { DraggableId } from 'react-beautiful-dnd';
+import styled from 'styled-components';
+import { i18n } from '@kbn/i18n';
+import { getAllFieldsByName } from '../../containers/source';
+import { COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME } from '../../lib/clipboard/clipboard';
+import { useKibana } from '../../lib/kibana';
+import { allowTopN } from './utils';
+import { useDeepEqualSelector } from '../../hooks/use_selector';
+import { ColumnHeaderOptions, TimelineId } from '../../../../common/types/timeline';
+import { SourcererScopeName } from '../../store/sourcerer/model';
+import { useSourcererScope } from '../../containers/sourcerer';
+import { timelineSelectors } from '../../../timelines/store/timeline';
+import { stopPropagationAndPreventDefault } from '../../../../../timelines/public';
+import { ShowTopNButton } from './actions/show_top_n';
+import { SHOW_TOP_N_KEYBOARD_SHORTCUT } from './keyboard_shortcut_constants';
+
+export const YOU_ARE_IN_A_DIALOG_CONTAINING_OPTIONS = (fieldName: string) =>
+ i18n.translate(
+ 'xpack.securitySolution.dragAndDrop.youAreInADialogContainingOptionsScreenReaderOnly',
+ {
+ values: { fieldName },
+ defaultMessage: `You are in a dialog, containing options for field {fieldName}. Press tab to navigate options. Press escape to exit.`,
+ }
+ );
+
+export const AdditionalContent = styled.div`
+ padding: 2px;
+`;
+
+AdditionalContent.displayName = 'AdditionalContent';
+
+const StyledHoverActionsContainer = styled.div<{ $showTopN: boolean }>`
+ padding: ${(props) => (props.$showTopN ? 'none' : props.theme.eui.paddingSizes.s)};
+
+ &:focus-within {
+ .timelines__hoverActionButton,
+ .securitySolution__hoverActionButton {
+ opacity: 1;
+ }
+ }
+
+ &:hover {
+ .timelines__hoverActionButton,
+ .securitySolution__hoverActionButton {
+ opacity: 1;
+ }
+ }
+
+ .timelines__hoverActionButton,
+ .securitySolution__hoverActionButton {
+ // TODO: Using this logic from discover
+ /* @include euiBreakpoint('m', 'l', 'xl') {
+ opacity: 0;
+ } */
+ opacity: 0;
+
+ &:focus {
+ opacity: 1;
+ }
+ }
+`;
+
+interface Props {
+ additionalContent?: React.ReactNode;
+ dataType?: string;
+ draggableId?: DraggableId;
+ field: string;
+ isObjectArray: boolean;
+ goGetTimelineId?: (args: boolean) => void;
+ onFilterAdded?: () => void;
+ ownFocus: boolean;
+ showTopN: boolean;
+ timelineId?: string | null;
+ toggleColumn?: (column: ColumnHeaderOptions) => void;
+ toggleTopN: () => void;
+ value?: string[] | string | null;
+}
+
+/** Returns a value for the `disabled` prop of `EuiFocusTrap` */
+const isFocusTrapDisabled = ({
+ ownFocus,
+ showTopN,
+}: {
+ ownFocus: boolean;
+ showTopN: boolean;
+}): boolean => {
+ if (showTopN) {
+ return false; // we *always* want to trap focus when showing Top N
+ }
+
+ return !ownFocus;
+};
+
+export const HoverActions: React.FC = React.memo(
+ ({
+ additionalContent = null,
+ dataType,
+ draggableId,
+ field,
+ goGetTimelineId,
+ isObjectArray,
+ onFilterAdded,
+ ownFocus,
+ showTopN,
+ timelineId,
+ toggleColumn,
+ toggleTopN,
+ value,
+ }) => {
+ const kibana = useKibana();
+ const { timelines } = kibana.services;
+ // Common actions used by the alert table and alert flyout
+ const {
+ addToTimeline: {
+ AddToTimelineButton,
+ keyboardShortcut: addToTimelineKeyboardShortcut,
+ useGetHandleStartDragToTimeline,
+ },
+ columnToggle: {
+ ColumnToggleButton,
+ columnToggleFn,
+ keyboardShortcut: columnToggleKeyboardShortcut,
+ },
+ copy: { CopyButton, keyboardShortcut: copyKeyboardShortcut },
+ filterForValue: {
+ FilterForValueButton,
+ filterForValueFn,
+ keyboardShortcut: filterForValueKeyboardShortcut,
+ },
+ filterOutValue: {
+ FilterOutValueButton,
+ filterOutValueFn,
+ keyboardShortcut: filterOutValueKeyboardShortcut,
+ },
+ } = timelines.getHoverActions();
+
+ const filterManagerBackup = useMemo(() => kibana.services.data.query.filterManager, [
+ kibana.services.data.query.filterManager,
+ ]);
+ const getManageTimeline = useMemo(() => timelineSelectors.getManageTimelineById(), []);
+ const { filterManager: activeFilterMananager } = useDeepEqualSelector((state) =>
+ getManageTimeline(state, timelineId ?? '')
+ );
+ const filterManager = useMemo(
+ () => (timelineId === TimelineId.active ? activeFilterMananager : filterManagerBackup),
+ [timelineId, activeFilterMananager, filterManagerBackup]
+ );
+
+ // Regarding data from useManageTimeline:
+ // * `indexToAdd`, which enables the alerts index to be appended to
+ // the `indexPattern` returned by `useWithSource`, may only be populated when
+ // this component is rendered in the context of the active timeline. This
+ // behavior enables the 'All events' view by appending the alerts index
+ // to the index pattern.
+ const activeScope: SourcererScopeName =
+ timelineId === TimelineId.active
+ ? SourcererScopeName.timeline
+ : timelineId != null &&
+ [TimelineId.detectionsPage, TimelineId.detectionsRulesDetailsPage].includes(
+ timelineId as TimelineId
+ )
+ ? SourcererScopeName.detections
+ : SourcererScopeName.default;
+ const { browserFields } = useSourcererScope(activeScope);
+
+ const handleStartDragToTimeline = useGetHandleStartDragToTimeline({ draggableId, field });
+
+ const handleFilterForValue = useCallback(
+ () => filterForValueFn({ field, value, filterManager, onFilterAdded }),
+ [filterForValueFn, field, value, filterManager, onFilterAdded]
+ );
+
+ const handleFilterOutValue = useCallback(
+ () => filterOutValueFn({ field, value, filterManager, onFilterAdded }),
+ [filterOutValueFn, field, value, filterManager, onFilterAdded]
+ );
+
+ const handleToggleColumn = useCallback(
+ () => (toggleColumn ? columnToggleFn({ toggleColumn, field }) : null),
+ [columnToggleFn, field, toggleColumn]
+ );
+
+ const isInit = useRef(true);
+ const defaultFocusedButtonRef = useRef(null);
+ const panelRef = useRef(null);
+
+ useEffect(() => {
+ if (isInit.current && goGetTimelineId != null && timelineId == null) {
+ isInit.current = false;
+ goGetTimelineId(true);
+ }
+ }, [goGetTimelineId, timelineId]);
+
+ useEffect(() => {
+ if (ownFocus) {
+ setTimeout(() => {
+ defaultFocusedButtonRef.current?.focus();
+ }, 0);
+ }
+ }, [ownFocus]);
+
+ const onKeyDown = useCallback(
+ (keyboardEvent: React.KeyboardEvent) => {
+ if (!ownFocus) {
+ return;
+ }
+ switch (keyboardEvent.key) {
+ case addToTimelineKeyboardShortcut:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleStartDragToTimeline();
+ break;
+ case columnToggleKeyboardShortcut:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleToggleColumn();
+ break;
+ case copyKeyboardShortcut:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ const copyToClipboardButton = panelRef.current?.querySelector(
+ `.${COPY_TO_CLIPBOARD_BUTTON_CLASS_NAME}`
+ );
+ if (copyToClipboardButton != null) {
+ copyToClipboardButton.click();
+ }
+ break;
+ case filterForValueKeyboardShortcut:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleFilterForValue();
+ break;
+ case filterOutValueKeyboardShortcut:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ handleFilterOutValue();
+ break;
+ case SHOW_TOP_N_KEYBOARD_SHORTCUT:
+ stopPropagationAndPreventDefault(keyboardEvent);
+ toggleTopN();
+ break;
+ case 'Enter':
+ break;
+ case 'Escape':
+ stopPropagationAndPreventDefault(keyboardEvent);
+ break;
+ default:
+ break;
+ }
+ },
+
+ [
+ addToTimelineKeyboardShortcut,
+ columnToggleKeyboardShortcut,
+ copyKeyboardShortcut,
+ filterForValueKeyboardShortcut,
+ filterOutValueKeyboardShortcut,
+ handleFilterForValue,
+ handleFilterOutValue,
+ handleStartDragToTimeline,
+ handleToggleColumn,
+ ownFocus,
+ toggleTopN,
+ ]
+ );
+
+ const showFilters = !showTopN && value != null;
+
+ return (
+
+
+
+