- {layerDatasource?.renderLayerSettings && (
+ {DISPLAY_RANDOM_SAMPLING_SETTINGS && layerDatasource?.renderLayerSettings && (
{
+ // skip random sampling FTs until we figure out next steps
+ describe.skip('lens layer actions tests', () => {
it('should allow creation of lens xy chart', async () => {
await PageObjects.visualize.navigateToNewVisualization();
await PageObjects.visualize.clickVisType('lens');
From 296769a314163d339108788bbf6361891b2ecf6f Mon Sep 17 00:00:00 2001
From: Boris Kirov
Date: Mon, 14 Nov 2022 15:22:30 +0100
Subject: [PATCH 07/20] [APM] Small design uplifts of the Mobile APM overview
page (#144998)
## Summary
closes https://github.com/elastic/kibana/issues/144455
In this PR we've updated some of the initial overview experience for
Mobile APM services by:
- rearranging the panels on the Overview page for improved mobile and
APM experience
- fitting all the Most used widgets into one panel, and rearranging them
- adding a callout for feedback
- adding a technical preview badge
- update the Embeddable component visual and size
![image](https://user-images.githubusercontent.com/13353203/201143633-20b8adb4-e342-4d7f-8e87-d7b3f7e10121.png)
Related links:
https://github.com/elastic/apm-dev/issues/823
https://github.com/elastic/kibana/issues/143498
https://github.com/elastic/kibana/issues/143501
https://github.com/elastic/kibana/issues/143504
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Kate Patticha
---
.../service_overview_charts/filters/index.tsx | 6 +-
.../most_used_chart.test.tsx.snap | 4 +-
.../most_used_chart/get_lens_attributes.ts | 4 +-
.../most_used_chart/index.tsx | 9 +-
.../service_oveview_mobile_charts.tsx | 216 +++++++++++-------
.../templates/apm_service_template/index.tsx | 7 +
6 files changed, 161 insertions(+), 85 deletions(-)
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx
index 22a1544d2013f4..c74de9051224c7 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_charts/filters/index.tsx
@@ -72,7 +72,11 @@ export function MobileFilters({
{data.mobileFilters.map((filter) => {
return (
-
+
+
-
+
{title}
+
-
+
)}
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.serviceOverview.mobileCallOutLink',
+ {
+ defaultMessage: 'Give feedback',
+ }
+ )}
+
+ ),
+ }}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.apm.serviceOverview.mostUsedTitle', {
+ defaultMessage: 'Most used',
+ })}
+
+
+
+
+ {/* Device */}
+
+
+
+ {/* NCT */}
+
+
+
+
+
+ {/* OS version */}
+
+
+
+ {/* App version */}
+
+
+
+
+
+
+
+
@@ -156,82 +292,6 @@ export function ServiceOverviewMobileCharts({
-
-
-
-
-
-
-
-
-
- {/* Device */}
-
-
-
- {/* NCT */}
-
-
-
-
-
-
-
- {/* OS Version */}
-
-
-
- {/* App version */}
-
-
-
-
-
);
}
diff --git a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
index ff978bebe8fb9b..58d97d79163843 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/apm_service_template/index.tsx
@@ -84,6 +84,8 @@ function TemplateWithContext({
const tabs = useTabs({ selectedTab });
+ const { agentName } = useApmServiceContext();
+
useBreadcrumb(
() => ({
title,
@@ -117,6 +119,11 @@ function TemplateWithContext({
end={end}
/>
+ {isMobileAgentName(agentName) && (
+
+
+
+ )}
From c4aca1fc656792e0e1d62442e39d01efe4a2fc02 Mon Sep 17 00:00:00 2001
From: Shahzad
Date: Mon, 14 Nov 2022 15:28:43 +0100
Subject: [PATCH 08/20] [Synthetics] Test run details step tabs (#144966)
---
.../journey_step_image_popover.tsx | 20 ++-
.../common/screenshot/empty_image.tsx | 20 ++-
.../journey_step_screenshot_container.tsx | 5 +-
.../hooks/use_selected_location.tsx | 2 +-
.../monitor_summary/last_test_run.tsx | 7 +-
.../monitor_summary/monitor_details_panel.tsx | 5 +-
.../monitor_summary/test_runs_table.tsx | 7 +-
.../overview/monitor_detail_flyout.tsx | 4 +-
.../components/step_number_nav.tsx | 82 +++++++++++
.../step_screenshot_details.tsx | 41 ++++++
.../components/test_run_details/step_tabs.tsx | 131 ++++++++++++++++++
.../test_run_details/test_run_details.tsx | 49 ++++++-
.../apps/synthetics/hooks/use_url_params.ts | 14 +-
13 files changed, 351 insertions(+), 36 deletions(-)
create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_number_nav.tsx
create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx
create mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_tabs.tsx
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx
index dda8c5343eb085..1e66d32578ad2e 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/monitor_test_result/journey_step_image_popover.tsx
@@ -9,13 +9,11 @@ import React from 'react';
import { css } from '@emotion/react';
import { EuiImage, EuiPopover, useEuiTheme } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
-import { useRouteMatch } from 'react-router-dom';
import { EmptyImage } from '../screenshot/empty_image';
import { ScreenshotRefImageData } from '../../../../../../common/runtime_types';
import { useCompositeImage } from '../../../hooks/use_composite_image';
import { EmptyThumbnail, thumbnailStyle } from './empty_thumbnail';
-import { STEP_DETAIL_ROUTE } from '../../../../../../common/constants';
const POPOVER_IMG_HEIGHT = 360;
const POPOVER_IMG_WIDTH = 640;
@@ -26,6 +24,7 @@ interface ScreenshotImageProps {
isStepFailed: boolean;
isLoading: boolean;
asThumbnail?: boolean;
+ size?: 'm';
}
const ScreenshotThumbnail: React.FC = ({
@@ -35,6 +34,7 @@ const ScreenshotThumbnail: React.FC {
return imageData ? (
) : asThumbnail ? (
) : (
-
+
);
};
/**
@@ -72,6 +72,7 @@ const RecomposedScreenshotImage: React.FC<
isStepFailed,
isLoading,
asThumbnail,
+ size,
}) => {
// initially an undefined URL value is passed to the image display, and a loading spinner is rendered.
// `useCompositeImage` will call `setImageData` when the image is composited, and the updated `imageData` will display.
@@ -85,6 +86,7 @@ const RecomposedScreenshotImage: React.FC<
isStepFailed={isStepFailed}
isLoading={isLoading}
asThumbnail={asThumbnail}
+ size={size}
/>
);
};
@@ -98,6 +100,7 @@ export interface StepImagePopoverProps {
isStepFailed: boolean;
isLoading: boolean;
asThumbnail?: boolean;
+ size?: 'm';
}
const JourneyStepImage: React.FC<
@@ -115,6 +118,7 @@ const JourneyStepImage: React.FC<
isStepFailed,
isLoading,
asThumbnail = true,
+ size,
}) => {
if (imgSrc) {
return (
@@ -125,6 +129,7 @@ const JourneyStepImage: React.FC<
isStepFailed={isStepFailed}
isLoading={isLoading}
asThumbnail={asThumbnail}
+ size={size}
/>
);
} else if (imgRef) {
@@ -138,6 +143,7 @@ const JourneyStepImage: React.FC<
isStepFailed={isStepFailed}
isLoading={isLoading}
asThumbnail={asThumbnail}
+ size={size}
/>
);
}
@@ -153,6 +159,7 @@ export const JourneyStepImagePopover: React.FC = ({
isStepFailed,
isLoading,
asThumbnail = true,
+ size,
}) => {
const { euiTheme } = useEuiTheme();
@@ -172,9 +179,7 @@ export const JourneyStepImagePopover: React.FC = ({
const isImageLoading = isLoading || (!!imgRef && !imageData);
- const isStepDetailPage = useRouteMatch(STEP_DETAIL_ROUTE)?.isExact;
-
- const thumbnailS = isStepDetailPage ? null : thumbnailStyle;
+ const thumbnailS = asThumbnail ? thumbnailStyle : null;
return (
= ({
isStepFailed={isStepFailed}
isLoading={isImageLoading}
asThumbnail={asThumbnail}
+ size={size}
/>
}
isOpen={isImagePopoverOpen}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx
index 57295b3e1daf23..d3c22311099271 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/common/screenshot/empty_image.tsx
@@ -19,6 +19,9 @@ import {
export const IMAGE_WIDTH = 360;
export const IMAGE_HEIGHT = 203;
+export const IMAGE_WIDTH_M = 200;
+export const IMAGE_HEIGHT_M = 114;
+
export const imageStyle = css`
padding: 0;
margin: auto;
@@ -31,17 +34,12 @@ export const imageStyle = css`
justify-content: center;
`;
-export const EmptyImage = ({
- isLoading = false,
- width = IMAGE_WIDTH,
- height = IMAGE_HEIGHT,
-}: {
- isLoading: boolean;
- width?: number;
- height?: number;
-}) => {
+export const EmptyImage = ({ size, isLoading = false }: { isLoading: boolean; size?: 'm' }) => {
const { euiTheme } = useEuiTheme();
+ const imgWidth = size === 'm' ? IMAGE_WIDTH_M : IMAGE_WIDTH;
+ const imgHeight = size === 'm' ? IMAGE_HEIGHT_M : IMAGE_HEIGHT;
+
return (
{
const [stepNumber, setStepNumber] = useState(initialStepNo);
const [isImagePopoverOpen, setIsImagePopoverOpen] = useState(false);
@@ -139,11 +141,12 @@ export const JourneyStepScreenshotContainer = ({
isStepFailed={stepStatus === 'failed'}
isLoading={Boolean(loading)}
asThumbnail={asThumbnail}
+ size={size}
/>
) : asThumbnail ? (
) : (
-
+
)}
);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx
index 779b8d8001ad55..30a522147cf4e6 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/hooks/use_selected_location.tsx
@@ -22,7 +22,7 @@ export const useSelectedLocation = () => {
if (!urlLocationId) {
const firstLocationId = locations?.[0]?.id;
if (firstLocationId) {
- updateUrlParams({ locationId: firstLocationId });
+ updateUrlParams({ locationId: firstLocationId }, true);
}
}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx
index 9fd35bc60cd0d9..c2e8e9691f1eb1 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/last_test_run.tsx
@@ -21,6 +21,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
+import { useParams } from 'react-router-dom';
import {
ConfigKey,
DataStream,
@@ -102,6 +103,8 @@ const PanelHeader = ({
const { basePath } = useSyntheticsSettingsContext();
+ const { monitorId } = useParams<{ monitorId: string }>();
+
const format = useKibanaDateFormat();
const lastRunTimestamp = useMemo(
@@ -160,9 +163,7 @@ const PanelHeader = ({
size="xs"
iconType="inspect"
iconSide="left"
- href={`${basePath}/app/uptime/journey/${
- latestPing?.monitor?.check_group ?? ''
- }/steps`}
+ href={`${basePath}/app/synthetics/monitor/${monitorId}/test-run/${latestPing?.monitor.check_group}`}
>
{i18n.translate('xpack.synthetics.monitorDetails.summary.viewTestRun', {
defaultMessage: 'View test run',
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx
index 80db9f667f5d7c..fb12c51f7eb537 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/monitor_details_panel.tsx
@@ -23,6 +23,7 @@ import { capitalize } from 'lodash';
import { i18n } from '@kbn/i18n';
import { useDispatch } from 'react-redux';
import { useParams } from 'react-router-dom';
+import { frequencyStr } from '../../monitors_page/overview/overview/monitor_detail_flyout';
import { useSelectedMonitor } from '../hooks/use_selected_monitor';
import { MonitorTags } from './monitor_tags';
import { MonitorEnabled } from '../../monitors_page/management/monitor_list_table/monitor_enabled';
@@ -81,7 +82,9 @@ export const MonitorDetailsPanel = () => {
{capitalize(monitor?.type)}
{FREQUENCY_LABEL}
- Every 10 mins
+
+ {monitor && frequencyStr(monitor[ConfigKey.SCHEDULE])}
+
{LOCATIONS_LABEL}
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx
index 6bb819be8d4484..19a7ebb33d7dbe 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_summary/test_runs_table.tsx
@@ -147,9 +147,12 @@ export const TestRunsTable = ({ paginable = true, from, to }: TestRunsTableProps
'data-test-subj': `row-${item.monitor.check_group}`,
onClick: (evt: MouseEvent) => {
const targetElem = evt.target as HTMLElement;
-
// we dont want to capture image click event
- if (targetElem.tagName !== 'IMG' && targetElem.tagName !== 'path') {
+ if (
+ targetElem.tagName !== 'IMG' &&
+ targetElem.tagName !== 'path' &&
+ !targetElem.parentElement?.classList.contains('euiLink')
+ ) {
history.push(`/monitor/${monitorId}/test-run/${item.monitor.check_group}`);
}
},
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx
index b448f98c3cfd5c..2904e4a9f858ee 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitors_page/overview/overview/monitor_detail_flyout.tsx
@@ -388,7 +388,7 @@ export function MonitorDetailFlyout(props: Props) {
},
{
title: FREQUENCY_HEADER_TEXT,
- description: freqeuncyStr(monitorSavedObject?.attributes[ConfigKey.SCHEDULE]),
+ description: frequencyStr(monitorSavedObject?.attributes[ConfigKey.SCHEDULE]),
},
monitorSavedObject?.attributes[ConfigKey.TAGS] &&
monitorSavedObject?.attributes[ConfigKey.TAGS].length
@@ -439,7 +439,7 @@ export function MonitorDetailFlyout(props: Props) {
);
}
-function freqeuncyStr(frequency: { number: string; unit: string }) {
+export function frequencyStr(frequency: { number: string; unit: string }) {
return translateUnitMessage(
`${frequency.number} ${unitToString(frequency.unit, parseInt(frequency.number, 10))}`
);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_number_nav.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_number_nav.tsx
new file mode 100644
index 00000000000000..254b1e62c96bff
--- /dev/null
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/components/step_number_nav.tsx
@@ -0,0 +1,82 @@
+/*
+ * 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 { i18n } from '@kbn/i18n';
+import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui';
+import { FormattedMessage } from '@kbn/i18n-react';
+
+interface Props {
+ stepIndex: number;
+ totalSteps: number;
+ handlePreviousStep: () => void;
+ handleNextStep: () => void;
+}
+
+export const StepNumberNav = ({
+ stepIndex,
+ totalSteps,
+ handleNextStep,
+ handlePreviousStep,
+}: Props) => {
+ const hasPreviousStep = stepIndex > 1;
+ const hasNextStep = stepIndex < totalSteps;
+
+ return (
+
+
+
+
+
+ {PREVIOUS_STEP_BUTTON_TEXT}
+
+
+
+
+
+
+
+
+
+ {NEXT_STEP_BUTTON_TEXT}
+
+
+
+
+
+ );
+};
+
+export const PREVIOUS_STEP_BUTTON_TEXT = i18n.translate(
+ 'xpack.synthetics.synthetics.stepDetail.previousStepButtonText',
+ {
+ defaultMessage: 'Previous',
+ }
+);
+
+export const NEXT_STEP_BUTTON_TEXT = i18n.translate(
+ 'xpack.synthetics.synthetics.stepDetail.nextStepButtonText',
+ {
+ defaultMessage: 'Next',
+ }
+);
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx
new file mode 100644
index 00000000000000..e5851191601d3c
--- /dev/null
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_screenshot_details.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui';
+import React from 'react';
+import { useTheme } from '@kbn/observability-plugin/public';
+import { useParams } from 'react-router-dom';
+import { JourneyStepScreenshotContainer } from '../common/screenshot/journey_step_screenshot_container';
+
+export const StepScreenshotDetails = ({ stepIndex }: { stepIndex: number }) => {
+ const { checkGroupId } = useParams<{ checkGroupId: string }>();
+
+ const theme = useTheme();
+ return (
+
+
+
+
+
+ {/* TODO: add image details*/}
+
+
+ );
+};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_tabs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_tabs.tsx
new file mode 100644
index 00000000000000..b7f884a7eee96d
--- /dev/null
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/step_tabs.tsx
@@ -0,0 +1,131 @@
+/*
+ * 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 { EuiCodeBlock, EuiLoadingContent, EuiTab, EuiTabs } from '@elastic/eui';
+import React, { useCallback, useEffect, useState } from 'react';
+import { i18n } from '@kbn/i18n';
+import { JourneyStep, SyntheticsJourneyApiResponse } from '../../../../../common/runtime_types';
+
+type TabId = 'code' | 'console' | 'stackTrace';
+
+export const StepTabs = ({
+ stepsData,
+ step,
+ loading,
+}: {
+ stepsData?: SyntheticsJourneyApiResponse;
+ step?: JourneyStep;
+ loading: boolean;
+}) => {
+ let tabs: Array<{ id: TabId; name: string }> = [
+ {
+ id: 'code',
+ name: CODE_EXECUTED,
+ },
+ {
+ id: 'console',
+ name: CONSOLE_LABEL,
+ },
+ ];
+
+ const isFailedStep = step?.synthetics.step?.status === 'failed';
+
+ if (isFailedStep) {
+ tabs = [
+ {
+ id: 'stackTrace',
+ name: STACKTRACE_LABEL,
+ },
+ ...tabs,
+ ];
+ }
+
+ const [selectedTabId, setSelectedTabId] = useState('code');
+
+ useEffect(() => {
+ if (isFailedStep) {
+ setSelectedTabId('stackTrace');
+ } else {
+ setSelectedTabId('code');
+ }
+ }, [isFailedStep]);
+
+ const onSelectedTabChanged = (id: TabId) => {
+ setSelectedTabId(id);
+ };
+
+ const renderTabs = () => {
+ return tabs.map((tab, index) => (
+ onSelectedTabChanged(tab.id)}
+ isSelected={tab.id === selectedTabId}
+ >
+ {tab.name}
+
+ ));
+ };
+
+ const renderTabContent = () => {
+ if (loading) {
+ return ;
+ }
+ switch (selectedTabId) {
+ case 'code':
+ return (
+
+ {step?.synthetics?.payload?.source}
+
+ );
+ case 'console':
+ return (
+
+ {step?.synthetics?.error?.stack}
+
+ );
+
+ default:
+ return (
+
+ {getBrowserConsoles(1)?.join('\n')}
+
+ );
+ }
+ };
+
+ const getBrowserConsoles = useCallback(
+ (index: number) => {
+ return stepsData?.steps
+ .filter(
+ (stepF) =>
+ stepF.synthetics?.type === 'journey/browserconsole' &&
+ stepF.synthetics?.step?.index! === index
+ )
+ .map((stepF) => stepF.synthetics?.payload?.text!);
+ },
+ [stepsData?.steps]
+ );
+
+ return (
+ <>
+ {renderTabs()}
+ {renderTabContent()}
+ >
+ );
+};
+
+const CODE_EXECUTED = i18n.translate('xpack.synthetics.testDetails.codeExecuted', {
+ defaultMessage: 'Code executed',
+});
+
+const STACKTRACE_LABEL = i18n.translate('xpack.synthetics.testDetails.stackTrace', {
+ defaultMessage: 'Stacktrace',
+});
+
+const CONSOLE_LABEL = i18n.translate('xpack.synthetics.testDetails.console', {
+ defaultMessage: 'Console',
+});
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx
index 5b3cf62d1b9956..4d109c2240e8e3 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/test_run_details/test_run_details.tsx
@@ -8,6 +8,10 @@
import React from 'react';
import { EuiFlexGroup, EuiFlexItem, EuiPanel, EuiSpacer, EuiTitle } from '@elastic/eui';
import moment from 'moment';
+import { FormattedMessage } from '@kbn/i18n-react';
+import { StepNumberNav } from './components/step_number_nav';
+import { StepScreenshotDetails } from './step_screenshot_details';
+import { StepTabs } from './step_tabs';
import { MonitorDetailsPanel } from '../monitor_details/monitor_summary/monitor_details_panel';
import { useJourneySteps } from '../monitor_details/hooks/use_journey_steps';
import { StepDurationPanel } from '../monitor_details/monitor_summary/step_duration_panel';
@@ -15,20 +19,55 @@ import { TestRunSteps } from './test_run_steps';
import { useTestRunDetailsBreadcrumbs } from './hooks/use_test_run_details_breadcrumbs';
export const TestRunDetails = () => {
- const { data: stepsData, loading: stepsLoading } = useJourneySteps();
+ // Step index from starts at 1 in synthetics
+ const [stepIndex, setStepIndex] = React.useState(1);
+
+ const { data: stepsData, loading: stepsLoading, stepEnds } = useJourneySteps();
useTestRunDetailsBreadcrumbs([
{ text: stepsData ? moment(stepsData.details?.timestamp).format('LLL') : '' },
]);
+ const step = stepEnds.find((stepN) => stepN.synthetics?.step?.index === stepIndex);
+
+ const totalSteps = stepsLoading ? 1 : stepEnds.length;
+
return (
-
- {/* TODO: Add step detail panel*/}
- Step 1 of {stepsData?.steps.length}
-
+
+
+
+
+
+
+
+
+
+ {
+ setStepIndex(stepIndex + 1);
+ }}
+ handlePreviousStep={() => {
+ setStepIndex(stepIndex - 1);
+ }}
+ />
+
+
+
+
+
+
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts
index 572468b805de98..56d547361db2aa 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/hooks/use_url_params.ts
@@ -18,7 +18,8 @@ export type GetUrlParams = () => SyntheticsUrlParams;
export type UpdateUrlParams = (
updatedParams: {
[key: string]: string | number | boolean | undefined;
- } | null
+ } | null,
+ replaceState?: boolean
) => void;
export type SyntheticsUrlParamsHook = () => [GetUrlParams, UpdateUrlParams];
@@ -34,7 +35,7 @@ export const useUrlParams: SyntheticsUrlParamsHook = () => {
const history = useHistory();
const updateUrlParams: UpdateUrlParams = useCallback(
- (updatedParams, clearAllParams = false) => {
+ (updatedParams, replaceState = false) => {
const currentParams = getParsedParams(search);
const mergedParams = {
...currentParams,
@@ -60,7 +61,14 @@ export const useUrlParams: SyntheticsUrlParamsHook = () => {
// only update the URL if the search has actually changed
if (search !== updatedSearch) {
- history.push({ pathname, search: updatedSearch || undefined });
+ if (replaceState) {
+ history.replace({
+ pathname,
+ search: updatedSearch || undefined,
+ });
+ } else {
+ history.push({ pathname, search: updatedSearch || undefined });
+ }
}
},
[history, pathname, search]
From e5b27b36bd1790f60400c41eff10cc7785240069 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Mike=20C=C3=B4t=C3=A9?=
Date: Mon, 14 Nov 2022 09:32:41 -0500
Subject: [PATCH 09/20] Allow task manager health stats to be logged as info
messages (#144986)
In this PR, I'm adding a new setting
(`xpack.task_manager.monitored_stats_health_verbose_log.level`) that
allows the task manager monitoring stats to be verbosely logged at info
level instead of warning.
The two supported values are:
- debug (default)
- info
This will help debug SDHs on Cloud where we won't want to turn on debug
level on the entire cluster but would still like to see the task manager
monitored stats over time.
## Cloud allow-list PR
https://github.com/elastic/cloud/pull/109563
## To verify
1. Set the following two configuration options:
```
xpack.task_manager.monitored_stats_health_verbose_log.enabled: true
xpack.task_manager.monitored_stats_health_verbose_log.level: info
```
2. Startup Kibana
3. Notice `Latest Monitored Stats:` are logged at info level
4. Remove `xpack.task_manager.monitored_stats_health_verbose_log.level`
configuration
5. Add the following configuration
```
logging:
loggers:
- name: plugins.taskManager
level: debug
```
6. Restart Kibana
7. Notice `Latest Monitored Stats:` are logged at debug level (as usual)
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../task_manager/server/config.test.ts | 3 ++
x-pack/plugins/task_manager/server/config.ts | 3 ++
.../server/ephemeral_task_lifecycle.test.ts | 1 +
.../managed_configuration.test.ts | 1 +
.../server/lib/log_health_metrics.test.ts | 31 +++++++++++++++++++
.../server/lib/log_health_metrics.ts | 7 ++++-
.../configuration_statistics.test.ts | 1 +
.../monitoring_stats_stream.test.ts | 1 +
.../task_manager/server/plugin.test.ts | 1 +
.../server/polling_lifecycle.test.ts | 1 +
.../task_manager/server/routes/health.test.ts | 3 ++
11 files changed, 52 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts
index f5ba0a3bcee0a7..54e9562ec62fb3 100644
--- a/x-pack/plugins/task_manager/server/config.test.ts
+++ b/x-pack/plugins/task_manager/server/config.test.ts
@@ -26,6 +26,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_health_verbose_log": Object {
"enabled": false,
+ "level": "debug",
"warn_delayed_task_start_in_seconds": 60,
},
"monitored_stats_required_freshness": 4000,
@@ -76,6 +77,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_health_verbose_log": Object {
"enabled": false,
+ "level": "debug",
"warn_delayed_task_start_in_seconds": 60,
},
"monitored_stats_required_freshness": 4000,
@@ -124,6 +126,7 @@ describe('config validation', () => {
"monitored_aggregated_stats_refresh_rate": 60000,
"monitored_stats_health_verbose_log": Object {
"enabled": false,
+ "level": "debug",
"warn_delayed_task_start_in_seconds": 60,
},
"monitored_stats_required_freshness": 4000,
diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts
index f650ed093cee0c..40b915e8979a2d 100644
--- a/x-pack/plugins/task_manager/server/config.ts
+++ b/x-pack/plugins/task_manager/server/config.ts
@@ -111,6 +111,9 @@ export const configSchema = schema.object(
}),
monitored_stats_health_verbose_log: schema.object({
enabled: schema.boolean({ defaultValue: false }),
+ level: schema.oneOf([schema.literal('debug'), schema.literal('info')], {
+ defaultValue: 'debug',
+ }),
/* The amount of seconds we allow a task to delay before printing a warning server log */
warn_delayed_task_start_in_seconds: schema.number({
defaultValue: DEFAULT_MONITORING_STATS_WARN_DELAYED_TASK_START_IN_SECONDS,
diff --git a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts
index f43bc0799ad73c..3ff7341faab4c9 100644
--- a/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts
+++ b/x-pack/plugins/task_manager/server/ephemeral_task_lifecycle.test.ts
@@ -52,6 +52,7 @@ describe('EphemeralTaskLifecycle', () => {
monitored_stats_running_average_window: 50,
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
monitored_task_execution_thresholds: {
diff --git a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
index aa945c74d71ffe..308aa9f556797f 100644
--- a/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
+++ b/x-pack/plugins/task_manager/server/integration_tests/managed_configuration.test.ts
@@ -47,6 +47,7 @@ describe('managed configuration', () => {
monitored_aggregated_stats_refresh_rate: 60000,
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug' as const,
warn_delayed_task_start_in_seconds: 60,
},
monitored_stats_required_freshness: 4000,
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
index 08c93f12ecad8e..0b8c0e5f42ccf3 100644
--- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.test.ts
@@ -31,6 +31,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -66,6 +67,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -88,6 +90,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -107,6 +110,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -120,11 +124,31 @@ describe('logHealthMetrics', () => {
expect(firstDebug).toMatchObject(health);
});
+ it('should log as info if status is OK and level is info', () => {
+ const logger = loggingSystemMock.create().get();
+ const config = getTaskManagerConfig({
+ monitored_stats_health_verbose_log: {
+ enabled: true,
+ level: 'info',
+ warn_delayed_task_start_in_seconds: 60,
+ },
+ });
+ const health = getMockMonitoredHealth();
+
+ logHealthMetrics(health, logger, config, true);
+
+ const firstInfo = JSON.parse(
+ (logger as jest.Mocked).info.mock.calls[0][0].replace('Latest Monitored Stats: ', '')
+ );
+ expect(firstInfo).toMatchObject(health);
+ });
+
it('should log as debug if status is OK even if not enabled', () => {
const logger = loggingSystemMock.create().get();
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -143,6 +167,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -168,6 +193,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -191,6 +217,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -234,6 +261,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -277,6 +305,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -301,6 +330,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
@@ -337,6 +367,7 @@ describe('logHealthMetrics', () => {
const config = getTaskManagerConfig({
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 60,
},
});
diff --git a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
index 55c25f6ce35776..86974c6ff747e4 100644
--- a/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
+++ b/x-pack/plugins/task_manager/server/lib/log_health_metrics.ts
@@ -15,6 +15,7 @@ import { MonitoredHealth } from '../routes/health';
import { calculateHealthStatus } from './calculate_health_status';
enum LogLevel {
+ Info = 'info',
Warn = 'warn',
Error = 'error',
Debug = 'debug',
@@ -30,7 +31,8 @@ export function logHealthMetrics(
config: TaskManagerConfig,
shouldRunTasks: boolean
) {
- let logLevel: LogLevel = LogLevel.Debug;
+ let logLevel: LogLevel =
+ config.monitored_stats_health_verbose_log.level === 'info' ? LogLevel.Info : LogLevel.Debug;
const enabled = config.monitored_stats_health_verbose_log.enabled;
const healthWithoutCapacity: MonitoredHealth = {
...monitoredHealth,
@@ -82,6 +84,9 @@ export function logHealthMetrics(
logLevel = LogLevel.Warn;
}
switch (logLevel) {
+ case LogLevel.Info:
+ logger.info(message);
+ break;
case LogLevel.Warn:
logger.warn(message);
break;
diff --git a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
index 776f5bc9388f7b..764b7aa15335b6 100644
--- a/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/configuration_statistics.test.ts
@@ -23,6 +23,7 @@ describe('Configuration Statistics Aggregator', () => {
monitored_aggregated_stats_refresh_rate: 5000,
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug' as const,
warn_delayed_task_start_in_seconds: 60,
},
monitored_stats_running_average_window: 50,
diff --git a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
index a6ef665966ddd7..610e0bf080b051 100644
--- a/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
+++ b/x-pack/plugins/task_manager/server/monitoring/monitoring_stats_stream.test.ts
@@ -27,6 +27,7 @@ describe('createMonitoringStatsStream', () => {
monitored_aggregated_stats_refresh_rate: 5000,
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug' as const,
warn_delayed_task_start_in_seconds: 60,
},
monitored_stats_running_average_window: 50,
diff --git a/x-pack/plugins/task_manager/server/plugin.test.ts b/x-pack/plugins/task_manager/server/plugin.test.ts
index 93e44c4000750b..ad02e6d7490f5d 100644
--- a/x-pack/plugins/task_manager/server/plugin.test.ts
+++ b/x-pack/plugins/task_manager/server/plugin.test.ts
@@ -47,6 +47,7 @@ const pluginInitializerContextParams = {
monitored_aggregated_stats_refresh_rate: 5000,
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug' as const,
warn_delayed_task_start_in_seconds: 60,
},
monitored_stats_required_freshness: 5000,
diff --git a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
index 8e89d929c1a021..f4616b2f8c2056 100644
--- a/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
+++ b/x-pack/plugins/task_manager/server/polling_lifecycle.test.ts
@@ -48,6 +48,7 @@ describe('TaskPollingLifecycle', () => {
monitored_aggregated_stats_refresh_rate: 5000,
monitored_stats_health_verbose_log: {
enabled: false,
+ level: 'debug' as const,
warn_delayed_task_start_in_seconds: 60,
},
monitored_stats_required_freshness: 5000,
diff --git a/x-pack/plugins/task_manager/server/routes/health.test.ts b/x-pack/plugins/task_manager/server/routes/health.test.ts
index f2d4dae1532a4c..f9ad895d0f5da3 100644
--- a/x-pack/plugins/task_manager/server/routes/health.test.ts
+++ b/x-pack/plugins/task_manager/server/routes/health.test.ts
@@ -208,6 +208,7 @@ describe('healthRoute', () => {
monitored_stats_required_freshness: 1000,
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 100,
},
monitored_aggregated_stats_refresh_rate: 60000,
@@ -267,6 +268,7 @@ describe('healthRoute', () => {
monitored_stats_required_freshness: 1000,
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 120,
},
monitored_aggregated_stats_refresh_rate: 60000,
@@ -344,6 +346,7 @@ describe('healthRoute', () => {
monitored_stats_required_freshness: 1000,
monitored_stats_health_verbose_log: {
enabled: true,
+ level: 'debug',
warn_delayed_task_start_in_seconds: 120,
},
monitored_aggregated_stats_refresh_rate: 60000,
From fa69b424bcc3fe8a2a78c66288a2b24d75051cba Mon Sep 17 00:00:00 2001
From: Chenhui Wang <54903978+wangch079@users.noreply.github.com>
Date: Mon, 14 Nov 2022 22:37:03 +0800
Subject: [PATCH 10/20] Update job index mapping (#144777)
## Summary
Part of https://github.com/elastic/enterprise-search-team/issues/3193
Part of https://github.com/elastic/enterprise-search-team/issues/3283
The changes in this PR:
1. adds metadata to `.elastic-connectors-sync-jobs` index mapping.
2. groups connector data under key `connector`
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../index_management/setup_indices.test.ts | 76 ++++++++++++-------
.../server/index_management/setup_indices.ts | 74 ++++++++++++------
2 files changed, 98 insertions(+), 52 deletions(-)
diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts
index 49a17f5e0d6c28..e312bada49e413 100644
--- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts
+++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.test.ts
@@ -154,47 +154,69 @@ describe('Setup Indices', () => {
version: CONNECTORS_VERSION,
},
properties: {
+ cancelation_requested_at: { type: 'date' },
+ canceled_at: { type: 'date' },
completed_at: { type: 'date' },
- connector_id: {
- type: 'keyword',
- },
- created_at: { type: 'date' },
- deleted_document_count: { type: 'integer' },
- error: { type: 'keyword' },
- filtering: {
+ connector: {
properties: {
- advanced_snippet: {
- properties: {
- created_at: { type: 'date' },
- updated_at: { type: 'date' },
- value: { type: 'object' },
- },
- },
- domain: { type: 'keyword' },
- rules: {
+ configuration: { type: 'object' },
+ filtering: {
properties: {
- created_at: { type: 'date' },
- field: { type: 'keyword' },
- id: { type: 'keyword' },
- order: { type: 'short' },
- policy: { type: 'keyword' },
- rule: { type: 'keyword' },
- updated_at: { type: 'date' },
- value: { type: 'keyword' },
+ advanced_snippet: {
+ properties: {
+ created_at: { type: 'date' },
+ updated_at: { type: 'date' },
+ value: { type: 'object' },
+ },
+ },
+ domain: { type: 'keyword' },
+ rules: {
+ properties: {
+ created_at: { type: 'date' },
+ field: { type: 'keyword' },
+ id: { type: 'keyword' },
+ order: { type: 'short' },
+ policy: { type: 'keyword' },
+ rule: { type: 'keyword' },
+ updated_at: { type: 'date' },
+ value: { type: 'keyword' },
+ },
+ },
+ warnings: {
+ properties: {
+ ids: { type: 'keyword' },
+ messages: { type: 'text' },
+ },
+ },
},
},
- warnings: {
+ id: { type: 'keyword' },
+ index_name: { type: 'keyword' },
+ language: { type: 'keyword' },
+ pipeline: {
properties: {
- ids: { type: 'keyword' },
- messages: { type: 'text' },
+ extract_binary_content: { type: 'boolean' },
+ name: { type: 'keyword' },
+ reduce_whitespace: { type: 'boolean' },
+ run_ml_inference: { type: 'boolean' },
},
},
+ service_type: { type: 'keyword' },
},
},
+ created_at: { type: 'date' },
+ deleted_document_count: { type: 'integer' },
+ error: { type: 'keyword' },
indexed_document_count: { type: 'integer' },
+ indexed_document_volume: { type: 'integer' },
+ last_seen: { type: 'date' },
+ metadata: { type: 'object' },
+ started_at: { type: 'date' },
status: {
type: 'keyword',
},
+ total_document_count: { type: 'integer' },
+ trigger_method: { type: 'keyword' },
worker_hostname: { type: 'keyword' },
},
};
diff --git a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts
index a6142459ca093b..36757667193a31 100644
--- a/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts
+++ b/x-pack/plugins/enterprise_search/server/index_management/setup_indices.ts
@@ -180,43 +180,67 @@ const indices: IndexDefinition[] = [
version: 1,
},
properties: {
+ cancelation_requested_at: { type: 'date' },
+ canceled_at: { type: 'date' },
completed_at: { type: 'date' },
- connector_id: { type: 'keyword' },
- created_at: { type: 'date' },
- deleted_document_count: { type: 'integer' },
- error: { type: 'keyword' },
- filtering: {
+ connector: {
properties: {
- advanced_snippet: {
- properties: {
- created_at: { type: 'date' },
- updated_at: { type: 'date' },
- value: { type: 'object' },
- },
- },
- domain: { type: 'keyword' },
- rules: {
+ configuration: { type: 'object' },
+ filtering: {
properties: {
- created_at: { type: 'date' },
- field: { type: 'keyword' },
- id: { type: 'keyword' },
- order: { type: 'short' },
- policy: { type: 'keyword' },
- rule: { type: 'keyword' },
- updated_at: { type: 'date' },
- value: { type: 'keyword' },
+ advanced_snippet: {
+ properties: {
+ created_at: { type: 'date' },
+ updated_at: { type: 'date' },
+ value: { type: 'object' },
+ },
+ },
+ domain: { type: 'keyword' },
+ rules: {
+ properties: {
+ created_at: { type: 'date' },
+ field: { type: 'keyword' },
+ id: { type: 'keyword' },
+ order: { type: 'short' },
+ policy: { type: 'keyword' },
+ rule: { type: 'keyword' },
+ updated_at: { type: 'date' },
+ value: { type: 'keyword' },
+ },
+ },
+ warnings: {
+ properties: {
+ ids: { type: 'keyword' },
+ messages: { type: 'text' },
+ },
+ },
},
},
- warnings: {
+ id: { type: 'keyword' },
+ index_name: { type: 'keyword' },
+ language: { type: 'keyword' },
+ pipeline: {
properties: {
- ids: { type: 'keyword' },
- messages: { type: 'text' },
+ extract_binary_content: { type: 'boolean' },
+ name: { type: 'keyword' },
+ reduce_whitespace: { type: 'boolean' },
+ run_ml_inference: { type: 'boolean' },
},
},
+ service_type: { type: 'keyword' },
},
},
+ created_at: { type: 'date' },
+ deleted_document_count: { type: 'integer' },
+ error: { type: 'keyword' },
indexed_document_count: { type: 'integer' },
+ indexed_document_volume: { type: 'integer' },
+ last_seen: { type: 'date' },
+ metadata: { type: 'object' },
+ started_at: { type: 'date' },
status: { type: 'keyword' },
+ total_document_count: { type: 'integer' },
+ trigger_method: { type: 'keyword' },
worker_hostname: { type: 'keyword' },
},
},
From 9c27f3d798e7f5f32bbe3c1648f24177d4c7663b Mon Sep 17 00:00:00 2001
From: Yngrid Coello
Date: Mon, 14 Nov 2022 15:40:49 +0100
Subject: [PATCH 11/20] [APM] Agent explorer (PoC) (#143844)
Closes [142218](https://github.com/elastic/kibana/issues/142218)
- Introducing the Agent explorer view
https://user-images.githubusercontent.com/1313018/198403801-bd9aab9c-1f7e-4775-b3ed-e0e488eef513.mov
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../src/lib/apm/apm_fields.ts | 1 +
.../src/scenarios/many_services.ts | 41 +++-
.../server/collectors/management/schema.ts | 4 +
.../server/collectors/management/types.ts | 1 +
src/plugins/telemetry/schema/oss_plugins.json | 6 +
x-pack/plugins/apm/common/agent_explorer.ts | 16 ++
x-pack/plugins/apm/common/agent_name.ts | 3 +
.../agent_explorer_docs_link/index.tsx | 60 +++++
.../agent_contextual_information/index.tsx | 121 ++++++++++
.../agent_instances_details/index.tsx | 217 ++++++++++++++++++
.../agent_explorer/agent_instances/index.tsx | 113 +++++++++
.../agent_explorer/agent_list/index.tsx | 210 +++++++++++++++++
.../app/settings/agent_explorer/index.tsx | 196 ++++++++++++++++
.../components/routing/settings/index.tsx | 42 +++-
.../routing/templates/settings_template.tsx | 41 +++-
.../shared/environment_badge/index.tsx | 35 +--
.../components/shared/item_badge/index.tsx | 47 ++++
.../shared/popover_tooltip/index.tsx | 41 ++++
.../public/hooks/use_default_time_range.ts | 21 ++
.../agent_explorer/get_agent_instances.ts | 108 +++++++++
.../get_agent_url_repository.ts | 47 ++++
.../routes/agent_explorer/get_agents.ts | 50 ++++
.../routes/agent_explorer/get_agents_items.ts | 136 +++++++++++
.../apm/server/routes/agent_explorer/route.ts | 119 ++++++++++
.../get_global_apm_server_route_repository.ts | 10 +-
.../get_services/get_services_items.ts | 2 +-
x-pack/plugins/observability/common/index.ts | 1 +
.../observability/common/ui_settings_keys.ts | 1 +
x-pack/plugins/observability/public/index.ts | 1 +
.../observability/server/ui_settings.ts | 24 +-
.../test/apm_api_integration/common/config.ts | 51 +++-
.../agent_explorer/agent_explorer.spec.ts | 191 +++++++++++++++
32 files changed, 1889 insertions(+), 68 deletions(-)
create mode 100644 x-pack/plugins/apm/common/agent_explorer.ts
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_explorer_docs_link/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_instances_details/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/shared/item_badge/index.tsx
create mode 100644 x-pack/plugins/apm/public/components/shared/popover_tooltip/index.tsx
create mode 100644 x-pack/plugins/apm/public/hooks/use_default_time_range.ts
create mode 100644 x-pack/plugins/apm/server/routes/agent_explorer/get_agent_instances.ts
create mode 100644 x-pack/plugins/apm/server/routes/agent_explorer/get_agent_url_repository.ts
create mode 100644 x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts
create mode 100644 x-pack/plugins/apm/server/routes/agent_explorer/get_agents_items.ts
create mode 100644 x-pack/plugins/apm/server/routes/agent_explorer/route.ts
create mode 100644 x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
diff --git a/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts b/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts
index e9b89fa4739d04..1d11dc1d7f3b39 100644
--- a/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts
+++ b/packages/kbn-apm-synthtrace/src/lib/apm/apm_fields.ts
@@ -81,6 +81,7 @@ export type ApmFields = Fields &
'service.name': string;
'service.version': string;
'service.environment': string;
+ 'service.language.name': string;
'service.node.name': string;
'service.runtime.name': string;
'service.runtime.version': string;
diff --git a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts
index 1829d46e172324..6ff91c23fb0589 100644
--- a/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts
+++ b/packages/kbn-apm-synthtrace/src/scenarios/many_services.ts
@@ -6,14 +6,14 @@
* Side Public License, v 1.
*/
-import { random } from 'lodash';
+import { flatten, random } from 'lodash';
import { apm, timerange } from '../..';
-import { Instance } from '../lib/apm/instance';
import { Scenario } from '../cli/scenario';
import { getLogger } from '../cli/utils/get_common_services';
import { RunOptions } from '../cli/utils/parse_run_cli_flags';
import { ApmFields } from '../lib/apm/apm_fields';
+import { Instance } from '../lib/apm/instance';
import { getSynthtraceEnvironment } from '../lib/utils/get_synthtrace_environment';
const ENVIRONMENT = getSynthtraceEnvironment(__filename);
@@ -24,6 +24,12 @@ const scenario: Scenario = async (runOptions: RunOptions) => {
const numServices = 500;
const languages = ['go', 'dotnet', 'java', 'python'];
const services = ['web', 'order-processing', 'api-backend', 'proxy'];
+ const agentVersions: Record = {
+ go: ['2.1.0', '2.0.0', '1.15.0', '1.14.0', '1.13.1'],
+ dotnet: ['1.18.0', '1.17.0', '1.16.1', '1.16.0', '1.15.0'],
+ java: ['1.34.1', '1.34.0', '1.33.0', '1.32.0', '1.32.0'],
+ python: ['6.12.0', '6.11.0', '6.10.2', '6.10.1', '6.10.0'],
+ };
return {
generate: ({ from, to }) => {
@@ -31,16 +37,27 @@ const scenario: Scenario = async (runOptions: RunOptions) => {
const successfulTimestamps = range.ratePerMinute(180);
- const instances = [...Array(numServices).keys()].map((index) =>
- apm
- .service({
- name: `${services[index % services.length]}-${
- languages[index % languages.length]
- }-${index}`,
- environment: ENVIRONMENT,
- agentName: languages[index % languages.length],
- })
- .instance(`instance-${index}`)
+ const instances = flatten(
+ [...Array(numServices).keys()].map((index) => {
+ const language = languages[index % languages.length];
+ const agentLanguageVersions = agentVersions[language];
+
+ const numOfInstances = (index % 3) + 1;
+
+ return [...Array(numOfInstances).keys()].map((instanceIndex) =>
+ apm
+ .service({
+ name: `${services[index % services.length]}-${language}-${index}`,
+ environment: ENVIRONMENT,
+ agentName: language,
+ })
+ .instance(`instance-${index}-${instanceIndex}`)
+ .defaults({
+ 'agent.version': agentLanguageVersions[index % agentLanguageVersions.length],
+ 'service.language.name': language,
+ })
+ );
+ })
);
const urls = ['GET /order/{id}', 'POST /basket/{id}', 'DELETE /basket', 'GET /products'];
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
index 0aa6830adf8679..150bc768601f48 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/schema.ts
@@ -442,6 +442,10 @@ export const stackManagementSchema: MakeSchemaFrom = {
type: 'boolean',
_meta: { description: 'Non-default value of setting.' },
},
+ 'observability:apmAgentExplorerView': {
+ type: 'boolean',
+ _meta: { description: 'Non-default value of setting.' },
+ },
'observability:apmAWSLambdaPriceFactor': {
type: 'text',
_meta: { description: 'Non-default value of setting.' },
diff --git a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
index 88220ed367886d..8a897721b6dc67 100644
--- a/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
+++ b/src/plugins/kibana_usage_collection/server/collectors/management/types.ts
@@ -46,6 +46,7 @@ export interface UsageStats {
'observability:apmAWSLambdaPriceFactor': string;
'observability:apmAWSLambdaRequestCostPerMillion': number;
'observability:enableInfrastructureHostsView': boolean;
+ 'observability:apmAgentExplorerView': boolean;
'visualize:enableLabs': boolean;
'visualization:heatmap:maxBuckets': number;
'visualization:colorMapping': string;
diff --git a/src/plugins/telemetry/schema/oss_plugins.json b/src/plugins/telemetry/schema/oss_plugins.json
index ddf1ba3df941a5..b59177259ece35 100644
--- a/src/plugins/telemetry/schema/oss_plugins.json
+++ b/src/plugins/telemetry/schema/oss_plugins.json
@@ -8916,6 +8916,12 @@
"description": "Non-default value of setting."
}
},
+ "observability:apmAgentExplorerView": {
+ "type": "boolean",
+ "_meta": {
+ "description": "Non-default value of setting."
+ }
+ },
"observability:apmAWSLambdaPriceFactor": {
"type": "text",
"_meta": {
diff --git a/x-pack/plugins/apm/common/agent_explorer.ts b/x-pack/plugins/apm/common/agent_explorer.ts
new file mode 100644
index 00000000000000..07ce5ed7ab1f04
--- /dev/null
+++ b/x-pack/plugins/apm/common/agent_explorer.ts
@@ -0,0 +1,16 @@
+/*
+ * 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.
+ */
+
+export enum AgentExplorerFieldName {
+ ServiceName = 'serviceName',
+ Environments = 'environments',
+ AgentName = 'agentName',
+ AgentVersion = 'agentVersion',
+ AgentLastVersion = 'agentLastVersion',
+ AgentDocsPageUrl = 'agentDocsPageUrl',
+ Instances = 'instances',
+}
diff --git a/x-pack/plugins/apm/common/agent_name.ts b/x-pack/plugins/apm/common/agent_name.ts
index 7902b57c7c72c3..ef4197fb072b6f 100644
--- a/x-pack/plugins/apm/common/agent_name.ts
+++ b/x-pack/plugins/apm/common/agent_name.ts
@@ -45,6 +45,9 @@ export const AGENT_NAMES: AgentName[] = [
...OPEN_TELEMETRY_AGENT_NAMES,
];
+export const isOpenTelemetryAgentName = (agentName: AgentName) =>
+ OPEN_TELEMETRY_AGENT_NAMES.includes(agentName);
+
export const JAVA_AGENT_NAMES: AgentName[] = ['java', 'opentelemetry/java'];
export function isJavaAgentName(
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_explorer_docs_link/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_explorer_docs_link/index.tsx
new file mode 100644
index 00000000000000..2e2014a787be5f
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_explorer_docs_link/index.tsx
@@ -0,0 +1,60 @@
+/*
+ * 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 { EuiIcon, EuiLink } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { isOpenTelemetryAgentName } from '../../../../../../common/agent_name';
+import { NOT_AVAILABLE_LABEL } from '../../../../../../common/i18n';
+import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent';
+
+interface AgentExplorerDocsLinkProps {
+ agentName: AgentName;
+ repositoryUrl?: string;
+}
+
+export function AgentExplorerDocsLink({
+ agentName,
+ repositoryUrl,
+}: AgentExplorerDocsLinkProps) {
+ if (!repositoryUrl) {
+ return <>{NOT_AVAILABLE_LABEL}>;
+ }
+
+ return (
+
+ {isOpenTelemetryAgentName(agentName) ? (
+
+ ) : (
+
+ )}{' '}
+ {i18n.translate('xpack.apm.agentExplorer.docsLink.message', {
+ defaultMessage: 'Docs',
+ })}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx
new file mode 100644
index 00000000000000..64ea20f1e14d85
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_contextual_information/index.tsx
@@ -0,0 +1,121 @@
+/*
+ * 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 { EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { TypeOf } from '@kbn/typed-react-router-config';
+import React from 'react';
+import { AgentExplorerFieldName } from '../../../../../../../common/agent_explorer';
+import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent';
+import { useApmPluginContext } from '../../../../../../context/apm_plugin/use_apm_plugin_context';
+import { useDefaultTimeRange } from '../../../../../../hooks/use_default_time_range';
+import { ApmRoutes } from '../../../../../routing/apm_route_config';
+import { ServiceLink } from '../../../../../shared/service_link';
+import { StickyProperties } from '../../../../../shared/sticky_properties';
+import { getComparisonEnabled } from '../../../../../shared/time_comparison/get_comparison_enabled';
+import { TruncateWithTooltip } from '../../../../../shared/truncate_with_tooltip';
+import { AgentExplorerDocsLink } from '../../agent_explorer_docs_link';
+
+export function AgentContextualInformation({
+ agentName,
+ serviceName,
+ agentDocsPageUrl,
+ instances,
+ query,
+}: {
+ agentName: AgentName;
+ serviceName: string;
+ agentDocsPageUrl?: string;
+ instances: number;
+ query: TypeOf['query'];
+}) {
+ const { core } = useApmPluginContext();
+ const comparisonEnabled = getComparisonEnabled({ core });
+ const { rangeFrom, rangeTo } = useDefaultTimeRange();
+
+ const stickyProperties = [
+ {
+ label: i18n.translate('xpack.apm.agentInstancesDetails.serviceLabel', {
+ defaultMessage: 'Service',
+ }),
+ fieldName: AgentExplorerFieldName.ServiceName,
+ val: (
+
+ }
+ />
+ ),
+ width: '25%',
+ },
+ {
+ label: i18n.translate('xpack.apm.agentInstancesDetails.agentNameLabel', {
+ defaultMessage: 'Agent Name',
+ }),
+ fieldName: AgentExplorerFieldName.AgentName,
+ val: (
+
+
+ {agentName}
+
+
+ ),
+ width: '25%',
+ },
+ {
+ label: i18n.translate('xpack.apm.agentInstancesDetails.intancesLabel', {
+ defaultMessage: 'Instances',
+ }),
+ fieldName: 'instances',
+ val: (
+
+
+ {instances}
+
+
+ ),
+ width: '25%',
+ },
+ {
+ label: i18n.translate(
+ 'xpack.apm.agentInstancesDetails.agentDocsUrlLabel',
+ {
+ defaultMessage: 'Agent documentation',
+ }
+ ),
+ fieldName: AgentExplorerFieldName.AgentDocsPageUrl,
+ val: (
+
+ }
+ />
+ ),
+ width: '25%',
+ },
+ ];
+
+ return ;
+}
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_instances_details/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_instances_details/index.tsx
new file mode 100644
index 00000000000000..61dbf513859520
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/agent_instances_details/index.tsx
@@ -0,0 +1,217 @@
+/*
+ * 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 { EuiLink, EuiLoadingContent, EuiText } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import { FormattedMessage } from '@kbn/i18n-react';
+import React from 'react';
+import { ValuesType } from 'utility-types';
+import { AgentExplorerFieldName } from '../../../../../../../common/agent_explorer';
+import { isOpenTelemetryAgentName } from '../../../../../../../common/agent_name';
+import {
+ getServiceNodeName,
+ SERVICE_NODE_NAME_MISSING,
+} from '../../../../../../../common/service_nodes';
+import { AgentName } from '../../../../../../../typings/es_schemas/ui/fields/agent';
+import { APIReturnType } from '../../../../../../services/rest/create_call_apm_api';
+import { unit } from '../../../../../../utils/style';
+import { EnvironmentBadge } from '../../../../../shared/environment_badge';
+import { ItemsBadge } from '../../../../../shared/item_badge';
+import { ServiceNodeMetricOverviewLink } from '../../../../../shared/links/apm/service_node_metric_overview_link';
+import {
+ ITableColumn,
+ ManagedTable,
+} from '../../../../../shared/managed_table';
+import { PopoverTooltip } from '../../../../../shared/popover_tooltip';
+import { TimestampTooltip } from '../../../../../shared/timestamp_tooltip';
+import { TruncateWithTooltip } from '../../../../../shared/truncate_with_tooltip';
+
+type AgentExplorerInstance = ValuesType<
+ APIReturnType<'GET /internal/apm/services/{serviceName}/agent_instances'>['items']
+>;
+
+enum AgentExplorerInstanceFieldName {
+ InstanceName = 'serviceNode',
+ Environments = 'environments',
+ AgentVersion = 'agentVersion',
+ LastReport = 'lastReport',
+}
+
+export function getInstanceColumns(
+ serviceName: string,
+ agentName: AgentName,
+ agentDocsPageUrl?: string
+): Array> {
+ return [
+ {
+ field: AgentExplorerInstanceFieldName.InstanceName,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerInstanceTable.InstanceColumnLabel',
+ {
+ defaultMessage: 'Instance',
+ }
+ ),
+ sortable: true,
+ render: (_, { serviceNode }) => {
+ const displayedName = getServiceNodeName(serviceNode);
+
+ return serviceNode === SERVICE_NODE_NAME_MISSING ? (
+ <>
+ {displayedName}
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.agentExplorerInstanceTable.noServiceNodeName.configurationOptions',
+ {
+ defaultMessage: 'configuration options',
+ }
+ )}
+
+ ),
+ }}
+ />
+
+
+
+ >
+ ) : (
+
+ {serviceNode ? (
+
+ {displayedName}
+
+ ) : (
+ {displayedName}
+ )}
+ >
+ }
+ />
+ );
+ },
+ },
+ {
+ field: AgentExplorerInstanceFieldName.Environments,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerInstanceTable.environmentColumnLabel',
+ {
+ defaultMessage: 'Environment',
+ }
+ ),
+ width: `${unit * 16}px`,
+ sortable: true,
+ render: (_, { environments }) => (
+
+ ),
+ },
+ {
+ field: AgentExplorerInstanceFieldName.AgentVersion,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerInstanceTable.agentVersionColumnLabel',
+ { defaultMessage: 'Agent Version' }
+ ),
+ width: `${unit * 16}px`,
+ sortable: true,
+ render: (_, { agentVersion }) => {
+ const versions = [agentVersion];
+ return (
+
+ );
+ },
+ },
+ {
+ field: AgentExplorerInstanceFieldName.LastReport,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerInstanceTable.lastReportColumnLabel',
+ {
+ defaultMessage: 'Last report',
+ }
+ ),
+ width: `${unit * 16}px`,
+ sortable: true,
+ render: (_, { lastReport }) => ,
+ },
+ ];
+}
+
+interface Props {
+ serviceName: string;
+ agentName: AgentName;
+ agentDocsPageUrl?: string;
+ items: AgentExplorerInstance[];
+ isLoading: boolean;
+}
+
+export function AgentInstancesDetails({
+ serviceName,
+ agentName,
+ agentDocsPageUrl,
+ items,
+ isLoading,
+}: Props) {
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+
+ return (
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx
new file mode 100644
index 00000000000000..33b2b780d4e346
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_instances/index.tsx
@@ -0,0 +1,113 @@
+/*
+ * 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 {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiFlyoutBody,
+ EuiFlyoutHeader,
+ EuiHorizontalRule,
+ EuiPortal,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { useApmParams } from '../../../../../hooks/use_apm_params';
+import { FETCH_STATUS } from '../../../../../hooks/use_fetcher';
+import { useProgressiveFetcher } from '../../../../../hooks/use_progressive_fetcher';
+import { useTimeRange } from '../../../../../hooks/use_time_range';
+import { ResponsiveFlyout } from '../../../transaction_details/waterfall_with_summary/waterfall_container/waterfall/responsive_flyout';
+import { AgentExplorerItem } from '../agent_list';
+import { AgentContextualInformation } from './agent_contextual_information';
+import { AgentInstancesDetails } from './agent_instances_details';
+
+function useAgentInstancesFetcher({ serviceName }: { serviceName: string }) {
+ const {
+ query: { environment, kuery },
+ } = useApmParams('/settings/agent-explorer');
+
+ const { start, end } = useTimeRange({ rangeFrom: 'now-24h', rangeTo: 'now' });
+
+ return useProgressiveFetcher(
+ (callApmApi) => {
+ return callApmApi(
+ 'GET /internal/apm/services/{serviceName}/agent_instances',
+ {
+ params: {
+ path: {
+ serviceName,
+ },
+ query: {
+ environment,
+ start,
+ end,
+ kuery,
+ },
+ },
+ }
+ );
+ },
+ [start, end, serviceName, environment, kuery]
+ );
+}
+
+interface Props {
+ agent: AgentExplorerItem;
+ onClose: () => void;
+}
+
+export function AgentInstances({ agent, onClose }: Props) {
+ const { query } = useApmParams('/settings/agent-explorer');
+
+ const instances = useAgentInstancesFetcher({
+ serviceName: agent.serviceName,
+ });
+
+ const isLoading = instances.status === FETCH_STATUS.LOADING;
+
+ return (
+
+
+
+
+
+
+
+ {i18n.translate(
+ 'xpack.apm.agentExplorer.instancesFlyout.title',
+ {
+ defaultMessage: 'Agent Instances',
+ }
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx
new file mode 100644
index 00000000000000..8d934c7570413e
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/agent_list/index.tsx
@@ -0,0 +1,210 @@
+/*
+ * 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 {
+ EuiButtonIcon,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiToolTip,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React, { useMemo, useState } from 'react';
+import { ValuesType } from 'utility-types';
+import { AgentExplorerFieldName } from '../../../../../../common/agent_explorer';
+import { AgentName } from '../../../../../../typings/es_schemas/ui/fields/agent';
+import { APIReturnType } from '../../../../../services/rest/create_call_apm_api';
+import { unit } from '../../../../../utils/style';
+import { AgentIcon } from '../../../../shared/agent_icon';
+import { EnvironmentBadge } from '../../../../shared/environment_badge';
+import { ItemsBadge } from '../../../../shared/item_badge';
+import { ITableColumn, ManagedTable } from '../../../../shared/managed_table';
+import { TruncateWithTooltip } from '../../../../shared/truncate_with_tooltip';
+import { AgentExplorerDocsLink } from '../agent_explorer_docs_link';
+import { AgentInstances } from '../agent_instances';
+
+export type AgentExplorerItem = ValuesType<
+ APIReturnType<'GET /internal/apm/get_agents_per_service'>['items']
+>;
+
+export function getAgentsColumns({
+ selectedAgent,
+ onAgentSelected,
+}: {
+ selectedAgent?: AgentExplorerItem;
+ onAgentSelected: (agent: AgentExplorerItem) => void;
+}): Array> {
+ return [
+ {
+ field: AgentExplorerFieldName.ServiceName,
+ name: '',
+ width: `${unit * 3}px`,
+ render: (_, agent) => {
+ const isSelected = selectedAgent === agent;
+
+ return (
+
+ onAgentSelected(agent)}
+ display={isSelected ? 'base' : 'empty'}
+ iconType={isSelected ? 'minimize' : 'expand'}
+ isSelected={isSelected}
+ />
+
+ );
+ },
+ },
+ {
+ field: AgentExplorerFieldName.ServiceName,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.serviceNameColumnLabel',
+ {
+ defaultMessage: 'Service Name',
+ }
+ ),
+ sortable: true,
+ render: (_, { serviceName, agentName }) => (
+
+
+
+
+
+ {serviceName}
+
+
+ }
+ />
+ ),
+ },
+ {
+ field: AgentExplorerFieldName.Environments,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.environmentColumnLabel',
+ {
+ defaultMessage: 'Environment',
+ }
+ ),
+ width: `${unit * 16}px`,
+ sortable: true,
+ render: (_, { environments }) => (
+
+ ),
+ },
+ {
+ field: AgentExplorerFieldName.Instances,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.instancesColumnLabel',
+ {
+ defaultMessage: 'Instances',
+ }
+ ),
+ width: `${unit * 8}px`,
+ sortable: true,
+ },
+ {
+ field: AgentExplorerFieldName.AgentName,
+ width: `${unit * 12}px`,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.agentNameColumnLabel',
+ { defaultMessage: 'Agent Name' }
+ ),
+ sortable: true,
+ },
+ {
+ field: AgentExplorerFieldName.AgentVersion,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.agentVersionColumnLabel',
+ { defaultMessage: 'Agent Version' }
+ ),
+ width: `${unit * 8}px`,
+ render: (_, { agentVersion }) => (
+
+ ),
+ },
+ {
+ field: AgentExplorerFieldName.AgentDocsPageUrl,
+ name: i18n.translate(
+ 'xpack.apm.agentExplorerTable.agentDocsColumnLabel',
+ { defaultMessage: 'Agent Docs' }
+ ),
+ width: `${unit * 8}px`,
+ render: (_, { agentName, agentDocsPageUrl }) => (
+
+
+
+ ),
+ },
+ ];
+}
+
+interface Props {
+ items: AgentExplorerItem[];
+ noItemsMessage: React.ReactNode;
+ isLoading: boolean;
+}
+
+export function AgentList({ items, noItemsMessage, isLoading }: Props) {
+ const [selectedAgent, setSelectedAgent] = useState();
+
+ const onAgentSelected = (agent: AgentExplorerItem) => {
+ setSelectedAgent(agent);
+ };
+
+ const onCloseFlyout = () => {
+ setSelectedAgent(undefined);
+ };
+
+ const agentColumns = useMemo(
+ () => getAgentsColumns({ selectedAgent, onAgentSelected }),
+ [selectedAgent]
+ );
+
+ return (
+ <>
+ {selectedAgent && (
+
+ )}
+
+ >
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
new file mode 100644
index 00000000000000..11f4f8d0d79a1d
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/app/settings/agent_explorer/index.tsx
@@ -0,0 +1,196 @@
+/*
+ * 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 {
+ EuiCallOut,
+ EuiEmptyPrompt,
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiSpacer,
+ EuiText,
+ EuiTitle,
+} from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+import { useHistory } from 'react-router-dom';
+import {
+ SERVICE_LANGUAGE_NAME,
+ SERVICE_NAME,
+} from '../../../../../common/elasticsearch_fieldnames';
+import { useApmParams } from '../../../../hooks/use_apm_params';
+import { FETCH_STATUS } from '../../../../hooks/use_fetcher';
+import { useProgressiveFetcher } from '../../../../hooks/use_progressive_fetcher';
+import { useTimeRange } from '../../../../hooks/use_time_range';
+import { KueryBar } from '../../../shared/kuery_bar';
+import * as urlHelpers from '../../../shared/links/url_helpers';
+import { SuggestionsSelect } from '../../../shared/suggestions_select';
+import { TechnicalPreviewBadge } from '../../../shared/technical_preview_badge';
+import { AgentList } from './agent_list';
+
+function useAgentExplorerFetcher({
+ start,
+ end,
+}: {
+ start: string;
+ end: string;
+}) {
+ const {
+ query: { environment, serviceName, agentLanguage, kuery },
+ } = useApmParams('/settings/agent-explorer');
+
+ return useProgressiveFetcher(
+ (callApmApi) => {
+ return callApmApi('GET /internal/apm/get_agents_per_service', {
+ params: {
+ query: {
+ environment,
+ serviceName,
+ agentLanguage,
+ kuery,
+ start,
+ end,
+ },
+ },
+ });
+ },
+ [environment, serviceName, agentLanguage, kuery, start, end]
+ );
+}
+
+export function AgentExplorer() {
+ const history = useHistory();
+
+ const {
+ query: { serviceName, agentLanguage },
+ } = useApmParams('/settings/agent-explorer');
+
+ const { start, end } = useTimeRange({ rangeFrom: 'now-24h', rangeTo: 'now' });
+ const agents = useAgentExplorerFetcher({ start, end });
+
+ const isLoading = agents.status === FETCH_STATUS.LOADING;
+
+ const noItemsMessage = (
+
+ {i18n.translate('xpack.apm.agentExplorer.notFoundLabel', {
+ defaultMessage: 'No Agents found',
+ })}
+
+ }
+ titleSize="s"
+ />
+ );
+
+ return (
+
+
+
+ {i18n.translate('xpack.apm.settings.agentExplorer.descriptionText', {
+ defaultMessage:
+ 'Agent Explorer Technical Preview provides an inventory and details of deployed Agents.',
+ })}
+
+
+
+
+
+
+
+
+ {i18n.translate('xpack.apm.settings.agentExplorer.title', {
+ defaultMessage: 'Agent explorer',
+ })}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {
+ urlHelpers.push(history, {
+ query: { serviceName: value ?? '' },
+ });
+ }}
+ placeholder={i18n.translate(
+ 'xpack.apm.agentExplorer.serviceNameSelect.placeholder',
+ {
+ defaultMessage: 'All',
+ }
+ )}
+ start={start}
+ end={end}
+ dataTestSubj="agentExplorerServiceNameSelect"
+ />
+
+
+ {
+ urlHelpers.push(history, {
+ query: { agentLanguage: value ?? '' },
+ });
+ }}
+ placeholder={i18n.translate(
+ 'xpack.apm.agentExplorer.agentLanguageSelect.placeholder',
+ {
+ defaultMessage: 'All',
+ }
+ )}
+ start={start}
+ end={end}
+ dataTestSubj="agentExplorerAgentLanguageSelect"
+ />
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/routing/settings/index.tsx b/x-pack/plugins/apm/public/components/routing/settings/index.tsx
index 161afd2706b680..2e1cfde3ee58b2 100644
--- a/x-pack/plugins/apm/public/components/routing/settings/index.tsx
+++ b/x-pack/plugins/apm/public/components/routing/settings/index.tsx
@@ -4,23 +4,25 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
-import React from 'react';
-import * as t from 'io-ts';
-import { Outlet } from '@kbn/typed-react-router-config';
import { i18n } from '@kbn/i18n';
+import { Outlet } from '@kbn/typed-react-router-config';
+import * as t from 'io-ts';
+import React from 'react';
import { Redirect } from 'react-router-dom';
import { agentConfigurationPageStepRt } from '../../../../common/agent_configuration/constants';
+import { environmentRt } from '../../../../common/environment_rt';
import { Breadcrumb } from '../../app/breadcrumb';
-import { SettingsTemplate } from '../templates/settings_template';
import { AgentConfigurations } from '../../app/settings/agent_configurations';
-import { CreateAgentConfigurationRouteView } from './create_agent_configuration_route_view';
-import { EditAgentConfigurationRouteView } from './edit_agent_configuration_route_view';
+import { AgentExplorer } from '../../app/settings/agent_explorer';
+import { AgentKeys } from '../../app/settings/agent_keys';
+import { AnomalyDetection } from '../../app/settings/anomaly_detection';
import { ApmIndices } from '../../app/settings/apm_indices';
import { CustomLinkOverview } from '../../app/settings/custom_link';
-import { Schema } from '../../app/settings/schema';
-import { AnomalyDetection } from '../../app/settings/anomaly_detection';
-import { AgentKeys } from '../../app/settings/agent_keys';
import { GeneralSettings } from '../../app/settings/general_settings';
+import { Schema } from '../../app/settings/schema';
+import { SettingsTemplate } from '../templates/settings_template';
+import { CreateAgentConfigurationRouteView } from './create_agent_configuration_route_view';
+import { EditAgentConfigurationRouteView } from './edit_agent_configuration_route_view';
function page({
title,
@@ -141,6 +143,28 @@ export const settings = {
element:
,
tab: 'agent-keys',
}),
+ '/settings/agent-explorer': {
+ ...page({
+ title: i18n.translate(
+ 'xpack.apm.views.settings.agentExplorer.title',
+ {
+ defaultMessage: 'Agent explorer',
+ }
+ ),
+ element:
,
+ tab: 'agent-explorer',
+ }),
+ params: t.type({
+ query: t.intersection([
+ environmentRt,
+ t.type({
+ kuery: t.string,
+ agentLanguage: t.string,
+ serviceName: t.string,
+ }),
+ ]),
+ }),
+ },
'/settings': {
element:
,
},
diff --git a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx
index 3305ecb6c4a548..8070018d9fea4b 100644
--- a/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx
+++ b/x-pack/plugins/apm/public/components/routing/templates/settings_template.tsx
@@ -6,13 +6,17 @@
*/
import { EuiPageHeaderProps } from '@elastic/eui';
+import { CoreStart } from '@kbn/core/public';
import { i18n } from '@kbn/i18n';
+import { enableAgentExplorerView } from '@kbn/observability-plugin/public';
import React from 'react';
-import { CoreStart } from '@kbn/core/public';
-import { ApmMainTemplate } from './apm_main_template';
+import { useDefaultEnvironment } from '../../../hooks/use_default_environment';
+import { Environment } from '../../../../common/environment_rt';
import { useApmPluginContext } from '../../../context/apm_plugin/use_apm_plugin_context';
import { useApmRouter } from '../../../hooks/use_apm_router';
+import { TechnicalPreviewBadge } from '../../shared/technical_preview_badge';
import { ApmRouter } from '../apm_route_config';
+import { ApmMainTemplate } from './apm_main_template';
type Tab = NonNullable
[0] & {
key:
@@ -22,7 +26,8 @@ type Tab = NonNullable[0] & {
| 'apm-indices'
| 'custom-links'
| 'schema'
- | 'general-settings';
+ | 'general-settings'
+ | 'agent-explorer';
hidden?: boolean;
};
@@ -34,7 +39,9 @@ interface Props {
export function SettingsTemplate({ children, selectedTab }: Props) {
const { core } = useApmPluginContext();
const router = useApmRouter();
- const tabs = getTabs({ core, selectedTab, router });
+ const defaultEnvironment = useDefaultEnvironment();
+
+ const tabs = getTabs({ core, selectedTab, router, defaultEnvironment });
return (
(
+ enableAgentExplorerView,
+ false
+ );
+
const tabs: Tab[] = [
{
key: 'general-settings',
@@ -77,6 +91,22 @@ function getTabs({
}),
href: router.link('/settings/agent-configuration'),
},
+ {
+ key: 'agent-explorer',
+ label: i18n.translate('xpack.apm.settings.agentExplorer', {
+ defaultMessage: 'Agent Explorer',
+ }),
+ href: router.link('/settings/agent-explorer', {
+ query: {
+ environment: defaultEnvironment,
+ kuery: '',
+ agentLanguage: '',
+ serviceName: '',
+ },
+ }),
+ append: ,
+ hidden: !agentExplorerEnabled,
+ },
{
key: 'agent-keys',
label: i18n.translate('xpack.apm.settings.agentKeys', {
@@ -117,9 +147,10 @@ function getTabs({
return tabs
.filter((t) => !t.hidden)
- .map(({ href, key, label }) => ({
+ .map(({ href, key, label, append }) => ({
href,
label,
+ append,
isSelected: key === selectedTab,
}));
}
diff --git a/x-pack/plugins/apm/public/components/shared/environment_badge/index.tsx b/x-pack/plugins/apm/public/components/shared/environment_badge/index.tsx
index aa269fb87222f7..9ac3139678316b 100644
--- a/x-pack/plugins/apm/public/components/shared/environment_badge/index.tsx
+++ b/x-pack/plugins/apm/public/components/shared/environment_badge/index.tsx
@@ -7,40 +7,23 @@
import React from 'react';
import { i18n } from '@kbn/i18n';
-import { EuiBadge, EuiToolTip } from '@elastic/eui';
+import { ItemsBadge } from '../item_badge';
interface Props {
environments: string[];
}
export function EnvironmentBadge({ environments = [] }: Props) {
- if (environments.length < 2) {
- return (
- <>
- {environments.map((env) => (
-
- {env}
-
- ))}
- >
- );
- }
return (
- (
-
- {env}
-
-
- ))}
- >
-
- {i18n.translate('xpack.apm.servicesTable.environmentCount', {
+
-
+ }
+ )}
+ />
);
}
diff --git a/x-pack/plugins/apm/public/components/shared/item_badge/index.tsx b/x-pack/plugins/apm/public/components/shared/item_badge/index.tsx
new file mode 100644
index 00000000000000..ab0b9154bd8cb3
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/item_badge/index.tsx
@@ -0,0 +1,47 @@
+/*
+ * 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 { EuiBadge, EuiToolTip } from '@elastic/eui';
+import { i18n } from '@kbn/i18n';
+import React from 'react';
+
+interface Props {
+ items: string[];
+ multipleItemsMessage?: string;
+}
+export function ItemsBadge({
+ items = [],
+ multipleItemsMessage = i18n.translate('xpack.apm.itemsBadge.placeholder', {
+ values: { itemsCount: items.length },
+ defaultMessage: '{itemsCount, plural, one {1 item} other {# items}}',
+ }),
+}: Props) {
+ if (items.length < 2) {
+ return (
+ <>
+ {items.map((item) => (
+
+ {item}
+
+ ))}
+ >
+ );
+ }
+ return (
+ (
+
+ {item}
+
+
+ ))}
+ >
+ {multipleItemsMessage}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/components/shared/popover_tooltip/index.tsx b/x-pack/plugins/apm/public/components/shared/popover_tooltip/index.tsx
new file mode 100644
index 00000000000000..a34c3d7bf124f0
--- /dev/null
+++ b/x-pack/plugins/apm/public/components/shared/popover_tooltip/index.tsx
@@ -0,0 +1,41 @@
+/*
+ * 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 { EuiButtonIcon, EuiPopover } from '@elastic/eui';
+import React, { useState } from 'react';
+
+interface PopoverTooltipProps {
+ ariaLabel?: string;
+ children: React.ReactNode;
+}
+
+export function PopoverTooltip({ ariaLabel, children }: PopoverTooltipProps) {
+ const [isPopoverOpen, setIsPopoverOpen] = useState(false);
+
+ return (
+ setIsPopoverOpen(false)}
+ button={
+ ) => {
+ setIsPopoverOpen(!isPopoverOpen);
+ event.stopPropagation();
+ }}
+ size="xs"
+ color="primary"
+ iconType="questionInCircle"
+ style={{ height: 'auto' }}
+ />
+ }
+ >
+ {children}
+
+ );
+}
diff --git a/x-pack/plugins/apm/public/hooks/use_default_time_range.ts b/x-pack/plugins/apm/public/hooks/use_default_time_range.ts
new file mode 100644
index 00000000000000..39732025cbec55
--- /dev/null
+++ b/x-pack/plugins/apm/public/hooks/use_default_time_range.ts
@@ -0,0 +1,21 @@
+/*
+ * 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 { UI_SETTINGS } from '@kbn/data-plugin/public';
+import { TimePickerTimeDefaults } from '../components/shared/date_picker/typings';
+import { useApmPluginContext } from '../context/apm_plugin/use_apm_plugin_context';
+
+export function useDefaultTimeRange() {
+ const { core } = useApmPluginContext();
+
+ const { from: rangeFrom, to: rangeTo } =
+ core.uiSettings.get(
+ UI_SETTINGS.TIMEPICKER_TIME_DEFAULTS
+ );
+
+ return { rangeFrom, rangeTo };
+}
diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_instances.ts b/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_instances.ts
new file mode 100644
index 00000000000000..abebc4285a1e2d
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_instances.ts
@@ -0,0 +1,108 @@
+/*
+ * 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 { ProcessorEvent } from '@kbn/observability-plugin/common';
+import {
+ kqlQuery,
+ rangeQuery,
+ termQuery,
+} from '@kbn/observability-plugin/server';
+import { SERVICE_NODE_NAME_MISSING } from '../../../common/service_nodes';
+import {
+ AGENT_NAME,
+ AGENT_VERSION,
+ SERVICE_ENVIRONMENT,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+} from '../../../common/elasticsearch_fieldnames';
+import { environmentQuery } from '../../../common/utils/environment_query';
+import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
+
+const MAX_NUMBER_OF_SERVICE_NODES = 500;
+
+export async function getAgentInstances({
+ environment,
+ serviceName,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+}: {
+ environment: string;
+ serviceName?: string;
+ kuery: string;
+ apmEventClient: APMEventClient;
+ start: number;
+ end: number;
+}) {
+ const response = await apmEventClient.search('get_agent_instances', {
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ track_total_hits: false,
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: AGENT_NAME,
+ },
+ },
+ {
+ exists: {
+ field: AGENT_VERSION,
+ },
+ },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(serviceName ? termQuery(SERVICE_NAME, serviceName) : []),
+ ],
+ },
+ },
+ aggs: {
+ serviceNodes: {
+ terms: {
+ field: SERVICE_NODE_NAME,
+ missing: SERVICE_NODE_NAME_MISSING,
+ size: MAX_NUMBER_OF_SERVICE_NODES,
+ },
+ aggs: {
+ environments: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ },
+ },
+ sample: {
+ top_metrics: {
+ metrics: [{ field: AGENT_VERSION } as const],
+ sort: {
+ '@timestamp': 'desc' as const,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ return (
+ response.aggregations?.serviceNodes.buckets.map((agentInstance) => ({
+ serviceNode: agentInstance.key as string,
+ environments: agentInstance.environments.buckets.map(
+ (environmentBucket) => environmentBucket.key as string
+ ),
+ agentVersion: agentInstance.sample.top[0].metrics[
+ AGENT_VERSION
+ ] as string,
+ lastReport: agentInstance.sample.top[0].sort[0] as string,
+ })) ?? []
+ );
+}
diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_url_repository.ts b/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_url_repository.ts
new file mode 100644
index 00000000000000..2ec8d5b66c4c5c
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/agent_explorer/get_agent_url_repository.ts
@@ -0,0 +1,47 @@
+/*
+ * 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 { isOpenTelemetryAgentName } from '../../../common/agent_name';
+import { AgentName } from '../../../typings/es_schemas/ui/fields/agent';
+
+const agentsDocPageName: Partial> = {
+ go: 'go',
+ java: 'java',
+ 'js-base': 'rum-js',
+ 'iOS/swift': 'swift',
+ 'rum-js': 'rum-js',
+ nodejs: 'nodejs',
+ python: 'python',
+ dotnet: 'dotnet',
+ ruby: 'ruby',
+ php: 'php',
+ 'opentelemetry/cpp': 'cpp',
+ 'opentelemetry/dotnet': 'net',
+ 'opentelemetry/erlang': 'erlang',
+ 'opentelemetry/go': 'go',
+ 'opentelemetry/java': 'java',
+ 'opentelemetry/nodejs': 'js',
+ 'opentelemetry/php': 'php',
+ 'opentelemetry/python': 'python',
+ 'opentelemetry/ruby': 'ruby',
+ 'opentelemetry/swift': 'swift',
+ 'opentelemetry/webjs': 'js',
+};
+
+export const getAgentDocsPageUrl = (agentName: AgentName) => {
+ const agentDocsPageName = agentsDocPageName[agentName];
+
+ if (!agentDocsPageName) {
+ return undefined;
+ }
+
+ if (isOpenTelemetryAgentName(agentName)) {
+ return `https://opentelemetry.io/docs/instrumentation/${agentDocsPageName}`;
+ }
+
+ return `https://www.elastic.co/guide/en/apm/agent/${agentDocsPageName}/current/`;
+};
diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts
new file mode 100644
index 00000000000000..bce408391d661a
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents.ts
@@ -0,0 +1,50 @@
+/*
+ * 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 { AgentName } from '../../../typings/es_schemas/ui/fields/agent';
+import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
+import { RandomSampler } from '../../lib/helpers/get_random_sampler';
+import { getAgentsItems } from './get_agents_items';
+import { getAgentDocsPageUrl } from './get_agent_url_repository';
+
+export async function getAgents({
+ environment,
+ serviceName,
+ agentLanguage,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+ randomSampler,
+}: {
+ environment: string;
+ serviceName?: string;
+ agentLanguage?: string;
+ kuery: string;
+ apmEventClient: APMEventClient;
+ start: number;
+ end: number;
+ randomSampler: RandomSampler;
+}) {
+ const items = await getAgentsItems({
+ environment,
+ serviceName,
+ agentLanguage,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+ randomSampler,
+ });
+
+ return {
+ items: items.map((item) => ({
+ ...item,
+ agentDocsPageUrl: getAgentDocsPageUrl(item.agentName as AgentName),
+ })),
+ };
+}
diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/get_agents_items.ts b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents_items.ts
new file mode 100644
index 00000000000000..9fbb4a772e8907
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/agent_explorer/get_agents_items.ts
@@ -0,0 +1,136 @@
+/*
+ * 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 { ProcessorEvent } from '@kbn/observability-plugin/common/processor_event';
+import {
+ kqlQuery,
+ rangeQuery,
+ termQuery,
+} from '@kbn/observability-plugin/server/utils/queries';
+import {
+ AGENT_NAME,
+ AGENT_VERSION,
+ SERVICE_ENVIRONMENT,
+ SERVICE_LANGUAGE_NAME,
+ SERVICE_NAME,
+ SERVICE_NODE_NAME,
+} from '../../../common/elasticsearch_fieldnames';
+import { environmentQuery } from '../../../common/utils/environment_query';
+import { AgentName } from '../../../typings/es_schemas/ui/fields/agent';
+import { APMEventClient } from '../../lib/helpers/create_es_client/create_apm_event_client';
+import { RandomSampler } from '../../lib/helpers/get_random_sampler';
+import { MAX_NUMBER_OF_SERVICES } from '../services/get_services/get_services_items';
+
+interface AggregationParams {
+ environment: string;
+ serviceName?: string;
+ agentLanguage?: string;
+ kuery: string;
+ apmEventClient: APMEventClient;
+ start: number;
+ end: number;
+ randomSampler: RandomSampler;
+}
+
+export async function getAgentsItems({
+ environment,
+ agentLanguage,
+ serviceName,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+ randomSampler,
+}: AggregationParams) {
+ const response = await apmEventClient.search('get_agent_details', {
+ apm: {
+ events: [ProcessorEvent.metric],
+ },
+ body: {
+ track_total_hits: false,
+ size: 0,
+ query: {
+ bool: {
+ filter: [
+ {
+ exists: {
+ field: AGENT_NAME,
+ },
+ },
+ {
+ exists: {
+ field: AGENT_VERSION,
+ },
+ },
+ ...rangeQuery(start, end),
+ ...environmentQuery(environment),
+ ...kqlQuery(kuery),
+ ...(serviceName ? termQuery(SERVICE_NAME, serviceName) : []),
+ ...(agentLanguage
+ ? termQuery(SERVICE_LANGUAGE_NAME, agentLanguage)
+ : []),
+ ],
+ },
+ },
+ aggs: {
+ sample: {
+ random_sampler: randomSampler,
+ aggs: {
+ services: {
+ terms: {
+ field: SERVICE_NAME,
+ size: MAX_NUMBER_OF_SERVICES,
+ },
+ aggs: {
+ instances: {
+ cardinality: {
+ field: SERVICE_NODE_NAME,
+ },
+ },
+ agentVersions: {
+ terms: {
+ field: AGENT_VERSION,
+ },
+ },
+ sample: {
+ top_metrics: {
+ metrics: [{ field: AGENT_NAME } as const],
+ sort: {
+ '@timestamp': 'desc' as const,
+ },
+ },
+ },
+ environments: {
+ terms: {
+ field: SERVICE_ENVIRONMENT,
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ },
+ });
+
+ return (
+ response.aggregations?.sample.services.buckets.map((bucket) => {
+ return {
+ serviceName: bucket.key as string,
+ environments: bucket.environments.buckets.map(
+ (env) => env.key as string
+ ),
+ agentName: bucket.sample.top[0].metrics[AGENT_NAME] as AgentName,
+ agentVersion: bucket.agentVersions.buckets.map(
+ (version) => version.key as string
+ ),
+ // service.node.name is set by the server only if a container.id or host.name are set. Otherwise should be explicitly set by agents.
+ instances: (bucket.instances.value as number) || 1,
+ };
+ }) ?? []
+ );
+}
diff --git a/x-pack/plugins/apm/server/routes/agent_explorer/route.ts b/x-pack/plugins/apm/server/routes/agent_explorer/route.ts
new file mode 100644
index 00000000000000..ad55723be9aefa
--- /dev/null
+++ b/x-pack/plugins/apm/server/routes/agent_explorer/route.ts
@@ -0,0 +1,119 @@
+/*
+ * 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';
+import { getApmEventClient } from '../../lib/helpers/get_apm_event_client';
+import { getRandomSampler } from '../../lib/helpers/get_random_sampler';
+import { createApmServerRoute } from '../apm_routes/create_apm_server_route';
+import {
+ environmentRt,
+ kueryRt,
+ probabilityRt,
+ rangeRt,
+} from '../default_api_types';
+import { getAgents } from './get_agents';
+import { getAgentInstances } from './get_agent_instances';
+
+const agentExplorerRoute = createApmServerRoute({
+ endpoint: 'GET /internal/apm/get_agents_per_service',
+ options: { tags: ['access:apm'] },
+ params: t.type({
+ query: t.intersection([
+ environmentRt,
+ kueryRt,
+ rangeRt,
+ probabilityRt,
+ t.partial({
+ serviceName: t.string,
+ agentLanguage: t.string,
+ }),
+ ]),
+ }),
+ async handler(resources): Promise<{
+ items: Array<{
+ serviceName: string;
+ environments: string[];
+ agentName: import('./../../../typings/es_schemas/ui/fields/agent').AgentName;
+ agentVersion: string[];
+ agentDocsPageUrl?: string;
+ instances: number;
+ }>;
+ }> {
+ const {
+ params,
+ request,
+ plugins: { security },
+ } = resources;
+
+ const {
+ environment,
+ kuery,
+ start,
+ end,
+ probability,
+ serviceName,
+ agentLanguage,
+ } = params.query;
+
+ const [apmEventClient, randomSampler] = await Promise.all([
+ getApmEventClient(resources),
+ getRandomSampler({ security, request, probability }),
+ ]);
+
+ return getAgents({
+ environment,
+ serviceName,
+ agentLanguage,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+ randomSampler,
+ });
+ },
+});
+
+const agentExplorerInstanceRoute = createApmServerRoute({
+ endpoint: 'GET /internal/apm/services/{serviceName}/agent_instances',
+ options: { tags: ['access:apm'] },
+ params: t.type({
+ path: t.type({ serviceName: t.string }),
+ query: t.intersection([environmentRt, kueryRt, rangeRt, probabilityRt]),
+ }),
+ async handler(resources): Promise<{
+ items: Array<{
+ serviceNode?: string;
+ environments: string[];
+ agentVersion: string;
+ lastReport: string;
+ }>;
+ }> {
+ const { params } = resources;
+
+ const { environment, kuery, start, end } = params.query;
+
+ const { serviceName } = params.path;
+
+ const apmEventClient = await getApmEventClient(resources);
+
+ return {
+ items: await getAgentInstances({
+ environment,
+ serviceName,
+ kuery,
+ apmEventClient,
+ start,
+ end,
+ }),
+ };
+ },
+});
+
+export const agentExplorerRouteRepository = {
+ ...agentExplorerRoute,
+ ...agentExplorerInstanceRoute,
+};
diff --git a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts
index 84ef941462b39d..91e78880bf5481 100644
--- a/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts
+++ b/x-pack/plugins/apm/server/routes/apm_routes/get_global_apm_server_route_repository.ts
@@ -10,12 +10,13 @@ import type {
ServerRouteRepository,
} from '@kbn/server-route-repository';
import { PickByValue } from 'utility-types';
+import { agentExplorerRouteRepository } from '../agent_explorer/route';
import { agentKeysRouteRepository } from '../agent_keys/route';
import { alertsChartPreviewRouteRepository } from '../alerts/route';
-import { dependencisRouteRepository } from '../dependencies/route';
import { correlationsRouteRepository } from '../correlations/route';
import { dataViewRouteRepository } from '../data_view/route';
import { debugTelemetryRoute } from '../debug_telemetry/route';
+import { dependencisRouteRepository } from '../dependencies/route';
import { environmentsRouteRepository } from '../environments/route';
import { errorsRouteRepository } from '../errors/route';
import { eventMetadataRouteRepository } from '../event_metadata/route';
@@ -25,6 +26,7 @@ import { historicalDataRouteRepository } from '../historical_data/route';
import { infrastructureRouteRepository } from '../infrastructure/route';
import { latencyDistributionRouteRepository } from '../latency_distribution/route';
import { metricsRouteRepository } from '../metrics/route';
+import { mobileRouteRepository } from '../mobile/route';
import { observabilityOverviewRouteRepository } from '../observability_overview/route';
import { serviceRouteRepository } from '../services/route';
import { serviceGroupRouteRepository } from '../service_groups/route';
@@ -33,15 +35,14 @@ import { agentConfigurationRouteRepository } from '../settings/agent_configurati
import { anomalyDetectionRouteRepository } from '../settings/anomaly_detection/route';
import { apmIndicesRouteRepository } from '../settings/apm_indices/route';
import { customLinkRouteRepository } from '../settings/custom_link/route';
+import { labsRouteRepository } from '../settings/labs/route';
import { sourceMapsRouteRepository } from '../source_maps/route';
import { spanLinksRouteRepository } from '../span_links/route';
+import { storageExplorerRouteRepository } from '../storage_explorer/route';
import { suggestionsRouteRepository } from '../suggestions/route';
import { timeRangeMetadataRoute } from '../time_range_metadata/route';
import { traceRouteRepository } from '../traces/route';
import { transactionRouteRepository } from '../transactions/route';
-import { storageExplorerRouteRepository } from '../storage_explorer/route';
-import { labsRouteRepository } from '../settings/labs/route';
-import { mobileRouteRepository } from '../mobile/route';
function getTypedGlobalApmServerRouteRepository() {
const repository = {
@@ -76,6 +77,7 @@ function getTypedGlobalApmServerRouteRepository() {
...debugTelemetryRoute,
...timeRangeMetadataRoute,
...labsRouteRepository,
+ ...agentExplorerRouteRepository,
...mobileRouteRepository,
};
diff --git a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts
index b72ab6f2860052..b489f811ab511a 100644
--- a/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts
+++ b/x-pack/plugins/apm/server/routes/services/get_services/get_services_items.ts
@@ -17,7 +17,7 @@ import { ServiceGroup } from '../../../../common/service_groups';
import { RandomSampler } from '../../../lib/helpers/get_random_sampler';
import { APMEventClient } from '../../../lib/helpers/create_es_client/create_apm_event_client';
-const MAX_NUMBER_OF_SERVICES = 500;
+export const MAX_NUMBER_OF_SERVICES = 500;
export async function getServicesItems({
environment,
diff --git a/x-pack/plugins/observability/common/index.ts b/x-pack/plugins/observability/common/index.ts
index a8332262eda946..4c48bd938562f5 100644
--- a/x-pack/plugins/observability/common/index.ts
+++ b/x-pack/plugins/observability/common/index.ts
@@ -26,6 +26,7 @@ export {
enableInfrastructureHostsView,
enableServiceMetrics,
enableAwsLambdaMetrics,
+ enableAgentExplorerView,
apmAWSLambdaPriceFactor,
apmAWSLambdaRequestCostPerMillion,
enableCriticalPath,
diff --git a/x-pack/plugins/observability/common/ui_settings_keys.ts b/x-pack/plugins/observability/common/ui_settings_keys.ts
index 52258c5711d1c3..a5af57e3453cac 100644
--- a/x-pack/plugins/observability/common/ui_settings_keys.ts
+++ b/x-pack/plugins/observability/common/ui_settings_keys.ts
@@ -21,6 +21,7 @@ export const apmLabsButton = 'observability:apmLabsButton';
export const enableInfrastructureHostsView = 'observability:enableInfrastructureHostsView';
export const enableAwsLambdaMetrics = 'observability:enableAwsLambdaMetrics';
export const enableServiceMetrics = 'observability:apmEnableServiceMetrics';
+export const enableAgentExplorerView = 'observability:apmAgentExplorerView';
export const apmAWSLambdaPriceFactor = 'observability:apmAWSLambdaPriceFactor';
export const apmAWSLambdaRequestCostPerMillion = 'observability:apmAWSLambdaRequestCostPerMillion';
export const enableCriticalPath = 'observability:apmEnableCriticalPath';
diff --git a/x-pack/plugins/observability/public/index.ts b/x-pack/plugins/observability/public/index.ts
index dcd7a1cd08c93f..eefdb546a060ed 100644
--- a/x-pack/plugins/observability/public/index.ts
+++ b/x-pack/plugins/observability/public/index.ts
@@ -30,6 +30,7 @@ export {
enableNewSyntheticsView,
apmServiceGroupMaxNumberOfServices,
enableInfrastructureHostsView,
+ enableAgentExplorerView,
} from '../common/ui_settings_keys';
export { uptimeOverviewLocatorID } from '../common';
diff --git a/x-pack/plugins/observability/server/ui_settings.ts b/x-pack/plugins/observability/server/ui_settings.ts
index 13d1fc645be71b..52d7bb1e66a967 100644
--- a/x-pack/plugins/observability/server/ui_settings.ts
+++ b/x-pack/plugins/observability/server/ui_settings.ts
@@ -6,8 +6,8 @@
*/
import { schema } from '@kbn/config-schema';
-import { i18n } from '@kbn/i18n';
import { UiSettingsParams } from '@kbn/core/types';
+import { i18n } from '@kbn/i18n';
import { observabilityFeatureId, ProgressiveLoadingQuality } from '../common';
import {
enableComparisonByDefault,
@@ -21,12 +21,13 @@ import {
apmTraceExplorerTab,
apmOperationsTab,
apmLabsButton,
- enableInfrastructureHostsView,
- enableServiceMetrics,
+ enableAgentExplorerView,
enableAwsLambdaMetrics,
apmAWSLambdaPriceFactor,
apmAWSLambdaRequestCostPerMillion,
enableCriticalPath,
+ enableInfrastructureHostsView,
+ enableServiceMetrics,
} from '../common/ui_settings_keys';
const technicalPreviewLabel = i18n.translate(
@@ -294,6 +295,23 @@ export const uiSettings: Record = {
type: 'boolean',
showInLabs: true,
},
+ [enableAgentExplorerView]: {
+ category: [observabilityFeatureId],
+ name: i18n.translate('xpack.observability.enableAgentExplorer', {
+ defaultMessage: 'Agent explorer',
+ }),
+ description: i18n.translate('xpack.observability.enableAgentExplorerDescription', {
+ defaultMessage: '{technicalPreviewLabel} Enables Agent explorer view.',
+ values: {
+ technicalPreviewLabel: `[${technicalPreviewLabel}]`,
+ },
+ }),
+ schema: schema.boolean(),
+ value: false,
+ requiresPageReload: true,
+ type: 'boolean',
+ showInLabs: true,
+ },
[apmAWSLambdaPriceFactor]: {
category: [observabilityFeatureId],
name: i18n.translate('xpack.observability.apmAWSLambdaPricePerGbSeconds', {
diff --git a/x-pack/test/apm_api_integration/common/config.ts b/x-pack/test/apm_api_integration/common/config.ts
index 8b98fc9422c069..86b5ae98201999 100644
--- a/x-pack/test/apm_api_integration/common/config.ts
+++ b/x-pack/test/apm_api_integration/common/config.ts
@@ -5,20 +5,25 @@
* 2.0.
*/
-import { FtrConfigProviderContext } from '@kbn/test';
-import supertest from 'supertest';
-import { format, UrlObject } from 'url';
import {
ApmUsername,
APM_TEST_PASSWORD,
} from '@kbn/apm-plugin/server/test_helpers/create_apm_users/authentication';
import { createApmUsers } from '@kbn/apm-plugin/server/test_helpers/create_apm_users/create_apm_users';
-import { InheritedFtrProviderContext, InheritedServices } from './ftr_provider_context';
+import { ApmSynthtraceEsClient } from '@kbn/apm-synthtrace';
+import { FtrConfigProviderContext } from '@kbn/test';
+import supertest from 'supertest';
+import { format, UrlObject } from 'url';
+import { MachineLearningAPIProvider } from '../../functional/services/ml/api';
import { APMFtrConfigName } from '../configs';
import { createApmApiClient } from './apm_api_supertest';
-import { RegistryProvider } from './registry';
import { bootstrapApmSynthtrace } from './bootstrap_apm_synthtrace';
-import { MachineLearningAPIProvider } from '../../functional/services/ml/api';
+import {
+ FtrProviderContext,
+ InheritedFtrProviderContext,
+ InheritedServices,
+} from './ftr_provider_context';
+import { RegistryProvider } from './registry';
export interface ApmFtrConfig {
name: APMFtrConfigName;
@@ -43,7 +48,37 @@ async function getApmApiClient({
export type CreateTestConfig = ReturnType;
-export function createTestConfig(config: ApmFtrConfig) {
+type ApmApiClientKey =
+ | 'noAccessUser'
+ | 'readUser'
+ | 'writeUser'
+ | 'annotationWriterUser'
+ | 'noMlAccessUser'
+ | 'manageOwnAgentKeysUser'
+ | 'createAndAllAgentKeysUser'
+ | 'monitorClusterAndIndicesUser';
+
+export interface CreateTest {
+ testFiles: string[];
+ servers: any;
+ servicesRequiredForTestAnalysis: string[];
+ services: InheritedServices & {
+ apmFtrConfig: () => ApmFtrConfig;
+ registry: ({ getService }: FtrProviderContext) => ReturnType;
+ synthtraceEsClient: (context: InheritedFtrProviderContext) => Promise;
+ apmApiClient: (
+ context: InheritedFtrProviderContext
+ ) => Record>>;
+ ml: ({ getService }: FtrProviderContext) => ReturnType;
+ };
+ junit: { reportName: string };
+ esTestCluster: any;
+ kbnTestServer: any;
+}
+
+export function createTestConfig(
+ config: ApmFtrConfig
+): ({ readConfigFile }: FtrConfigProviderContext) => Promise {
const { license, name, kibanaConfig } = config;
return async ({ readConfigFile }: FtrConfigProviderContext) => {
@@ -51,7 +86,7 @@ export function createTestConfig(config: ApmFtrConfig) {
require.resolve('../../api_integration/config.ts')
);
- const services = xPackAPITestsConfig.get('services') as InheritedServices;
+ const services = xPackAPITestsConfig.get('services');
const servers = xPackAPITestsConfig.get('servers');
const kibanaServer = servers.kibana as UrlObject;
const kibanaServerUrl = format(kibanaServer);
diff --git a/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts b/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
new file mode 100644
index 00000000000000..016b699083b727
--- /dev/null
+++ b/x-pack/test/apm_api_integration/tests/agent_explorer/agent_explorer.spec.ts
@@ -0,0 +1,191 @@
+/*
+ * 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 expect from '@kbn/expect';
+import { apm, timerange } from '@kbn/apm-synthtrace';
+import { APIClientRequestParamsOf } from '@kbn/apm-plugin/public/services/rest/create_call_apm_api';
+import { RecursivePartial } from '@kbn/apm-plugin/typings/common';
+import { keyBy } from 'lodash';
+import { FtrProviderContext } from '../../common/ftr_provider_context';
+
+export default function ApiTest({ getService }: FtrProviderContext) {
+ const registry = getService('registry');
+ const apmApiClient = getService('apmApiClient');
+ const synthtraceEsClient = getService('synthtraceEsClient');
+
+ const start = new Date('2021-01-01T00:00:00.000Z').getTime();
+ const end = new Date('2021-01-01T00:15:00.000Z').getTime() - 1;
+ const goServiceName = 'opbeans-go';
+ const nodeServiceName = 'opbeans-node';
+
+ async function callApi(
+ overrides?: RecursivePartial<
+ APIClientRequestParamsOf<'GET /internal/apm/get_agents_per_service'>['params']
+ >
+ ) {
+ return await apmApiClient.readUser({
+ endpoint: 'GET /internal/apm/get_agents_per_service',
+ params: {
+ query: {
+ probability: 1,
+ environment: 'ENVIRONMENT_ALL',
+ start: new Date(start).toISOString(),
+ end: new Date(end).toISOString(),
+ kuery: '',
+ ...overrides?.query,
+ },
+ },
+ });
+ }
+
+ registry.when('Agent explorer when data is not loaded', { config: 'basic', archives: [] }, () => {
+ it('handles empty state', async () => {
+ const { status, body } = await callApi();
+
+ expect(status).to.be(200);
+ expect(body.items).to.be.empty();
+ });
+ });
+
+ registry.when('Agent explorer', { config: 'basic', archives: [] }, () => {
+ describe('when data is loaded', () => {
+ before(async () => {
+ const serviceGo = apm
+ .service({
+ name: goServiceName,
+ environment: 'production',
+ agentName: 'go',
+ })
+ .instance('instance-go')
+ .defaults({
+ 'agent.version': '5.1.2',
+ 'service.language.name': 'go',
+ });
+
+ const serviceNodeStaging = apm
+ .service({
+ name: nodeServiceName,
+ environment: 'staging',
+ agentName: 'nodejs',
+ })
+ .instance('instance-node-staging')
+ .defaults({
+ 'agent.version': '1.0.0',
+ 'service.language.name': 'javascript',
+ });
+
+ const serviceNodeDev = apm
+ .service({
+ name: nodeServiceName,
+ environment: 'dev',
+ agentName: 'nodejs',
+ })
+ .instance('instance-node-dev')
+ .defaults({
+ 'agent.version': '1.0.3',
+ 'service.language.name': 'javascript',
+ });
+
+ await synthtraceEsClient.index([
+ timerange(start, end)
+ .interval('5m')
+ .rate(1)
+ .generator((timestamp) =>
+ serviceGo
+ .transaction({ transactionName: 'GET /api/product/list' })
+ .duration(2000)
+ .timestamp(timestamp)
+ ),
+ timerange(start, end)
+ .interval('5m')
+ .rate(1)
+ .generator((timestamp) =>
+ serviceNodeStaging
+ .transaction({ transactionName: 'GET /api/users/list' })
+ .duration(2000)
+ .timestamp(timestamp)
+ ),
+ timerange(start, end)
+ .interval('5m')
+ .rate(1)
+ .generator((timestamp) =>
+ serviceNodeDev
+ .transaction({ transactionName: 'GET /api/users/list' })
+ .duration(2000)
+ .timestamp(timestamp)
+ ),
+ ]);
+ });
+
+ after(() => synthtraceEsClient.clean());
+
+ it('returns correct agents information', async () => {
+ const { status, body } = await callApi();
+ expect(status).to.be(200);
+ expect(body.items).to.have.length(2);
+
+ const agents = keyBy(body.items, 'serviceName');
+
+ const goAgent = agents[goServiceName];
+ expect(goAgent?.environments).to.have.length(1);
+ expect(goAgent?.environments).to.contain('production');
+ expect(goAgent?.agentName).to.be('go');
+ expect(goAgent?.agentVersion).to.contain('5.1.2');
+ expect(goAgent?.agentDocsPageUrl).to.be(
+ 'https://www.elastic.co/guide/en/apm/agent/go/current/'
+ );
+
+ const nodeAgent = agents[nodeServiceName];
+ expect(nodeAgent?.environments).to.have.length(2);
+ expect(nodeAgent?.environments).to.contain('staging');
+ expect(nodeAgent?.environments).to.contain('dev');
+ expect(nodeAgent?.agentName).to.be('nodejs');
+ expect(nodeAgent?.agentVersion).to.contain('1.0.0');
+ expect(nodeAgent?.agentVersion).to.contain('1.0.3');
+ expect(nodeAgent?.agentDocsPageUrl).to.be(
+ 'https://www.elastic.co/guide/en/apm/agent/nodejs/current/'
+ );
+ });
+
+ const matchingFilterTests = [
+ ['environment', 'dev', nodeServiceName],
+ ['serviceName', nodeServiceName, nodeServiceName],
+ ['agentLanguage', 'go', goServiceName],
+ ['kuery', `service.name : ${goServiceName}`, goServiceName],
+ ];
+
+ matchingFilterTests.forEach(([filterName, filterValue, expectedService]) => {
+ it(`returns only agents matching selected ${filterName}`, async () => {
+ const { status, body } = await callApi({
+ query: {
+ [filterName]: filterValue,
+ },
+ });
+ expect(status).to.be(200);
+ expect(body.items).to.have.length(1);
+ expect(body.items[0]?.serviceName).to.be(expectedService);
+ });
+ });
+
+ const notMatchingFilterTests = [
+ ['serviceName', 'my-service'],
+ ['agentLanguage', 'my-language'],
+ ];
+
+ notMatchingFilterTests.forEach(([filterName, filterValue]) => {
+ it(`returns empty agents when there is no matching ${filterName}`, async () => {
+ const { status, body } = await callApi({
+ query: {
+ [filterName]: filterValue,
+ },
+ });
+ expect(status).to.be(200);
+ expect(body.items).to.be.empty();
+ });
+ });
+ });
+ });
+}
From 2c996970fca9ebc6b3ca4ba47be90910bf961d2d Mon Sep 17 00:00:00 2001
From: Michael Katsoulis
Date: Mon, 14 Nov 2022 16:56:29 +0200
Subject: [PATCH 12/20] Set the correct enrollment token in case of multi page
layout in k8s manifest (#145098)
## Summary
Setting the correct enrolment token to kubernetes manifest in case of
multi page layout steps when kubernetes has been selected as a platform.
### Checklist
Delete any items that are not applicable to this PR.
- [ ] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [ ]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [ ] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [ ] Any UI touched in this PR is usable by keyboard only (learn more
about [keyboard accessibility](https://webaim.org/techniques/keyboard/))
- [ ] Any UI touched in this PR does not create any new axe failures
(run axe in browser:
[FF](https://addons.mozilla.org/en-US/firefox/addon/axe-devtools/),
[Chrome](https://chrome.google.com/webstore/detail/axe-web-accessibility-tes/lhdoppojpmngadmnindnejefpokejbdd?hl=en-US))
- [ ] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)
- [ ] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [ ] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
Closes https://github.com/elastic/kibana/issues/145072
---
.../page_steps/install_agent/install_agent_managed.tsx | 1 +
1 file changed, 1 insertion(+)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx
index ecbda47baad793..ec78655ce7fede 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/page_steps/install_agent/install_agent_managed.tsx
@@ -64,6 +64,7 @@ export const InstallElasticAgentManagedPageStep: React.FC
InstallManagedAgentStep({
installCommand: installManagedCommands,
apiKeyData: { item: enrollmentAPIKey },
+ enrollToken: enrollmentAPIKey.api_key,
selectedApiKeyId: enrollmentAPIKey.id,
isComplete: commandCopied || !!enrolledAgentIds.length,
fullCopyButton: true,
From 6ac78d740e65a929809c18a657ca463e19344e98 Mon Sep 17 00:00:00 2001
From: Jonathan Buttner <56361221+jonathan-buttner@users.noreply.github.com>
Date: Mon, 14 Nov 2022 10:05:46 -0500
Subject: [PATCH 13/20] [ResponseOps][Actions] Don't show rule.tags for test
mode (#145001)
This PR fixes a bug where the `rule.tags` were shown as an option in the
connector test mode. The test mode doesn't provide variables so it
shouldn't be shown.
### Rule Form
Still available when in the rule flow
![image](https://user-images.githubusercontent.com/56361221/201150790-0d23f0f7-4fb8-4fe1-973a-e4afde297192.png)
### Test Mode
Not available in the test mode
![image](https://user-images.githubusercontent.com/56361221/201150621-52e07b7f-ef97-42d9-80f1-eced195b7a9a.png)
---
.../stack/opsgenie/create_alert/index.tsx | 10 +++++--
.../stack/opsgenie/create_alert/tags.test.tsx | 29 +++++++++++++------
.../stack/opsgenie/create_alert/tags.tsx | 7 +++--
3 files changed, 33 insertions(+), 13 deletions(-)
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/index.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/index.tsx
index e5ca5c3741f5d4..75e67debc81437 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/index.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/index.tsx
@@ -44,6 +44,7 @@ const FormView: React.FC = ({
messageVariables,
subActionParams,
showSaveError,
+ executionMode,
}) => {
const isMessageInvalid =
(errors['subActionParams.message'] !== undefined &&
@@ -72,7 +73,11 @@ const FormView: React.FC = ({
-
+
@@ -104,7 +109,7 @@ FormView.displayName = 'FormView';
export type CreateAlertProps = Pick<
ActionParamsProps,
- 'errors' | 'index' | 'messageVariables' | 'editAction'
+ 'errors' | 'index' | 'messageVariables' | 'editAction' | 'executionMode'
> & {
subActionParams?: Partial;
editSubAction: EditActionCallback;
@@ -121,6 +126,7 @@ const CreateAlertComponent: React.FC = ({
messageVariables,
subActionParams,
showSaveError,
+ executionMode,
}) => {
const [showingMoreOptions, setShowingMoreOptions] = useState(false);
const [showJsonEditor, setShowJsonEditor] = useState(false);
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.test.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.test.tsx
index be010274f4179e..dcfd95aab0caf9 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.test.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.test.tsx
@@ -9,6 +9,7 @@ import React from 'react';
import { screen, render, waitFor, act } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Tags } from './tags';
+import { ActionConnectorMode } from '@kbn/triggers-actions-ui-plugin/public';
describe('Tags', () => {
const onChange = jest.fn();
@@ -16,6 +17,7 @@ describe('Tags', () => {
const options = {
values: [],
onChange,
+ executionMode: ActionConnectorMode.ActionForm,
};
beforeEach(() => jest.clearAllMocks());
@@ -74,9 +76,7 @@ describe('Tags', () => {
expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled();
});
- act(() => {
- userEvent.click(screen.getByTestId('comboBoxSearchInput'));
- });
+ userEvent.click(screen.getByTestId('comboBoxSearchInput'));
userEvent.type(screen.getByTestId('comboBoxSearchInput'), 'awesome{enter}');
@@ -99,9 +99,7 @@ describe('Tags', () => {
expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled();
});
- act(() => {
- userEvent.click(screen.getByTestId('comboBoxSearchInput'));
- });
+ userEvent.click(screen.getByTestId('comboBoxSearchInput'));
await waitFor(() => {
expect(screen.getByTestId('opsgenie-tags-rule-tags')).toBeInTheDocument();
@@ -116,9 +114,7 @@ describe('Tags', () => {
expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled();
});
- act(() => {
- userEvent.click(screen.getByTestId('comboBoxSearchInput'));
- });
+ userEvent.click(screen.getByTestId('comboBoxSearchInput'));
await waitFor(() => {
expect(screen.getByTestId('opsgenie-tags-rule-tags')).toBeInTheDocument();
@@ -140,4 +136,19 @@ describe('Tags', () => {
`)
);
});
+
+ it('does not contain the rule.tags option when in test mode', async () => {
+ render();
+
+ await waitFor(() => {
+ expect(screen.getByTestId('comboBoxSearchInput')).not.toBeDisabled();
+ });
+
+ userEvent.click(screen.getByTestId('comboBoxSearchInput'));
+
+ await waitFor(() => {
+ expect(screen.queryByTestId('opsgenie-tags-rule-tags')).not.toBeInTheDocument();
+ expect(screen.queryByText('The tags of the rule.')).not.toBeInTheDocument();
+ });
+ });
});
diff --git a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.tsx b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.tsx
index cf393b543c14a3..ffaa3a5460f909 100644
--- a/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.tsx
+++ b/x-pack/plugins/stack_connectors/public/connector_types/stack/opsgenie/create_alert/tags.tsx
@@ -17,6 +17,8 @@ import {
EuiTextColor,
} from '@elastic/eui';
+import { ActionConnectorMode, ActionParamsProps } from '@kbn/triggers-actions-ui-plugin/public';
+import type { OpsgenieActionParams } from '../../../../../server/connector_types/stack';
import { RULE_TAGS_TEMPLATE } from '../../../../../common/opsgenie';
import * as i18n from './translations';
import { EditActionCallback } from '../types';
@@ -24,6 +26,7 @@ import { EditActionCallback } from '../types';
interface TagsProps {
onChange: EditActionCallback;
values: string[];
+ executionMode: ActionParamsProps['executionMode'];
}
const options: Array> = [
@@ -35,7 +38,7 @@ const options: Array> = [
},
];
-const TagsComponent: React.FC = ({ onChange, values }) => {
+const TagsComponent: React.FC = ({ onChange, values, executionMode }) => {
const tagOptions = useMemo(() => values.map((value) => getTagAsOption(value)), [values]);
const onCreateOption = useCallback(
@@ -85,7 +88,7 @@ const TagsComponent: React.FC = ({ onChange, values }) => {
rowHeight={50}
fullWidth
isClearable
- options={options}
+ options={executionMode === ActionConnectorMode.ActionForm ? options : undefined}
selectedOptions={tagOptions}
onCreateOption={onCreateOption}
onChange={onTagsChange}
From 94437f8addaba2656525c1ca8aad99044528d4aa Mon Sep 17 00:00:00 2001
From: Muhammad Ibragimov <53621505+mibragimov@users.noreply.github.com>
Date: Mon, 14 Nov 2022 20:10:53 +0500
Subject: [PATCH 14/20] [Guided onboarding] Fix card footer button markup
(#145050)
---
.../guide_card_footer.test.tsx.snap | 126 ++++++++++--------
.../landing_page/guide_card_footer.tsx | 46 ++++---
2 files changed, 98 insertions(+), 74 deletions(-)
diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap
index 74be4594642a54..6f9f2dcc9e2e3a 100644
--- a/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap
+++ b/packages/kbn-guided-onboarding/src/components/landing_page/__snapshots__/guide_card_footer.test.tsx.snap
@@ -12,8 +12,32 @@ exports[`guide card footer snapshots should render the footer when the guide has
-
+
+
+ View guide
+
+
+
+
+`;
+
+exports[`guide card footer snapshots should render the footer when the guide has not started yet 1`] = `
+
+
View guide
-
-
-`;
-
-exports[`guide card footer snapshots should render the footer when the guide has not started yet 1`] = `
-
-
- View guide
-
-
+
+
`;
exports[`guide card footer snapshots should render the footer when the guide is in progress 1`] = `
@@ -69,19 +77,23 @@ exports[`guide card footer snapshots should render the footer when the guide is
-
-
- Continue
-
-
+
+ Continue
+
+
+
`;
@@ -110,34 +122,42 @@ exports[`guide card footer snapshots should render the footer when the guide is
-
-
- Continue
-
-
+
+ Continue
+
+
+
`;
exports[`guide card footer snapshots should render the footer when the guided onboarding has not started yet 1`] = `
-
-
- View guide
-
-
+
+ View guide
+
+
+
`;
diff --git a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx
index b123e86688811f..b109db15c0abad 100644
--- a/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx
+++ b/packages/kbn-guided-onboarding/src/components/landing_page/guide_card_footer.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { css } from '@emotion/react';
-import { EuiButton, EuiProgress, EuiSpacer } from '@elastic/eui';
+import { EuiButton, EuiProgress, EuiSpacer, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { GuideId, GuideState } from '../../types';
import { UseCase } from './use_case_card';
@@ -54,16 +54,18 @@ export interface GuideCardFooterProps {
export const GuideCardFooter = ({ guides, useCase, activateGuide }: GuideCardFooterProps) => {
const guideState = guides.find((guide) => guide.guideId === (useCase as GuideId));
const viewGuideButton = (
-
- activateGuide(useCase, guideState)}
- >
- {viewGuideLabel}
-
-
+
+
+ activateGuide(useCase, guideState)}
+ >
+ {viewGuideLabel}
+
+
+
);
// guide has not started yet
if (!guideState || guideState.status === 'not_started') {
@@ -108,16 +110,18 @@ export const GuideCardFooter = ({ guides, useCase, activateGuide }: GuideCardFoo
}}
/>
-
- activateGuide(useCase, guideState)}
- >
- {continueGuideLabel}
-
-
+
+
+ activateGuide(useCase, guideState)}
+ >
+ {continueGuideLabel}
+
+
+
>
);
};
From 046543209e38a842cf5504950e8b00feb849b724 Mon Sep 17 00:00:00 2001
From: Alison Goryachev
Date: Mon, 14 Nov 2022 10:21:41 -0500
Subject: [PATCH 15/20] [Guided onboarding] Address design feedback (#144957)
---
.../public/components/guide_button_popover.tsx | 1 +
.../home/public/application/components/add_data/add_data.tsx | 2 +-
.../fleet/public/components/with_guided_onboarding_tour.tsx | 2 +-
3 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/src/plugins/guided_onboarding/public/components/guide_button_popover.tsx b/src/plugins/guided_onboarding/public/components/guide_button_popover.tsx
index fe341108f089eb..658d78f81ebbcb 100644
--- a/src/plugins/guided_onboarding/public/components/guide_button_popover.tsx
+++ b/src/plugins/guided_onboarding/public/components/guide_button_popover.tsx
@@ -39,6 +39,7 @@ export const GuideButtonPopover = ({
data-test-subj="manualCompletionPopover"
button={button}
isOpen={isPopoverShown}
+ repositionOnScroll
closePopover={() => {
/* do nothing, the popover is closed once the panel is opened */
}}
diff --git a/src/plugins/home/public/application/components/add_data/add_data.tsx b/src/plugins/home/public/application/components/add_data/add_data.tsx
index a3cdbd92410202..fdfc286a5cbec3 100644
--- a/src/plugins/home/public/application/components/add_data/add_data.tsx
+++ b/src/plugins/home/public/application/components/add_data/add_data.tsx
@@ -81,7 +81,7 @@ export const AddData: FC = ({ addBasePath, application, isDarkMode, isClo
>
diff --git a/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx b/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx
index ba4afddf9cc991..b58f3fb5009e5e 100644
--- a/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx
+++ b/x-pack/plugins/fleet/public/components/with_guided_onboarding_tour.tsx
@@ -85,7 +85,7 @@ export const WithGuidedOnboardingTour: FunctionComponent<{
return config ? (
{config.description}}
+ content={{config.description}}
isStepOpen={isGuidedOnboardingTourOpen}
maxWidth={350}
onFinish={() => setIsGuidedOnboardingTourOpen(false)}
From 6b6cdf8ab7eedd1d6a93bd0815ae8f416f47c239 Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Mon, 14 Nov 2022 10:22:02 -0500
Subject: [PATCH 16/20] [Security Solution][Endpoint] Misc. updates in support
of `get-file` response action (#144948)
## Summary
- Updates the `get-file` action response `outputs` to match latest from
endpoint
- Fix server size `doesFileHanveChunks()` and remove the `.keyword` from
the search field term (index mapping will be setup correctly for these
indexes)
- Updates the names of the File storage indexes
- Sets the `endpointRbacV1Enabled` FF to `true` (enables feature by
default)
- Uses Fleet exposed function utilities to retrieve the indexes for
File's metadata and data chunks
The following Fleet changes were also done
- Created common methods in fleet for retrieving the file metadata and
data indexes using an integration name (should protect us against index
names going forward and avoid having integrations in kibana keep
hard-coded values)
- Removed the .keyword from a few places in the file server service
(still need to test)
- Adjusted both the Fleet and the Security Solution code to use the new
methods for getting the integration specific index names (cc/
@juliaElastic )
---
.../fleet/common/constants/file_storage.ts | 12 ++++
.../plugins/fleet/common/constants/index.ts | 1 +
x-pack/plugins/fleet/common/index.ts | 2 +
.../fleet/common/services/file_storage.ts | 65 +++++++++++++++++++
x-pack/plugins/fleet/common/services/index.ts | 2 +
.../fleet/server/constants/fleet_es_assets.ts | 8 ++-
.../plugins/fleet/server/constants/index.ts | 2 +
.../fleet/server/services/agents/uploads.ts | 11 ++--
.../fleet/server/services/files/index.test.ts | 17 +++--
.../fleet/server/services/files/index.ts | 18 +++--
.../tasks/check_deleted_files_task.test.ts | 18 ++---
.../common/endpoint/constants.ts | 6 +-
.../endpoint_action_generator.ts | 11 +++-
.../common/endpoint/types/actions.ts | 10 ++-
.../common/experimental_features.ts | 2 +-
.../pages/integration_tests/index.test.tsx | 2 +-
.../services/endpoint_response_actions.ts | 23 ++++---
.../endpoint/services/actions/action_files.ts | 4 +-
.../endpoint/services/actions/utils.test.ts | 11 +++-
.../apis/security/privileges.ts | 12 +++-
.../apis/security/privileges_basic.ts | 12 +++-
.../apis/agents/uploads.ts | 8 ++-
22 files changed, 203 insertions(+), 54 deletions(-)
create mode 100644 x-pack/plugins/fleet/common/constants/file_storage.ts
create mode 100644 x-pack/plugins/fleet/common/services/file_storage.ts
diff --git a/x-pack/plugins/fleet/common/constants/file_storage.ts b/x-pack/plugins/fleet/common/constants/file_storage.ts
new file mode 100644
index 00000000000000..a1988570a58737
--- /dev/null
+++ b/x-pack/plugins/fleet/common/constants/file_storage.ts
@@ -0,0 +1,12 @@
+/*
+ * 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.
+ */
+
+// File storage indexes supporting endpoint Upload/download
+// If needing to get an integration specific index name, use the utility functions
+// found in `common/services/file_storage`
+export const FILE_STORAGE_METADATA_INDEX_PATTERN = '.fleet-files-*';
+export const FILE_STORAGE_DATA_INDEX_PATTERN = '.fleet-file-data-*';
diff --git a/x-pack/plugins/fleet/common/constants/index.ts b/x-pack/plugins/fleet/common/constants/index.ts
index 3aeed30c7e41be..f42b2a372ebb63 100644
--- a/x-pack/plugins/fleet/common/constants/index.ts
+++ b/x-pack/plugins/fleet/common/constants/index.ts
@@ -18,6 +18,7 @@ export * from './preconfiguration';
export * from './download_source';
export * from './fleet_server_policy_config';
export * from './authz';
+export * from './file_storage';
// TODO: This is the default `index.max_result_window` ES setting, which dictates
// the maximum amount of results allowed to be returned from a search. It's possible
diff --git a/x-pack/plugins/fleet/common/index.ts b/x-pack/plugins/fleet/common/index.ts
index e8995b4bf6b741..626a40885b502f 100644
--- a/x-pack/plugins/fleet/common/index.ts
+++ b/x-pack/plugins/fleet/common/index.ts
@@ -65,6 +65,8 @@ export {
INVALID_NAMESPACE_CHARACTERS,
// TODO Should probably not be exposed by Fleet
decodeCloudId,
+ getFileMetadataIndexName,
+ getFileDataIndexName,
} from './services';
export type { FleetAuthz } from './authz';
diff --git a/x-pack/plugins/fleet/common/services/file_storage.ts b/x-pack/plugins/fleet/common/services/file_storage.ts
new file mode 100644
index 00000000000000..4c5e9ac204c58f
--- /dev/null
+++ b/x-pack/plugins/fleet/common/services/file_storage.ts
@@ -0,0 +1,65 @@
+/*
+ * 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 { FILE_STORAGE_DATA_INDEX_PATTERN, FILE_STORAGE_METADATA_INDEX_PATTERN } from '../constants';
+
+/**
+ * Returns the index name for File Metadata storage for a given integration
+ * @param integrationName
+ */
+export const getFileMetadataIndexName = (integrationName: string): string => {
+ if (FILE_STORAGE_METADATA_INDEX_PATTERN.indexOf('*') !== -1) {
+ return FILE_STORAGE_METADATA_INDEX_PATTERN.replace('*', integrationName);
+ }
+
+ throw new Error(
+ `Unable to define integration file data index. No '*' in index pattern: ${FILE_STORAGE_METADATA_INDEX_PATTERN}`
+ );
+};
+/**
+ * Returns the index name for File data (chunks) storage for a given integration
+ * @param integrationName
+ */
+export const getFileDataIndexName = (integrationName: string): string => {
+ if (FILE_STORAGE_DATA_INDEX_PATTERN.indexOf('*') !== -1) {
+ return FILE_STORAGE_DATA_INDEX_PATTERN.replace('*', integrationName);
+ }
+
+ throw new Error(
+ `Unable to define integration file data index. No '*' in index pattern: ${FILE_STORAGE_DATA_INDEX_PATTERN}`
+ );
+};
+
+/**
+ * Returns back the integration name for a given File Data (chunks) index name.
+ *
+ * @example
+ * // Given a File data index pattern of `.fleet-file-data-*`:
+ *
+ * getIntegrationNameFromFileDataIndexName('.fleet-file-data-agent');
+ * // return 'agent'
+ *
+ * getIntegrationNameFromFileDataIndexName('.fleet-file-data-agent-00001');
+ * // return 'agent'
+ */
+export const getIntegrationNameFromFileDataIndexName = (indexName: string): string => {
+ const integrationNameIndexPosition = FILE_STORAGE_DATA_INDEX_PATTERN.split('-').indexOf('*');
+
+ if (integrationNameIndexPosition === -1) {
+ throw new Error(
+ `Unable to parse index name. No '*' in index pattern: ${FILE_STORAGE_DATA_INDEX_PATTERN}`
+ );
+ }
+
+ const indexPieces = indexName.split('-');
+
+ if (indexPieces[integrationNameIndexPosition]) {
+ return indexPieces[integrationNameIndexPosition];
+ }
+
+ throw new Error(`Index name ${indexName} does not seem to be a File Data storage index`);
+};
diff --git a/x-pack/plugins/fleet/common/services/index.ts b/x-pack/plugins/fleet/common/services/index.ts
index 8261ccb3f82c0e..0fd88833b4ea5d 100644
--- a/x-pack/plugins/fleet/common/services/index.ts
+++ b/x-pack/plugins/fleet/common/services/index.ts
@@ -48,3 +48,5 @@ export {
getRegistryDataStreamAssetBaseName,
getComponentTemplateNameForDatastream,
} from './datastream_es_name';
+
+export * from './file_storage';
diff --git a/x-pack/plugins/fleet/server/constants/fleet_es_assets.ts b/x-pack/plugins/fleet/server/constants/fleet_es_assets.ts
index 7b76fc195e0974..223a9d293d0702 100644
--- a/x-pack/plugins/fleet/server/constants/fleet_es_assets.ts
+++ b/x-pack/plugins/fleet/server/constants/fleet_es_assets.ts
@@ -5,6 +5,8 @@
* 2.0.
*/
+import { getFileDataIndexName, getFileMetadataIndexName } from '../../common';
+
import { getESAssetMetadata } from '../services/epm/elasticsearch/meta';
const meta = getESAssetMetadata();
@@ -195,6 +197,6 @@ on_failure:
value:
- 'failed in Fleet agent final_pipeline: {{ _ingest.on_failure_message }}'`;
-// File storage indexes supporting endpoint Upload/download
-export const FILE_STORAGE_METADATA_INDEX_PATTERN = '.fleet-*-files';
-export const FILE_STORAGE_DATA_INDEX_PATTERN = '.fleet-*-file-data';
+// Fleet Agent indexes for storing files
+export const FILE_STORAGE_METADATA_AGENT_INDEX = getFileMetadataIndexName('agent');
+export const FILE_STORAGE_DATA_AGENT_INDEX = getFileDataIndexName('agent');
diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts
index 33004fe3030cf4..815e2cbaa9b9fd 100644
--- a/x-pack/plugins/fleet/server/constants/index.ts
+++ b/x-pack/plugins/fleet/server/constants/index.ts
@@ -83,3 +83,5 @@ export {
FLEET_INSTALL_FORMAT_VERSION,
FLEET_AGENT_POLICIES_SCHEMA_VERSION,
} from './fleet_es_assets';
+export { FILE_STORAGE_DATA_AGENT_INDEX } from './fleet_es_assets';
+export { FILE_STORAGE_METADATA_AGENT_INDEX } from './fleet_es_assets';
diff --git a/x-pack/plugins/fleet/server/services/agents/uploads.ts b/x-pack/plugins/fleet/server/services/agents/uploads.ts
index 2269a080e41b9a..7596f9cef7a643 100644
--- a/x-pack/plugins/fleet/server/services/agents/uploads.ts
+++ b/x-pack/plugins/fleet/server/services/agents/uploads.ts
@@ -17,14 +17,15 @@ import type { AgentDiagnostics } from '../../../common/types/models';
import { appContextService } from '../app_context';
import {
AGENT_ACTIONS_INDEX,
- agentRouteService,
AGENT_ACTIONS_RESULTS_INDEX,
+ agentRouteService,
} from '../../../common';
-import { SO_SEARCH_LIMIT } from '../../constants';
-
-const FILE_STORAGE_METADATA_AGENT_INDEX = '.fleet-agent-files';
-const FILE_STORAGE_DATA_AGENT_INDEX = '.fleet-agent-file-data';
+import {
+ FILE_STORAGE_DATA_AGENT_INDEX,
+ FILE_STORAGE_METADATA_AGENT_INDEX,
+ SO_SEARCH_LIMIT,
+} from '../../constants';
export async function getAgentUploads(
esClient: ElasticsearchClient,
diff --git a/x-pack/plugins/fleet/server/services/files/index.test.ts b/x-pack/plugins/fleet/server/services/files/index.test.ts
index 2448686fd9b0a1..f3cd191a19902f 100644
--- a/x-pack/plugins/fleet/server/services/files/index.test.ts
+++ b/x-pack/plugins/fleet/server/services/files/index.test.ts
@@ -8,16 +8,19 @@
import type { ElasticsearchClientMock } from '@kbn/core/server/mocks';
import { elasticsearchServiceMock } from '@kbn/core/server/mocks';
-import { ES_SEARCH_LIMIT } from '../../../common/constants';
import {
FILE_STORAGE_DATA_INDEX_PATTERN,
FILE_STORAGE_METADATA_INDEX_PATTERN,
-} from '../../constants/fleet_es_assets';
+} from '../../../common/constants/file_storage';
+
+import { getFileDataIndexName, getFileMetadataIndexName } from '../../../common/services';
+
+import { ES_SEARCH_LIMIT } from '../../../common/constants';
import { fileIdsWithoutChunksByIndex, getFilesByStatus, updateFilesStatus } from '.';
-const ENDPOINT_FILE_METADATA_INDEX = '.fleet-endpoint-files';
-const ENDPOINT_FILE_INDEX = '.fleet-endpoint-file-data';
+const ENDPOINT_FILE_METADATA_INDEX = getFileMetadataIndexName('endpoint');
+const ENDPOINT_FILE_INDEX = getFileDataIndexName('endpoint');
describe('files service', () => {
let esClientMock: ElasticsearchClientMock;
@@ -66,7 +69,7 @@ describe('files service', () => {
size: ES_SEARCH_LIMIT,
query: {
term: {
- 'file.Status.keyword': status,
+ 'file.Status': status,
},
},
_source: false,
@@ -132,7 +135,7 @@ describe('files service', () => {
must: [
{
terms: {
- 'bid.keyword': Array.from(files.map((file) => file._id)),
+ bid: Array.from(files.map((file) => file._id)),
},
},
{
@@ -157,7 +160,7 @@ describe('files service', () => {
describe('#updateFilesStatus()', () => {
it('calls esClient.updateByQuery with expected values', () => {
- const FAKE_INTEGRATION_METADATA_INDEX = '.fleet-someintegration-files';
+ const FAKE_INTEGRATION_METADATA_INDEX = getFileMetadataIndexName('someintegration');
const files = {
[ENDPOINT_FILE_METADATA_INDEX]: new Set(['delete1', 'delete2']),
[FAKE_INTEGRATION_METADATA_INDEX]: new Set(['delete2', 'delete3']),
diff --git a/x-pack/plugins/fleet/server/services/files/index.ts b/x-pack/plugins/fleet/server/services/files/index.ts
index 77e14496b0a00d..1dadbb66d57884 100644
--- a/x-pack/plugins/fleet/server/services/files/index.ts
+++ b/x-pack/plugins/fleet/server/services/files/index.ts
@@ -6,13 +6,19 @@
*/
import type { ElasticsearchClient } from '@kbn/core/server';
-import type { UpdateByQueryResponse, SearchHit } from '@elastic/elasticsearch/lib/api/types';
+import type { SearchHit, UpdateByQueryResponse } from '@elastic/elasticsearch/lib/api/types';
import type { FileStatus } from '@kbn/files-plugin/common/types';
import {
FILE_STORAGE_DATA_INDEX_PATTERN,
FILE_STORAGE_METADATA_INDEX_PATTERN,
-} from '../../constants/fleet_es_assets';
+} from '../../../common/constants';
+
+import {
+ getFileMetadataIndexName,
+ getIntegrationNameFromFileDataIndexName,
+} from '../../../common/services';
+
import { ES_SEARCH_LIMIT } from '../../../common/constants';
/**
@@ -34,7 +40,7 @@ export async function getFilesByStatus(
size: ES_SEARCH_LIMIT,
query: {
term: {
- 'file.Status.keyword': status,
+ 'file.Status': status,
},
},
_source: false,
@@ -82,7 +88,7 @@ export async function fileIdsWithoutChunksByIndex(
must: [
{
terms: {
- 'bid.keyword': Array.from(allFileIds),
+ bid: Array.from(allFileIds),
},
},
{
@@ -102,8 +108,8 @@ export async function fileIdsWithoutChunksByIndex(
chunks.hits.hits.forEach((hit) => {
const fileId = hit._source?.bid;
if (!fileId) return;
- const integration = hit._index.split('-')[1];
- const metadataIndex = `.fleet-${integration}-files`;
+ const integration = getIntegrationNameFromFileDataIndexName(hit._index);
+ const metadataIndex = getFileMetadataIndexName(integration);
if (noChunkFileIdsByIndex[metadataIndex]?.delete(fileId)) {
allFileIds.delete(fileId);
}
diff --git a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts
index fac96a8e0b15e3..353b52acc8c13d 100644
--- a/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts
+++ b/x-pack/plugins/fleet/server/tasks/check_deleted_files_task.test.ts
@@ -13,15 +13,17 @@ import type { CoreSetup } from '@kbn/core/server';
import type { ElasticsearchClientMock } from '@kbn/core-elasticsearch-client-server-mocks';
import { loggingSystemMock } from '@kbn/core/server/mocks';
+import { getFileDataIndexName, getFileMetadataIndexName } from '../../common';
+
import { createAppContextStartContractMock } from '../mocks';
-import {
- FILE_STORAGE_DATA_INDEX_PATTERN,
- FILE_STORAGE_METADATA_INDEX_PATTERN,
-} from '../constants/fleet_es_assets';
+
import { appContextService } from '../services';
import { CheckDeletedFilesTask, TYPE, VERSION } from './check_deleted_files_task';
+const MOCK_FILE_METADATA_INDEX = getFileMetadataIndexName('mock');
+const MOCK_FILE_DATA_INDEX = getFileDataIndexName('mock');
+
const MOCK_TASK_INSTANCE = {
id: `${TYPE}:${VERSION}`,
runAt: new Date(),
@@ -118,12 +120,12 @@ describe('check deleted files task', () => {
hits: [
{
_id: 'metadata-testid1',
- _index: FILE_STORAGE_METADATA_INDEX_PATTERN,
+ _index: MOCK_FILE_METADATA_INDEX,
_source: { file: { status: 'READY' } },
},
{
_id: 'metadata-testid2',
- _index: FILE_STORAGE_METADATA_INDEX_PATTERN,
+ _index: MOCK_FILE_METADATA_INDEX,
_source: { file: { status: 'READY' } },
},
],
@@ -147,7 +149,7 @@ describe('check deleted files task', () => {
hits: [
{
_id: 'data-testid1',
- _index: FILE_STORAGE_DATA_INDEX_PATTERN,
+ _index: MOCK_FILE_DATA_INDEX,
_source: {
bid: 'metadata-testid1',
},
@@ -160,7 +162,7 @@ describe('check deleted files task', () => {
expect(esClient.updateByQuery).toHaveBeenCalledWith(
{
- index: FILE_STORAGE_METADATA_INDEX_PATTERN,
+ index: MOCK_FILE_METADATA_INDEX,
query: {
ids: {
values: ['metadata-testid2'],
diff --git a/x-pack/plugins/security_solution/common/endpoint/constants.ts b/x-pack/plugins/security_solution/common/endpoint/constants.ts
index 078adada6d2b67..c34236a908e497 100644
--- a/x-pack/plugins/security_solution/common/endpoint/constants.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/constants.ts
@@ -6,6 +6,8 @@
*/
/** endpoint data streams that are used for host isolation */
+import { getFileDataIndexName, getFileMetadataIndexName } from '@kbn/fleet-plugin/common';
+
/** for index patterns `.logs-endpoint.actions-* and .logs-endpoint.action.responses-*`*/
export const ENDPOINT_ACTIONS_DS = '.logs-endpoint.actions';
export const ENDPOINT_ACTIONS_INDEX = `${ENDPOINT_ACTIONS_DS}-default`;
@@ -42,8 +44,8 @@ export const policyIndexPattern = 'metrics-endpoint.policy-*';
export const telemetryIndexPattern = 'metrics-endpoint.telemetry-*';
// File storage indexes supporting endpoint Upload/download
-export const FILE_STORAGE_METADATA_INDEX = '.fleet-endpoint-files';
-export const FILE_STORAGE_DATA_INDEX = '.fleet-endpoint-file-data';
+export const FILE_STORAGE_METADATA_INDEX = getFileMetadataIndexName('endpoint');
+export const FILE_STORAGE_DATA_INDEX = getFileDataIndexName('endpoint');
// Endpoint API routes
export const BASE_ENDPOINT_ROUTE = '/api/endpoint';
diff --git a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
index 538efd053611a6..4a7eeb07fb82af 100644
--- a/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/data_generators/endpoint_action_generator.ts
@@ -89,9 +89,16 @@ export class EndpointActionGenerator extends BaseDataGenerator {
type: 'json',
content: {
code: 'ra_get-file_success_done',
- path: '/some/path/bad_file.txt',
- size: 1234,
zip_size: 123,
+ contents: [
+ {
+ type: 'file',
+ path: '/some/path/bad_file.txt',
+ size: 1234,
+ file_name: 'bad_file.txt',
+ sha256: '9558c5cb39622e9b3653203e772b129d6c634e7dbd7af1b244352fc1d704601f',
+ },
+ ],
},
};
}
diff --git a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
index 8ac8745a5d35bc..3597e1e7f6e9b6 100644
--- a/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
+++ b/x-pack/plugins/security_solution/common/endpoint/types/actions.ts
@@ -53,9 +53,15 @@ export interface KillProcessActionOutputContent {
export interface ResponseActionGetFileOutputContent {
code: string;
- path: string;
- size: number;
zip_size: number;
+ /** The contents of the zip file. One entry per file */
+ contents: Array<{
+ path: string;
+ sha256: string;
+ size: number;
+ file_name: string;
+ type: string;
+ }>;
}
export const ActivityLogItemTypes = {
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index a7dfd6025ed1a5..936257003b755c 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -70,7 +70,7 @@ export const allowedExperimentalValues = Object.freeze({
* Enables endpoint package level rbac for response actions only.
* if endpointRbacEnabled is enabled, it will take precedence.
*/
- endpointRbacV1Enabled: false,
+ endpointRbacV1Enabled: true,
/**
* Enables the Guided Onboarding tour in security
diff --git a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
index 55d6730b8dc8cc..1711aee8b1932d 100644
--- a/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
@@ -96,7 +96,7 @@ describe('when in the Administration tab', () => {
});
mockedContext.history.push('/administration/response_actions_history');
- expect(await render().findByTestId('noIngestPermissions')).toBeTruthy();
+ expect(await render().findByTestId('noPrivilegesPage')).toBeTruthy();
});
});
diff --git a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
index c4113f0132c053..8247006e079259 100644
--- a/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
+++ b/x-pack/plugins/security_solution/scripts/endpoint/agent_emulator/services/endpoint_response_actions.ts
@@ -281,15 +281,22 @@ const getOutputDataIfNeeded = (action: ActionDetails): ResponseOutput => {
output: {
type: 'json',
content: {
- code: 'ra_get-file-success',
- path: (
- action as ActionDetails<
- ResponseActionGetFileOutputContent,
- ResponseActionGetFileParameters
- >
- ).parameters?.path,
- size: 1234,
+ code: 'ra_get-file_success_done',
zip_size: 123,
+ contents: [
+ {
+ type: 'file',
+ path: (
+ action as ActionDetails<
+ ResponseActionGetFileOutputContent,
+ ResponseActionGetFileParameters
+ >
+ ).parameters?.path,
+ size: 1234,
+ file_name: 'bad_file.txt',
+ sha256: '9558c5cb39622e9b3653203e772b129d6c634e7dbd7af1b244352fc1d704601f',
+ },
+ ],
},
},
} as ResponseOutput;
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts
index cb161ac189d590..18e09d26896236 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/action_files.ts
@@ -99,8 +99,6 @@ export const getFileInfo = async (
}
}
- // TODO: add `ttl` to the return payload by retrieving the value from ILM?
-
return {
name,
id,
@@ -123,7 +121,7 @@ const doesFileHaveChunks = async (
body: {
query: {
term: {
- 'bid.keyword': fileId,
+ bid: fileId,
},
},
// Setting `_source` to false - we don't need the actual document to be returned
diff --git a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
index 920540fd300823..cd97bf96673bfd 100644
--- a/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/services/actions/utils.test.ts
@@ -445,8 +445,15 @@ describe('When using Actions service utilities', () => {
'456': {
content: {
code: 'ra_get-file_success_done',
- path: '/some/path/bad_file.txt',
- size: 1234,
+ contents: [
+ {
+ file_name: 'bad_file.txt',
+ path: '/some/path/bad_file.txt',
+ sha256: '9558c5cb39622e9b3653203e772b129d6c634e7dbd7af1b244352fc1d704601f',
+ size: 1234,
+ type: 'file',
+ },
+ ],
zip_size: 123,
},
type: 'json',
diff --git a/x-pack/test/api_integration/apis/security/privileges.ts b/x-pack/test/api_integration/apis/security/privileges.ts
index 987c22e35064f1..d11ad982a0f45b 100644
--- a/x-pack/test/api_integration/apis/security/privileges.ts
+++ b/x-pack/test/api_integration/apis/security/privileges.ts
@@ -29,7 +29,17 @@ export default function ({ getService }: FtrProviderContext) {
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
ml: ['all', 'read', 'minimal_all', 'minimal_read'],
- siem: ['all', 'read', 'minimal_all', 'minimal_read'],
+ siem: [
+ 'all',
+ 'read',
+ 'minimal_all',
+ 'minimal_read',
+ 'actions_log_management_all',
+ 'actions_log_management_read',
+ 'host_isolation_all',
+ 'process_operations_all',
+ 'file_operations_all',
+ ],
uptime: ['all', 'read', 'minimal_all', 'minimal_read'],
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'],
infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'],
diff --git a/x-pack/test/api_integration/apis/security/privileges_basic.ts b/x-pack/test/api_integration/apis/security/privileges_basic.ts
index 3d470259267e41..ba4fefd9ae691c 100644
--- a/x-pack/test/api_integration/apis/security/privileges_basic.ts
+++ b/x-pack/test/api_integration/apis/security/privileges_basic.ts
@@ -96,7 +96,17 @@ export default function ({ getService }: FtrProviderContext) {
actions: ['all', 'read', 'minimal_all', 'minimal_read'],
stackAlerts: ['all', 'read', 'minimal_all', 'minimal_read'],
ml: ['all', 'read', 'minimal_all', 'minimal_read'],
- siem: ['all', 'read', 'minimal_all', 'minimal_read'],
+ siem: [
+ 'actions_log_management_all',
+ 'actions_log_management_read',
+ 'all',
+ 'file_operations_all',
+ 'host_isolation_all',
+ 'minimal_all',
+ 'minimal_read',
+ 'process_operations_all',
+ 'read',
+ ],
uptime: ['all', 'read', 'minimal_all', 'minimal_read'],
securitySolutionCases: ['all', 'read', 'minimal_all', 'minimal_read', 'cases_delete'],
infrastructure: ['all', 'read', 'minimal_all', 'minimal_read'],
diff --git a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts
index fc56b4fc46c18f..07e44bfa2a641c 100644
--- a/x-pack/test/fleet_api_integration/apis/agents/uploads.ts
+++ b/x-pack/test/fleet_api_integration/apis/agents/uploads.ts
@@ -7,6 +7,10 @@
import expect from '@kbn/expect';
import { AGENT_ACTIONS_INDEX, AGENT_ACTIONS_RESULTS_INDEX } from '@kbn/fleet-plugin/common';
+import {
+ FILE_STORAGE_DATA_AGENT_INDEX,
+ FILE_STORAGE_METADATA_AGENT_INDEX,
+} from '@kbn/fleet-plugin/server/constants';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { setupFleetAndAgents } from './services';
import { skipIfNoDockerRegistry } from '../../helpers';
@@ -57,7 +61,7 @@ export default function (providerContext: FtrProviderContext) {
);
await esClient.update({
- index: '.fleet-agent-files',
+ index: FILE_STORAGE_METADATA_AGENT_INDEX,
id: 'file1',
refresh: true,
body: {
@@ -102,7 +106,7 @@ export default function (providerContext: FtrProviderContext) {
it('should get agent uploaded file', async () => {
await esClient.update({
- index: '.fleet-agent-file-data',
+ index: FILE_STORAGE_DATA_AGENT_INDEX,
id: 'file1.0',
refresh: true,
body: {
From 6c9cc6626b9c9fd0a2411426949f20df15d6c7fa Mon Sep 17 00:00:00 2001
From: Christos Nasikas
Date: Mon, 14 Nov 2022 17:25:57 +0200
Subject: [PATCH 17/20] [Cases] Fix selected label in the assignees filtering
(#145113)
## Summary
With the introduction of the "No assignees" filtering in
https://github.com/elastic/kibana/pull/143390 we no longer have
assignees for filtering. Having the text say "1 assignee selected" when
selecting the "No assignees" filtering is misleading. This PR fixes this
issue with the label.
### Checklist
Delete any items that are not applicable to this PR.
- [x] Any text added follows [EUI's writing
guidelines](https://elastic.github.io/eui/#/guidelines/writing), uses
sentence case text and includes [i18n
support](https://github.com/elastic/kibana/blob/main/packages/kbn-i18n/README.md)
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
### For maintainers
- [x] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)
---
.../all_cases/assignees_filter.test.tsx | 20 ++++++++++++++++++-
.../components/all_cases/translations.ts | 2 +-
2 files changed, 20 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/cases/public/components/all_cases/assignees_filter.test.tsx b/x-pack/plugins/cases/public/components/all_cases/assignees_filter.test.tsx
index c1b0d0cf2445f8..e6592d1358bdea 100644
--- a/x-pack/plugins/cases/public/components/all_cases/assignees_filter.test.tsx
+++ b/x-pack/plugins/cases/public/components/all_cases/assignees_filter.test.tsx
@@ -132,7 +132,7 @@ describe('AssigneesFilterPopover', () => {
await waitFor(async () => {
userEvent.click(screen.getByTestId('options-filter-popover-button-assignees'));
- expect(screen.getByText('1 assignee filtered')).toBeInTheDocument();
+ expect(screen.getByText('1 filter selected')).toBeInTheDocument();
});
await waitForEuiPopoverOpen();
@@ -140,6 +140,24 @@ describe('AssigneesFilterPopover', () => {
expect(screen.getByText('Damaged Raccoon')).toBeInTheDocument();
});
+ it('shows the total when the multiple users are selected', async () => {
+ const props = {
+ ...defaultProps,
+ selectedAssignees: [userProfiles[0], userProfiles[1]],
+ };
+ appMockRender.render();
+
+ await waitFor(async () => {
+ userEvent.click(screen.getByTestId('options-filter-popover-button-assignees'));
+ expect(screen.getByText('2 filters selected')).toBeInTheDocument();
+ });
+
+ await waitForEuiPopoverOpen();
+
+ expect(screen.getByText('Damaged Raccoon')).toBeInTheDocument();
+ expect(screen.getByText('Physical Dinosaur')).toBeInTheDocument();
+ });
+
it('shows three users when initially rendered', async () => {
appMockRender.render();
diff --git a/x-pack/plugins/cases/public/components/all_cases/translations.ts b/x-pack/plugins/cases/public/components/all_cases/translations.ts
index 1a215cd1ef8891..2c5f2697541780 100644
--- a/x-pack/plugins/cases/public/components/all_cases/translations.ts
+++ b/x-pack/plugins/cases/public/components/all_cases/translations.ts
@@ -127,7 +127,7 @@ export const CLEAR_FILTERS = i18n.translate(
export const TOTAL_ASSIGNEES_FILTERED = (total: number) =>
i18n.translate('xpack.cases.allCasesView.totalFilteredUsers', {
- defaultMessage: '{total, plural, one {# assignee} other {# assignees}} filtered',
+ defaultMessage: '{total, plural, one {# filter} other {# filters}} selected',
values: { total },
});
From e27be55baeedc246fae72755a6657d57d2ebd56f Mon Sep 17 00:00:00 2001
From: "Devin W. Hurley"
Date: Mon, 14 Nov 2022 10:35:36 -0500
Subject: [PATCH 18/20] [Security Solution] [Exceptions] Adds a modal to
confirm deletion of exception list (#145034)
---
.../cypress/tasks/exceptions_table.ts | 2 ++
.../exceptions/pages/shared_lists/index.tsx | 26 +++++--------------
.../exceptions/translations/shared_list.ts | 6 +++++
3 files changed, 14 insertions(+), 20 deletions(-)
diff --git a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts
index cee6929b3887de..482ea64cf4a8bd 100644
--- a/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts
+++ b/x-pack/plugins/security_solution/cypress/tasks/exceptions_table.ts
@@ -32,6 +32,8 @@ export const exportExceptionList = () => {
export const deleteExceptionListWithoutRuleReference = () => {
cy.get(EXCEPTIONS_OVERFLOW_ACTIONS_BTN).first().click();
cy.get(EXCEPTIONS_TABLE_DELETE_BTN).first().click();
+ cy.get(EXCEPTIONS_TABLE_MODAL).should('exist');
+ cy.get(EXCEPTIONS_TABLE_MODAL_CONFIRM_BTN).first().click();
cy.get(EXCEPTIONS_TABLE_MODAL).should('not.exist');
};
diff --git a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx
index 0218dda4dbf5f7..d2d1ecf20ad366 100644
--- a/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx
+++ b/x-pack/plugins/security_solution/public/exceptions/pages/shared_lists/index.tsx
@@ -129,20 +129,12 @@ export const SharedLists = React.memo(() => {
({ id, listId, namespaceType }: { id: string; listId: string; namespaceType: NamespaceType }) =>
async () => {
try {
- if (exceptionsListsRef[id] != null && exceptionsListsRef[id].rules.length === 0) {
- await deleteExceptionList({
- id,
- namespaceType,
- onError: handleDeleteError,
- onSuccess: handleDeleteSuccess(listId),
- });
-
- if (refreshExceptions != null) {
- refreshExceptions();
- }
- } else {
+ if (exceptionsListsRef[id] != null) {
setReferenceModalState({
- contentText: i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length),
+ contentText:
+ exceptionsListsRef[id].rules.length > 0
+ ? i18n.referenceErrorMessage(exceptionsListsRef[id].rules.length)
+ : i18n.defaultDeleteListMessage(exceptionsListsRef[id].name),
rulesReferences: exceptionsListsRef[id].rules.map(({ name }) => name),
isLoading: true,
listId: id,
@@ -155,13 +147,7 @@ export const SharedLists = React.memo(() => {
handleDeleteError(error);
}
},
- [
- deleteExceptionList,
- exceptionsListsRef,
- handleDeleteError,
- handleDeleteSuccess,
- refreshExceptions,
- ]
+ [exceptionsListsRef, handleDeleteError]
);
const handleExportSuccess = useCallback(
diff --git a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts
index 5eaf096400db7e..e545b782951c53 100644
--- a/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts
+++ b/x-pack/plugins/security_solution/public/exceptions/translations/shared_list.ts
@@ -133,6 +133,12 @@ export const REFERENCE_MODAL_TITLE = i18n.translate(
}
);
+export const defaultDeleteListMessage = (listName: string) =>
+ i18n.translate('xpack.securitySolution.exceptions.referenceModalDefaultDescription', {
+ defaultMessage: 'Are you sure you wish to DELETE exception list with the name {listName}?',
+ values: { listName },
+ });
+
export const REFERENCE_MODAL_CANCEL_BUTTON = i18n.translate(
'xpack.securitySolution.exceptions.referenceModalCancelButton',
{
From 843eefa7a7e596d6961aa1e1bdda98f530297113 Mon Sep 17 00:00:00 2001
From: Janki Salvi <117571355+js-jankisalvi@users.noreply.github.com>
Date: Mon, 14 Nov 2022 16:36:14 +0100
Subject: [PATCH 19/20] [Cases] truncate long case name by word break (#145003)
## Summary
This PR updates uses `break-word` to display text into the next line for
the long case name on the case management page.
Fixes: https://github.com/elastic/kibana/issues/142647
**Before**
![image](https://user-images.githubusercontent.com/117571355/201154944-f61db2f9-e9d8-4e29-bdbb-240f69e4e2d9.png)
![image](https://user-images.githubusercontent.com/117571355/201155420-44c7cb68-64a5-4c29-8c78-cc0c02e8dc2f.png)
**After**
![image](https://user-images.githubusercontent.com/117571355/201155177-9d52bc3e-7d24-4a8b-a9b0-a490fd8375e4.png)
![image](https://user-images.githubusercontent.com/117571355/201155281-a4f2e27f-a0a0-46be-9ce7-aa11f05d955a.png)
### Checklist
- [x] This renders correctly on smaller devices using a responsive
layout. (You can test this [in your
browser](https://www.browserstack.com/guide/responsive-testing-on-local-server))
- [x] This was checked for [cross-browser
compatibility](https://www.elastic.co/support/matrix#matrix_browsers)
---
x-pack/plugins/cases/public/common/use_cases_toast.tsx | 1 +
x-pack/plugins/cases/public/components/truncated_text/index.tsx | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/cases/public/common/use_cases_toast.tsx b/x-pack/plugins/cases/public/common/use_cases_toast.tsx
index 984a5ed607351b..d866f9791fc2d6 100644
--- a/x-pack/plugins/cases/public/common/use_cases_toast.tsx
+++ b/x-pack/plugins/cases/public/common/use_cases_toast.tsx
@@ -32,6 +32,7 @@ const Title = styled.span`
-webkit-line-clamp: ${LINE_CLAMP};
-webkit-box-orient: vertical;
overflow: hidden;
+ word-break: break-word;
`;
const EuiTextStyled = styled(EuiText)`
${({ theme }) => `
diff --git a/x-pack/plugins/cases/public/components/truncated_text/index.tsx b/x-pack/plugins/cases/public/components/truncated_text/index.tsx
index 7d663dc989c967..83005f8c92e89e 100644
--- a/x-pack/plugins/cases/public/components/truncated_text/index.tsx
+++ b/x-pack/plugins/cases/public/components/truncated_text/index.tsx
@@ -16,7 +16,7 @@ const Text = styled.span`
-webkit-line-clamp: ${LINE_CLAMP};
-webkit-box-orient: vertical;
overflow: hidden;
- word-break: normal;
+ word-break: break-word;
`;
interface Props {
From 9fda59f5129203122765d6913abab3455bb4cdee Mon Sep 17 00:00:00 2001
From: Paul Tavares <56442535+paul-tavares@users.noreply.github.com>
Date: Mon, 14 Nov 2022 10:41:52 -0500
Subject: [PATCH 20/20] [security solution][endpoint] Add new experimental
feature flag for `get-file` and use it hide/display `get-file` response
action (#145042)
## Summary
- Adds new experimental feature flag that controls the availability of
the `get-file` response action
- UI updated to remove `get-file` from the console if FF is `false`
- Server APIs updated to not register `get-file` related APIs if FF is
`false`
- Hides the "File Operation" kibana feature privilege
---
.../common/experimental_features.ts | 5 ++++
.../experimental_features_service.ts | 25 +++++++++++++++++
.../get_file_action.test.tsx | 1 +
.../get_processes_action.test.tsx | 2 ++
.../integration_tests/isolate_action.test.tsx | 2 ++
.../kill_process_action.test.tsx | 2 ++
.../integration_tests/release_action.test.tsx | 2 ++
.../suspend_process_action.test.tsx | 2 ++
.../lib/console_commands_definition.ts | 17 +++++++++---
.../components/hooks.tsx | 13 ++++++++-
.../response_actions_log.test.tsx | 2 ++
.../view/response_actions_list_page.test.tsx | 2 ++
.../security_solution/server/config.mock.ts | 5 +++-
.../server/endpoint/routes/actions/index.ts | 8 ++++--
.../routes/actions/response_actions.ts | 27 ++++++++++---------
.../security_solution/server/features.ts | 14 +++++++---
16 files changed, 105 insertions(+), 24 deletions(-)
create mode 100644 x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts
diff --git a/x-pack/plugins/security_solution/common/experimental_features.ts b/x-pack/plugins/security_solution/common/experimental_features.ts
index 936257003b755c..bb812c226b605a 100644
--- a/x-pack/plugins/security_solution/common/experimental_features.ts
+++ b/x-pack/plugins/security_solution/common/experimental_features.ts
@@ -81,6 +81,11 @@ export const allowedExperimentalValues = Object.freeze({
* Enables the alert details page currently only accessible via the alert details flyout and alert table context menu
*/
alertDetailsPageEnabled: false,
+
+ /**
+ * Enables the `get-file` endpoint response action
+ */
+ responseActionGetFileEnabled: false,
});
type ExperimentalConfigKeys = Array;
diff --git a/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts b/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts
new file mode 100644
index 00000000000000..bba0299899f427
--- /dev/null
+++ b/x-pack/plugins/security_solution/public/common/__mocks__/experimental_features_service.ts
@@ -0,0 +1,25 @@
+/*
+ * 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 { ExperimentalFeatures } from '../../../common/experimental_features';
+import { allowedExperimentalValues } from '../../../common/experimental_features';
+
+const ExperimentalFeaturesServiceMock = {
+ init: jest.fn(),
+
+ get: jest.fn(() => {
+ const ff: ExperimentalFeatures = {
+ ...allowedExperimentalValues,
+
+ responseActionGetFileEnabled: true,
+ };
+
+ return ff;
+ }),
+};
+
+export { ExperimentalFeaturesServiceMock as ExperimentalFeaturesService };
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx
index d62738896b3c2e..6f8f4f3177ea83 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_file_action.test.tsx
@@ -30,6 +30,7 @@ import type { HttpFetchOptionsWithPath } from '@kbn/core-http-browser';
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
jest.mock('../../../../../common/components/user_privileges');
+jest.mock('../../../../../common/experimental_features_service');
describe('When using get-file action from response actions console', () => {
let render: (
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx
index a99e8cdd17ef7b..2851ac8640fb70 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/get_processes_action.test.tsx
@@ -20,6 +20,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
+jest.mock('../../../../../common/experimental_features_service');
+
describe('When using processes action from response actions console', () => {
let render: (
capabilities?: EndpointCapabilities[]
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx
index ab98acc5bd3903..e25df018e2bd10 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/isolate_action.test.tsx
@@ -21,6 +21,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
+jest.mock('../../../../../common/experimental_features_service');
+
describe('When using isolate action from response actions console', () => {
let render: (
capabilities?: EndpointCapabilities[]
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx
index e65a8d8e751e47..3f8160f5cf19cf 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/kill_process_action.test.tsx
@@ -25,6 +25,8 @@ import type {
} from '../../../../../../common/endpoint/types';
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
+jest.mock('../../../../../common/experimental_features_service');
+
describe('When using the kill-process action from response actions console', () => {
let render: (
capabilities?: EndpointCapabilities[]
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx
index 633c82a47897fd..ec75d6aaa28dfc 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/release_action.test.tsx
@@ -21,6 +21,8 @@ import { getEndpointAuthzInitialState } from '../../../../../../common/endpoint/
import type { EndpointCapabilities } from '../../../../../../common/endpoint/service/response_actions/constants';
import { ENDPOINT_CAPABILITIES } from '../../../../../../common/endpoint/service/response_actions/constants';
+jest.mock('../../../../../common/experimental_features_service');
+
describe('When using the release action from response actions console', () => {
let render: (
capabilities?: EndpointCapabilities[]
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx
index e02bbcb689c571..fead471cd0c136 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/command_render_components/integration_tests/suspend_process_action.test.tsx
@@ -25,6 +25,8 @@ import type {
} from '../../../../../../common/endpoint/types';
import { endpointActionResponseCodes } from '../../lib/endpoint_action_response_codes';
+jest.mock('../../../../../common/experimental_features_service');
+
describe('When using the suspend-process action from response actions console', () => {
let render: (
capabilities?: EndpointCapabilities[]
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts
index b74304a21280ae..b4068c95d14071 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_responder/lib/console_commands_definition.ts
@@ -6,6 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
+import { ExperimentalFeaturesService } from '../../../../common/experimental_features_service';
import type {
EndpointCapabilities,
ConsoleResponseActionCommands,
@@ -134,6 +135,8 @@ export const getEndpointConsoleCommands = ({
endpointCapabilities: ImmutableArray;
endpointPrivileges: EndpointPrivileges;
}): CommandDefinition[] => {
+ const isGetFileEnabled = ExperimentalFeaturesService.get().responseActionGetFileEnabled;
+
const doesEndpointSupportCommand = (commandName: ConsoleResponseActionCommands) => {
const responderCapability = commandToCapabilitiesMap.get(commandName);
if (responderCapability) {
@@ -142,7 +145,7 @@ export const getEndpointConsoleCommands = ({
return false;
};
- return [
+ const consoleCommands: CommandDefinition[] = [
{
name: 'isolate',
about: getCommandAboutInfo({
@@ -365,7 +368,11 @@ export const getEndpointConsoleCommands = ({
helpDisabled: doesEndpointSupportCommand('processes') === false,
helpHidden: !getRbacControl({ commandName: 'processes', privileges: endpointPrivileges }),
},
- {
+ ];
+
+ // `get-file` is currently behind feature flag
+ if (isGetFileEnabled) {
+ consoleCommands.push({
name: 'get-file',
about: getCommandAboutInfo({
aboutInfo: i18n.translate('xpack.securitySolution.endpointConsoleCommands.getFile.about', {
@@ -411,6 +418,8 @@ export const getEndpointConsoleCommands = ({
commandName: 'get-file',
privileges: endpointPrivileges,
}),
- },
- ];
+ });
+ }
+
+ return consoleCommands;
};
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx
index e72e8614d67645..cc36974c3fe69b 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/components/hooks.tsx
@@ -10,6 +10,7 @@ import type {
DurationRange,
OnRefreshChangeProps,
} from '@elastic/eui/src/components/date_picker/types';
+import { ExperimentalFeaturesService } from '../../../../common/experimental_features_service';
import type {
ConsoleResponseActionCommands,
ResponseActionsApiCommandNames,
@@ -232,7 +233,17 @@ export const useActionsLogFilter = ({
}))
: isHostsFilter
? []
- : RESPONSE_ACTION_API_COMMANDS_NAMES.map((commandName) => ({
+ : RESPONSE_ACTION_API_COMMANDS_NAMES.filter((commandName) => {
+ // `get-file` is currently behind FF
+ if (
+ commandName === 'get-file' &&
+ !ExperimentalFeaturesService.get().responseActionGetFileEnabled
+ ) {
+ return false;
+ }
+
+ return true;
+ }).map((commandName) => ({
key: commandName,
label: getUiCommand(commandName),
checked:
diff --git a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx
index a5d05026d30d0a..64b30352771364 100644
--- a/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/components/endpoint_response_actions_list/response_actions_log.test.tsx
@@ -119,6 +119,8 @@ jest.mock('@kbn/kibana-react-plugin/public', () => {
jest.mock('../../hooks/endpoint/use_get_endpoints_list');
+jest.mock('../../../common/experimental_features_service');
+
jest.mock('../../../common/components/user_privileges');
let mockUseGetFileInfo: {
diff --git a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx
index a0b3b9050b7ab5..8b82608f27783e 100644
--- a/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/response_actions/view/response_actions_list_page.test.tsx
@@ -20,6 +20,8 @@ import { MANAGEMENT_PATH } from '../../../../../common/constants';
import { getActionListMock } from '../../../components/endpoint_response_actions_list/mocks';
import { useGetEndpointsList } from '../../../hooks/endpoint/use_get_endpoints_list';
+jest.mock('../../../../common/experimental_features_service');
+
let mockUseGetEndpointActionList: {
isFetched?: boolean;
isFetching?: boolean;
diff --git a/x-pack/plugins/security_solution/server/config.mock.ts b/x-pack/plugins/security_solution/server/config.mock.ts
index d643c5d22b4346..da20826276ae6e 100644
--- a/x-pack/plugins/security_solution/server/config.mock.ts
+++ b/x-pack/plugins/security_solution/server/config.mock.ts
@@ -11,7 +11,10 @@ import { parseExperimentalConfigValue } from '../common/experimental_features';
import type { ConfigType } from './config';
export const createMockConfig = (): ConfigType => {
- const enableExperimental: string[] = [];
+ const enableExperimental: Array = [
+ // Remove property below once `get-file` FF is enabled or removed
+ 'responseActionGetFileEnabled',
+ ];
return {
[SIGNALS_INDEX_KEY]: DEFAULT_SIGNALS_INDEX,
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts
index e5b3e90de88785..6bba9bc38c9627 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/index.ts
@@ -25,7 +25,11 @@ export function registerActionRoutes(
registerActionAuditLogRoutes(router, endpointContext);
registerActionListRoutes(router, endpointContext);
registerActionDetailsRoutes(router, endpointContext);
- registerActionFileDownloadRoutes(router, endpointContext);
registerResponseActionRoutes(router, endpointContext);
- registerActionFileInfoRoute(router, endpointContext);
+
+ // APIs specific to `get-file` are behind FF
+ if (endpointContext.experimentalFeatures.responseActionGetFileEnabled) {
+ registerActionFileDownloadRoutes(router, endpointContext);
+ registerActionFileInfoRoute(router, endpointContext);
+ }
}
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
index 1ddf38e390d10f..ec08ef4c7fd835 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/actions/response_actions.ts
@@ -160,18 +160,21 @@ export function registerResponseActionRoutes(
)
);
- router.post(
- {
- path: GET_FILE_ROUTE,
- validate: EndpointActionGetFileSchema,
- options: { authRequired: true, tags: ['access:securitySolution'] },
- },
- withEndpointAuthz(
- { all: ['canWriteFileOperations'] },
- logger,
- responseActionRequestHandler(endpointContext, 'get-file')
- )
- );
+ // `get-file` currently behind FF
+ if (endpointContext.experimentalFeatures.responseActionGetFileEnabled) {
+ router.post(
+ {
+ path: GET_FILE_ROUTE,
+ validate: EndpointActionGetFileSchema,
+ options: { authRequired: true, tags: ['access:securitySolution'] },
+ },
+ withEndpointAuthz(
+ { all: ['canWriteFileOperations'] },
+ logger,
+ responseActionRequestHandler(endpointContext, 'get-file')
+ )
+ );
+ }
}
const commandToFeatureKeyMap = new Map([
diff --git a/x-pack/plugins/security_solution/server/features.ts b/x-pack/plugins/security_solution/server/features.ts
index 8dd4b56f655b63..4711e978a25fed 100644
--- a/x-pack/plugins/security_solution/server/features.ts
+++ b/x-pack/plugins/security_solution/server/features.ts
@@ -495,15 +495,21 @@ const subFeatures: SubFeatureConfig[] = [
];
function getSubFeatures(experimentalFeatures: ConfigType['experimentalFeatures']) {
+ let filteredSubFeatures: SubFeatureConfig[] = [];
+
if (experimentalFeatures.endpointRbacEnabled) {
- return subFeatures;
+ filteredSubFeatures = subFeatures;
+ } else if (experimentalFeatures.endpointRbacV1Enabled) {
+ filteredSubFeatures = responseActionSubFeatures;
}
- if (experimentalFeatures.endpointRbacV1Enabled) {
- return responseActionSubFeatures;
+ if (!experimentalFeatures.responseActionGetFileEnabled) {
+ filteredSubFeatures = filteredSubFeatures.filter((subFeat) => {
+ return subFeat.name !== 'File Operations';
+ });
}
- return [];
+ return filteredSubFeatures;
}
export const getKibanaPrivilegesFeaturePrivileges = (