@@ -68,7 +89,7 @@ export const GuideStep = ({
id={accordionId}
buttonContent={stepTitleContent}
arrowDisplay="right"
- initialIsOpen={stepStatus === 'in_progress' || stepStatus === 'active'}
+ initialIsOpen={isAccordionOpen}
>
<>
@@ -82,21 +103,15 @@ export const GuideStep = ({
- {(stepStatus === 'in_progress' || stepStatus === 'active') && (
+ {isAccordionOpen && (
navigateToStep(stepConfig.id, stepConfig.location)}
+ onClick={() => handleButtonClick()}
fill
data-test-subj="activeStepButtonLabel"
>
- {stepStatus === 'active'
- ? i18n.translate('guidedOnboarding.dropdownPanel.startStepButtonLabel', {
- defaultMessage: 'Start',
- })
- : i18n.translate('guidedOnboarding.dropdownPanel.continueStepButtonLabel', {
- defaultMessage: 'Continue',
- })}
+ {getStepButtonLabel()}
diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts
index 57d81fdfe1301..f91c0af1ba446 100644
--- a/src/plugins/guided_onboarding/public/constants/guides_config/search.ts
+++ b/src/plugins/guided_onboarding/public/constants/guides_config/search.ts
@@ -41,6 +41,12 @@ export const searchConfig: GuideConfig = {
appID: 'guidedOnboardingExample',
path: 'stepTwo',
},
+ manualCompletion: {
+ title: 'Manual completion step title',
+ description:
+ 'Mark the step complete by opening the panel and clicking the button "Mark done"',
+ readyToCompleteOnNavigation: true,
+ },
},
{
id: 'search_experience',
diff --git a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts
index 8eafa3b51c408..e67e318a61f01 100644
--- a/src/plugins/guided_onboarding/public/constants/guides_config/security.ts
+++ b/src/plugins/guided_onboarding/public/constants/guides_config/security.ts
@@ -35,6 +35,11 @@ export const securityConfig: GuideConfig = {
'Nullam ligula enim, malesuada a finibus vel, cursus sed risus.',
'Vivamus pretium, elit dictum lacinia aliquet, libero nibh dictum enim, a rhoncus leo magna in sapien.',
],
+ manualCompletion: {
+ title: 'Manual completion step title',
+ description:
+ 'Mark the step complete by opening the panel and clicking the button "Mark done"',
+ },
},
{
id: 'alertsCases',
diff --git a/src/plugins/guided_onboarding/public/services/api.mocks.ts b/src/plugins/guided_onboarding/public/services/api.mocks.ts
index 19dd67c7d7b1b..ed2985a1dd74a 100644
--- a/src/plugins/guided_onboarding/public/services/api.mocks.ts
+++ b/src/plugins/guided_onboarding/public/services/api.mocks.ts
@@ -28,26 +28,6 @@ export const searchAddDataActiveState: GuideState = {
],
};
-export const searchAddDataInProgressState: GuideState = {
- isActive: true,
- status: 'in_progress',
- steps: [
- {
- id: 'add_data',
- status: 'in_progress',
- },
- {
- id: 'browse_docs',
- status: 'inactive',
- },
- {
- id: 'search_experience',
- status: 'inactive',
- },
- ],
- guideId: 'search',
-};
-
export const securityAddDataInProgressState: GuideState = {
guideId: 'security',
status: 'in_progress',
@@ -61,10 +41,14 @@ export const securityAddDataInProgressState: GuideState = {
id: 'rules',
status: 'inactive',
},
+ {
+ id: 'alertsCases',
+ status: 'inactive',
+ },
],
};
-export const securityRulesActivesState: GuideState = {
+export const securityRulesActiveState: GuideState = {
guideId: 'security',
isActive: true,
status: 'in_progress',
@@ -77,6 +61,10 @@ export const securityRulesActivesState: GuideState = {
id: 'rules',
status: 'active',
},
+ {
+ id: 'alertsCases',
+ status: 'inactive',
+ },
],
};
@@ -93,5 +81,9 @@ export const noGuideActiveState: GuideState = {
id: 'rules',
status: 'inactive',
},
+ {
+ id: 'alertsCases',
+ status: 'inactive',
+ },
],
};
diff --git a/src/plugins/guided_onboarding/public/services/api.test.ts b/src/plugins/guided_onboarding/public/services/api.test.ts
index 5deb3d50987f2..8765af0fb07f6 100644
--- a/src/plugins/guided_onboarding/public/services/api.test.ts
+++ b/src/plugins/guided_onboarding/public/services/api.test.ts
@@ -18,7 +18,7 @@ import {
noGuideActiveState,
searchAddDataActiveState,
securityAddDataInProgressState,
- securityRulesActivesState,
+ securityRulesActiveState,
} from './api.mocks';
const searchGuide = 'search';
@@ -315,17 +315,58 @@ describe('GuidedOnboarding ApiService', () => {
});
});
+ it(`marks the step as 'ready_to_complete' if it's configured for manual completion`, async () => {
+ const securityRulesInProgressState = {
+ ...securityRulesActiveState,
+ steps: [
+ securityRulesActiveState.steps[0],
+ {
+ id: securityRulesActiveState.steps[1].id,
+ status: 'in_progress',
+ },
+ securityRulesActiveState.steps[2],
+ ],
+ };
+ httpClient.get.mockResolvedValue({
+ state: [securityRulesInProgressState],
+ });
+ apiService.setup(httpClient);
+
+ await apiService.completeGuideStep('security', 'rules');
+
+ expect(httpClient.put).toHaveBeenCalledTimes(1);
+ // Verify the completed step now has a "ready_to_complete" status, and the subsequent step is "inactive"
+ expect(httpClient.put).toHaveBeenLastCalledWith(`${API_BASE_PATH}/state`, {
+ body: JSON.stringify({
+ ...securityRulesInProgressState,
+ steps: [
+ securityRulesInProgressState.steps[0],
+ {
+ id: securityRulesInProgressState.steps[1].id,
+ status: 'ready_to_complete',
+ },
+ {
+ id: securityRulesInProgressState.steps[2].id,
+ status: 'inactive',
+ },
+ ],
+ }),
+ });
+ });
+
it('returns undefined if the selected guide is not active', async () => {
const startState = await apiService.completeGuideStep('observability', 'add_data'); // not active
expect(startState).not.toBeDefined();
});
it('does nothing if the step is not in progress', async () => {
- await apiService.updateGuideState(searchAddDataActiveState, false);
+ httpClient.get.mockResolvedValue({
+ state: [searchAddDataActiveState],
+ });
+ apiService.setup(httpClient);
await apiService.completeGuideStep(searchGuide, firstStep);
- // Expect only 1 call from updateGuideState()
- expect(httpClient.put).toHaveBeenCalledTimes(1);
+ expect(httpClient.put).toHaveBeenCalledTimes(0);
});
});
@@ -384,7 +425,7 @@ describe('GuidedOnboarding ApiService', () => {
expect(httpClient.put).toHaveBeenCalledTimes(1);
// this assertion depends on the guides config
expect(httpClient.put).toHaveBeenCalledWith(`${API_BASE_PATH}/state`, {
- body: JSON.stringify(securityRulesActivesState),
+ body: JSON.stringify(securityRulesActiveState),
});
});
diff --git a/src/plugins/guided_onboarding/public/services/api.ts b/src/plugins/guided_onboarding/public/services/api.ts
index 7c970717be5f1..7895f9f2a7f24 100644
--- a/src/plugins/guided_onboarding/public/services/api.ts
+++ b/src/plugins/guided_onboarding/public/services/api.ts
@@ -13,8 +13,12 @@ import { GuidedOnboardingApi } from '../types';
import {
getGuideConfig,
getInProgressStepId,
+ getStepConfig,
+ getUpdatedSteps,
isIntegrationInGuideStep,
isLastStep,
+ isStepInProgress,
+ isStepReadyToComplete,
} from './helpers';
import { API_BASE_PATH } from '../../common/constants';
import type { GuideState, GuideId, GuideStep, GuideStepIds } from '../../common/types';
@@ -210,16 +214,7 @@ export class ApiService implements GuidedOnboardingApi {
*/
public isGuideStepActive$(guideId: GuideId, stepId: GuideStepIds): Observable
{
return this.fetchActiveGuideState$().pipe(
- map((activeGuideState) => {
- // Return false right away if the guide itself is not active
- if (activeGuideState?.guideId !== guideId) {
- return false;
- }
-
- // If the guide is active, next check the step
- const selectedStep = activeGuideState.steps.find((step) => step.id === stepId);
- return selectedStep ? selectedStep.status === 'in_progress' : false;
- })
+ map((activeGuideState) => isStepInProgress(activeGuideState, guideId, stepId))
);
}
@@ -282,34 +277,20 @@ export class ApiService implements GuidedOnboardingApi {
return undefined;
}
- const currentStepIndex = guideState.steps.findIndex((step) => step.id === stepId);
- const currentStep = guideState.steps[currentStepIndex];
- const isCurrentStepInProgress = currentStep ? currentStep.status === 'in_progress' : false;
-
- if (isCurrentStepInProgress) {
- const updatedSteps: GuideStep[] = guideState.steps.map((step, stepIndex) => {
- const isCurrentStep = step.id === currentStep!.id;
- const isNextStep = stepIndex === currentStepIndex + 1;
-
- // Mark the current step as complete
- if (isCurrentStep) {
- return {
- id: step.id,
- status: 'complete',
- };
- }
+ const isCurrentStepInProgress = isStepInProgress(guideState, guideId, stepId);
+ const isCurrentStepReadyToComplete = isStepReadyToComplete(guideState, guideId, stepId);
- // Update the next step to active status
- if (isNextStep) {
- return {
- id: step.id,
- status: 'active',
- };
- }
+ const stepConfig = getStepConfig(guideState.guideId, stepId);
+ const isManualCompletion = stepConfig ? !!stepConfig.manualCompletion : false;
- // All other steps return as-is
- return step;
- });
+ if (isCurrentStepInProgress || isCurrentStepReadyToComplete) {
+ const updatedSteps = getUpdatedSteps(
+ guideState,
+ stepId,
+ // if current step is in progress and configured for manual completion,
+ // set the status to ready_to_complete
+ isManualCompletion && isCurrentStepInProgress
+ );
const currentGuide: GuideState = {
guideId,
@@ -318,7 +299,13 @@ export class ApiService implements GuidedOnboardingApi {
steps: updatedSteps,
};
- return await this.updateGuideState(currentGuide, true);
+ return await this.updateGuideState(
+ currentGuide,
+ // the panel is opened when the step is being set to complete.
+ // that happens when the step is not configured for manual completion
+ // or it's already ready_to_complete
+ !isManualCompletion || isCurrentStepReadyToComplete
+ );
}
return undefined;
diff --git a/src/plugins/guided_onboarding/public/services/helpers.test.ts b/src/plugins/guided_onboarding/public/services/helpers.test.ts
index 586566fe9488b..9dc7519a02019 100644
--- a/src/plugins/guided_onboarding/public/services/helpers.test.ts
+++ b/src/plugins/guided_onboarding/public/services/helpers.test.ts
@@ -11,7 +11,7 @@ import { isIntegrationInGuideStep, isLastStep } from './helpers';
import {
noGuideActiveState,
securityAddDataInProgressState,
- securityRulesActivesState,
+ securityRulesActiveState,
} from './api.mocks';
const searchGuide = 'search';
@@ -42,7 +42,7 @@ describe('GuidedOnboarding ApiService helpers', () => {
expect(result).toBe(false);
});
it('returns false if no integration is defined in the guide step', () => {
- const result = isIntegrationInGuideStep(securityRulesActivesState, 'endpoint');
+ const result = isIntegrationInGuideStep(securityRulesActiveState, 'endpoint');
expect(result).toBe(false);
});
it('returns false if no guide is active', () => {
diff --git a/src/plugins/guided_onboarding/public/services/helpers.ts b/src/plugins/guided_onboarding/public/services/helpers.ts
index 0e738646c558d..30fbd5051215b 100644
--- a/src/plugins/guided_onboarding/public/services/helpers.ts
+++ b/src/plugins/guided_onboarding/public/services/helpers.ts
@@ -9,24 +9,30 @@
import type { GuideId, GuideState, GuideStepIds } from '../../common/types';
import { guidesConfig } from '../constants/guides_config';
import { GuideConfig, StepConfig } from '../types';
+import { GuideStep } from '../../common/types';
-export const getGuideConfig = (guideID?: string): GuideConfig | undefined => {
- if (guideID && Object.keys(guidesConfig).includes(guideID)) {
- return guidesConfig[guideID as GuideId];
+export const getGuideConfig = (guideId?: GuideId): GuideConfig | undefined => {
+ if (guideId && Object.keys(guidesConfig).includes(guideId)) {
+ return guidesConfig[guideId];
}
};
-const getStepIndex = (guideID: string, stepID: string): number => {
- const guide = getGuideConfig(guideID);
+export const getStepConfig = (guideId: GuideId, stepId: GuideStepIds): StepConfig | undefined => {
+ const guideConfig = getGuideConfig(guideId);
+ return guideConfig?.steps.find((step) => step.id === stepId);
+};
+
+const getStepIndex = (guideId: GuideId, stepId: GuideStepIds): number => {
+ const guide = getGuideConfig(guideId);
if (guide) {
- return guide.steps.findIndex((step: StepConfig) => step.id === stepID);
+ return guide.steps.findIndex((step: StepConfig) => step.id === stepId);
}
return -1;
};
-export const isLastStep = (guideID: string, stepID: string): boolean => {
- const guide = getGuideConfig(guideID);
- const activeStepIndex = getStepIndex(guideID, stepID);
+export const isLastStep = (guideId: GuideId, stepId: GuideStepIds): boolean => {
+ const guide = getGuideConfig(guideId);
+ const activeStepIndex = getStepIndex(guideId, stepId);
const stepsNumber = guide?.steps.length || 0;
if (stepsNumber > 0) {
return activeStepIndex === stepsNumber - 1;
@@ -56,3 +62,70 @@ export const isIntegrationInGuideStep = (state: GuideState, integration?: string
}
return false;
};
+
+const isGuideActive = (guideState: GuideState | undefined, guideId: GuideId): boolean => {
+ // false if guideState is undefined or the guide is not active
+ return !!(guideState && guideState.isActive && guideState.guideId === guideId);
+};
+
+export const isStepInProgress = (
+ guideState: GuideState | undefined,
+ guideId: GuideId,
+ stepId: GuideStepIds
+): boolean => {
+ if (!isGuideActive(guideState, guideId)) {
+ return false;
+ }
+
+ // false if the step is not 'in_progress'
+ const selectedStep = guideState!.steps.find((step) => step.id === stepId);
+ return selectedStep ? selectedStep.status === 'in_progress' : false;
+};
+
+export const isStepReadyToComplete = (
+ guideState: GuideState | undefined,
+ guideId: GuideId,
+ stepId: GuideStepIds
+): boolean => {
+ if (!isGuideActive(guideState, guideId)) {
+ return false;
+ }
+
+ // false if the step is not 'ready_to_complete'
+ const selectedStep = guideState!.steps.find((step) => step.id === stepId);
+ return selectedStep ? selectedStep.status === 'ready_to_complete' : false;
+};
+
+export const getUpdatedSteps = (
+ guideState: GuideState,
+ stepId: GuideStepIds,
+ setToReadyToComplete?: boolean
+): GuideStep[] => {
+ const currentStepIndex = guideState.steps.findIndex((step) => step.id === stepId);
+ const currentStep = guideState.steps[currentStepIndex];
+ return guideState.steps.map((step, stepIndex) => {
+ const isCurrentStep = step.id === currentStep!.id;
+ const isNextStep = stepIndex === currentStepIndex + 1;
+
+ if (isCurrentStep) {
+ return {
+ id: step.id,
+ status: setToReadyToComplete ? 'ready_to_complete' : 'complete',
+ };
+ }
+
+ // if the current step is being updated to 'ready_to_complete, the next step stays inactive
+ // otherwise update the next step to active status
+ if (isNextStep) {
+ return setToReadyToComplete
+ ? step
+ : {
+ id: step.id,
+ status: 'active',
+ };
+ }
+
+ // All other steps return as-is
+ return step;
+ });
+};
diff --git a/src/plugins/guided_onboarding/public/types.ts b/src/plugins/guided_onboarding/public/types.ts
index ba7271756cbb7..0ca4a5801fb15 100755
--- a/src/plugins/guided_onboarding/public/types.ts
+++ b/src/plugins/guided_onboarding/public/types.ts
@@ -65,6 +65,11 @@ export interface StepConfig {
};
status?: StepStatus;
integration?: string;
+ manualCompletion?: {
+ title: string;
+ description: string;
+ readyToCompleteOnNavigation?: boolean;
+ };
}
export interface GuideConfig {
title: string;
From 45e4164a9b1186f623c9d5dff91b32b4d6258f0b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?=
Date: Thu, 13 Oct 2022 11:35:56 +0200
Subject: [PATCH 06/35] [Synthetics UI] Monitor details page: move tabs to the
header (#143176)
Co-authored-by: shahzad31
---
.../plugins/synthetics/common/constants/ui.ts | 4 +
.../monitor_details/monitor_details_page.tsx | 27 +--
.../monitor_details/monitor_detials_tabs.tsx | 70 --------
.../monitor_errors/monitor_errors.tsx | 2 +-
.../monitor_history/monitor_history.tsx | 2 +-
.../public/apps/synthetics/routes.tsx | 158 +++++++++++++-----
.../translations/translations/fr-FR.json | 3 -
.../translations/translations/ja-JP.json | 3 -
.../translations/translations/zh-CN.json | 3 -
9 files changed, 129 insertions(+), 143 deletions(-)
delete mode 100644 x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx
diff --git a/x-pack/plugins/synthetics/common/constants/ui.ts b/x-pack/plugins/synthetics/common/constants/ui.ts
index 226eda1986886..4fb8a374f944b 100644
--- a/x-pack/plugins/synthetics/common/constants/ui.ts
+++ b/x-pack/plugins/synthetics/common/constants/ui.ts
@@ -7,6 +7,10 @@
export const MONITOR_ROUTE = '/monitor/:monitorId?';
+export const MONITOR_HISTORY_ROUTE = '/monitor/:monitorId/history';
+
+export const MONITOR_ERRORS_ROUTE = '/monitor/:monitorId/errors';
+
export const MONITOR_ADD_ROUTE = '/add-monitor';
export const MONITOR_EDIT_ROUTE = '/edit-monitor/:monitorId';
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx
index 58b5f724d53b9..cdf4ba1cfb2ce 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_details_page.tsx
@@ -5,31 +5,12 @@
* 2.0.
*/
-import React, { useEffect } from 'react';
-import { useDispatch } from 'react-redux';
-import { useParams } from 'react-router-dom';
+import React from 'react';
import { useSelectedMonitor } from './hooks/use_selected_monitor';
-import { useSelectedLocation } from './hooks/use_selected_location';
-import { getMonitorAction, getMonitorRecentPingsAction } from '../../state/monitor_details';
import { useMonitorListBreadcrumbs } from '../monitors_page/hooks/use_breadcrumbs';
-import { MonitorDetailsTabs } from './monitor_detials_tabs';
-export const MonitorDetailsPage = () => {
- const { monitor } = useSelectedMonitor();
+export const MonitorDetailsPage: React.FC<{ children: React.ReactElement }> = ({ children }) => {
+ const { monitor } = useSelectedMonitor();
useMonitorListBreadcrumbs([{ text: monitor?.name ?? '' }]);
-
- const dispatch = useDispatch();
-
- const selectedLocation = useSelectedLocation();
- const { monitorId } = useParams<{ monitorId: string }>();
-
- useEffect(() => {
- dispatch(getMonitorAction.get({ monitorId }));
-
- if (selectedLocation) {
- dispatch(getMonitorRecentPingsAction.get({ monitorId, locationId: selectedLocation.label }));
- }
- }, [dispatch, monitorId, selectedLocation]);
-
- return ;
+ return children;
};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx
deleted file mode 100644
index 6667034da5fdf..0000000000000
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_detials_tabs.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
- * or more contributor license agreements. Licensed under the Elastic License
- * 2.0; you may not use this file except in compliance with the Elastic License
- * 2.0.
- */
-
-import { EuiIcon, EuiSpacer, EuiTabbedContent } from '@elastic/eui';
-import React from 'react';
-import { i18n } from '@kbn/i18n';
-import { ErrorsTabContent } from './monitor_errors/monitor_errors';
-import { HistoryTabContent } from './monitor_history/monitor_history';
-import { MonitorSummary } from './monitor_summary/monitor_summary';
-
-export const MonitorDetailsTabs = () => {
- const tabs = [
- {
- id: 'summary',
- name: SUMMARY_LABEL,
- content: (
- <>
-
-
- >
- ),
- },
- {
- id: 'history',
- name: HISTORY_LABEL,
- content: (
- <>
-
-
- >
- ),
- },
- {
- id: 'errors',
- name: ERRORS_LABEL,
- prepend: ,
- content: (
- <>
-
-
- >
- ),
- },
- ];
-
- return (
- {}}
- />
- );
-};
-
-const SUMMARY_LABEL = i18n.translate('xpack.synthetics.monitorSummary.summary', {
- defaultMessage: 'Summary',
-});
-
-const HISTORY_LABEL = i18n.translate('xpack.synthetics.monitorSummary.history', {
- defaultMessage: 'History',
-});
-
-const ERRORS_LABEL = i18n.translate('xpack.synthetics.monitorSummary.errors', {
- defaultMessage: 'Errors',
-});
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx
index 75fb0d7ccf3ae..479aa6ac72bfb 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_errors/monitor_errors.tsx
@@ -7,6 +7,6 @@
import { EuiText } from '@elastic/eui';
import React from 'react';
-export const ErrorsTabContent = () => {
+export const MonitorErrors = () => {
return Monitor errors tabs content;
};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx
index 5b90c2c2d73e1..a1522f58047da 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/components/monitor_details/monitor_history/monitor_history.tsx
@@ -7,6 +7,6 @@
import { EuiText } from '@elastic/eui';
import React from 'react';
-export const HistoryTabContent = () => {
+export const MonitorHistory = () => {
return Monitor history tab content;
};
diff --git a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
index 6d6cdd18e8953..92ec0afce43ff 100644
--- a/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
+++ b/x-pack/plugins/synthetics/public/apps/synthetics/routes.tsx
@@ -7,8 +7,15 @@
import { EuiThemeComputed } from '@elastic/eui/src/services/theme/types';
import React, { FC, useEffect } from 'react';
-import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiLink, useEuiTheme } from '@elastic/eui';
-import { Route, Switch, useHistory } from 'react-router-dom';
+import {
+ EuiFlexGroup,
+ EuiFlexItem,
+ EuiIcon,
+ EuiLink,
+ EuiPageHeaderProps,
+ useEuiTheme,
+} from '@elastic/eui';
+import { Route, Switch, useHistory, useRouteMatch } from 'react-router-dom';
import { OutPortal } from 'react-reverse-portal';
import { FormattedMessage } from '@kbn/i18n-react';
import { i18n } from '@kbn/i18n';
@@ -31,12 +38,14 @@ import {
MonitorDetailsLinkPortalNode,
} from './components/monitor_add_edit/portals';
import {
+ GETTING_STARTED_ROUTE,
+ MONITORS_ROUTE,
MONITOR_ADD_ROUTE,
MONITOR_EDIT_ROUTE,
- MONITORS_ROUTE,
- OVERVIEW_ROUTE,
- GETTING_STARTED_ROUTE,
+ MONITOR_ERRORS_ROUTE,
+ MONITOR_HISTORY_ROUTE,
MONITOR_ROUTE,
+ OVERVIEW_ROUTE,
} from '../../../common/constants';
import { PLUGIN } from '../../../common/constants/plugin';
import { MonitorPage } from './components/monitors_page/monitor_page';
@@ -45,6 +54,9 @@ import { RunTestManually } from './components/monitor_details/run_test_manually'
import { MonitorDetailsStatus } from './components/monitor_details/monitor_details_status';
import { MonitorDetailsLocation } from './components/monitor_details/monitor_details_location';
import { MonitorDetailsLastRun } from './components/monitor_details/monitor_details_last_run';
+import { MonitorSummary } from './components/monitor_details/monitor_summary/monitor_summary';
+import { MonitorHistory } from './components/monitor_details/monitor_history/monitor_history';
+import { MonitorErrors } from './components/monitor_details/monitor_errors/monitor_errors';
type RouteProps = LazyObservabilityPageTemplateProps & {
path: string;
@@ -89,33 +101,41 @@ const getRoutes = (
values: { baseTitle },
}),
path: MONITOR_ROUTE,
- component: () => ,
+ component: () => (
+
+
+
+ ),
dataTestSubj: 'syntheticsMonitorDetailsPage',
- pageHeader: {
- pageTitle: ,
- breadcrumbs: [
- {
- text: (
- <>
- {' '}
-
- >
- ),
- color: 'primary',
- 'aria-current': false,
- href: `${syntheticsPath}${MONITORS_ROUTE}`,
- },
- ],
- rightSideItems: [
- ,
- ,
- ,
- ,
- ],
- },
+ pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'summary'),
+ },
+ {
+ title: i18n.translate('xpack.synthetics.monitorHistory.title', {
+ defaultMessage: 'Synthetics Monitor History | {baseTitle}',
+ values: { baseTitle },
+ }),
+ path: MONITOR_HISTORY_ROUTE,
+ component: () => (
+
+
+
+ ),
+ dataTestSubj: 'syntheticsMonitorHistoryPage',
+ pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'history'),
+ },
+ {
+ title: i18n.translate('xpack.synthetics.monitorErrors.title', {
+ defaultMessage: 'Synthetics Monitor Errors | {baseTitle}',
+ values: { baseTitle },
+ }),
+ path: MONITOR_ERRORS_ROUTE,
+ component: () => (
+
+
+
+ ),
+ dataTestSubj: 'syntheticsMonitorHistoryPage',
+ pageHeader: getMonitorSummaryHeader(history, syntheticsPath, 'errors'),
},
{
title: i18n.translate('xpack.synthetics.overviewRoute.title', {
@@ -156,10 +176,7 @@ const getRoutes = (
defaultMessage="Management"
/>
),
- onClick: () =>
- history.push({
- pathname: MONITORS_ROUTE,
- }),
+ href: `${syntheticsPath}${MONITORS_ROUTE}`,
},
],
},
@@ -188,10 +205,7 @@ const getRoutes = (
defaultMessage="Overview"
/>
),
- onClick: () =>
- history.push({
- pathname: OVERVIEW_ROUTE,
- }),
+ href: `${syntheticsPath}${OVERVIEW_ROUTE}`,
},
{
label: (
@@ -272,6 +286,72 @@ const getRoutes = (
];
};
+const getMonitorSummaryHeader = (
+ history: ReturnType,
+ syntheticsPath: string,
+ selectedTab: 'summary' | 'history' | 'errors'
+): EuiPageHeaderProps => {
+ // Not a component, but it doesn't matter. Hooks are just functions
+ const match = useRouteMatch<{ monitorId: string }>(MONITOR_ROUTE); // eslint-disable-line react-hooks/rules-of-hooks
+
+ if (!match) {
+ return {};
+ }
+
+ const search = history.location.search;
+ const monitorId = match.params.monitorId;
+
+ return {
+ pageTitle: ,
+ breadcrumbs: [
+ {
+ text: (
+ <>
+ {' '}
+
+ >
+ ),
+ color: 'primary',
+ 'aria-current': false,
+ href: `${syntheticsPath}${MONITORS_ROUTE}`,
+ },
+ ],
+ rightSideItems: [
+ ,
+ ,
+ ,
+ ,
+ ],
+ tabs: [
+ {
+ label: i18n.translate('xpack.synthetics.monitorSummaryTab.title', {
+ defaultMessage: 'Summary',
+ }),
+ isSelected: selectedTab === 'summary',
+ href: `${syntheticsPath}${MONITOR_ROUTE.replace(':monitorId?', monitorId)}${search}`,
+ },
+ {
+ label: i18n.translate('xpack.synthetics.monitorHistoryTab.title', {
+ defaultMessage: 'History',
+ }),
+ isSelected: selectedTab === 'history',
+ href: `${syntheticsPath}${MONITOR_HISTORY_ROUTE.replace(':monitorId', monitorId)}${search}`,
+ },
+ {
+ label: i18n.translate('xpack.synthetics.monitorErrorsTab.title', {
+ defaultMessage: 'Errors',
+ }),
+ prepend: ,
+ isSelected: selectedTab === 'errors',
+ href: `${syntheticsPath}${MONITOR_ERRORS_ROUTE.replace(':monitorId', monitorId)}${search}`,
+ },
+ ],
+ };
+};
+
const RouteInit: React.FC> = ({ path, title }) => {
useEffect(() => {
document.title = title;
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index dd64f4ded51b6..3750a4af42538 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -31343,10 +31343,7 @@
"xpack.synthetics.monitorStatusBar.timestampFromNowTextAriaLabel": "Temps depuis la dernière vérification",
"xpack.synthetics.monitorStatusBar.type.ariaLabel": "Type de moniteur",
"xpack.synthetics.monitorStatusBar.type.label": "Type",
- "xpack.synthetics.monitorSummary.errors": "Erreurs",
- "xpack.synthetics.monitorSummary.history": "Historique",
"xpack.synthetics.monitorSummary.runTestManually": "Exécuter le test manuellement",
- "xpack.synthetics.monitorSummary.summary": "Résumé",
"xpack.synthetics.navigateToAlertingButton.content": "Gérer les règles",
"xpack.synthetics.navigateToAlertingUi": "Quitter Uptime et accéder à la page de gestion Alerting",
"xpack.synthetics.noDataConfig.beatsCard.description": "Monitorez de façon proactive la disponibilité de vos sites et services. Recevez des alertes et corrigez les problèmes plus rapidement pour optimiser l'expérience de vos utilisateurs.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index 55e4b7a85b6a9..f0d7d45fec244 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -31319,10 +31319,7 @@
"xpack.synthetics.monitorStatusBar.timestampFromNowTextAriaLabel": "最終確認からの経過時間",
"xpack.synthetics.monitorStatusBar.type.ariaLabel": "モニタータイプ",
"xpack.synthetics.monitorStatusBar.type.label": "型",
- "xpack.synthetics.monitorSummary.errors": "エラー",
- "xpack.synthetics.monitorSummary.history": "履歴",
"xpack.synthetics.monitorSummary.runTestManually": "手動でテストを実行",
- "xpack.synthetics.monitorSummary.summary": "まとめ",
"xpack.synthetics.navigateToAlertingButton.content": "ルールの管理",
"xpack.synthetics.navigateToAlertingUi": "Uptime を離れてアラート管理ページに移動します",
"xpack.synthetics.noDataConfig.beatsCard.description": "サイトとサービスの可用性をアクティブに監視するアラートを受信し、問題をより迅速に解決して、ユーザーエクスペリエンスを最適化します。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 93f70fddc202f..482a967318153 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -31354,10 +31354,7 @@
"xpack.synthetics.monitorStatusBar.timestampFromNowTextAriaLabel": "自上次检查以来经过的时间",
"xpack.synthetics.monitorStatusBar.type.ariaLabel": "监测类型",
"xpack.synthetics.monitorStatusBar.type.label": "类型",
- "xpack.synthetics.monitorSummary.errors": "错误",
- "xpack.synthetics.monitorSummary.history": "历史记录",
"xpack.synthetics.monitorSummary.runTestManually": "手动运行测试",
- "xpack.synthetics.monitorSummary.summary": "摘要",
"xpack.synthetics.navigateToAlertingButton.content": "管理规则",
"xpack.synthetics.navigateToAlertingUi": "离开 Uptime 并前往“Alerting 管理”页面",
"xpack.synthetics.noDataConfig.beatsCard.description": "主动监测站点和服务的可用性。接收告警并更快地解决问题,从而优化用户体验。",
From e7942ccdbdd38e8ea3a67c315dc0ed9b53b5292d Mon Sep 17 00:00:00 2001
From: Joe Reuter
Date: Thu, 13 Oct 2022 11:43:23 +0200
Subject: [PATCH 07/35] [Lens] Fix race condition in embeddable initialization
(#143069)
* fix race condition
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
---
.../lens/public/embeddable/embeddable.tsx | 16 ++++++++++++----
1 file changed, 12 insertions(+), 4 deletions(-)
diff --git a/x-pack/plugins/lens/public/embeddable/embeddable.tsx b/x-pack/plugins/lens/public/embeddable/embeddable.tsx
index 8da6ca0e6d5c0..77164cfa1e1fa 100644
--- a/x-pack/plugins/lens/public/embeddable/embeddable.tsx
+++ b/x-pack/plugins/lens/public/embeddable/embeddable.tsx
@@ -304,13 +304,21 @@ export class Embeddable
this.lensInspector = getLensInspectorService(deps.inspector);
this.expressionRenderer = deps.expressionRenderer;
+ let containerStateChangedCalledAlready = false;
this.initializeSavedVis(initialInput)
- .then(() => this.onContainerStateChanged(initialInput))
+ .then(() => {
+ if (!containerStateChangedCalledAlready) {
+ this.onContainerStateChanged(initialInput);
+ } else {
+ this.reload();
+ }
+ })
.catch((e) => this.onFatalError(e));
- this.subscription = this.getUpdated$().subscribe(() =>
- this.onContainerStateChanged(this.input)
- );
+ this.subscription = this.getUpdated$().subscribe(() => {
+ containerStateChangedCalledAlready = true;
+ this.onContainerStateChanged(this.input);
+ });
const input$ = this.getInput$();
this.embeddableTitle = this.getTitle();
From fbacdc6315ccc599fe82795a42b0c77559494aa6 Mon Sep 17 00:00:00 2001
From: Mark Hopkin
Date: Thu, 13 Oct 2022 11:26:23 +0100
Subject: [PATCH 08/35] [Fleet] Simplify `skipArchive` behaviour for input
packages (#141671)
* add test for missing pkg policy field
* test create package policy API
* always try and get info from registry
* remove skipArchive
* fix mock
* remove comment
* change log message
* add download key to archive packages
* add clarifying comment
---
.../server/services/epm/packages/get.test.ts | 4 ++
.../fleet/server/services/epm/packages/get.ts | 11 ++--
.../server/services/epm/registry/index.ts | 4 +-
.../fleet_api_integration/apis/epm/get.ts | 12 ++++
.../apis/package_policy/create.ts | 66 +++++++++++++++++++
5 files changed, 90 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
index 1dacf4ed7df8a..20ed655d97176 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/get.test.ts
@@ -188,6 +188,10 @@ describe('When using EPM `get` services', () => {
name: 'my-package',
version: '1.0.0',
} as RegistryPackage);
+ MockRegistry.fetchInfo.mockResolvedValue({
+ name: 'my-package',
+ version: '1.0.0',
+ } as RegistryPackage);
MockRegistry.getPackage.mockResolvedValue({
paths: [],
packageInfo: {
diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts
index 0ce4e3c1cbea3..b8b447a8de526 100644
--- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts
+++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts
@@ -152,13 +152,12 @@ export async function getPackageInfo({
// If same version is available in registry and skipArchive is true, use the info from the registry (faster),
// otherwise build it from the archive
let paths: string[];
- let packageInfo: RegistryPackage | ArchivePackage | undefined = skipArchive
- ? await Registry.fetchInfo(pkgName, resolvedPkgVersion).catch(() => undefined)
- : undefined;
-
+ const registryInfo = await Registry.fetchInfo(pkgName, resolvedPkgVersion).catch(() => undefined);
+ let packageInfo;
// We need to get input only packages from source to get all fields
// see https://github.com/elastic/package-registry/issues/864
- if (packageInfo && packageInfo.type !== 'input') {
+ if (registryInfo && skipArchive && registryInfo.type !== 'input') {
+ packageInfo = registryInfo;
// Fix the paths
paths =
packageInfo.assets?.map((path) =>
@@ -293,7 +292,7 @@ export async function getPackageFromSource(options: {
}
} else {
res = await Registry.getPackage(pkgName, pkgVersion, { ignoreUnverified });
- logger.debug(`retrieved uninstalled package ${pkgName}-${pkgVersion}`);
+ logger.debug(`retrieved package ${pkgName}-${pkgVersion} from registry`);
}
if (!res) {
throw new FleetError(`package info for ${pkgName}-${pkgVersion} does not exist`);
diff --git a/x-pack/plugins/fleet/server/services/epm/registry/index.ts b/x-pack/plugins/fleet/server/services/epm/registry/index.ts
index 3f648a7fd73dc..e4ebf9013f55e 100644
--- a/x-pack/plugins/fleet/server/services/epm/registry/index.ts
+++ b/x-pack/plugins/fleet/server/services/epm/registry/index.ts
@@ -265,7 +265,9 @@ async function getPackageInfoFromArchiveOrCache(
archiveBuffer,
ensureContentType(archivePath)
);
- setPackageInfo({ packageInfo, name, version });
+ // set the download URL as it isn't contained in the manifest
+ // this allows us to re-download the archive during package install
+ setPackageInfo({ packageInfo: { ...packageInfo, download: archivePath }, name, version });
return packageInfo;
} else {
return cachedInfo;
diff --git a/x-pack/test/fleet_api_integration/apis/epm/get.ts b/x-pack/test/fleet_api_integration/apis/epm/get.ts
index 2a63e1c8b3905..280922b2e4a33 100644
--- a/x-pack/test/fleet_api_integration/apis/epm/get.ts
+++ b/x-pack/test/fleet_api_integration/apis/epm/get.ts
@@ -144,6 +144,18 @@ export default function (providerContext: FtrProviderContext) {
expect(packageInfo.name).to.equal('apache');
await uninstallPackage(testPkgName, testPkgVersion);
});
+ it('should return all fields for input only packages', async function () {
+ // input packages have to get their package info from the manifest directly
+ // not from the package registry. This is because they contain a field the registry
+ // does not support
+ const res = await supertest
+ .get(`/api/fleet/epm/packages/integration_to_input/0.9.1`)
+ .expect(200);
+
+ const packageInfo = res.body.item;
+ expect(packageInfo.policy_templates.length).to.equal(1);
+ expect(packageInfo.policy_templates[0].vars).not.to.be(undefined);
+ });
describe('Pkg verification', () => {
it('should return validation error for unverified input only pkg', async function () {
const res = await supertest.get(`/api/fleet/epm/packages/input_only/0.1.0`).expect(400);
diff --git a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
index 10b1b709d188f..21a5fa0f6dc2a 100644
--- a/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
+++ b/x-pack/test/fleet_api_integration/apis/package_policy/create.ts
@@ -446,6 +446,72 @@ export default function (providerContext: FtrProviderContext) {
expect(policy.name).to.equal(nameWithWhitespace.trim());
});
+ describe('input only packages', () => {
+ it('should return 400 if dataset not provided for input only pkg', async function () {
+ await supertest
+ .post(`/api/fleet/package_policies`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ policy_id: agentPolicyId,
+ package: {
+ name: 'integration_to_input',
+ version: '0.9.1',
+ },
+ name: 'integration_to_input-1',
+ description: '',
+ namespace: 'default',
+ inputs: {
+ 'logs-logfile': {
+ enabled: true,
+ streams: {
+ 'integration_to_input.logs': {
+ enabled: true,
+ vars: {
+ paths: ['/tmp/test.log'],
+ tags: ['tag1'],
+ ignore_older: '72h',
+ },
+ },
+ },
+ },
+ },
+ })
+ .expect(400);
+ });
+ it('should successfully create an input only package policy with all required vars', async function () {
+ await supertest
+ .post(`/api/fleet/package_policies`)
+ .set('kbn-xsrf', 'xxxx')
+ .send({
+ policy_id: agentPolicyId,
+ package: {
+ name: 'integration_to_input',
+ version: '0.9.1',
+ },
+ name: 'integration_to_input-2',
+ description: '',
+ namespace: 'default',
+ inputs: {
+ 'logs-logfile': {
+ enabled: true,
+ streams: {
+ 'integration_to_input.logs': {
+ enabled: true,
+ vars: {
+ paths: ['/tmp/test.log'],
+ tags: ['tag1'],
+ ignore_older: '72h',
+ 'data_stream.dataset': 'generic',
+ },
+ },
+ },
+ },
+ },
+ })
+ .expect(200);
+ });
+ });
+
describe('Simplified package policy', () => {
it('should work with valid values', async () => {
await supertest
From e74b4e3e3546701a1a52d0f1607a9ad0098390f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?David=20S=C3=A1nchez?=
Date: Thu, 13 Oct 2022 12:47:09 +0200
Subject: [PATCH 09/35] [Security Solution][Endpoint] Adds trusted application
privileges for API actions (#142472)
* Adds trusted application privileges for API actions
* Unify error messages
* Removes validateCanManageEndpointArtifacts check
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../endpoint/validators/base_validator.ts | 7 ++++++
.../validators/trusted_app_validator.ts | 24 ++++++++++++-------
2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
index 872d8da7cdb3d..ecc2ac7893336 100644
--- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
+++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/base_validator.ts
@@ -10,6 +10,7 @@ import { schema } from '@kbn/config-schema';
import { isEqual } from 'lodash/fp';
import type { ExceptionListItemSchema } from '@kbn/securitysolution-io-ts-list-types';
import { OperatingSystem } from '@kbn/securitysolution-utils';
+import type { EndpointAuthz } from '../../../../common/endpoint/types/authz';
import type { EndpointAppContextService } from '../../../endpoint/endpoint_app_context_services';
import type { ExceptionItemLikeOptions } from '../types';
import { getEndpointAuthzInitialState } from '../../../../common/endpoint/service/authz';
@@ -73,6 +74,12 @@ export class BaseValidator {
}
}
+ protected async validateHasPrivilege(privilege: keyof EndpointAuthz): Promise {
+ if (!(await this.endpointAuthzPromise)[privilege]) {
+ throw new EndpointArtifactExceptionValidationError('Endpoint authorization failure', 403);
+ }
+ }
+
protected isItemByPolicy(item: ExceptionItemLikeOptions): boolean {
return isArtifactByPolicy(item);
}
diff --git a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
index ebc3c05460923..86b11249af9bd 100644
--- a/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
+++ b/x-pack/plugins/security_solution/server/lists_integration/endpoint/validators/trusted_app_validator.ts
@@ -175,10 +175,18 @@ export class TrustedAppValidator extends BaseValidator {
return item.listId === ENDPOINT_TRUSTED_APPS_LIST_ID;
}
+ protected async validateHasWritePrivilege(): Promise {
+ return super.validateHasPrivilege('canWriteTrustedApplications');
+ }
+
+ protected async validateHasReadPrivilege(): Promise {
+ return super.validateHasPrivilege('canReadTrustedApplications');
+ }
+
async validatePreCreateItem(
item: CreateExceptionListItemOptions
): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasWritePrivilege();
await this.validateTrustedAppData(item);
await this.validateCanCreateByPolicyArtifacts(item);
await this.validateByPolicyItem(item);
@@ -187,27 +195,27 @@ export class TrustedAppValidator extends BaseValidator {
}
async validatePreDeleteItem(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasWritePrivilege();
}
async validatePreGetOneItem(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasReadPrivilege();
}
async validatePreMultiListFind(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasReadPrivilege();
}
async validatePreExport(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasWritePrivilege();
}
async validatePreSingleListFind(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasReadPrivilege();
}
async validatePreGetListSummary(): Promise {
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasReadPrivilege();
}
async validatePreUpdateItem(
@@ -216,7 +224,7 @@ export class TrustedAppValidator extends BaseValidator {
): Promise {
const updatedItem = _updatedItem as ExceptionItemLikeOptions;
- await this.validateCanManageEndpointArtifacts();
+ await this.validateHasWritePrivilege();
await this.validateTrustedAppData(updatedItem);
try {
From 84e5c6d1a84ac6f68725b18142d08734149ce584 Mon Sep 17 00:00:00 2001
From: Dima Arnautov
Date: Thu, 13 Oct 2022 12:58:32 +0200
Subject: [PATCH 10/35] [ML] Disable notifications polling for the basic
license (#143212)
---
.../ml/public/application/components/ml_page/side_nav.tsx | 8 +++++++-
1 file changed, 7 insertions(+), 1 deletion(-)
diff --git a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx
index 4dbc2400aa7cf..6959f5cf17a53 100644
--- a/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx
+++ b/x-pack/plugins/ml/public/application/components/ml_page/side_nav.tsx
@@ -84,7 +84,13 @@ export function useSideNavItems(activeRoute: MlRoute | undefined) {
{
id: 'notifications',
pathId: ML_PAGES.NOTIFICATIONS,
- name: ,
+ name: disableLinks ? (
+ i18n.translate('xpack.ml.navMenu.notificationsTabLinkText', {
+ defaultMessage: 'Notifications',
+ })
+ ) : (
+
+ ),
disabled: disableLinks,
testSubj: 'mlMainTab notifications',
},
From 8801f984a6a1d4b29987b7131e843c947b3fdac3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Thu, 13 Oct 2022 13:59:10 +0200
Subject: [PATCH 11/35] [EBT] `userId` is optional (#143261)
---
.../security/public/analytics/register_user_context.ts | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/x-pack/plugins/security/public/analytics/register_user_context.ts b/x-pack/plugins/security/public/analytics/register_user_context.ts
index bc48846913d02..19ecf0a6896fa 100644
--- a/x-pack/plugins/security/public/analytics/register_user_context.ts
+++ b/x-pack/plugins/security/public/analytics/register_user_context.ts
@@ -12,6 +12,11 @@ import { Sha256 } from '@kbn/crypto-browser';
import type { AuthenticationServiceSetup } from '..';
+interface UserIdContext {
+ userId?: string;
+ isElasticCloudUser: boolean;
+}
+
/**
* Set up the Analytics context provider for the User information.
* @param analytics Core's Analytics service. The Setup contract.
@@ -24,7 +29,7 @@ export function registerUserContext(
authc: AuthenticationServiceSetup,
cloudId?: string
) {
- analytics.registerContextProvider({
+ analytics.registerContextProvider({
name: 'user_id',
context$: from(authc.getCurrentUser()).pipe(
map((user) => {
@@ -50,7 +55,7 @@ export function registerUserContext(
schema: {
userId: {
type: 'keyword',
- _meta: { description: 'The user id scoped as seen by Cloud (hashed)' },
+ _meta: { description: 'The user id scoped as seen by Cloud (hashed)', optional: true },
},
isElasticCloudUser: {
type: 'boolean',
From 17668d773c2b4791284766d81b7b99227cc7dc14 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=B8ren=20Louv-Jansen?=
Date: Thu, 13 Oct 2022 14:00:09 +0200
Subject: [PATCH 12/35] [APM] Fix tooltip text (#143243)
---
.../service_overview_dependencies_table/index.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
index 8e7c7a0336d30..c4c1690b6bb89 100644
--- a/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
+++ b/x-pack/plugins/apm/public/components/app/service_overview/service_overview_dependencies_table/index.tsx
@@ -140,7 +140,7 @@ export function ServiceOverviewDependenciesTable({
'xpack.apm.serviceOverview.dependenciesTableTitleTip',
{
defaultMessage:
- 'Uninstrumented downstream services or external connections derived from the exit spans of instrumented services.',
+ 'Downstream services and external connections to uninstrumented services',
}
)}
>
From 0980919e244439cccd421e2f697f3540e97c229f Mon Sep 17 00:00:00 2001
From: Yaroslav Kuznietsov
Date: Thu, 13 Oct 2022 15:59:17 +0300
Subject: [PATCH 13/35] [Lens][TSVB] Functional tests for converting of Metric.
(#143164)
* Added test for metric with params.
* Added test for invalid model.
* Added test for unsupported aggregation.
* Added test for sibling pipeline agg.
* Added parent pipeline agg tests.
* Added functional tests for static value.
* Added tests for converting metric with params, unsupported metrics and not valid panels.
* Added tests for color ranges.
---
.../page_objects/visual_builder_page.ts | 12 +++
.../lens/group3/open_in_lens/tsvb/metric.ts | 99 ++++++++++++++++++-
.../test/functional/page_objects/lens_page.ts | 16 +++
3 files changed, 126 insertions(+), 1 deletion(-)
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index 3e62d830cddbf..be13c856c3f02 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -417,6 +417,18 @@ export class VisualBuilderPageObject extends FtrService {
});
}
+ public async createColorRule(nth = 0) {
+ await this.clickPanelOptions('metric');
+
+ const elements = await this.testSubjects.findAll('AddAddBtn');
+ await elements[nth].click();
+ await this.visChart.waitForVisualizationRenderingStabilized();
+ await this.retry.waitFor('new color rule is added', async () => {
+ const currentAddButtons = await this.testSubjects.findAll('AddAddBtn');
+ return currentAddButtons.length > elements.length;
+ });
+ }
+
public async selectAggType(value: string, nth = 0) {
const elements = await this.testSubjects.findAll('aggSelector');
await this.comboBox.setElement(elements[nth], value);
diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
index 273473b67a31e..6ec2ef5cc984a 100644
--- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
+++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
@@ -9,9 +9,16 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const { visualize, visualBuilder, lens } = getPageObjects(['visualBuilder', 'visualize', 'lens']);
+ const { visualize, visualBuilder, lens, header } = getPageObjects([
+ 'visualBuilder',
+ 'visualize',
+ 'lens',
+ 'header',
+ ]);
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const find = getService('find');
describe('Metric', function describeIndexTests() {
before(async () => {
@@ -40,5 +47,95 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const metricData = await lens.getMetricVisualizationData();
expect(metricData[0].title).to.eql('Count of records');
});
+
+ it('should draw static value', async () => {
+ await visualBuilder.selectAggType('Static Value');
+ await visualBuilder.setStaticValue(10);
+
+ await header.waitUntilLoadingHasFinished();
+
+ const button = await testSubjects.find('visualizeEditInLensButton');
+ await button.click();
+ await lens.waitForVisualization('mtrVis');
+ await retry.try(async () => {
+ const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`);
+ expect(layers).to.have.length(1);
+
+ const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
+ expect(dimensions).to.have.length(1);
+ expect(await dimensions[0].getVisibleText()).to.be('10');
+ });
+ });
+
+ it('should convert metric with params', async () => {
+ await visualBuilder.selectAggType('Value Count');
+ await visualBuilder.setFieldForAggregation('bytes');
+
+ await header.waitUntilLoadingHasFinished();
+
+ const button = await testSubjects.find('visualizeEditInLensButton');
+ await button.click();
+ await lens.waitForVisualization('mtrVis');
+ await retry.try(async () => {
+ const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`);
+ expect(layers).to.have.length(1);
+
+ const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
+ expect(dimensions).to.have.length(1);
+ expect(await dimensions[0].getVisibleText()).to.be('Count of bytes');
+ });
+ });
+
+ it('should not allow converting of unsupported metric', async () => {
+ await visualBuilder.selectAggType('Counter Rate');
+ await visualBuilder.setFieldForAggregation('machine.ram');
+
+ await header.waitUntilLoadingHasFinished();
+
+ const canEdit = await testSubjects.exists('visualizeEditInLensButton');
+ expect(canEdit).to.be(false);
+ });
+
+ it('should not allow converting of not valid panel', async () => {
+ await visualBuilder.selectAggType('Value Count');
+ await header.waitUntilLoadingHasFinished();
+ const canEdit = await testSubjects.exists('visualizeEditInLensButton');
+ expect(canEdit).to.be(false);
+ });
+
+ it('should convert color ranges', async () => {
+ await visualBuilder.clickPanelOptions('metric');
+ await visualBuilder.setColorRuleOperator('>= greater than or equal');
+ await visualBuilder.setColorRuleValue(10);
+ await visualBuilder.setColorPickerValue('#54B399');
+
+ await header.waitUntilLoadingHasFinished();
+ const button = await testSubjects.find('visualizeEditInLensButton');
+ await button.click();
+
+ await lens.waitForVisualization('mtrVis');
+ await retry.try(async () => {
+ const closePalettePanels = await testSubjects.findAll(
+ 'lns-indexPattern-PalettePanelContainerBack'
+ );
+ if (closePalettePanels.length) {
+ await lens.closePalettePanel();
+ await lens.closeDimensionEditor();
+ }
+
+ const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
+ expect(dimensions).to.have.length(1);
+
+ dimensions[0].click();
+
+ await lens.openPalettePanel('lnsMetric');
+ const colorStops = await lens.getPaletteColorStops();
+
+ expect(colorStops).to.eql([
+ { stop: '10', color: 'rgba(84, 179, 153, 1)' },
+ { stop: '', color: undefined },
+ ]);
+ });
+ });
});
}
diff --git a/x-pack/test/functional/page_objects/lens_page.ts b/x-pack/test/functional/page_objects/lens_page.ts
index c37a9af94eb7e..05e3c6bb53f57 100644
--- a/x-pack/test/functional/page_objects/lens_page.ts
+++ b/x-pack/test/functional/page_objects/lens_page.ts
@@ -1586,5 +1586,21 @@ export function LensPageProvider({ getService, getPageObjects }: FtrProviderCont
throw new Error(`Warning with text "${warningText}" not found`);
}
},
+
+ async getPaletteColorStops() {
+ const stops = await find.allByCssSelector(
+ `[data-test-subj^="lnsPalettePanel_dynamicColoring_range_value_"]`
+ );
+ const colorsElements = await testSubjects.findAll('euiColorPickerAnchor');
+ const colors = await Promise.all(
+ colorsElements.map((c) => c.getComputedStyle('background-color'))
+ );
+
+ return await Promise.all(
+ stops.map(async (stop, index) => {
+ return { stop: await stop.getAttribute('value'), color: colors[index] };
+ })
+ );
+ },
});
}
From 6cb56f106223dbc65057ebc834e4c09de8bb7cbe Mon Sep 17 00:00:00 2001
From: Philippe Oberti
Date: Thu, 13 Oct 2022 08:52:25 -0500
Subject: [PATCH 14/35] [TIP] Cleanup structure and imports for the entire
indicators module (#142788)
- rename indicator_field_label to field_label
- rename indicator_field_value to field_value
- rename index.tsx to index.ts
- move indicators_page.tsx under pages folder and rename to indicators.tsx
- rename use_indicators_filters_context to use_filters_context
- rename use_indicators_total_count to use_total_count
- add index.ts at root of indicators module and update imports in other modules accordingly
---
.../mocks/mock_indicators_filters_context.tsx | 2 +-
.../public/common/mocks/story_providers.tsx | 2 +-
.../public/common/mocks/test_providers.tsx | 2 +-
.../containers/field_types_provider.tsx | 5 ++--
.../integrations_guard.test.tsx | 4 +--
.../integrations_guard/integrations_guard.tsx | 5 ++--
.../field_label.tsx} | 0
.../index.tsx => field_label/index.ts} | 2 +-
.../__snapshots__/field.test.tsx.snap} | 0
.../field.stories.tsx} | 2 +-
.../field.test.tsx} | 2 +-
.../field_value.tsx} | 2 +-
.../index.tsx => field_value/index.ts} | 2 +-
.../fields_table/fields_table.stories.tsx | 2 +-
.../flyout/fields_table/fields_table.tsx | 2 +-
.../components/flyout/flyout.stories.tsx | 2 +-
.../indicators/components/flyout/flyout.tsx | 2 +-
.../indicator_value_actions.tsx | 2 +-
.../overview_tab/block/block.stories.tsx | 2 +-
.../flyout/overview_tab/block/block.tsx | 4 +--
.../highlighted_values_table.tsx | 2 +-
.../overview_tab/overview_tab.stories.tsx | 2 +-
.../flyout/overview_tab/overview_tab.tsx | 2 +-
.../flyout/table_tab/table_tab.stories.tsx | 2 +-
.../flyout/table_tab/table_tab.test.tsx | 2 +-
.../table/components/cell_actions.tsx | 4 +--
.../table/components/cell_renderer.tsx | 2 +-
.../table/hooks/use_column_settings.ts | 2 +-
.../components/table/table.stories.tsx | 2 +-
.../indicators/components/table/table.tsx | 4 +--
.../context.ts | 0
.../filters.tsx} | 2 +-
.../{indicators_filters => filters}/index.ts | 3 +-
.../public/modules/indicators/hooks/index.ts | 5 ++--
...ters_context.ts => use_filters_context.ts} | 2 +-
.../indicators/hooks/use_indicators.test.tsx | 2 +-
...ount.test.tsx => use_total_count.test.tsx} | 4 +--
...rs_total_count.tsx => use_total_count.tsx} | 0
.../public/modules/indicators/index.ts | 13 +++++++++
.../public/modules/indicators/pages/index.ts | 8 ++++++
.../indicators.test.tsx} | 19 ++++++-------
.../indicators.tsx} | 28 +++++++++----------
.../services/fetch_aggregated_indicators.ts | 3 +-
.../indicators/services/fetch_indicators.ts | 3 +-
.../public/modules/indicators/utils/index.ts | 14 ++++++++++
.../filter_in/filter_in.stories.tsx | 4 +--
.../components/filter_in/filter_in.test.tsx | 4 +--
.../filter_out/filter_out.stories.tsx | 4 +--
.../components/filter_out/filter_out.test.tsx | 4 +--
.../query_bar/hooks/use_filter_in_out.ts | 7 +++--
.../hooks/use_filters/use_filters.ts | 5 +---
.../add_to_timeline/add_to_timeline.tsx | 5 +---
.../timeline/hooks/use_add_to_timeline.ts | 2 +-
.../hooks/use_investigate_in_timeline.ts | 3 +-
.../threat_intelligence/public/plugin.tsx | 4 +--
55 files changed, 122 insertions(+), 96 deletions(-)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_label/indicator_field_label.tsx => field_label/field_label.tsx} (100%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_label/index.tsx => field_label/index.ts} (86%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_value/__snapshots__/indicator_field.test.tsx.snap => field_value/__snapshots__/field.test.tsx.snap} (100%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_value/indicator_field.stories.tsx => field_value/field.stories.tsx} (94%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_value/indicator_field.test.tsx => field_value/field.test.tsx} (95%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_value/indicator_field_value.tsx => field_value/field_value.tsx} (96%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/components/{indicator_field_value/index.tsx => field_value/index.ts} (86%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/containers/{indicators_filters => filters}/context.ts (100%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/containers/{indicators_filters/indicators_filters.tsx => filters/filters.tsx} (98%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/containers/{indicators_filters => filters}/index.ts (87%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/{use_indicators_filters_context.ts => use_filters_context.ts} (93%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/{use_indicators_total_count.test.tsx => use_total_count.test.tsx} (92%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/{use_indicators_total_count.tsx => use_total_count.tsx} (100%)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/index.ts
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/pages/index.ts
rename x-pack/plugins/threat_intelligence/public/modules/indicators/{indicators_page.test.tsx => pages/indicators.test.tsx} (76%)
rename x-pack/plugins/threat_intelligence/public/modules/indicators/{indicators_page.tsx => pages/indicators.tsx} (76%)
create mode 100644 x-pack/plugins/threat_intelligence/public/modules/indicators/utils/index.ts
diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx
index 0be85bea8ccf1..b93a67bd0a5c2 100644
--- a/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx
+++ b/x-pack/plugins/threat_intelligence/public/common/mocks/mock_indicators_filters_context.tsx
@@ -6,7 +6,7 @@
*/
import { FilterManager } from '@kbn/data-plugin/public';
-import { IndicatorsFiltersContextValue } from '../../modules/indicators/containers/indicators_filters/context';
+import { IndicatorsFiltersContextValue } from '../../modules/indicators';
export const mockTimeRange = { from: '2022-10-03T07:48:31.498Z', to: '2022-10-03T07:48:31.498Z' };
diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx
index 7cea653c45e5f..f1f5e3c215eb2 100644
--- a/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx
+++ b/x-pack/plugins/threat_intelligence/public/common/mocks/story_providers.tsx
@@ -16,7 +16,7 @@ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context';
import { SecuritySolutionContext } from '../../containers/security_solution_context';
import { getSecuritySolutionContextMock } from './mock_security_context';
-import { IndicatorsFiltersContext } from '../../modules/indicators/containers/indicators_filters/context';
+import { IndicatorsFiltersContext } from '../../modules/indicators';
import { FieldTypesContext } from '../../containers/field_types_provider';
import { generateFieldTypeMap } from './mock_field_type_map';
import { mockUiSettingsService } from './mock_kibana_ui_settings_service';
diff --git a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx
index c41f8972fd605..38eca1caad028 100644
--- a/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx
+++ b/x-pack/plugins/threat_intelligence/public/common/mocks/test_providers.tsx
@@ -24,7 +24,7 @@ import { SecuritySolutionPluginContext } from '../../types';
import { getSecuritySolutionContextMock } from './mock_security_context';
import { mockUiSetting } from './mock_kibana_ui_settings_service';
import { SecuritySolutionContext } from '../../containers/security_solution_context';
-import { IndicatorsFiltersContext } from '../../modules/indicators/containers/indicators_filters/context';
+import { IndicatorsFiltersContext } from '../../modules/indicators';
import { mockIndicatorsFiltersContext } from './mock_indicators_filters_context';
import { FieldTypesContext } from '../../containers/field_types_provider';
import { generateFieldTypeMap } from './mock_field_type_map';
diff --git a/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx b/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx
index 050ecb4a3fe10..109ad335d30a8 100644
--- a/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx
+++ b/x-pack/plugins/threat_intelligence/public/containers/field_types_provider.tsx
@@ -5,9 +5,8 @@
* 2.0.
*/
-import React, { createContext, useMemo } from 'react';
-import { FC } from 'react';
-import { useSourcererDataView } from '../modules/indicators/hooks/use_sourcerer_data_view';
+import React, { createContext, FC, useMemo } from 'react';
+import { useSourcererDataView } from '../modules/indicators';
export type FieldTypesContextValue = Record;
diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx
index fe3c88d6479e1..099559f8651b9 100644
--- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.test.tsx
@@ -11,9 +11,9 @@ import { IntegrationsGuard } from '.';
import { TestProvidersComponent } from '../../common/mocks/test_providers';
import { useTIDocumentationLink } from '../../hooks/use_documentation_link';
import { useIntegrationsPageLink } from '../../hooks/use_integrations_page_link';
-import { useIndicatorsTotalCount } from '../../modules/indicators/hooks/use_indicators_total_count';
+import { useIndicatorsTotalCount } from '../../modules/indicators';
-jest.mock('../../modules/indicators/hooks/use_indicators_total_count');
+jest.mock('../../modules/indicators/hooks/use_total_count');
jest.mock('../../hooks/use_integrations_page_link');
jest.mock('../../hooks/use_documentation_link');
diff --git a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx
index 82a8d05470da2..eb639d6c50f75 100644
--- a/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx
+++ b/x-pack/plugins/threat_intelligence/public/containers/integrations_guard/integrations_guard.tsx
@@ -6,10 +6,9 @@
*/
import { EuiLoadingLogo } from '@elastic/eui';
-import React from 'react';
-import { FC } from 'react';
+import React, { FC } from 'react';
import { EmptyPage } from '../../modules/empty_page';
-import { useIndicatorsTotalCount } from '../../modules/indicators/hooks/use_indicators_total_count';
+import { useIndicatorsTotalCount } from '../../modules/indicators';
import { SecuritySolutionPluginTemplateWrapper } from '../security_solution_plugin_template_wrapper';
/**
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_label/field_label.tsx
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/indicator_field_label.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_label/field_label.tsx
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_label/index.ts
similarity index 86%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_label/index.ts
index a2f2520c9541b..20bfa1be1286b 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_label/index.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_label/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './indicator_field_label';
+export * from './field_label';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/__snapshots__/indicator_field.test.tsx.snap b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/__snapshots__/field.test.tsx.snap
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/__snapshots__/indicator_field.test.tsx.snap
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/__snapshots__/field.test.tsx.snap
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.stories.tsx
similarity index 94%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.stories.tsx
index 9548691e49ec5..da56583404a15 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.stories.tsx
@@ -8,7 +8,7 @@
import React from 'react';
import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
import { generateMockIndicator } from '../../../../../common/types/indicator';
-import { IndicatorFieldValue } from './indicator_field_value';
+import { IndicatorFieldValue } from '.';
export default {
component: IndicatorFieldValue,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.test.tsx
similarity index 95%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.test.tsx
index c695a2c4ebe84..3142b6cd68dac 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field.test.tsx
@@ -7,7 +7,7 @@
import React from 'react';
import { render } from '@testing-library/react';
-import { IndicatorFieldValue } from './indicator_field_value';
+import { IndicatorFieldValue } from '.';
import { generateMockIndicator } from '../../../../../common/types/indicator';
import { EMPTY_VALUE } from '../../../../../common/constants';
import { TestProvidersComponent } from '../../../../common/mocks/test_providers';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field_value.tsx
similarity index 96%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field_value.tsx
index 55dfa883c30ad..699ccee505562 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/indicator_field_value.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/field_value.tsx
@@ -10,7 +10,7 @@ import { useFieldTypes } from '../../../../hooks/use_field_types';
import { EMPTY_VALUE } from '../../../../../common/constants';
import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator';
import { DateFormatter } from '../../../../components/date_formatter';
-import { unwrapValue } from '../../utils/unwrap_value';
+import { unwrapValue } from '../../utils';
export interface IndicatorFieldValueProps {
/**
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/index.ts
similarity index 86%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/index.ts
index 724caf3c75243..377ac4a266b0b 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/indicator_field_value/index.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/field_value/index.ts
@@ -5,4 +5,4 @@
* 2.0.
*/
-export * from './indicator_field_value';
+export * from './field_value';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx
index eb0ed8fb045ed..80bd24d59adc9 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.stories.tsx
@@ -10,7 +10,7 @@ import { mockIndicatorsFiltersContext } from '../../../../../common/mocks/mock_i
import { IndicatorFieldsTable } from '.';
import { generateMockIndicator } from '../../../../../../common/types/indicator';
import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers';
-import { IndicatorsFiltersContext } from '../../../containers/indicators_filters';
+import { IndicatorsFiltersContext } from '../../../containers/filters';
export default {
component: IndicatorFieldsTable,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx
index eb5b2d0ca2589..3fe1f62599059 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/fields_table/fields_table.tsx
@@ -9,7 +9,7 @@ import { EuiBasicTableColumn, EuiInMemoryTable, EuiInMemoryTableProps } from '@e
import { FormattedMessage } from '@kbn/i18n-react';
import React, { useMemo, VFC } from 'react';
import { Indicator } from '../../../../../../common/types/indicator';
-import { IndicatorFieldValue } from '../../indicator_field_value';
+import { IndicatorFieldValue } from '../../field_value';
import { IndicatorValueActions } from '../indicator_value_actions';
export interface IndicatorFieldsTableProps {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx
index 69236e778178b..88f6762aab0d4 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.stories.tsx
@@ -14,7 +14,7 @@ import { mockUiSettingsService } from '../../../../common/mocks/mock_kibana_ui_s
import { mockKibanaTimelinesService } from '../../../../common/mocks/mock_kibana_timelines_service';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
import { IndicatorsFlyout } from '.';
-import { IndicatorsFiltersContext } from '../../containers/indicators_filters';
+import { IndicatorsFiltersContext } from '../../containers/filters';
export default {
component: IndicatorsFlyout,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx
index 11102df797017..e43495c9b0a6c 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/flyout.tsx
@@ -26,7 +26,7 @@ import { DateFormatter } from '../../../../components/date_formatter/date_format
import { Indicator, RawIndicatorFieldId } from '../../../../../common/types/indicator';
import { IndicatorsFlyoutJson } from './json_tab';
import { IndicatorsFlyoutTable } from './table_tab';
-import { unwrapValue } from '../../utils/unwrap_value';
+import { unwrapValue } from '../../utils';
import { IndicatorsFlyoutOverview } from './overview_tab';
export const TITLE_TEST_ID = 'tiIndicatorFlyoutTitle';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx
index 473bc5d0088f7..c6eb8b82e2124 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/indicator_value_actions/indicator_value_actions.tsx
@@ -18,7 +18,7 @@ import { Indicator } from '../../../../../../common/types/indicator';
import { FilterInButtonIcon } from '../../../../query_bar/components/filter_in';
import { FilterOutButtonIcon } from '../../../../query_bar/components/filter_out';
import { AddToTimelineContextMenu } from '../../../../timeline/components/add_to_timeline';
-import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils/field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils';
import { CopyToClipboardContextMenu } from '../../copy_to_clipboard';
export const TIMELINE_BUTTON_TEST_ID = 'TimelineButton';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx
index e30d352c2644f..0ae9c8b962d9a 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.stories.tsx
@@ -6,7 +6,7 @@
*/
import React from 'react';
-import { IndicatorsFiltersContext } from '../../../../containers/indicators_filters';
+import { IndicatorsFiltersContext } from '../../../../containers/filters';
import { StoryProvidersComponent } from '../../../../../../common/mocks/story_providers';
import { generateMockIndicator } from '../../../../../../../common/types/indicator';
import { IndicatorBlock } from '.';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx
index 0866edde505bf..3baa182530b86 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/block/block.tsx
@@ -9,8 +9,8 @@ import { EuiPanel, EuiSpacer, EuiText } from '@elastic/eui';
import React, { VFC } from 'react';
import { css, euiStyled } from '@kbn/kibana-react-plugin/common';
import { Indicator } from '../../../../../../../common/types/indicator';
-import { IndicatorFieldValue } from '../../../indicator_field_value';
-import { IndicatorFieldLabel } from '../../../indicator_field_label';
+import { IndicatorFieldValue } from '../../../field_value';
+import { IndicatorFieldLabel } from '../../../field_label';
import { IndicatorValueActions } from '../../indicator_value_actions';
/**
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx
index 7ccbbdf2f1c99..5c60ed4684d9b 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/highlighted_values_table/highlighted_values_table.tsx
@@ -7,7 +7,7 @@
import React, { useMemo, VFC } from 'react';
import { Indicator, RawIndicatorFieldId } from '../../../../../../../common/types/indicator';
-import { unwrapValue } from '../../../../utils/unwrap_value';
+import { unwrapValue } from '../../../../utils';
import { IndicatorFieldsTable } from '../../fields_table';
/**
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx
index 005edd9c4201d..4c74ea25330d7 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.stories.tsx
@@ -10,7 +10,7 @@ import { Story } from '@storybook/react';
import { StoryProvidersComponent } from '../../../../../common/mocks/story_providers';
import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator';
import { IndicatorsFlyoutOverview } from '.';
-import { IndicatorsFiltersContext } from '../../../containers/indicators_filters';
+import { IndicatorsFiltersContext } from '../../../containers/filters';
export default {
component: IndicatorsFlyoutOverview,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx
index 7abbc1508fb58..8dc7f6a466574 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/overview_tab/overview_tab.tsx
@@ -18,7 +18,7 @@ import { FormattedMessage } from '@kbn/i18n-react';
import React, { useMemo, VFC } from 'react';
import { EMPTY_VALUE } from '../../../../../../common/constants';
import { Indicator, RawIndicatorFieldId } from '../../../../../../common/types/indicator';
-import { unwrapValue } from '../../../utils/unwrap_value';
+import { unwrapValue } from '../../../utils';
import { IndicatorEmptyPrompt } from '../empty_prompt';
import { IndicatorBlock } from './block';
import { HighlightedValuesTable } from './highlighted_values_table';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx
index 60808a46356a8..1842d52171db3 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.stories.tsx
@@ -14,7 +14,7 @@ import { mockUiSettingsService } from '../../../../../common/mocks/mock_kibana_u
import { mockKibanaTimelinesService } from '../../../../../common/mocks/mock_kibana_timelines_service';
import { generateMockIndicator, Indicator } from '../../../../../../common/types/indicator';
import { IndicatorsFlyoutTable } from '.';
-import { IndicatorsFiltersContext } from '../../../containers/indicators_filters';
+import { IndicatorsFiltersContext } from '../../../containers/filters';
export default {
component: IndicatorsFlyoutTable,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx
index 8503bcdace2cc..aae9aa41cbf2f 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/flyout/table_tab/table_tab.test.tsx
@@ -14,7 +14,7 @@ import {
RawIndicatorFieldId,
} from '../../../../../../common/types/indicator';
import { IndicatorsFlyoutTable, TABLE_TEST_ID } from '.';
-import { unwrapValue } from '../../../utils/unwrap_value';
+import { unwrapValue } from '../../../utils';
import { EMPTY_PROMPT_TEST_ID } from '../empty_prompt';
const mockIndicator: Indicator = generateMockIndicator();
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx
index baed5eda31478..969308200797a 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_actions.tsx
@@ -11,8 +11,8 @@ import { Indicator } from '../../../../../../common/types/indicator';
import { AddToTimelineCellAction } from '../../../../timeline/components/add_to_timeline';
import { FilterInCellAction } from '../../../../query_bar/components/filter_in';
import { FilterOutCellAction } from '../../../../query_bar/components/filter_out';
-import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils/field_value';
-import type { Pagination } from '../../../services/fetch_indicators';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../utils';
+import type { Pagination } from '../../../services';
export const CELL_TIMELINE_BUTTON_TEST_ID = 'tiIndicatorsTableCellTimelineButton';
export const CELL_FILTER_IN_BUTTON_TEST_ID = 'tiIndicatorsTableCellFilterInButton';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx
index 97c37c1598ccd..d4b52a0c82af6 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/components/cell_renderer.tsx
@@ -10,7 +10,7 @@ import React, { useContext, useEffect } from 'react';
import { euiDarkVars as themeDark, euiLightVars as themeLight } from '@kbn/ui-theme';
import { useKibana } from '../../../../../hooks/use_kibana';
import { Indicator } from '../../../../../../common/types/indicator';
-import { IndicatorFieldValue } from '../../indicator_field_value';
+import { IndicatorFieldValue } from '../../field_value';
import { IndicatorsTableContext } from '../contexts';
import { ActionsRowCell } from '.';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts
index ff61c9d525616..05c58ac4b2fcf 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/hooks/use_column_settings.ts
@@ -10,7 +10,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react';
import negate from 'lodash/negate';
import { RawIndicatorFieldId } from '../../../../../../common/types/indicator';
import { useKibana } from '../../../../../hooks/use_kibana';
-import { translateFieldLabel } from '../../indicator_field_label';
+import { translateFieldLabel } from '../../field_label';
export const DEFAULT_COLUMNS: EuiDataGridColumn[] = [
RawIndicatorFieldId.TimeStamp,
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx
index f11977145d7a4..4822d403e3f4b 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.stories.tsx
@@ -11,7 +11,7 @@ import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indi
import { StoryProvidersComponent } from '../../../../common/mocks/story_providers';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
import { IndicatorsTable } from '.';
-import { IndicatorsFiltersContext } from '../../containers/indicators_filters/context';
+import { IndicatorsFiltersContext } from '../../containers/filters/context';
import { DEFAULT_COLUMNS } from './hooks';
export default {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx
index 78fe1aaab2ea0..e5c4b7c205d58 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/components/table/table.tsx
@@ -27,8 +27,8 @@ import { IndicatorsTableContext, IndicatorsTableContextValue } from './contexts'
import { IndicatorsFlyout } from '../flyout';
import { ColumnSettingsValue, useToolbarOptions } from './hooks';
import { useFieldTypes } from '../../../../hooks/use_field_types';
-import { getFieldSchema } from '../../utils/get_field_schema';
-import { Pagination } from '../../services/fetch_indicators';
+import { getFieldSchema } from '../../utils';
+import { Pagination } from '../../services';
export interface IndicatorsTableProps {
indicators: Indicator[];
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/context.ts
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/context.ts
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/context.ts
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/filters.tsx
similarity index 98%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/filters.tsx
index 2253b1c548968..8d4b986ffd509 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/indicators_filters.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/filters.tsx
@@ -8,7 +8,7 @@
import React, { FC, useMemo } from 'react';
import { useKibana } from '../../../../hooks/use_kibana';
import { useSecurityContext } from '../../../../hooks/use_security_context';
-import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from './context';
+import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from '.';
/**
* Container used to wrap components and share the {@link FilterManager} through React context.
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/index.ts
similarity index 87%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/index.ts
index 4ef23e3e95001..2eafda804a6c1 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/indicators_filters/index.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/containers/filters/index.ts
@@ -5,6 +5,5 @@
* 2.0.
*/
-export * from './indicators_filters';
-
+export * from './filters';
export * from './context';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/index.ts
index 23461fc809957..124171ce2d0a6 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/index.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/index.ts
@@ -6,6 +6,7 @@
*/
export * from './use_aggregated_indicators';
-export * from './use_indicators_filters_context';
-export * from './use_indicators_total_count';
+export * from './use_filters_context';
+export * from './use_indicators';
export * from './use_sourcerer_data_view';
+export * from './use_total_count';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_filters_context.ts
similarity index 93%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_filters_context.ts
index e4c7c48d03d1b..8fec9d03852b2 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_filters_context.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_filters_context.ts
@@ -9,7 +9,7 @@ import { useContext } from 'react';
import {
IndicatorsFiltersContext,
IndicatorsFiltersContextValue,
-} from '../containers/indicators_filters/context';
+} from '../containers/filters/context';
/**
* Hook to retrieve {@link IndicatorsFiltersContext} (contains FilterManager)
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
index 40d64636fa346..9292321658e86 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators.test.tsx
@@ -5,7 +5,7 @@
* 2.0.
*/
-import { renderHook, act } from '@testing-library/react-hooks';
+import { act, renderHook } from '@testing-library/react-hooks';
import { useIndicators, UseIndicatorsParams, UseIndicatorsValue } from './use_indicators';
import { TestProvidersComponent } from '../../../common/mocks/test_providers';
import { createFetchIndicators } from '../services/fetch_indicators';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx
similarity index 92%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.test.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx
index 7b5afb43e9311..af7ece94a6f51 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.test.tsx
@@ -5,10 +5,10 @@
* 2.0.
*/
-import { TestProvidersComponent, mockedSearchService } from '../../../common/mocks/test_providers';
+import { mockedSearchService, TestProvidersComponent } from '../../../common/mocks/test_providers';
import { act, renderHook } from '@testing-library/react-hooks';
import { BehaviorSubject } from 'rxjs';
-import { useIndicatorsTotalCount } from './use_indicators_total_count';
+import { useIndicatorsTotalCount } from '.';
const indicatorsResponse = { rawResponse: { hits: { hits: [], total: 0 } } };
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.tsx
similarity index 100%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_indicators_total_count.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/hooks/use_total_count.tsx
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/index.ts
new file mode 100644
index 0000000000000..73affbb6e8a63
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/index.ts
@@ -0,0 +1,13 @@
+/*
+ * 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 * from './containers/filters/context';
+export * from './hooks/use_filters_context';
+export * from './hooks/use_sourcerer_data_view';
+export * from './hooks/use_total_count';
+export * from './utils/field_value';
+export * from './utils/unwrap_value';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/index.ts
new file mode 100644
index 0000000000000..b4e67e7869557
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 * from './indicators';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.test.tsx
similarity index 76%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.test.tsx
index 527507584f0e1..d51410857c26e 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.test.tsx
@@ -7,18 +7,17 @@
import React from 'react';
import { render } from '@testing-library/react';
-import { IndicatorsPage } from './indicators_page';
-import { useIndicators } from './hooks/use_indicators';
-import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
-import { useFilters } from '../query_bar/hooks/use_filters';
+import { IndicatorsPage } from '.';
+import { useAggregatedIndicators, useIndicators } from '../hooks';
+import { useFilters } from '../../query_bar/hooks/use_filters';
import moment from 'moment';
-import { TestProvidersComponent } from '../../common/mocks/test_providers';
-import { TABLE_TEST_ID } from './components/table';
-import { mockTimeRange } from '../../common/mocks/mock_indicators_filters_context';
+import { TestProvidersComponent } from '../../../common/mocks/test_providers';
+import { TABLE_TEST_ID } from '../components/table';
+import { mockTimeRange } from '../../../common/mocks/mock_indicators_filters_context';
-jest.mock('../query_bar/hooks/use_filters');
-jest.mock('./hooks/use_indicators');
-jest.mock('./hooks/use_aggregated_indicators');
+jest.mock('../../query_bar/hooks/use_filters');
+jest.mock('../hooks/use_indicators');
+jest.mock('../hooks/use_aggregated_indicators');
const stub = () => {};
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx
similarity index 76%
rename from x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
rename to x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx
index 195bd74238241..6b79442164676 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/indicators_page.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/pages/indicators.tsx
@@ -7,20 +7,20 @@
import React, { FC, VFC } from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
-import { IndicatorsBarChartWrapper } from './components/barchart';
-import { IndicatorsTable } from './components/table';
-import { useIndicators } from './hooks/use_indicators';
-import { DefaultPageLayout } from '../../components/layout';
-import { useFilters } from '../query_bar/hooks/use_filters';
-import { FiltersGlobal } from '../../containers/filters_global';
-import { useSourcererDataView } from './hooks/use_sourcerer_data_view';
-import { FieldTypesProvider } from '../../containers/field_types_provider';
-import { InspectorProvider } from '../../containers/inspector';
-import { useColumnSettings } from './components/table/hooks';
-import { useAggregatedIndicators } from './hooks/use_aggregated_indicators';
-import { IndicatorsFilters } from './containers/indicators_filters';
-import { useSecurityContext } from '../../hooks/use_security_context';
-import { UpdateStatus } from '../../components/update_status';
+import { IndicatorsBarChartWrapper } from '../components/barchart';
+import { IndicatorsTable } from '../components/table';
+import { useIndicators } from '../hooks/use_indicators';
+import { DefaultPageLayout } from '../../../components/layout';
+import { useFilters } from '../../query_bar/hooks/use_filters';
+import { FiltersGlobal } from '../../../containers/filters_global';
+import { useSourcererDataView } from '../hooks/use_sourcerer_data_view';
+import { FieldTypesProvider } from '../../../containers/field_types_provider';
+import { InspectorProvider } from '../../../containers/inspector';
+import { useColumnSettings } from '../components/table/hooks';
+import { useAggregatedIndicators } from '../hooks/use_aggregated_indicators';
+import { IndicatorsFilters } from '../containers/filters';
+import { useSecurityContext } from '../../../hooks/use_security_context';
+import { UpdateStatus } from '../../../components/update_status';
const queryClient = new QueryClient();
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts
index 0edc0cca34e5c..f69d28e663bc2 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_aggregated_indicators.ts
@@ -11,8 +11,7 @@ import type { Filter, Query, TimeRange } from '@kbn/es-query';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { calculateBarchartColumnTimeInterval } from '../../../common/utils/dates';
import { RawIndicatorFieldId } from '../../../../common/types/indicator';
-import { getIndicatorQueryParams } from '../utils/get_indicator_query_params';
-import { search } from '../utils/search';
+import { getIndicatorQueryParams, search } from '../utils';
const TIMESTAMP_FIELD = RawIndicatorFieldId.TimeStamp;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts
index f06038c320116..babf442a5f3fb 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/services/fetch_indicators.ts
@@ -9,8 +9,7 @@ import { ISearchStart } from '@kbn/data-plugin/public';
import type { Filter, Query, TimeRange } from '@kbn/es-query';
import { RequestAdapter } from '@kbn/inspector-plugin/common';
import { Indicator } from '../../../../common/types/indicator';
-import { getIndicatorQueryParams } from '../utils/get_indicator_query_params';
-import { search } from '../utils/search';
+import { getIndicatorQueryParams, search } from '../utils';
export interface RawIndicatorsResponse {
hits: {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/index.ts b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/index.ts
new file mode 100644
index 0000000000000..9a542d2960bd1
--- /dev/null
+++ b/x-pack/plugins/threat_intelligence/public/modules/indicators/utils/index.ts
@@ -0,0 +1,14 @@
+/*
+ * 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 * from './display_name';
+export * from './field_value';
+export * from './get_field_schema';
+export * from './get_indicator_query_params';
+export * from './get_runtime_mappings';
+export * from './search';
+export * from './unwrap_value';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx
index d32adf70ee103..b808f37f02486 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.stories.tsx
@@ -11,8 +11,8 @@ import { EuiContextMenuPanel, EuiDataGrid, EuiDataGridColumn } from '@elastic/eu
import { EuiDataGridColumnVisibility } from '@elastic/eui/src/components/datagrid/data_grid_types';
import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
-import { FilterInButtonIcon, FilterInContextMenu, FilterInCellAction } from '.';
-import { IndicatorsFiltersContext } from '../../../indicators/containers/indicators_filters/context';
+import { FilterInButtonIcon, FilterInCellAction, FilterInContextMenu } from '.';
+import { IndicatorsFiltersContext } from '../../../indicators';
export default {
title: 'FilterIn',
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.test.tsx
index 29349b5442790..18d555da85013 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_in/filter_in.test.tsx
@@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react';
import { render } from '@testing-library/react';
import { EuiButtonIcon } from '@elastic/eui';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
-import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context';
+import { useIndicatorsFiltersContext } from '../../../indicators';
import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context';
import {
FilterInButtonEmpty,
@@ -18,7 +18,7 @@ import {
FilterInContextMenu,
} from '.';
-jest.mock('../../../indicators/hooks/use_indicators_filters_context');
+jest.mock('../../../indicators/hooks/use_filters_context');
const mockIndicator: Indicator = generateMockIndicator();
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx
index 463b249998eb2..5553a62e8118d 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.stories.tsx
@@ -11,8 +11,8 @@ import { EuiContextMenuPanel, EuiDataGrid, EuiDataGridColumn } from '@elastic/eu
import { EuiDataGridColumnVisibility } from '@elastic/eui/src/components/datagrid/data_grid_types';
import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
-import { FilterOutButtonIcon, FilterOutContextMenu, FilterOutCellAction } from '.';
-import { IndicatorsFiltersContext } from '../../../indicators/containers/indicators_filters/context';
+import { FilterOutButtonIcon, FilterOutCellAction, FilterOutContextMenu } from '.';
+import { IndicatorsFiltersContext } from '../../../indicators';
export default {
title: 'FilterOut',
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.test.tsx b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.test.tsx
index 68c08cbebd0fc..c35416d5a3128 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.test.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/components/filter_out/filter_out.test.tsx
@@ -9,7 +9,7 @@ import React, { FunctionComponent } from 'react';
import { render } from '@testing-library/react';
import { EuiButtonIcon } from '@elastic/eui';
import { generateMockIndicator, Indicator } from '../../../../../common/types/indicator';
-import { useIndicatorsFiltersContext } from '../../../indicators/hooks/use_indicators_filters_context';
+import { useIndicatorsFiltersContext } from '../../../indicators';
import { mockIndicatorsFiltersContext } from '../../../../common/mocks/mock_indicators_filters_context';
import {
FilterOutButtonEmpty,
@@ -18,7 +18,7 @@ import {
FilterOutContextMenu,
} from '.';
-jest.mock('../../../indicators/hooks/use_indicators_filters_context');
+jest.mock('../../../indicators/hooks/use_filters_context');
const mockIndicator: Indicator = generateMockIndicator();
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.ts
index d44bb8528afab..a7bc8d2581617 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filter_in_out.ts
@@ -7,8 +7,11 @@
import { useCallback } from 'react';
import { Filter } from '@kbn/es-query';
-import { useIndicatorsFiltersContext } from '../../indicators/hooks/use_indicators_filters_context';
-import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/utils/field_value';
+import {
+ fieldAndValueValid,
+ getIndicatorFieldAndValue,
+ useIndicatorsFiltersContext,
+} from '../../indicators';
import { FilterIn, FilterOut, updateFiltersArray } from '../utils/filter';
import { Indicator } from '../../../../common/types/indicator';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts
index c50a10c17b709..1018320073500 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/query_bar/hooks/use_filters/use_filters.ts
@@ -6,10 +6,7 @@
*/
import { useContext } from 'react';
-import {
- IndicatorsFiltersContext,
- IndicatorsFiltersContextValue,
-} from '../../../indicators/containers/indicators_filters';
+import { IndicatorsFiltersContext, IndicatorsFiltersContextValue } from '../../../indicators';
export type UseFiltersValue = IndicatorsFiltersContextValue;
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
index 211d37dc6b4e4..1a1f4d61475e5 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/components/add_to_timeline/add_to_timeline.tsx
@@ -17,10 +17,7 @@ import {
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { generateDataProvider } from '../../utils/data_provider';
-import {
- fieldAndValueValid,
- getIndicatorFieldAndValue,
-} from '../../../indicators/utils/field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../../indicators';
import { useKibana } from '../../../../hooks/use_kibana';
import { Indicator } from '../../../../../common/types/indicator';
import { useStyles } from './styles';
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.ts
index ab69481d3b528..7e1e4af8a7d39 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_add_to_timeline.ts
@@ -8,7 +8,7 @@
import { DataProvider } from '@kbn/timelines-plugin/common';
import { AddToTimelineButtonProps } from '@kbn/timelines-plugin/public';
import { generateDataProvider } from '../utils/data_provider';
-import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/utils/field_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators';
import { Indicator } from '../../../../common/types/indicator';
export interface UseAddToTimelineParam {
diff --git a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
index efae8b441a673..41b33a9e4c963 100644
--- a/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
+++ b/x-pack/plugins/threat_intelligence/public/modules/timeline/hooks/use_investigate_in_timeline.ts
@@ -10,8 +10,7 @@ import moment from 'moment';
import { DataProvider } from '@kbn/timelines-plugin/common';
import { generateDataProvider } from '../utils/data_provider';
import { SecuritySolutionContext } from '../../../containers/security_solution_context';
-import { fieldAndValueValid, getIndicatorFieldAndValue } from '../../indicators/utils/field_value';
-import { unwrapValue } from '../../indicators/utils/unwrap_value';
+import { fieldAndValueValid, getIndicatorFieldAndValue, unwrapValue } from '../../indicators';
import {
Indicator,
IndicatorFieldEventEnrichmentMap,
diff --git a/x-pack/plugins/threat_intelligence/public/plugin.tsx b/x-pack/plugins/threat_intelligence/public/plugin.tsx
index 0ec3064c5052b..4af2f41af9c4f 100755
--- a/x-pack/plugins/threat_intelligence/public/plugin.tsx
+++ b/x-pack/plugins/threat_intelligence/public/plugin.tsx
@@ -12,11 +12,11 @@ import React, { Suspense, VFC } from 'react';
import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { KibanaContextProvider } from './hooks/use_kibana';
import {
+ SecuritySolutionPluginContext,
Services,
ThreatIntelligencePluginSetup,
ThreatIntelligencePluginStart,
ThreatIntelligencePluginStartDeps,
- SecuritySolutionPluginContext,
} from './types';
import { SecuritySolutionContext } from './containers/security_solution_context';
import { EnterpriseGuard } from './containers/enterprise_guard';
@@ -27,7 +27,7 @@ interface AppProps {
securitySolutionContext: SecuritySolutionPluginContext;
}
-const LazyIndicatorsPage = React.lazy(() => import('./modules/indicators/indicators_page'));
+const LazyIndicatorsPage = React.lazy(() => import('./modules/indicators/pages/indicators'));
const IndicatorsPage: VFC = () => (
From d21c5a7e0e16289a4020161595c92c64f4628233 Mon Sep 17 00:00:00 2001
From: DeDe Morton
Date: Thu, 13 Oct 2022 06:56:10 -0700
Subject: [PATCH 15/35] Fix typo in API preview (#143245)
---
.../single_page_layout/hooks/devtools_request.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
index 55e91154060b7..ca8c24b9a748d 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/single_page_layout/hooks/devtools_request.tsx
@@ -54,7 +54,7 @@ export function useDevToolsRequest({
'xpack.fleet.createPackagePolicy.devtoolsRequestWithAgentPolicyDescription',
{
defaultMessage:
- 'These Kibana requests creates a new agent policy and a new package policy.',
+ 'These Kibana requests create a new agent policy and a new package policy.',
}
),
];
From 63a4b2283f8272ca9ce96b4d19d5c05448377d8b Mon Sep 17 00:00:00 2001
From: "Joey F. Poon"
Date: Thu, 13 Oct 2022 09:35:33 -0500
Subject: [PATCH 16/35] [Security Solution] relax metadata + policy response
API permissions (#143244)
---
.../server/endpoint/routes/metadata/index.ts | 4 +-
.../endpoint/routes/metadata/metadata.test.ts | 41 ++++++++++++++++---
.../endpoint/routes/policy/handlers.test.ts | 4 +-
.../server/endpoint/routes/policy/index.ts | 2 +-
.../server/endpoint/routes/policy/service.ts | 2 +-
.../apis/endpoint_authz.ts | 6 ---
6 files changed, 41 insertions(+), 18 deletions(-)
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
index abe65089143aa..d959add145663 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/index.ts
@@ -67,10 +67,10 @@ export function registerEndpointRoutes(
{
path: HOST_METADATA_GET_ROUTE,
validate: GetMetadataRequestSchema,
- options: { authRequired: true, tags: ['access:securitySolution'] },
+ options: { authRequired: true },
},
withEndpointAuthz(
- { all: ['canReadSecuritySolution'] },
+ { any: ['canReadSecuritySolution', 'canAccessFleet'] },
logger,
getMetadataRequestHandler(endpointAppContext, logger)
)
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
index 58cda4c9737c9..86b57fcb85022 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/metadata/metadata.test.ts
@@ -674,7 +674,6 @@ describe('test endpoint routes', () => {
expect(esSearchMock).toHaveBeenCalledTimes(1);
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
expect(mockResponse.notFound).toBeCalled();
const message = mockResponse.notFound.mock.calls[0][0]?.body;
@@ -706,7 +705,6 @@ describe('test endpoint routes', () => {
expect(esSearchMock).toHaveBeenCalledTimes(1);
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
expect(mockResponse.ok).toBeCalled();
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
@@ -741,7 +739,6 @@ describe('test endpoint routes', () => {
expect(esSearchMock).toHaveBeenCalledTimes(1);
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
expect(mockResponse.ok).toBeCalled();
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
@@ -778,7 +775,6 @@ describe('test endpoint routes', () => {
expect(esSearchMock).toHaveBeenCalledTimes(1);
expect(routeConfig.options).toEqual({
authRequired: true,
- tags: ['access:securitySolution'],
});
expect(mockResponse.ok).toBeCalled();
const result = mockResponse.ok.mock.calls[0][0]?.body as HostInfo;
@@ -814,7 +810,37 @@ describe('test endpoint routes', () => {
expect(mockResponse.badRequest).toBeCalled();
});
- it('should get forbidden if no security solution access', async () => {
+ it('should work if no security solution access but has fleet access', async () => {
+ const response = legacyMetadataSearchResponseMock(
+ new EndpointDocGenerator().generateHostMetadata()
+ );
+ const mockRequest = httpServerMock.createKibanaRequest({
+ params: { id: response.hits.hits[0]._id },
+ });
+ const esSearchMock = mockScopedClient.asInternalUser.search;
+
+ mockAgentClient.getAgent.mockResolvedValue(agentGenerator.generate({ status: 'online' }));
+ esSearchMock.mockResponseOnce(response);
+
+ [routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
+ path.startsWith(HOST_METADATA_GET_ROUTE)
+ )!;
+
+ const contextOverrides = {
+ endpointAuthz: getEndpointAuthzInitialStateMock({
+ canReadSecuritySolution: false,
+ }),
+ };
+ await routeHandler(
+ createRouteHandlerContext(mockScopedClient, mockSavedObjectClient, contextOverrides),
+ mockRequest,
+ mockResponse
+ );
+
+ expect(mockResponse.ok).toBeCalled();
+ });
+
+ it('should get forbidden if no security solution or fleet access', async () => {
const mockRequest = httpServerMock.createKibanaRequest();
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
@@ -822,7 +848,10 @@ describe('test endpoint routes', () => {
)!;
const contextOverrides = {
- endpointAuthz: getEndpointAuthzInitialStateMock({ canReadSecuritySolution: false }),
+ endpointAuthz: getEndpointAuthzInitialStateMock({
+ canAccessFleet: false,
+ canReadSecuritySolution: false,
+ }),
};
await routeHandler(
createRouteHandlerContext(mockScopedClient, mockSavedObjectClient, contextOverrides),
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
index a9a7c7c4837e3..e92f316f7c125 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/handlers.test.ts
@@ -55,7 +55,7 @@ describe('test policy response handler', () => {
const response = createSearchResponse(new EndpointDocGenerator().generatePolicyResponse());
const hostPolicyResponseHandler = getHostPolicyResponseHandler();
- mockScopedClient.asCurrentUser.search.mockResponseOnce(response);
+ mockScopedClient.asInternalUser.search.mockResponseOnce(response);
const mockRequest = httpServerMock.createKibanaRequest({
params: { agentId: 'id' },
});
@@ -78,7 +78,7 @@ describe('test policy response handler', () => {
it('should return not found when there is no response policy for host', async () => {
const hostPolicyResponseHandler = getHostPolicyResponseHandler();
- mockScopedClient.asCurrentUser.search.mockResponseOnce(createSearchResponse());
+ mockScopedClient.asInternalUser.search.mockResponseOnce(createSearchResponse());
const mockRequest = httpServerMock.createKibanaRequest({
params: { agentId: 'id' },
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts
index 18eb1c961ee76..568a61d2691d8 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/index.ts
@@ -30,7 +30,7 @@ export function registerPolicyRoutes(router: IRouter, endpointAppContext: Endpoi
options: { authRequired: true },
},
withEndpointAuthz(
- { all: ['canAccessEndpointManagement'] },
+ { any: ['canReadSecuritySolution', 'canAccessFleet'] },
logger,
getHostPolicyResponseHandler()
)
diff --git a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts
index c0639bcbcb848..1afafa2b61245 100644
--- a/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts
+++ b/x-pack/plugins/security_solution/server/endpoint/routes/policy/service.ts
@@ -51,7 +51,7 @@ export async function getPolicyResponseByAgentId(
dataClient: IScopedClusterClient
): Promise {
const query = getESQueryPolicyResponseByAgentID(agentID, index);
- const response = await dataClient.asCurrentUser.search(query);
+ const response = await dataClient.asInternalUser.search(query);
if (response.hits.hits.length > 0 && response.hits.hits[0]._source != null) {
return {
diff --git a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts
index ca6a72f50d00e..9b7212b20b833 100644
--- a/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts
+++ b/x-pack/test/security_solution_endpoint_api_int/apis/endpoint_authz.ts
@@ -8,7 +8,6 @@
import { wrapErrorAndRejectPromise } from '@kbn/security-solution-plugin/common/endpoint/data_loaders/utils';
import {
AGENT_POLICY_SUMMARY_ROUTE,
- BASE_POLICY_RESPONSE_ROUTE,
GET_PROCESSES_ROUTE,
ISOLATE_HOST_ROUTE,
ISOLATE_HOST_ROUTE_V2,
@@ -49,11 +48,6 @@ export default function ({ getService }: FtrProviderContext) {
path: '/api/endpoint/action_log/one?start_date=2021-12-01&end_date=2021-12-04',
body: undefined,
},
- {
- method: 'get',
- path: `${BASE_POLICY_RESPONSE_ROUTE}?agentId=1`,
- body: undefined,
- },
{
method: 'post',
path: ISOLATE_HOST_ROUTE,
From d4d2a77fd5d15a558dabae7828ee2de5df78a9bf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20Haro?=
Date: Thu, 13 Oct 2022 17:59:09 +0200
Subject: [PATCH 17/35] [Security/EBT] Skip `user_id` registration on anonymous
pages (#143280)
Co-authored-by: Larry Gregory
---
.../analytics/analytics_service.test.ts | 63 +++++++++++++++++--
.../public/analytics/analytics_service.ts | 8 ++-
x-pack/plugins/security/public/plugin.tsx | 1 +
3 files changed, 65 insertions(+), 7 deletions(-)
diff --git a/x-pack/plugins/security/public/analytics/analytics_service.test.ts b/x-pack/plugins/security/public/analytics/analytics_service.test.ts
index 28a272c12f9ec..be13fa25c1c8d 100644
--- a/x-pack/plugins/security/public/analytics/analytics_service.test.ts
+++ b/x-pack/plugins/security/public/analytics/analytics_service.test.ts
@@ -34,9 +34,12 @@ describe('AnalyticsService', () => {
const authc = authenticationMock.createSetup();
authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+ const { analytics, http } = coreMock.createSetup();
+
analyticsService.setup({
authc,
- analytics: coreMock.createSetup().analytics,
+ analytics,
+ http,
securityLicense: licenseMock.create({ allowLogin: true }),
});
analyticsService.start({ http: mockCore.http });
@@ -63,9 +66,12 @@ describe('AnalyticsService', () => {
const authc = authenticationMock.createSetup();
authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+ const { analytics, http } = coreMock.createSetup();
+
analyticsService.setup({
authc,
- analytics: coreMock.createSetup().analytics,
+ analytics,
+ http,
securityLicense: licenseMock.create(licenseFeatures$.asObservable()),
});
analyticsService.start({ http: mockCore.http });
@@ -116,9 +122,12 @@ describe('AnalyticsService', () => {
const authc = authenticationMock.createSetup();
authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+ const { analytics, http } = coreMock.createSetup();
+
analyticsService.setup({
authc,
- analytics: coreMock.createSetup().analytics,
+ analytics,
+ http,
securityLicense: licenseMock.create({ allowLogin: true }),
});
analyticsService.start({ http: mockCore.http });
@@ -141,9 +150,12 @@ describe('AnalyticsService', () => {
const authc = authenticationMock.createSetup();
authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+ const { analytics, http } = coreMock.createSetup();
+
analyticsService.setup({
authc,
- analytics: coreMock.createSetup().analytics,
+ analytics,
+ http,
securityLicense: licenseMock.create({ allowLogin: false }),
});
analyticsService.start({ http: mockCore.http });
@@ -167,9 +179,12 @@ describe('AnalyticsService', () => {
const authc = authenticationMock.createSetup();
authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+ const { analytics, http } = coreMock.createSetup();
+
analyticsService.setup({
authc,
- analytics: coreMock.createSetup().analytics,
+ analytics,
+ http,
securityLicense: licenseMock.create({ allowLogin: true }),
});
analyticsService.start({ http: mockCore.http });
@@ -185,4 +200,42 @@ describe('AnalyticsService', () => {
mockCurrentAuthTypeInfo
);
});
+
+ it('does not register the analytics context provider if the page is anonymous', () => {
+ const authc = authenticationMock.createSetup();
+ const { analytics, http } = coreMock.createSetup();
+
+ http.anonymousPaths.isAnonymous.mockReturnValue(true);
+
+ analyticsService.setup({
+ authc,
+ analytics,
+ http,
+ securityLicense: licenseMock.create({ allowLogin: false }),
+ });
+
+ expect(analytics.registerContextProvider).not.toHaveBeenCalled();
+ });
+
+ it('registers the user_id analytics context provider if the page is not anonymous', () => {
+ const authc = authenticationMock.createSetup();
+ authc.getCurrentUser.mockResolvedValue(securityMock.createMockAuthenticatedUser());
+
+ const { analytics, http } = coreMock.createSetup();
+
+ http.anonymousPaths.isAnonymous.mockReturnValue(false);
+
+ analyticsService.setup({
+ authc,
+ analytics,
+ http,
+ securityLicense: licenseMock.create({ allowLogin: false }),
+ });
+
+ expect(analytics.registerContextProvider).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: 'user_id',
+ })
+ );
+ });
});
diff --git a/x-pack/plugins/security/public/analytics/analytics_service.ts b/x-pack/plugins/security/public/analytics/analytics_service.ts
index 87c402e9983a0..1c87db674eb40 100644
--- a/x-pack/plugins/security/public/analytics/analytics_service.ts
+++ b/x-pack/plugins/security/public/analytics/analytics_service.ts
@@ -11,6 +11,7 @@ import { throttleTime } from 'rxjs/operators';
import type {
AnalyticsServiceSetup as CoreAnalyticsServiceSetup,
+ HttpSetup,
HttpStart,
} from '@kbn/core/public';
@@ -22,6 +23,7 @@ interface AnalyticsServiceSetupParams {
securityLicense: SecurityLicense;
analytics: CoreAnalyticsServiceSetup;
authc: AuthenticationServiceSetup;
+ http: HttpSetup;
cloudId?: string;
}
@@ -43,9 +45,11 @@ export class AnalyticsService {
private securityLicense!: SecurityLicense;
private securityFeaturesSubscription?: Subscription;
- public setup({ analytics, authc, cloudId, securityLicense }: AnalyticsServiceSetupParams) {
+ public setup({ analytics, authc, cloudId, http, securityLicense }: AnalyticsServiceSetupParams) {
this.securityLicense = securityLicense;
- registerUserContext(analytics, authc, cloudId);
+ if (http.anonymousPaths.isAnonymous(window.location.pathname) === false) {
+ registerUserContext(analytics, authc, cloudId);
+ }
}
public start({ http }: AnalyticsServiceStartParams) {
diff --git a/x-pack/plugins/security/public/plugin.tsx b/x-pack/plugins/security/public/plugin.tsx
index 2a91479824062..c56c40f63b4d0 100644
--- a/x-pack/plugins/security/public/plugin.tsx
+++ b/x-pack/plugins/security/public/plugin.tsx
@@ -113,6 +113,7 @@ export class SecurityPlugin
analytics: core.analytics,
authc: this.authc,
cloudId: cloud?.cloudId,
+ http: core.http,
securityLicense: license,
});
From ee313b34c2a6b879b54468287a7f76bd3189e3c0 Mon Sep 17 00:00:00 2001
From: Yaroslav Kuznietsov
Date: Thu, 13 Oct 2022 19:00:16 +0300
Subject: [PATCH 18/35] [Lens][TSVB] Navigate to Lens Guage functional tests.
(#143214)
* Added tests for converting metric with params, unsupported metrics and not valid panels.
* Added tests for color ranges.
* Added tests for gauge.
* Fixed tests.
---
.../page_objects/visual_builder_page.ts | 14 ++-
test/functional/services/combo_box.ts | 15 +++
.../lens/group3/open_in_lens/tsvb/gauge.ts | 92 ++++++++++++++++++-
.../lens/group3/open_in_lens/tsvb/metric.ts | 7 +-
4 files changed, 114 insertions(+), 14 deletions(-)
diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts
index be13c856c3f02..edbd53e80d8c5 100644
--- a/test/functional/page_objects/visual_builder_page.ts
+++ b/test/functional/page_objects/visual_builder_page.ts
@@ -418,8 +418,6 @@ export class VisualBuilderPageObject extends FtrService {
}
public async createColorRule(nth = 0) {
- await this.clickPanelOptions('metric');
-
const elements = await this.testSubjects.findAll('AddAddBtn');
await elements[nth].click();
await this.visChart.waitForVisualizationRenderingStabilized();
@@ -710,16 +708,16 @@ export class VisualBuilderPageObject extends FtrService {
public async setColorRuleOperator(condition: string): Promise {
await this.retry.try(async () => {
- await this.comboBox.clearInputField('colorRuleOperator');
- await this.comboBox.set('colorRuleOperator', condition);
+ await this.comboBox.clearLastInputField('colorRuleOperator');
+ await this.comboBox.setForLastInput('colorRuleOperator', condition);
});
}
- public async setColorRuleValue(value: number): Promise {
+ public async setColorRuleValue(value: number, nth: number = 0): Promise {
await this.retry.try(async () => {
- const colorRuleValueInput = await this.find.byCssSelector(
- '[data-test-subj="colorRuleValue"]'
- );
+ const colorRuleValueInput = (
+ await this.find.allByCssSelector('[data-test-subj="colorRuleValue"]')
+ )[nth];
await colorRuleValueInput.type(value.toString());
});
}
diff --git a/test/functional/services/combo_box.ts b/test/functional/services/combo_box.ts
index 98b04f83aa47f..96d51cd24611a 100644
--- a/test/functional/services/combo_box.ts
+++ b/test/functional/services/combo_box.ts
@@ -36,6 +36,13 @@ export class ComboBoxService extends FtrService {
await this.setElement(comboBox, value);
}
+ public async setForLastInput(comboBoxSelector: string, value: string): Promise {
+ this.log.debug(`comboBox.set, comboBoxSelector: ${comboBoxSelector}`);
+ const comboBoxes = await this.testSubjects.findAll(comboBoxSelector);
+ const comboBox = comboBoxes[comboBoxes.length - 1];
+ await this.setElement(comboBox, value);
+ }
+
/**
* Clicks option in combobox dropdown
*
@@ -308,4 +315,12 @@ export class ComboBoxService extends FtrService {
const input = await comboBoxElement.findByTagName('input');
await input.clearValueWithKeyboard();
}
+
+ public async clearLastInputField(comboBoxSelector: string): Promise {
+ this.log.debug(`comboBox.clearInputField, comboBoxSelector:${comboBoxSelector}`);
+ const comboBoxElements = await this.testSubjects.findAll(comboBoxSelector);
+ const comboBoxElement = comboBoxElements[comboBoxElements.length - 1];
+ const input = await comboBoxElement.findByTagName('input');
+ await input.clearValueWithKeyboard();
+ }
}
diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts
index bb2bc9a15ce9b..307847d82f61c 100644
--- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts
+++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/gauge.ts
@@ -9,9 +9,16 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../../../ftr_provider_context';
export default function ({ getPageObjects, getService }: FtrProviderContext) {
- const { visualize, visualBuilder, lens } = getPageObjects(['visualBuilder', 'visualize', 'lens']);
+ const { visualize, visualBuilder, lens, header } = getPageObjects([
+ 'visualBuilder',
+ 'visualize',
+ 'lens',
+ 'header',
+ ]);
const testSubjects = getService('testSubjects');
+ const retry = getService('retry');
+ const find = getService('find');
describe('Gauge', function describeIndexTests() {
before(async () => {
@@ -40,5 +47,88 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const metricData = await lens.getMetricVisualizationData();
expect(metricData[0].title).to.eql('Count of records');
});
+
+ it('should convert metric with params', async () => {
+ await visualBuilder.selectAggType('Value Count');
+ await visualBuilder.setFieldForAggregation('bytes');
+
+ await header.waitUntilLoadingHasFinished();
+
+ const button = await testSubjects.find('visualizeEditInLensButton');
+ await button.click();
+ await lens.waitForVisualization('mtrVis');
+ await retry.try(async () => {
+ const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`);
+ expect(layers).to.have.length(1);
+
+ const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
+ expect(dimensions).to.have.length(2);
+ expect(await dimensions[0].getVisibleText()).to.be('Count of bytes');
+ expect(await dimensions[1].getVisibleText()).to.be('overall_max(count(bytes))');
+ });
+ });
+
+ it('should not allow converting of unsupported metric', async () => {
+ await visualBuilder.selectAggType('Counter Rate');
+ await visualBuilder.setFieldForAggregation('machine.ram');
+
+ await header.waitUntilLoadingHasFinished();
+
+ const canEdit = await testSubjects.exists('visualizeEditInLensButton');
+ expect(canEdit).to.be(false);
+ });
+
+ it('should not allow converting of not valid panel', async () => {
+ await visualBuilder.selectAggType('Value Count');
+ await header.waitUntilLoadingHasFinished();
+ const canEdit = await testSubjects.exists('visualizeEditInLensButton');
+ expect(canEdit).to.be(false);
+ });
+
+ it('should convert color ranges', async () => {
+ await visualBuilder.setMetricsGroupByTerms('extension.raw');
+
+ await visualBuilder.clickPanelOptions('gauge');
+
+ await visualBuilder.setColorRuleOperator('>= greater than or equal');
+ await visualBuilder.setColorRuleValue(10);
+ await visualBuilder.setColorPickerValue('#54B399', 2);
+
+ await visualBuilder.createColorRule();
+
+ await visualBuilder.setColorRuleOperator('>= greater than or equal');
+ await visualBuilder.setColorRuleValue(100, 1);
+ await visualBuilder.setColorPickerValue('#54A000', 4);
+
+ await header.waitUntilLoadingHasFinished();
+ const button = await testSubjects.find('visualizeEditInLensButton');
+ await button.click();
+
+ await lens.waitForVisualization('mtrVis');
+ await retry.try(async () => {
+ const closePalettePanels = await testSubjects.findAll(
+ 'lns-indexPattern-PalettePanelContainerBack'
+ );
+ if (closePalettePanels.length) {
+ await lens.closePalettePanel();
+ await lens.closeDimensionEditor();
+ }
+
+ const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
+ expect(dimensions).to.have.length(3);
+
+ dimensions[0].click();
+
+ await lens.openPalettePanel('lnsMetric');
+ const colorStops = await lens.getPaletteColorStops();
+
+ expect(colorStops).to.eql([
+ { stop: '', color: 'rgba(104, 188, 0, 1)' },
+ { stop: '10', color: 'rgba(84, 179, 153, 1)' },
+ { stop: '100', color: 'rgba(84, 160, 0, 1)' },
+ { stop: '', color: undefined },
+ ]);
+ });
+ });
});
}
diff --git a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
index 6ec2ef5cc984a..7a157d569bf0f 100644
--- a/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
+++ b/x-pack/test/functional/apps/lens/group3/open_in_lens/tsvb/metric.ts
@@ -18,7 +18,6 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
const testSubjects = getService('testSubjects');
const retry = getService('retry');
- const find = getService('find');
describe('Metric', function describeIndexTests() {
before(async () => {
@@ -58,8 +57,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await button.click();
await lens.waitForVisualization('mtrVis');
await retry.try(async () => {
- const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`);
- expect(layers).to.have.length(1);
+ expect(await lens.getLayerCount()).to.be(1);
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
expect(dimensions).to.have.length(1);
@@ -77,8 +75,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
await button.click();
await lens.waitForVisualization('mtrVis');
await retry.try(async () => {
- const layers = await find.allByCssSelector(`[data-test-subj^="lns-layerPanel-"]`);
- expect(layers).to.have.length(1);
+ expect(await lens.getLayerCount()).to.be(1);
const dimensions = await testSubjects.findAll('lns-dimensionTrigger');
expect(dimensions).to.have.length(1);
From 42a7a0926704263493ff0571f13d1692b4adc105 Mon Sep 17 00:00:00 2001
From: Mark Hopkin
Date: Thu, 13 Oct 2022 17:04:01 +0100
Subject: [PATCH 19/35] [Fleet] Add link to skip multi step add integration
workflow (#143279)
* add link to skip multi page add int flow
* fix types
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
.../add_first_integration_splash.tsx | 8 +++++-
.../components/bottom_bar.tsx | 16 +++++++-----
.../multi_page_layout/index.tsx | 25 +++++++++----------
.../multi_page_layout/types.ts | 2 +-
4 files changed, 30 insertions(+), 21 deletions(-)
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx
index 1e0aeb3236492..e1e53efa16985 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/add_first_integration_splash.tsx
@@ -230,7 +230,7 @@ export const AddFirstIntegrationSplashScreen: React.FC<{
error?: RequestError | null;
packageInfo?: PackageInfo;
isLoading: boolean;
- cancelClickHandler: React.ReactEventHandler;
+ cancelClickHandler?: React.ReactEventHandler;
cancelUrl: string;
onNext: () => void;
}> = ({
@@ -276,6 +276,12 @@ export const AddFirstIntegrationSplashScreen: React.FC<{
+ }
cancelClickHandler={cancelClickHandler}
isLoading={isLoading}
onNext={onNext}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx
index 228a89fa4e495..61875a063207d 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/components/bottom_bar.tsx
@@ -29,8 +29,9 @@ export const NotObscuredByBottomBar = styled('div')`
export const CreatePackagePolicyBottomBar: React.FC<{
isLoading?: boolean;
isDisabled?: boolean;
- cancelClickHandler: React.ReactEventHandler;
+ cancelClickHandler?: React.ReactEventHandler;
cancelUrl?: string;
+ cancelMessage?: React.ReactElement;
actionMessage: React.ReactElement;
onNext: () => void;
noAnimation?: boolean;
@@ -42,6 +43,7 @@ export const CreatePackagePolicyBottomBar: React.FC<{
cancelClickHandler,
cancelUrl,
actionMessage,
+ cancelMessage,
isDisabled = false,
noAnimation = false,
}) => {
@@ -53,10 +55,12 @@ export const CreatePackagePolicyBottomBar: React.FC<{
{/* eslint-disable-next-line @elastic/eui/href-or-on-click */}
-
+ {cancelMessage || (
+
+ )}
@@ -85,7 +89,7 @@ export const CreatePackagePolicyBottomBar: React.FC<{
};
export const AgentStandaloneBottomBar: React.FC<{
- cancelClickHandler: React.ReactEventHandler;
+ cancelClickHandler?: React.ReactEventHandler;
cancelUrl?: string;
onNext: () => void;
noAnimation?: boolean;
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx
index c14d4bcd67752..6ce311b36aa6a 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/index.tsx
@@ -10,10 +10,9 @@ import { i18n } from '@kbn/i18n';
import { splitPkgKey } from '../../../../../../../common/services';
-import { useGetPackageInfoByKey, useGetSettings } from '../../../../hooks';
+import { useGetPackageInfoByKey, useGetSettings, useLink } from '../../../../hooks';
import type { AddToPolicyParams, CreatePackagePolicyParams } from '../types';
-import { useCancelAddPackagePolicy } from '../hooks';
import { useGetAgentPolicyOrDefault } from './hooks';
@@ -55,11 +54,12 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
queryParamsPolicyId,
}) => {
const { params } = useRouteMatch();
-
- const { pkgName, pkgVersion } = splitPkgKey(params.pkgkey);
+ const { pkgkey, policyId, integration } = params;
+ const { pkgName, pkgVersion } = splitPkgKey(pkgkey);
const [onSplash, setOnSplash] = useState(true);
const [currentStep, setCurrentStep] = useState(0);
const [isManaged, setIsManaged] = useState(true);
+ const { getHref } = useLink();
const [enrolledAgentIds, setEnrolledAgentIds] = useState([]);
const toggleIsManaged = (newIsManaged: boolean) => {
setIsManaged(newIsManaged);
@@ -85,19 +85,21 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
const settings = useMemo(() => settingsData?.item, [settingsData]);
const integrationInfo = useMemo(() => {
- if (!params.integration) return;
+ if (!integration) return;
return packageInfo?.policy_templates?.find(
- (policyTemplate) => policyTemplate.name === params.integration
+ (policyTemplate) => policyTemplate.name === integration
);
- }, [packageInfo?.policy_templates, params]);
+ }, [packageInfo?.policy_templates, integration]);
const splashScreenNext = () => {
setOnSplash(false);
};
- const { cancelClickHandler, cancelUrl } = useCancelAddPackagePolicy({
- from,
- pkgkey: params.pkgkey,
+ const cancelUrl = getHref('add_integration_to_policy', {
+ pkgkey,
+ useMultiPageLayout: false,
+ ...(integration ? { integration } : {}),
+ ...(policyId ? { agentPolicyId: policyId } : {}),
});
if (onSplash || !packageInfo) {
@@ -108,7 +110,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
integrationInfo={integrationInfo}
packageInfo={packageInfo}
cancelUrl={cancelUrl}
- cancelClickHandler={cancelClickHandler}
onNext={splashScreenNext}
/>
);
@@ -125,7 +126,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
const stepsBack = () => {
if (currentStep === 0) {
- cancelClickHandler(null);
return;
}
@@ -142,7 +142,6 @@ export const CreatePackagePolicyMultiPage: CreatePackagePolicyParams = ({
packageInfo={packageInfo}
integrationInfo={integrationInfo}
cancelUrl={cancelUrl}
- cancelClickHandler={cancelClickHandler}
onNext={stepsNext}
onBack={stepsBack}
isManaged={isManaged}
diff --git a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts
index 327be01e0b3e9..d3a195e0513d4 100644
--- a/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts
+++ b/x-pack/plugins/fleet/public/applications/fleet/sections/agent_policy/create_package_policy_page/multi_page_layout/types.ts
@@ -27,7 +27,7 @@ export interface MultiPageStepLayoutProps {
enrollmentAPIKey?: EnrollmentAPIKey;
packageInfo: PackageInfo;
integrationInfo?: RegistryPolicyTemplate;
- cancelClickHandler: React.ReactEventHandler;
+ cancelClickHandler?: React.ReactEventHandler;
onBack: React.ReactEventHandler;
cancelUrl: string;
steps: MultiPageStep[];
From ec98f01c9f60a42ea4544bcca3f49052cc636089 Mon Sep 17 00:00:00 2001
From: Zacqary Adam Xeper
Date: Thu, 13 Oct 2022 11:07:28 -0500
Subject: [PATCH 20/35] [RAM] Move Connectors to own page (#142485)
* Move Connectors to own page
* Move create connector button to header
* Update permissions IDs
* Fix some tests
* Test fixes
* Fix more tests
* Fix more tests
* Fix FTR 7
* Fix jest
* Fix FTR 4
* Fix FTR 6
* Fix translations
* Fix breadcrumbs
* Fix empty prompt design
* Update empty prompt text
* Update import warning url
* Add documentation buttons
Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
---
x-pack/plugins/actions/server/feature.ts | 6 +-
.../saved_objects/get_import_warnings.ts | 2 +-
x-pack/plugins/cases/public/plugin.ts | 2 +-
.../ml/public/application/management/index.ts | 2 +-
x-pack/plugins/reporting/public/plugin.ts | 2 +-
.../translations/translations/fr-FR.json | 1 -
.../translations/translations/ja-JP.json | 1 -
.../translations/translations/zh-CN.json | 1 -
.../public/application/app.tsx | 20 +-
.../prompts/empty_connectors_prompt.tsx | 63 +++--
.../public/application/connectors_app.tsx | 104 ++++++++
.../public/application/constants/plugin.ts | 2 +-
.../public/application/home.test.tsx | 6 +-
.../public/application/home.tsx | 47 +---
.../public/application/lib/breadcrumb.test.ts | 4 +-
.../public/application/lib/breadcrumb.ts | 15 +-
.../public/application/lib/doc_title.test.ts | 2 +-
.../public/application/lib/doc_title.ts | 7 +-
.../components/actions_connectors_list.tsx | 236 +++++++++++-------
.../public/common/constants/index.ts | 1 +
.../triggers_actions_ui/public/plugin.ts | 74 +++++-
x-pack/plugins/watcher/public/plugin.ts | 2 +-
.../feature_controls/management_security.ts | 2 +-
.../alert_create_flyout.ts | 2 +-
.../apps/triggers_actions_ui/connectors.ts | 13 +-
.../apps/triggers_actions_ui/details.ts | 16 +-
.../apps/triggers_actions_ui/home_page.ts | 26 +-
x-pack/test/functional_with_es_ssl/config.ts | 3 +
.../page_objects/triggers_actions_ui_page.ts | 5 +
29 files changed, 454 insertions(+), 213 deletions(-)
create mode 100644 x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx
diff --git a/x-pack/plugins/actions/server/feature.ts b/x-pack/plugins/actions/server/feature.ts
index c176e9f261551..aa0b88dbee633 100644
--- a/x-pack/plugins/actions/server/feature.ts
+++ b/x-pack/plugins/actions/server/feature.ts
@@ -28,7 +28,7 @@ export const ACTIONS_FEATURE = {
app: [],
order: FEATURE_ORDER,
management: {
- insightsAndAlerting: ['triggersActions'],
+ insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'],
},
privileges: {
all: {
@@ -36,7 +36,7 @@ export const ACTIONS_FEATURE = {
api: [],
catalogue: [],
management: {
- insightsAndAlerting: ['triggersActions'],
+ insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'],
},
savedObject: {
all: [
@@ -53,7 +53,7 @@ export const ACTIONS_FEATURE = {
api: [],
catalogue: [],
management: {
- insightsAndAlerting: ['triggersActions'],
+ insightsAndAlerting: ['triggersActions', 'triggersActionsConnectors'],
},
savedObject: {
// action execution requires 'read' over `actions`, but 'all' over `action_task_params`
diff --git a/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts b/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts
index 2c8eef94f9a4d..ce2b290ed2ab4 100644
--- a/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts
+++ b/x-pack/plugins/actions/server/saved_objects/get_import_warnings.ts
@@ -29,7 +29,7 @@ export function getImportWarnings(
{
type: 'action_required',
message,
- actionPath: '/app/management/insightsAndAlerting/triggersActions/connectors',
+ actionPath: '/app/management/insightsAndAlerting/triggersActionsConnectors',
buttonLabel: GO_TO_CONNECTORS_BUTTON_LABLE,
} as SavedObjectsImportWarning,
];
diff --git a/x-pack/plugins/cases/public/plugin.ts b/x-pack/plugins/cases/public/plugin.ts
index 53b3b57d4e2ef..73c8866ca71a3 100644
--- a/x-pack/plugins/cases/public/plugin.ts
+++ b/x-pack/plugins/cases/public/plugin.ts
@@ -67,7 +67,7 @@ export class CasesUiPlugin
plugins.management.sections.section.insightsAndAlerting.registerApp({
id: APP_ID,
title: APP_TITLE,
- order: 0,
+ order: 1,
async mount(params: ManagementAppMountParams) {
const [coreStart, pluginsStart] = (await core.getStartServices()) as [
CoreStart,
diff --git a/x-pack/plugins/ml/public/application/management/index.ts b/x-pack/plugins/ml/public/application/management/index.ts
index 86f7fa0ff92ab..f64d7cbb5bb64 100644
--- a/x-pack/plugins/ml/public/application/management/index.ts
+++ b/x-pack/plugins/ml/public/application/management/index.ts
@@ -23,7 +23,7 @@ export function registerManagementSection(
title: i18n.translate('xpack.ml.management.jobsListTitle', {
defaultMessage: 'Machine Learning',
}),
- order: 2,
+ order: 4,
async mount(params: ManagementAppMountParams) {
const { mountApp } = await import('./jobs_list');
return mountApp(core, params, deps);
diff --git a/x-pack/plugins/reporting/public/plugin.ts b/x-pack/plugins/reporting/public/plugin.ts
index df47c6f28a6e3..d8f4132a59541 100644
--- a/x-pack/plugins/reporting/public/plugin.ts
+++ b/x-pack/plugins/reporting/public/plugin.ts
@@ -172,7 +172,7 @@ export class ReportingPublicPlugin
management.sections.section.insightsAndAlerting.registerApp({
id: 'reporting',
title: this.title,
- order: 1,
+ order: 3,
mount: async (params) => {
params.setBreadcrumbs([{ text: this.breadcrumbText }]);
const [[start, startDeps], { mountManagementSection }] = await Promise.all([
diff --git a/x-pack/plugins/translations/translations/fr-FR.json b/x-pack/plugins/translations/translations/fr-FR.json
index 3750a4af42538..f82a3b264a722 100644
--- a/x-pack/plugins/translations/translations/fr-FR.json
+++ b/x-pack/plugins/translations/translations/fr-FR.json
@@ -32297,7 +32297,6 @@
"xpack.triggersActionsUI.fieldBrowser.viewSelected": "sélectionné",
"xpack.triggersActionsUI.home.appTitle": "Règles et connecteurs",
"xpack.triggersActionsUI.home.breadcrumbTitle": "Règles et connecteurs",
- "xpack.triggersActionsUI.home.connectorsTabTitle": "Connecteurs",
"xpack.triggersActionsUI.home.docsLinkText": "Documentation",
"xpack.triggersActionsUI.home.rulesTabTitle": "Règles",
"xpack.triggersActionsUI.home.sectionDescription": "Détecter les conditions à l'aide des règles, et entreprendre des actions à l'aide des connecteurs.",
diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json
index f0d7d45fec244..fa482be26a856 100644
--- a/x-pack/plugins/translations/translations/ja-JP.json
+++ b/x-pack/plugins/translations/translations/ja-JP.json
@@ -32271,7 +32271,6 @@
"xpack.triggersActionsUI.fieldBrowser.viewSelected": "選択済み",
"xpack.triggersActionsUI.home.appTitle": "ルールとコネクター",
"xpack.triggersActionsUI.home.breadcrumbTitle": "ルールとコネクター",
- "xpack.triggersActionsUI.home.connectorsTabTitle": "コネクター",
"xpack.triggersActionsUI.home.docsLinkText": "ドキュメント",
"xpack.triggersActionsUI.home.rulesTabTitle": "ルール",
"xpack.triggersActionsUI.home.sectionDescription": "ルールを使用して条件を検出し、コネクターを使用してアクションを実行します。",
diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json
index 482a967318153..424fa52fcee66 100644
--- a/x-pack/plugins/translations/translations/zh-CN.json
+++ b/x-pack/plugins/translations/translations/zh-CN.json
@@ -32308,7 +32308,6 @@
"xpack.triggersActionsUI.fieldBrowser.viewSelected": "已选定",
"xpack.triggersActionsUI.home.appTitle": "规则和连接器",
"xpack.triggersActionsUI.home.breadcrumbTitle": "规则和连接器",
- "xpack.triggersActionsUI.home.connectorsTabTitle": "连接器",
"xpack.triggersActionsUI.home.docsLinkText": "文档",
"xpack.triggersActionsUI.home.rulesTabTitle": "规则",
"xpack.triggersActionsUI.home.sectionDescription": "使规则检测条件,并使用连接器采取操作。",
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx
index 5f2dcfcb5ff62..d0c61c884e528 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/app.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/app.tsx
@@ -31,11 +31,17 @@ import {
AlertsTableConfigurationRegistryContract,
RuleTypeRegistryContract,
} from '../types';
-import { Section, routeToRuleDetails, legacyRouteToRuleDetails } from './constants';
+import {
+ Section,
+ routeToRuleDetails,
+ legacyRouteToRuleDetails,
+ routeToConnectors,
+} from './constants';
import { setDataViewsService } from '../common/lib/data_apis';
import { KibanaContextProvider, useKibana } from '../common/lib/kibana';
import { ConnectorProvider } from './context/connector_context';
+import { CONNECTORS_PLUGIN_ID } from '../common/constants';
const TriggersActionsUIHome = lazy(() => import('./home'));
const RuleDetailsRoute = lazy(
@@ -73,7 +79,7 @@ export const renderApp = (deps: TriggersAndActionsUiServices) => {
export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
const { dataViews, uiSettings, theme$ } = deps;
- const sections: Section[] = ['rules', 'connectors', 'logs', 'alerts'];
+ const sections: Section[] = ['rules', 'logs', 'alerts'];
const isDarkMode = useObservable(uiSettings.get$('theme:darkMode'));
const sectionsRegex = sections.join('|');
@@ -96,6 +102,7 @@ export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) => {
const {
actions: { validateEmailAddresses },
+ application: { navigateToApp },
} = useKibana().services;
return (
@@ -114,6 +121,15 @@ export const AppWithoutRouter = ({ sectionsRegex }: { sectionsRegex: string }) =
path={legacyRouteToRuleDetails}
render={({ match }) => }
/>
+ {
+ navigateToApp(`management/insightsAndAlerting/${CONNECTORS_PLUGIN_ID}`);
+ return null;
+ }}
+ />
+
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx
index 4637b6aa1d801..f6b149328db97 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/components/prompts/empty_connectors_prompt.tsx
@@ -7,11 +7,25 @@
import { FormattedMessage } from '@kbn/i18n-react';
import React from 'react';
-import { EuiButton, EuiEmptyPrompt, EuiIcon, EuiSpacer, EuiTitle } from '@elastic/eui';
+import {
+ EuiButton,
+ EuiButtonEmpty,
+ EuiPageTemplate,
+ EuiIcon,
+ EuiSpacer,
+ EuiTitle,
+} from '@elastic/eui';
+import { DocLinksStart } from '@kbn/core-doc-links-browser';
import './empty_connectors_prompt.scss';
-export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => void }) => (
- void;
+ docLinks: DocLinksStart;
+}) => (
+
@@ -33,24 +47,39 @@ export const EmptyConnectorsPrompt = ({ onCTAClicked }: { onCTAClicked: () => vo
}
actions={
-
-
-
+ <>
+
+
+
+
+
+
+
+ >
}
/>
);
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx
new file mode 100644
index 0000000000000..78b2559cc3dec
--- /dev/null
+++ b/x-pack/plugins/triggers_actions_ui/public/application/connectors_app.tsx
@@ -0,0 +1,104 @@
+/*
+ * 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, { lazy } from 'react';
+import { Switch, Route, Router } from 'react-router-dom';
+import { ChromeBreadcrumb, CoreStart, CoreTheme, ScopedHistory } from '@kbn/core/public';
+import { render, unmountComponentAtNode } from 'react-dom';
+import { I18nProvider } from '@kbn/i18n-react';
+import useObservable from 'react-use/lib/useObservable';
+import { Observable } from 'rxjs';
+import { KibanaFeature } from '@kbn/features-plugin/common';
+import { KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
+import { ChartsPluginStart } from '@kbn/charts-plugin/public';
+import { DataPublicPluginStart } from '@kbn/data-plugin/public';
+import { DataViewsPublicPluginStart } from '@kbn/data-views-plugin/public';
+import type { DataViewEditorStart } from '@kbn/data-view-editor-plugin/public';
+import { UnifiedSearchPublicPluginStart } from '@kbn/unified-search-plugin/public';
+import { PluginStartContract as AlertingStart } from '@kbn/alerting-plugin/public';
+import type { SpacesPluginStart } from '@kbn/spaces-plugin/public';
+
+import { Storage } from '@kbn/kibana-utils-plugin/public';
+import { EuiThemeProvider } from '@kbn/kibana-react-plugin/common';
+import { ActionsPublicPluginSetup } from '@kbn/actions-plugin/public';
+import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
+import {
+ ActionTypeRegistryContract,
+ AlertsTableConfigurationRegistryContract,
+ RuleTypeRegistryContract,
+} from '../types';
+
+import { setDataViewsService } from '../common/lib/data_apis';
+import { KibanaContextProvider, useKibana } from '../common/lib/kibana';
+import { ConnectorProvider } from './context/connector_context';
+
+const ActionsConnectorsList = lazy(
+ () => import('./sections/actions_connectors_list/components/actions_connectors_list')
+);
+
+export interface TriggersAndActionsUiServices extends CoreStart {
+ actions: ActionsPublicPluginSetup;
+ data: DataPublicPluginStart;
+ dataViews: DataViewsPublicPluginStart;
+ dataViewEditor: DataViewEditorStart;
+ charts: ChartsPluginStart;
+ alerting?: AlertingStart;
+ spaces?: SpacesPluginStart;
+ storage?: Storage;
+ isCloud: boolean;
+ setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void;
+ actionTypeRegistry: ActionTypeRegistryContract;
+ ruleTypeRegistry: RuleTypeRegistryContract;
+ alertsTableConfigurationRegistry: AlertsTableConfigurationRegistryContract;
+ history: ScopedHistory;
+ kibanaFeatures: KibanaFeature[];
+ element: HTMLElement;
+ theme$: Observable;
+ unifiedSearch: UnifiedSearchPublicPluginStart;
+}
+
+export const renderApp = (deps: TriggersAndActionsUiServices) => {
+ const { element } = deps;
+ render(, element);
+ return () => {
+ unmountComponentAtNode(element);
+ };
+};
+
+export const App = ({ deps }: { deps: TriggersAndActionsUiServices }) => {
+ const { dataViews, uiSettings, theme$ } = deps;
+ const isDarkMode = useObservable(uiSettings.get$('theme:darkMode'));
+
+ setDataViewsService(dataViews);
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export const AppWithoutRouter = () => {
+ const {
+ actions: { validateEmailAddresses },
+ } = useKibana().services;
+
+ return (
+
+
+
+
+
+ );
+};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts
index bf5cf6d58c69c..93ba3dd88b709 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/constants/plugin.ts
@@ -9,7 +9,7 @@ export const PLUGIN = {
ID: 'triggersActionsUi',
getI18nName: (i18n: any): string => {
return i18n.translate('xpack.triggersActionsUI.appName', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
});
},
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx
index 0024fef8ac125..83c856663f8f8 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/home.test.tsx
@@ -69,8 +69,8 @@ describe('home', () => {
let home = mountWithIntl();
- // Just rules/connectors
- expect(home.find('.euiTab__content').length).toBe(3);
+ // Just rules/logs
+ expect(home.find('.euiTab__content').length).toBe(2);
(getIsExperimentalFeatureEnabled as jest.Mock).mockImplementation((feature: string) => {
if (feature === 'internalAlertsTable') {
@@ -81,6 +81,6 @@ describe('home', () => {
home = mountWithIntl();
// alerts now too!
- expect(home.find('.euiTab__content').length).toBe(4);
+ expect(home.find('.euiTab__content').length).toBe(3);
});
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
index 8963e63eb44fc..894db7ce5a59e 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/home.tsx
@@ -11,25 +11,15 @@ import { FormattedMessage } from '@kbn/i18n-react';
import { EuiSpacer, EuiButtonEmpty, EuiPageHeader } from '@elastic/eui';
import { getIsExperimentalFeatureEnabled } from '../common/get_experimental_features';
-import {
- Section,
- routeToConnectors,
- routeToRules,
- routeToInternalAlerts,
- routeToLogs,
-} from './constants';
+import { Section, routeToRules, routeToInternalAlerts, routeToLogs } from './constants';
import { getAlertingSectionBreadcrumb } from './lib/breadcrumb';
import { getCurrentDocTitle } from './lib/doc_title';
-import { hasShowActionsCapability } from './lib/capabilities';
import { HealthCheck } from './components/health_check';
import { HealthContextProvider } from './context/health_context';
import { useKibana } from '../common/lib/kibana';
import { suspendedComponentWithProps } from './lib/suspended_component_with_props';
-const ActionsConnectorsList = lazy(
- () => import('./sections/actions_connectors_list/components/actions_connectors_list')
-);
const RulesList = lazy(() => import('./sections/rules_list/components/rules_list'));
const LogsList = lazy(() => import('./sections/logs_list/components/logs_list'));
const AlertsPage = lazy(() => import('./sections/alerts_table/alerts_page'));
@@ -44,16 +34,9 @@ export const TriggersActionsUIHome: React.FunctionComponent {
- const {
- chrome,
- application: { capabilities },
-
- setBreadcrumbs,
- docLinks,
- } = useKibana().services;
+ const { chrome, setBreadcrumbs, docLinks } = useKibana().services;
const isInternalAlertsTableEnabled = getIsExperimentalFeatureEnabled('internalAlertsTable');
- const canShowActions = hasShowActionsCapability(capabilities);
const tabs: Array<{
id: Section;
name: React.ReactNode;
@@ -66,18 +49,6 @@ export const TriggersActionsUIHome: React.FunctionComponent
- ),
- });
- }
-
tabs.push({
id: 'logs',
name: ,
@@ -111,10 +82,7 @@ export const TriggersActionsUIHome: React.FunctionComponent
-
+
}
rightSideItems={[
@@ -133,7 +101,7 @@ export const TriggersActionsUIHome: React.FunctionComponent
}
tabs={tabs.map((tab) => ({
@@ -155,13 +123,6 @@ export const TriggersActionsUIHome: React.FunctionComponent
- {canShowActions && (
-
- )}
{
});
expect(getAlertingSectionBreadcrumb('home', true)).toMatchObject({
text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
}),
href: `${routeToHome}`,
});
@@ -44,7 +44,7 @@ describe('getAlertingSectionBreadcrumb', () => {
});
expect(getAlertingSectionBreadcrumb('home', false)).toMatchObject({
text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
}),
});
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts
index 46a15b12bb733..c0b9551968870 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/breadcrumb.ts
@@ -6,7 +6,7 @@
*/
import { i18n } from '@kbn/i18n';
-import { routeToHome, routeToConnectors, routeToRules } from '../constants';
+import { routeToHome, routeToConnectors, routeToRules, routeToLogs } from '../constants';
export const getAlertingSectionBreadcrumb = (
type: string,
@@ -14,6 +14,17 @@ export const getAlertingSectionBreadcrumb = (
): { text: string; href?: string } => {
// Home and sections
switch (type) {
+ case 'logs':
+ return {
+ text: i18n.translate('xpack.triggersActionsUI.logs.breadcrumbTitle', {
+ defaultMessage: 'Logs',
+ }),
+ ...(returnHref
+ ? {
+ href: `${routeToLogs}`,
+ }
+ : {}),
+ };
case 'connectors':
return {
text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', {
@@ -39,7 +50,7 @@ export const getAlertingSectionBreadcrumb = (
default:
return {
text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
}),
...(returnHref
? {
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts
index af352de0cc6e7..8d4996ceb8259 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.test.ts
@@ -9,7 +9,7 @@ import { getCurrentDocTitle } from './doc_title';
describe('getCurrentDocTitle', () => {
test('if change calls return the proper doc title ', async () => {
- expect(getCurrentDocTitle('home') === 'Rules and Connectors').toBeTruthy();
+ expect(getCurrentDocTitle('home') === 'Rules').toBeTruthy();
expect(getCurrentDocTitle('connectors') === 'Connectors').toBeTruthy();
expect(getCurrentDocTitle('rules') === 'Rules').toBeTruthy();
});
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts
index fab9e19c8acee..16699e750d223 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/application/lib/doc_title.ts
@@ -11,6 +11,11 @@ export const getCurrentDocTitle = (page: string): string => {
let updatedTitle: string;
switch (page) {
+ case 'logs':
+ updatedTitle = i18n.translate('xpack.triggersActionsUI.logs.breadcrumbTitle', {
+ defaultMessage: 'Logs',
+ });
+ break;
case 'connectors':
updatedTitle = i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', {
defaultMessage: 'Connectors',
@@ -23,7 +28,7 @@ export const getCurrentDocTitle = (page: string): string => {
break;
default:
updatedTitle = i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
});
}
return updatedTitle;
diff --git a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
index e973fce282dcb..55885aa3c14c3 100644
--- a/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
+++ b/x-pack/plugins/triggers_actions_ui/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx
@@ -22,6 +22,7 @@ import {
Criteria,
EuiButtonEmpty,
EuiBadge,
+ EuiPageTemplate,
} from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { omit } from 'lodash';
@@ -52,6 +53,8 @@ import {
} from '../../../../common/connectors_selection';
import { CreateConnectorFlyout } from '../../action_connector_form/create_connector_flyout';
import { EditConnectorFlyout } from '../../action_connector_form/edit_connector_flyout';
+import { getAlertingSectionBreadcrumb } from '../../../lib/breadcrumb';
+import { getCurrentDocTitle } from '../../../lib/doc_title';
const ConnectorIconTipWithSpacing = withTheme(({ theme }: { theme: EuiTheme }) => {
return (
@@ -83,6 +86,9 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
notifications: { toasts },
application: { capabilities },
actionTypeRegistry,
+ setBreadcrumbs,
+ chrome,
+ docLinks,
} = useKibana().services;
const canDelete = hasDeleteActionsCapability(capabilities);
const canExecute = hasExecuteActionsCapability(capabilities);
@@ -107,6 +113,12 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
}, []);
const [showWarningText, setShowWarningText] = useState(false);
+ // Set breadcrumb and page title
+ useEffect(() => {
+ setBreadcrumbs([getAlertingSectionBreadcrumb('connectors')]);
+ chrome.docTitle.change(getCurrentDocTitle('connectors'));
+ }, [chrome, setBreadcrumbs]);
+
useEffect(() => {
(async () => {
try {
@@ -428,110 +440,144 @@ const ActionsConnectorsList: React.FunctionComponent = () => {
/>
,
],
- toolsRight: canSave
- ? [
- setAddFlyoutVisibility(true)}
- >
-
- ,
- ]
- : [],
}}
/>
);
return (
-
- {
- if (selectedItems.length === 0 || selectedItems.length === deleted.length) {
- const updatedActions = actions.filter(
- (action) => action.id && !connectorsToDelete.includes(action.id)
- );
- setActions(updatedActions);
- setSelectedItems([]);
- }
- setConnectorsToDelete([]);
- }}
- onErrors={async () => {
- // Refresh the actions from the server, some actions may have beend deleted
- await loadActions();
- setConnectorsToDelete([]);
- }}
- onCancel={async () => {
- setConnectorsToDelete([]);
- }}
- apiDeleteCall={deleteActions}
- idsToDelete={connectorsToDelete}
- singleTitle={i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.singleTitle',
- { defaultMessage: 'connector' }
- )}
- multipleTitle={i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.multipleTitle',
- { defaultMessage: 'connectors' }
- )}
- showWarningText={showWarningText}
- warningText={i18n.translate(
- 'xpack.triggersActionsUI.sections.actionsConnectorsList.warningText',
- {
- defaultMessage:
- '{connectors, plural, one {This connector is} other {Some connectors are}} currently in use.',
- values: {
- connectors: connectorsToDelete.length,
- },
- }
- )}
- setIsLoadingState={(isLoading: boolean) => setIsLoadingActionTypes(isLoading)}
- />
-
-
- {/* Render the view based on if there's data or if they can save */}
- {(isLoadingActions || isLoadingActionTypes) && }
- {actionConnectorTableItems.length !== 0 && table}
- {actionConnectorTableItems.length === 0 &&
- canSave &&
- !isLoadingActions &&
- !isLoadingActionTypes && (
- setAddFlyoutVisibility(true)} />
- )}
- {actionConnectorTableItems.length === 0 && !canSave && }
- {addFlyoutVisible ? (
- {
- setAddFlyoutVisibility(false);
- }}
- onTestConnector={(connector) => editItem(connector, EditConnectorTabs.Test)}
- onConnectorCreated={loadActions}
- actionTypeRegistry={actionTypeRegistry}
+ <>
+ {actionConnectorTableItems.length !== 0 && (
+ setAddFlyoutVisibility(true)}
+ iconType="plusInCircle"
+ >
+
+ ,
+ ]
+ : []
+ ).concat([
+
+
+ ,
+ ])}
/>
- ) : null}
- {editConnectorProps.initialConnector ? (
- {
- setEditConnectorProps(omit(editConnectorProps, 'initialConnector'));
+ )}
+
+ {
+ if (selectedItems.length === 0 || selectedItems.length === deleted.length) {
+ const updatedActions = actions.filter(
+ (action) => action.id && !connectorsToDelete.includes(action.id)
+ );
+ setActions(updatedActions);
+ setSelectedItems([]);
+ }
+ setConnectorsToDelete([]);
}}
- onConnectorUpdated={(connector) => {
- setEditConnectorProps({ ...editConnectorProps, initialConnector: connector });
- loadActions();
+ onErrors={async () => {
+ // Refresh the actions from the server, some actions may have beend deleted
+ await loadActions();
+ setConnectorsToDelete([]);
}}
- actionTypeRegistry={actionTypeRegistry}
+ onCancel={async () => {
+ setConnectorsToDelete([]);
+ }}
+ apiDeleteCall={deleteActions}
+ idsToDelete={connectorsToDelete}
+ singleTitle={i18n.translate(
+ 'xpack.triggersActionsUI.sections.actionsConnectorsList.singleTitle',
+ { defaultMessage: 'connector' }
+ )}
+ multipleTitle={i18n.translate(
+ 'xpack.triggersActionsUI.sections.actionsConnectorsList.multipleTitle',
+ { defaultMessage: 'connectors' }
+ )}
+ showWarningText={showWarningText}
+ warningText={i18n.translate(
+ 'xpack.triggersActionsUI.sections.actionsConnectorsList.warningText',
+ {
+ defaultMessage:
+ '{connectors, plural, one {This connector is} other {Some connectors are}} currently in use.',
+ values: {
+ connectors: connectorsToDelete.length,
+ },
+ }
+ )}
+ setIsLoadingState={(isLoading: boolean) => setIsLoadingActionTypes(isLoading)}
/>
- ) : null}
-
+
+
+ {/* Render the view based on if there's data or if they can save */}
+ {(isLoadingActions || isLoadingActionTypes) && }
+ {actionConnectorTableItems.length !== 0 && table}
+ {actionConnectorTableItems.length === 0 &&
+ canSave &&
+ !isLoadingActions &&
+ !isLoadingActionTypes && (
+ setAddFlyoutVisibility(true)}
+ docLinks={docLinks}
+ />
+ )}
+ {actionConnectorTableItems.length === 0 && !canSave && }
+ {addFlyoutVisible ? (
+ {
+ setAddFlyoutVisibility(false);
+ }}
+ onTestConnector={(connector) => editItem(connector, EditConnectorTabs.Test)}
+ onConnectorCreated={loadActions}
+ actionTypeRegistry={actionTypeRegistry}
+ />
+ ) : null}
+ {editConnectorProps.initialConnector ? (
+ {
+ setEditConnectorProps(omit(editConnectorProps, 'initialConnector'));
+ }}
+ onConnectorUpdated={(connector) => {
+ setEditConnectorProps({ ...editConnectorProps, initialConnector: connector });
+ loadActions();
+ }}
+ actionTypeRegistry={actionTypeRegistry}
+ />
+ ) : null}
+
+ >
);
};
diff --git a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts
index 794a7e90bf53f..1db1551ecfd38 100644
--- a/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/common/constants/index.ts
@@ -12,3 +12,4 @@ export { builtInGroupByTypes } from './group_by_types';
export const VIEW_LICENSE_OPTIONS_LINK = 'https://www.elastic.co/subscriptions';
export const PLUGIN_ID = 'triggersActions';
+export const CONNECTORS_PLUGIN_ID = 'triggersActionsConnectors';
diff --git a/x-pack/plugins/triggers_actions_ui/public/plugin.ts b/x-pack/plugins/triggers_actions_ui/public/plugin.ts
index 1374f10355e16..9e4645b164f21 100644
--- a/x-pack/plugins/triggers_actions_ui/public/plugin.ts
+++ b/x-pack/plugins/triggers_actions_ui/public/plugin.ts
@@ -69,7 +69,7 @@ import type {
} from './types';
import { TriggersActionsUiConfigType } from '../common/types';
import { registerAlertsTableConfiguration } from './application/sections/alerts_table/alerts_page/register_alerts_table_configuration';
-import { PLUGIN_ID } from './common/constants';
+import { PLUGIN_ID, CONNECTORS_PLUGIN_ID } from './common/constants';
import type { AlertsTableStateProps } from './application/sections/alerts_table/alerts_table_state';
import { getAlertsTableStateLazy } from './common/get_alerts_table_state';
import { ActionAccordionFormProps } from './application/sections/action_connector_form/action_form';
@@ -182,12 +182,24 @@ export class Plugin
ExperimentalFeaturesService.init({ experimentalFeatures: this.experimentalFeatures });
const featureTitle = i18n.translate('xpack.triggersActionsUI.managementSection.displayName', {
- defaultMessage: 'Rules and Connectors',
+ defaultMessage: 'Rules',
});
const featureDescription = i18n.translate(
'xpack.triggersActionsUI.managementSection.displayDescription',
{
- defaultMessage: 'Detect conditions using rules, and take actions using connectors.',
+ defaultMessage: 'Detect conditions using rules.',
+ }
+ );
+ const connectorsFeatureTitle = i18n.translate(
+ 'xpack.triggersActionsUI.managementSection.connectors.displayName',
+ {
+ defaultMessage: 'Connectors',
+ }
+ );
+ const connectorsFeatureDescription = i18n.translate(
+ 'xpack.triggersActionsUI.managementSection.connectors.displayDescription',
+ {
+ defaultMessage: 'Connect third-party software with your alerting data.',
}
);
@@ -201,6 +213,15 @@ export class Plugin
showOnHomePage: false,
category: 'admin',
});
+ plugins.home.featureCatalogue.register({
+ id: CONNECTORS_PLUGIN_ID,
+ title: connectorsFeatureTitle,
+ description: connectorsFeatureDescription,
+ icon: 'watchesApp',
+ path: '/app/management/insightsAndAlerting/triggersActions',
+ showOnHomePage: false,
+ category: 'admin',
+ });
}
plugins.management.sections.section.insightsAndAlerting.registerApp({
@@ -250,6 +271,53 @@ export class Plugin
},
});
+ plugins.management.sections.section.insightsAndAlerting.registerApp({
+ id: CONNECTORS_PLUGIN_ID,
+ title: connectorsFeatureTitle,
+ order: 2,
+ async mount(params: ManagementAppMountParams) {
+ const [coreStart, pluginsStart] = (await core.getStartServices()) as [
+ CoreStart,
+ PluginsStart,
+ unknown
+ ];
+
+ const { renderApp } = await import('./application/connectors_app');
+
+ // The `/api/features` endpoint requires the "Global All" Kibana privilege. Users with a
+ // subset of this privilege are not authorized to access this endpoint and will receive a 404
+ // error that causes the Alerting view to fail to load.
+ let kibanaFeatures: KibanaFeature[];
+ try {
+ kibanaFeatures = await pluginsStart.features.getFeatures();
+ } catch (err) {
+ kibanaFeatures = [];
+ }
+
+ return renderApp({
+ ...coreStart,
+ actions: plugins.actions,
+ data: pluginsStart.data,
+ dataViews: pluginsStart.dataViews,
+ dataViewEditor: pluginsStart.dataViewEditor,
+ charts: pluginsStart.charts,
+ alerting: pluginsStart.alerting,
+ spaces: pluginsStart.spaces,
+ unifiedSearch: pluginsStart.unifiedSearch,
+ isCloud: Boolean(plugins.cloud?.isCloudEnabled),
+ element: params.element,
+ theme$: params.theme$,
+ storage: new Storage(window.localStorage),
+ setBreadcrumbs: params.setBreadcrumbs,
+ history: params.history,
+ actionTypeRegistry,
+ ruleTypeRegistry,
+ alertsTableConfigurationRegistry,
+ kibanaFeatures,
+ });
+ },
+ });
+
if (this.experimentalFeatures.internalAlertsTable) {
registerAlertsTableConfiguration({
alertsTableConfigurationRegistry: this.alertsTableConfigurationRegistry,
diff --git a/x-pack/plugins/watcher/public/plugin.ts b/x-pack/plugins/watcher/public/plugin.ts
index 8f3f549654130..9a6564e6e286c 100644
--- a/x-pack/plugins/watcher/public/plugin.ts
+++ b/x-pack/plugins/watcher/public/plugin.ts
@@ -41,7 +41,7 @@ export class WatcherUIPlugin implements Plugin {
const watcherESApp = esSection.registerApp({
id: 'watcher',
title: pluginName,
- order: 3,
+ order: 5,
mount: async ({ element, setBreadcrumbs, history, theme$ }) => {
const [coreStart] = await getStartServices();
const {
diff --git a/x-pack/test/functional/apps/management/feature_controls/management_security.ts b/x-pack/test/functional/apps/management/feature_controls/management_security.ts
index c1e09d9f3eaab..87e6e4611a3fd 100644
--- a/x-pack/test/functional/apps/management/feature_controls/management_security.ts
+++ b/x-pack/test/functional/apps/management/feature_controls/management_security.ts
@@ -64,7 +64,7 @@ export default function ({ getPageObjects, getService }: FtrProviderContext) {
expect(sections).to.have.length(2);
expect(sections[0]).to.eql({
sectionId: 'insightsAndAlerting',
- sectionLinks: ['triggersActions', 'cases', 'jobsListLink'],
+ sectionLinks: ['triggersActions', 'cases', 'triggersActionsConnectors', 'jobsListLink'],
});
expect(sections[1]).to.eql({
sectionId: 'kibana',
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
index d2ad05486a18a..ea9ee9ddbdf38 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alert_create_flyout.ts
@@ -122,7 +122,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
afterEach(async () => {
// Reset the Rules tab without reloading the entire page
// This is safer than trying to close the alert flyout, which may or may not be open at the end of a test
- await testSubjects.click('connectorsTab');
+ await testSubjects.click('logsTab');
await testSubjects.click('rulesTab');
});
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
index cabbadb43ac57..c0e9a145fe47e 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts
@@ -18,6 +18,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const retry = getService('retry');
const supertest = getService('supertest');
const objectRemover = new ObjectRemover(supertest);
+ const browser = getService('browser');
describe('Connectors', function () {
before(async () => {
@@ -26,8 +27,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
.set('kbn-xsrf', 'foo')
.send(getTestActionData())
.expect(200);
- await pageObjects.common.navigateToApp('triggersActions');
- await testSubjects.click('connectorsTab');
+ await pageObjects.common.navigateToApp('triggersActionsConnectors');
objectRemover.add(createdAction.id, 'action', 'actions');
});
@@ -75,6 +75,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const updatedConnectorName = `${connectorName}updated`;
const createdAction = await createConnector(connectorName);
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
@@ -112,6 +113,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const indexName = generateUniqueKey();
const createdAction = await createIndexConnector(connectorName, indexName);
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
@@ -141,6 +143,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const indexName = generateUniqueKey();
const createdAction = await createIndexConnector(connectorName, indexName);
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
@@ -168,6 +171,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const connectorName = generateUniqueKey();
const createdAction = await createConnector(connectorName);
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
+
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList();
@@ -195,6 +200,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await createConnector(connectorName);
const createdAction = await createConnector(generateUniqueKey());
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
@@ -220,6 +226,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await createConnector(connectorName);
const createdAction = await createConnector(generateUniqueKey());
objectRemover.add(createdAction.id, 'action', 'actions');
+ await browser.refresh();
await pageObjects.triggersActionsUI.searchConnectors(connectorName);
@@ -285,7 +292,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
connector_type_id: '.slack',
})
.expect(200);
- await testSubjects.click('connectorsTab');
return createdAction;
}
@@ -303,7 +309,6 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
secrets: {},
})
.expect(200);
- await testSubjects.click('connectorsTab');
return createdAction;
}
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
index 7af323fdee7c1..6c5de895ed1fd 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/details.ts
@@ -437,7 +437,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('rulesList');
// delete connector
- await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
+ await pageObjects.common.navigateToApp('triggersActionsConnectors');
await pageObjects.triggersActionsUI.searchConnectors(connector.name);
await testSubjects.click('deleteConnector');
await testSubjects.existOrFail('deleteIdsConfirmation');
@@ -447,8 +447,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const toastTitle = await pageObjects.common.closeToast();
expect(toastTitle).to.eql('Deleted 1 connector');
+ // Wait to ensure the table is finished loading
+ await pageObjects.triggersActionsUI.tableFinishedLoading();
+
// click on first alert
- await pageObjects.triggersActionsUI.changeTabs('rulesTab');
+ await pageObjects.common.navigateToApp('triggersActions');
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(rule.name);
const editButton = await testSubjects.find('openEditRuleFlyoutButton');
@@ -501,7 +504,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
await testSubjects.existOrFail('rulesList');
// delete connector
- await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
+ await pageObjects.common.navigateToApp('triggersActionsConnectors');
await pageObjects.triggersActionsUI.searchConnectors(connector.name);
await testSubjects.click('deleteConnector');
await testSubjects.existOrFail('deleteIdsConfirmation');
@@ -511,8 +514,11 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
const toastTitle = await pageObjects.common.closeToast();
expect(toastTitle).to.eql('Deleted 1 connector');
+ // Wait to ensure the table is finished loading
+ await pageObjects.triggersActionsUI.tableFinishedLoading();
+
// click on first rule
- await pageObjects.triggersActionsUI.changeTabs('rulesTab');
+ await pageObjects.common.navigateToApp('triggersActions');
await pageObjects.triggersActionsUI.clickOnAlertInAlertsList(alert.name);
const editButton = await testSubjects.find('openEditRuleFlyoutButton');
@@ -553,7 +559,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
// verify content
await testSubjects.existOrFail('rulesList');
- await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
+ await pageObjects.common.navigateToApp('triggersActionsConnectors');
await pageObjects.triggersActionsUI.searchConnectors('new connector');
await testSubjects.click('deleteConnector');
await testSubjects.existOrFail('deleteIdsConfirmation');
diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
index 4769f01e8d4f0..77ece6d043a59 100644
--- a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
+++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts
@@ -31,7 +31,7 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('Loads the Alerts page', async () => {
await pageObjects.common.navigateToApp('triggersActions');
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
- expect(headingText).to.be('Rules and Connectors');
+ expect(headingText).to.be('Rules');
});
});
@@ -45,8 +45,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
it('Loads the Alerts page but with error', async () => {
await pageObjects.common.navigateToApp('triggersActions');
- const headingText = await pageObjects.triggersActionsUI.getRulesListTitle();
- expect(headingText).to.be('No permissions to create rules');
+ const exists = await testSubjects.exists('noPermissionPrompt');
+ expect(exists).to.be(true);
});
});
@@ -60,26 +60,10 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => {
});
it('Loads the Alerts page', async () => {
- await log.debug('Checking for section heading to say Rules and Connectors.');
+ await log.debug('Checking for section heading to say Rules.');
const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText();
- expect(headingText).to.be('Rules and Connectors');
- });
-
- describe('Connectors tab', () => {
- it('renders the connectors tab', async () => {
- // Navigate to the connectors tab
- await pageObjects.triggersActionsUI.changeTabs('connectorsTab');
-
- await pageObjects.header.waitUntilLoadingHasFinished();
-
- // Verify url
- const url = await browser.getCurrentUrl();
- expect(url).to.contain(`/connectors`);
-
- // Verify content
- await testSubjects.existOrFail('actionsList');
- });
+ expect(headingText).to.be('Rules');
});
describe('Alerts tab', () => {
diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts
index fea3ffcf9233d..36b2b0ae44aee 100644
--- a/x-pack/test/functional_with_es_ssl/config.ts
+++ b/x-pack/test/functional_with_es_ssl/config.ts
@@ -61,6 +61,9 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
triggersActions: {
pathname: '/app/management/insightsAndAlerting/triggersActions',
},
+ triggersActionsConnectors: {
+ pathname: '/app/management/insightsAndAlerting/triggersActionsConnectors',
+ },
},
esTestCluster: {
...xpackFunctionalConfig.get('esTestCluster'),
diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
index 2b17eb48a2afc..d26b1124f8271 100644
--- a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
+++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts
@@ -61,6 +61,11 @@ export function TriggersActionsPageProvider({ getService }: FtrProviderContext)
await this.clickCreateFirstConnectorButton();
}
},
+ async tableFinishedLoading() {
+ await find.byCssSelector(
+ '.euiBasicTable[data-test-subj="actionsTable"]:not(.euiBasicTable-loading)'
+ );
+ },
async searchConnectors(searchText: string) {
const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch');
await searchBox.click();
From 97eb9b61632f90648b04162e5c1c0dc153ecbd08 Mon Sep 17 00:00:00 2001
From: Kevin Logan <56395104+kevinlog@users.noreply.github.com>
Date: Thu, 13 Oct 2022 12:14:47 -0400
Subject: [PATCH 21/35] [Security Solution] Move component tests to jest
integration (#143233)
---
.../search_bar.test.tsx | 20 +++++++++----------
.../event_filters_list.test.tsx | 14 ++++++-------
.../{ => integration_tests}/index.test.tsx | 17 ++++++++--------
3 files changed, 24 insertions(+), 27 deletions(-)
rename x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/{ => integration_tests}/search_bar.test.tsx (84%)
rename x-pack/plugins/security_solution/public/management/pages/event_filters/view/{ => integration_tests}/event_filters_list.test.tsx (79%)
rename x-pack/plugins/security_solution/public/management/pages/{ => integration_tests}/index.test.tsx (68%)
diff --git a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx
similarity index 84%
rename from x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx
index 57611fcdf2484..1b1debe616844 100644
--- a/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/search_bar.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/endpoint_hosts/view/components/integration_tests/search_bar.test.tsx
@@ -6,18 +6,17 @@
*/
import React from 'react';
-import type { AppContextTestRender } from '../../../../../common/mock/endpoint';
-import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
-import { endpointPageHttpMock } from '../../mocks';
+import type { AppContextTestRender } from '../../../../../../common/mock/endpoint';
+import { createAppRootMockRenderer } from '../../../../../../common/mock/endpoint';
+import { endpointPageHttpMock } from '../../../mocks';
import { act, waitFor, cleanup } from '@testing-library/react';
-import { getEndpointListPath } from '../../../../common/routing';
-import { AdminSearchBar } from './search_bar';
+import { getEndpointListPath } from '../../../../../common/routing';
+import { AdminSearchBar } from '../search_bar';
import { fireEvent } from '@testing-library/dom';
-import { uiQueryParams } from '../../store/selectors';
-import type { EndpointIndexUIQueryParams } from '../../types';
+import { uiQueryParams } from '../../../store/selectors';
+import type { EndpointIndexUIQueryParams } from '../../../types';
-// FLAKY: https://github.com/elastic/kibana/issues/140618
-describe.skip('when rendering the endpoint list `AdminSearchBar`', () => {
+describe('when rendering the endpoint list `AdminSearchBar`', () => {
let render: (
urlParams?: EndpointIndexUIQueryParams
) => Promise>;
@@ -85,8 +84,7 @@ describe.skip('when rendering the endpoint list `AdminSearchBar`', () => {
expect(getQueryParamsFromStore().admin_query).toBe("(language:kuery,query:'host.name: foo')");
});
- // FLAKY: https://github.com/elastic/kibana/issues/132398
- it.skip.each([
+ it.each([
['nothing', ''],
['spaces', ' '],
])(
diff --git a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx
similarity index 79%
rename from x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx
index 0ef525418f385..75b35eaea2be7 100644
--- a/x-pack/plugins/security_solution/public/management/pages/event_filters/view/event_filters_list.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/event_filters/view/integration_tests/event_filters_list.test.tsx
@@ -8,13 +8,13 @@
import { act, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
-import { EVENT_FILTERS_PATH } from '../../../../../common/constants';
-import type { AppContextTestRender } from '../../../../common/mock/endpoint';
-import { createAppRootMockRenderer } from '../../../../common/mock/endpoint';
-import { EventFiltersList } from './event_filters_list';
-import { exceptionsListAllHttpMocks } from '../../../mocks/exceptions_list_http_mocks';
-import { SEARCHABLE_FIELDS } from '../constants';
-import { parseQueryFilterToKQL } from '../../../common/utils';
+import { EVENT_FILTERS_PATH } from '../../../../../../common/constants';
+import type { AppContextTestRender } from '../../../../../common/mock/endpoint';
+import { createAppRootMockRenderer } from '../../../../../common/mock/endpoint';
+import { EventFiltersList } from '../event_filters_list';
+import { exceptionsListAllHttpMocks } from '../../../../mocks/exceptions_list_http_mocks';
+import { SEARCHABLE_FIELDS } from '../../constants';
+import { parseQueryFilterToKQL } from '../../../../common/utils';
describe('When on the Event Filters list page', () => {
let render: () => ReturnType;
diff --git a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
similarity index 68%
rename from x-pack/plugins/security_solution/public/management/pages/index.test.tsx
rename to x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
index 1df471633c3c2..0fcc8aa1f0ca5 100644
--- a/x-pack/plugins/security_solution/public/management/pages/index.test.tsx
+++ b/x-pack/plugins/security_solution/public/management/pages/integration_tests/index.test.tsx
@@ -7,14 +7,14 @@
import React from 'react';
-import { ManagementContainer } from '.';
-import '../../common/mock/match_media';
-import type { AppContextTestRender } from '../../common/mock/endpoint';
-import { createAppRootMockRenderer } from '../../common/mock/endpoint';
-import { useUserPrivileges } from '../../common/components/user_privileges';
-import { endpointPageHttpMock } from './endpoint_hosts/mocks';
+import { ManagementContainer } from '..';
+import '../../../common/mock/match_media';
+import type { AppContextTestRender } from '../../../common/mock/endpoint';
+import { createAppRootMockRenderer } from '../../../common/mock/endpoint';
+import { useUserPrivileges } from '../../../common/components/user_privileges';
+import { endpointPageHttpMock } from '../endpoint_hosts/mocks';
-jest.mock('../../common/components/user_privileges');
+jest.mock('../../../common/components/user_privileges');
describe('when in the Administration tab', () => {
let render: () => ReturnType;
@@ -34,8 +34,7 @@ describe('when in the Administration tab', () => {
expect(await render().findByTestId('noIngestPermissions')).not.toBeNull();
});
- // FLAKY: https://github.com/elastic/kibana/issues/135166
- it.skip('should display the Management view if user has privileges', async () => {
+ it('should display the Management view if user has privileges', async () => {
(useUserPrivileges as jest.Mock).mockReturnValue({
endpointPrivileges: { loading: false, canAccessEndpointManagement: true },
});
From e45170e50ab2bf2e5a3fcf5d98f30e4ba298d8cd Mon Sep 17 00:00:00 2001
From: Nathan Reese
Date: Thu, 13 Oct 2022 10:23:43 -0600
Subject: [PATCH 22/35] [Maps] layer groups (#142528)
* [Maps] layer groups
* createLayerGroup
* create layer group
* setChildren
* display layer group legend
* display nested layers in TOC
* setLayerVisibility
* set parent on layer re-order
* LayerGroup.getBounds
* clean-up LayerGroup
* edit layer panel
* LayerGroup.cloneDescriptor
* clean up
* remove layer
* fix reorder bug
* move children on layer move
* fix re-order bug when dragging layer group with collapsed details
* add check for dragging to same location
* add logic to prevent dragging layer group into its own family tree
* [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix'
* add layer to layer group combine action with layer group
* clean up
* fix bug where unable to move layer to bottom
* mouse cursor styles
* clean up combine styling
* fix jest tests
* update toc_entry_actions_popover snapshots
* click confirm model on removeLayer in functional tests
* update cloneLayer to move clones beneath parent
* LayerGroup.getErrors
* Update x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts
Co-authored-by: Nick Peihl
* fix show this layer only action when layer is nested
* recursive count children for remove layer warning
* Update x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx
Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
* resolve error with show this layer only on layer group
* update remove statement to support plural
* perserve layer order when cloning layer group
Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
Co-authored-by: Nick Peihl
Co-authored-by: gchaps <33642766+gchaps@users.noreply.github.com>
---
x-pack/plugins/maps/common/constants.ts | 1 +
.../layer_descriptor_types.ts | 8 +
.../public/actions/data_request_actions.ts | 69 +-
.../maps/public/actions/get_layers_extent.tsx | 66 +
x-pack/plugins/maps/public/actions/index.ts | 1 +
.../maps/public/actions/layer_actions.ts | 159 ++-
.../layers/heatmap_layer/heatmap_layer.ts | 4 +-
.../maps/public/classes/layers/layer.test.ts | 2 +-
.../maps/public/classes/layers/layer.tsx | 26 +-
.../classes/layers/layer_group/index.ts | 8 +
.../layers/layer_group/layer_group.tsx | 385 +++++
.../blended_vector_layer.test.tsx | 8 +-
.../blended_vector_layer.ts | 10 +-
.../geojson_vector_layer.tsx | 12 +-
.../mvt_vector_layer/mvt_vector_layer.tsx | 19 +-
.../layers/vector_layer/vector_layer.test.tsx | 8 +-
.../layers/vector_layer/vector_layer.tsx | 15 +-
.../components/remove_layer_confirm_modal.tsx | 74 +
.../edit_layer_panel/edit_layer_panel.tsx | 108 +-
.../flyout_footer/flyout_footer.tsx | 139 +-
.../edit_layer_panel/flyout_footer/index.ts | 3 +-
.../layer_settings/layer_settings.tsx | 13 +-
.../layer_control/_index.scss | 1 +
.../__snapshots__/layer_toc.test.tsx.snap | 7 +
.../layer_control/layer_toc/_layer_toc.scss | 11 +
.../layer_control/layer_toc/index.ts | 18 +-
.../layer_toc/layer_toc.test.tsx | 12 +-
.../layer_control/layer_toc/layer_toc.tsx | 232 ++-
.../__snapshots__/toc_entry.test.tsx.snap | 92 ++
.../layer_toc/toc_entry/toc_entry.test.tsx | 12 +
.../layer_toc/toc_entry/toc_entry.tsx | 7 +-
.../toc_entry_actions_popover.test.tsx.snap | 1256 +++++++++--------
.../toc_entry_actions_popover.tsx | 70 +-
.../toc_entry_button/toc_entry_button.tsx | 141 +-
.../maps/public/reducers/map/layer_utils.ts | 7 +-
.../maps/public/selectors/map_selectors.ts | 32 +-
.../test/functional/page_objects/gis_page.ts | 1 +
37 files changed, 2063 insertions(+), 974 deletions(-)
create mode 100644 x-pack/plugins/maps/public/actions/get_layers_extent.tsx
create mode 100644 x-pack/plugins/maps/public/classes/layers/layer_group/index.ts
create mode 100644 x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx
create mode 100644 x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx
create mode 100644 x-pack/plugins/maps/public/connected_components/right_side_controls/layer_control/layer_toc/_layer_toc.scss
diff --git a/x-pack/plugins/maps/common/constants.ts b/x-pack/plugins/maps/common/constants.ts
index 96318890a2d94..94ae72a050e21 100644
--- a/x-pack/plugins/maps/common/constants.ts
+++ b/x-pack/plugins/maps/common/constants.ts
@@ -53,6 +53,7 @@ export enum LAYER_TYPE {
HEATMAP = 'HEATMAP',
BLENDED_VECTOR = 'BLENDED_VECTOR',
MVT_VECTOR = 'MVT_VECTOR',
+ LAYER_GROUP = 'LAYER_GROUP',
}
export enum SOURCE_TYPES {
diff --git a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts
index 160a34f8bcc0d..a547ef9c6d93a 100644
--- a/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts
+++ b/x-pack/plugins/maps/common/descriptor_types/layer_descriptor_types.ts
@@ -71,6 +71,7 @@ export type LayerDescriptor = {
style?: StyleDescriptor | null;
query?: Query;
includeInFitToBounds?: boolean;
+ parent?: string;
};
export type VectorLayerDescriptor = LayerDescriptor & {
@@ -89,3 +90,10 @@ export type EMSVectorTileLayerDescriptor = LayerDescriptor & {
type: LAYER_TYPE.EMS_VECTOR_TILE;
style: EMSVectorTileStyleDescriptor;
};
+
+export type LayerGroupDescriptor = LayerDescriptor & {
+ type: LAYER_TYPE.LAYER_GROUP;
+ label: string;
+ sourceDescriptor: null;
+ visible: boolean;
+};
diff --git a/x-pack/plugins/maps/public/actions/data_request_actions.ts b/x-pack/plugins/maps/public/actions/data_request_actions.ts
index b5ce42ebefc09..5e27b488065ab 100644
--- a/x-pack/plugins/maps/public/actions/data_request_actions.ts
+++ b/x-pack/plugins/maps/public/actions/data_request_actions.ts
@@ -9,9 +9,7 @@
import { AnyAction, Dispatch } from 'redux';
import { ThunkDispatch } from 'redux-thunk';
-import bbox from '@turf/bbox';
import uuid from 'uuid/v4';
-import { multiPoint } from '@turf/helpers';
import { FeatureCollection } from 'geojson';
import { Adapters } from '@kbn/inspector-plugin/common/adapters';
import { MapStoreState } from '../reducers/store';
@@ -49,7 +47,9 @@ import { ILayer } from '../classes/layers/layer';
import { IVectorLayer } from '../classes/layers/vector_layer';
import { DataRequestMeta, MapExtent, DataFilters } from '../../common/descriptor_types';
import { DataRequestAbortError } from '../classes/util/data_request';
-import { scaleBounds, turfBboxToBounds } from '../../common/elasticsearch_util';
+import { scaleBounds } from '../../common/elasticsearch_util';
+import { getLayersExtent } from './get_layers_extent';
+import { isLayerGroup } from '../classes/layers/layer_group';
const FIT_TO_BOUNDS_SCALE_FACTOR = 0.1;
@@ -101,7 +101,7 @@ export function cancelAllInFlightRequests() {
export function updateStyleMeta(layerId: string | null) {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
const layer = getLayerById(layerId, getState());
- if (!layer) {
+ if (!layer || isLayerGroup(layer)) {
return;
}
@@ -378,8 +378,8 @@ export function fitToLayerExtent(layerId: string) {
if (targetLayer) {
try {
- const bounds = await targetLayer.getBounds(
- getDataRequestContext(dispatch, getState, layerId, false, false)
+ const bounds = await targetLayer.getBounds((boundsLayerId) =>
+ getDataRequestContext(dispatch, getState, boundsLayerId, false, false)
);
if (bounds) {
await dispatch(setGotoWithBounds(scaleBounds(bounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
@@ -401,65 +401,22 @@ export function fitToLayerExtent(layerId: string) {
export function fitToDataBounds(onNoBounds?: () => void) {
return async (dispatch: Dispatch, getState: () => MapStoreState) => {
- const layerList = getLayerList(getState());
-
- if (!layerList.length) {
- return;
- }
-
- const boundsPromises = layerList.map(async (layer: ILayer) => {
- if (!(await layer.isFittable())) {
- return null;
- }
- return layer.getBounds(
- getDataRequestContext(dispatch, getState, layer.getId(), false, false)
- );
+ const rootLayers = getLayerList(getState()).filter((layer) => {
+ return layer.getParent() === undefined;
});
- let bounds;
- try {
- bounds = await Promise.all(boundsPromises);
- } catch (error) {
- if (!(error instanceof DataRequestAbortError)) {
- // eslint-disable-next-line no-console
- console.warn(
- 'Unhandled getBounds error for layer. Only DataRequestAbortError should be surfaced',
- error
- );
- }
- // new fitToDataBounds request has superseded this thread of execution. Results no longer needed.
- return;
- }
-
- const corners = [];
- for (let i = 0; i < bounds.length; i++) {
- const b = bounds[i];
-
- // filter out undefined bounds (uses Infinity due to turf responses)
- if (
- b === null ||
- b.minLon === Infinity ||
- b.maxLon === Infinity ||
- b.minLat === -Infinity ||
- b.maxLat === -Infinity
- ) {
- continue;
- }
-
- corners.push([b.minLon, b.minLat]);
- corners.push([b.maxLon, b.maxLat]);
- }
+ const extent = await getLayersExtent(rootLayers, (boundsLayerId) =>
+ getDataRequestContext(dispatch, getState, boundsLayerId, false, false)
+ );
- if (!corners.length) {
+ if (extent === null) {
if (onNoBounds) {
onNoBounds();
}
return;
}
- const dataBounds = turfBboxToBounds(bbox(multiPoint(corners)));
-
- dispatch(setGotoWithBounds(scaleBounds(dataBounds, FIT_TO_BOUNDS_SCALE_FACTOR)));
+ dispatch(setGotoWithBounds(scaleBounds(extent, FIT_TO_BOUNDS_SCALE_FACTOR)));
};
}
diff --git a/x-pack/plugins/maps/public/actions/get_layers_extent.tsx b/x-pack/plugins/maps/public/actions/get_layers_extent.tsx
new file mode 100644
index 0000000000000..81d8367bd2803
--- /dev/null
+++ b/x-pack/plugins/maps/public/actions/get_layers_extent.tsx
@@ -0,0 +1,66 @@
+/*
+ * 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 bbox from '@turf/bbox';
+import { multiPoint } from '@turf/helpers';
+import { MapExtent } from '../../common/descriptor_types';
+import { turfBboxToBounds } from '../../common/elasticsearch_util';
+import { ILayer } from '../classes/layers/layer';
+import type { DataRequestContext } from './data_request_actions';
+import { DataRequestAbortError } from '../classes/util/data_request';
+
+export async function getLayersExtent(
+ layers: ILayer[],
+ getDataRequestContext: (layerId: string) => DataRequestContext
+): Promise {
+ if (!layers.length) {
+ return null;
+ }
+
+ const boundsPromises = layers.map(async (layer: ILayer) => {
+ if (!(await layer.isFittable())) {
+ return null;
+ }
+ return layer.getBounds(getDataRequestContext);
+ });
+
+ let bounds;
+ try {
+ bounds = await Promise.all(boundsPromises);
+ } catch (error) {
+ if (!(error instanceof DataRequestAbortError)) {
+ // eslint-disable-next-line no-console
+ console.warn(
+ 'Unhandled getBounds error for layer. Only DataRequestAbortError should be surfaced',
+ error
+ );
+ }
+ // new fitToDataBounds request has superseded this thread of execution. Results no longer needed.
+ return null;
+ }
+
+ const corners = [];
+ for (let i = 0; i < bounds.length; i++) {
+ const b = bounds[i];
+
+ // filter out undefined bounds (uses Infinity due to turf responses)
+ if (
+ b === null ||
+ b.minLon === Infinity ||
+ b.maxLon === Infinity ||
+ b.minLat === -Infinity ||
+ b.maxLat === -Infinity
+ ) {
+ continue;
+ }
+
+ corners.push([b.minLon, b.minLat]);
+ corners.push([b.maxLon, b.maxLat]);
+ }
+
+ return corners.length ? turfBboxToBounds(bbox(multiPoint(corners))) : null;
+}
diff --git a/x-pack/plugins/maps/public/actions/index.ts b/x-pack/plugins/maps/public/actions/index.ts
index 96db1cebe7d39..235f8d141411e 100644
--- a/x-pack/plugins/maps/public/actions/index.ts
+++ b/x-pack/plugins/maps/public/actions/index.ts
@@ -24,3 +24,4 @@ export {
openOnHoverTooltip,
updateOpenTooltips,
} from './tooltip_actions';
+export { getLayersExtent } from './get_layers_extent';
diff --git a/x-pack/plugins/maps/public/actions/layer_actions.ts b/x-pack/plugins/maps/public/actions/layer_actions.ts
index 317f6e09053e5..5ba9edaee58d0 100644
--- a/x-pack/plugins/maps/public/actions/layer_actions.ts
+++ b/x-pack/plugins/maps/public/actions/layer_actions.ts
@@ -75,6 +75,7 @@ import { IESAggField } from '../classes/fields/agg';
import { IField } from '../classes/fields/field';
import type { IESSource } from '../classes/sources/es_source';
import { getDrawMode, getOpenTOCDetails } from '../selectors/ui_selectors';
+import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group';
export function trackCurrentLayerState(layerId: string) {
return {
@@ -160,8 +161,12 @@ export function cloneLayer(layerId: string) {
return;
}
- const clonedDescriptor = await layer.cloneDescriptor();
- dispatch(addLayer(clonedDescriptor));
+ (await layer.cloneDescriptor()).forEach((layerDescriptor) => {
+ dispatch(addLayer(layerDescriptor));
+ if (layer.getParent()) {
+ dispatch(moveLayerToLeftOfTarget(layerDescriptor.id, layerId));
+ }
+ });
};
}
@@ -249,12 +254,19 @@ export function setLayerVisibility(layerId: string, makeVisible: boolean) {
dispatch: ThunkDispatch,
getState: () => MapStoreState
) => {
- // if the current-state is invisible, we also want to sync data
- // e.g. if a layer was invisible at start-up, it won't have any data loaded
const layer = getLayerById(layerId, getState());
+ if (!layer) {
+ return;
+ }
+
+ if (isLayerGroup(layer)) {
+ (layer as LayerGroup).getChildren().forEach((childLayer) => {
+ dispatch(setLayerVisibility(childLayer.getId(), makeVisible));
+ });
+ }
// If the layer visibility is already what we want it to be, do nothing
- if (!layer || layer.isVisible() === makeVisible) {
+ if (layer.isVisible() === makeVisible) {
return;
}
@@ -263,6 +275,9 @@ export function setLayerVisibility(layerId: string, makeVisible: boolean) {
layerId,
visibility: makeVisible,
});
+
+ // if the current-state is invisible, we also want to sync data
+ // e.g. if a layer was invisible at start-up, it won't have any data loaded
if (makeVisible) {
dispatch(syncDataForLayerId(layerId, false));
}
@@ -290,7 +305,7 @@ export function hideAllLayers() {
getState: () => MapStoreState
) => {
getLayerList(getState()).forEach((layer: ILayer, index: number) => {
- if (layer.isVisible() && !layer.isBasemap(index)) {
+ if (!layer.isBasemap(index)) {
dispatch(setLayerVisibility(layer.getId(), false));
}
});
@@ -303,9 +318,7 @@ export function showAllLayers() {
getState: () => MapStoreState
) => {
getLayerList(getState()).forEach((layer: ILayer, index: number) => {
- if (!layer.isVisible()) {
- dispatch(setLayerVisibility(layer.getId(), true));
- }
+ dispatch(setLayerVisibility(layer.getId(), true));
});
};
}
@@ -316,23 +329,20 @@ export function showThisLayerOnly(layerId: string) {
getState: () => MapStoreState
) => {
getLayerList(getState()).forEach((layer: ILayer, index: number) => {
- if (layer.isBasemap(index)) {
- return;
- }
-
- // show target layer
- if (layer.getId() === layerId) {
- if (!layer.isVisible()) {
- dispatch(setLayerVisibility(layerId, true));
- }
+ if (layer.isBasemap(index) || layer.getId() === layerId) {
return;
}
// hide all other layers
- if (layer.isVisible()) {
- dispatch(setLayerVisibility(layer.getId(), false));
- }
+ dispatch(setLayerVisibility(layer.getId(), false));
});
+
+ // show target layer after hiding all other layers
+ // since hiding layer group will hide its children
+ const targetLayer = getLayerById(layerId, getState());
+ if (targetLayer) {
+ dispatch(setLayerVisibility(layerId, true));
+ }
};
}
@@ -602,6 +612,15 @@ export function setLayerQuery(id: string, query: Query) {
};
}
+export function setLayerParent(id: string, parent: string | undefined) {
+ return {
+ type: UPDATE_LAYER_PROP,
+ id,
+ propName: 'parent',
+ newValue: parent,
+ };
+}
+
export function removeSelectedLayer() {
return (
dispatch: ThunkDispatch,
@@ -657,6 +676,12 @@ function removeLayerFromLayerList(layerId: string) {
if (openTOCDetails.includes(layerId)) {
dispatch(hideTOCDetails(layerId));
}
+
+ if (isLayerGroup(layerGettingRemoved)) {
+ (layerGettingRemoved as LayerGroup).getChildren().forEach((childLayer) => {
+ dispatch(removeLayerFromLayerList(childLayer.getId()));
+ });
+ }
};
}
@@ -786,7 +811,7 @@ export function updateMetaFromTiles(layerId: string, mbMetaFeatures: TileMetaFea
}
function clearInspectorAdapters(layer: ILayer, adapters: Adapters) {
- if (!layer.getSource().isESSource()) {
+ if (isLayerGroup(layer) || !layer.getSource().isESSource()) {
return;
}
@@ -811,3 +836,93 @@ function hasByValueStyling(styleDescriptor: StyleDescriptor) {
})
);
}
+
+export function createLayerGroup(draggedLayerId: string, combineLayerId: string) {
+ return (
+ dispatch: ThunkDispatch,
+ getState: () => MapStoreState
+ ) => {
+ const group = LayerGroup.createDescriptor({});
+ const combineLayerDescriptor = getLayerDescriptor(getState(), combineLayerId);
+ if (combineLayerDescriptor?.parent) {
+ group.parent = combineLayerDescriptor.parent;
+ }
+ dispatch({
+ type: ADD_LAYER,
+ layer: group,
+ });
+ // Move group to left of combine-layer
+ dispatch(moveLayerToLeftOfTarget(group.id, combineLayerId));
+
+ dispatch(showTOCDetails(group.id));
+ dispatch(setLayerParent(draggedLayerId, group.id));
+ dispatch(setLayerParent(combineLayerId, group.id));
+
+ // Move dragged-layer to left of combine-layer
+ dispatch(moveLayerToLeftOfTarget(draggedLayerId, combineLayerId));
+ };
+}
+
+export function moveLayerToLeftOfTarget(moveLayerId: string, targetLayerId: string) {
+ return (
+ dispatch: ThunkDispatch,
+ getState: () => MapStoreState
+ ) => {
+ const layers = getLayerList(getState());
+ const moveLayerIndex = layers.findIndex((layer) => layer.getId() === moveLayerId);
+ const targetLayerIndex = layers.findIndex((layer) => layer.getId() === targetLayerId);
+ if (moveLayerIndex === -1 || targetLayerIndex === -1) {
+ return;
+ }
+ const moveLayer = layers[moveLayerIndex];
+
+ const newIndex =
+ moveLayerIndex > targetLayerIndex
+ ? // When layer is moved to the right, new left sibling index is to the left of destination
+ targetLayerIndex + 1
+ : // When layer is moved to the left, new left sibling index is the destination index
+ targetLayerIndex;
+ const newOrder = [];
+ for (let i = 0; i < layers.length; i++) {
+ newOrder.push(i);
+ }
+ newOrder.splice(moveLayerIndex, 1);
+ newOrder.splice(newIndex, 0, moveLayerIndex);
+ dispatch(updateLayerOrder(newOrder));
+
+ if (isLayerGroup(moveLayer)) {
+ (moveLayer as LayerGroup).getChildren().forEach((childLayer) => {
+ dispatch(moveLayerToLeftOfTarget(childLayer.getId(), targetLayerId));
+ });
+ }
+ };
+}
+
+export function moveLayerToBottom(moveLayerId: string) {
+ return (
+ dispatch: ThunkDispatch,
+ getState: () => MapStoreState
+ ) => {
+ const layers = getLayerList(getState());
+ const moveLayerIndex = layers.findIndex((layer) => layer.getId() === moveLayerId);
+ if (moveLayerIndex === -1) {
+ return;
+ }
+ const moveLayer = layers[moveLayerIndex];
+
+ const newIndex = 0;
+ const newOrder = [];
+ for (let i = 0; i < layers.length; i++) {
+ newOrder.push(i);
+ }
+ newOrder.splice(moveLayerIndex, 1);
+ newOrder.splice(newIndex, 0, moveLayerIndex);
+ dispatch(updateLayerOrder(newOrder));
+
+ if (isLayerGroup(moveLayer)) {
+ (moveLayer as LayerGroup).getChildren().forEach((childLayer) => {
+ dispatch(moveLayerToBottom(childLayer.getId()));
+ });
+ }
+ };
+}
diff --git a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts
index ec9cec3a914ba..bc013cb958a4f 100644
--- a/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts
+++ b/x-pack/plugins/maps/public/classes/layers/heatmap_layer/heatmap_layer.ts
@@ -200,10 +200,10 @@ export class HeatmapLayer extends AbstractLayer {
return this.getCurrentStyle().renderLegendDetails(metricFields[0]);
}
- async getBounds(syncContext: DataRequestContext) {
+ async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) {
return await syncBoundsData({
layerId: this.getId(),
- syncContext,
+ syncContext: getDataRequestContext(this.getId()),
source: this.getSource(),
sourceQuery: this.getQuery(),
});
diff --git a/x-pack/plugins/maps/public/classes/layers/layer.test.ts b/x-pack/plugins/maps/public/classes/layers/layer.test.ts
index 194b41680872c..908c38a2eef27 100644
--- a/x-pack/plugins/maps/public/classes/layers/layer.test.ts
+++ b/x-pack/plugins/maps/public/classes/layers/layer.test.ts
@@ -18,7 +18,7 @@ class MockSource {
this._fitToBounds = fitToBounds;
}
cloneDescriptor() {
- return {};
+ return [{}];
}
async supportsFitToBounds() {
diff --git a/x-pack/plugins/maps/public/classes/layers/layer.tsx b/x-pack/plugins/maps/public/classes/layers/layer.tsx
index b01f2b9b8ca04..ef1a72649bbf0 100644
--- a/x-pack/plugins/maps/public/classes/layers/layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/layer.tsx
@@ -20,7 +20,6 @@ import {
MAX_ZOOM,
MB_SOURCE_ID_LAYER_ID_PREFIX_DELIMITER,
MIN_ZOOM,
- SOURCE_BOUNDS_DATA_REQUEST_ID,
SOURCE_DATA_REQUEST_ID,
} from '../../../common/constants';
import { copyPersistentState } from '../../reducers/copy_persistent_state';
@@ -41,7 +40,9 @@ import { LICENSED_FEATURES } from '../../licensed_features';
import { IESSource } from '../sources/es_source';
export interface ILayer {
- getBounds(dataRequestContext: DataRequestContext): Promise;
+ getBounds(
+ getDataRequestContext: (layerId: string) => DataRequestContext
+ ): Promise;
getDataRequest(id: string): DataRequest | undefined;
getDisplayName(source?: ISource): Promise;
getId(): string;
@@ -68,7 +69,6 @@ export interface ILayer {
getImmutableSourceProperties(): Promise;
renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs): ReactElement | null;
isLayerLoading(): boolean;
- isLoadingBounds(): boolean;
isFilteredByGlobalTime(): Promise;
hasErrors(): boolean;
getErrors(): string;
@@ -92,7 +92,7 @@ export interface ILayer {
getQueryableIndexPatternIds(): string[];
getType(): LAYER_TYPE;
isVisible(): boolean;
- cloneDescriptor(): Promise;
+ cloneDescriptor(): Promise;
renderStyleEditor(
onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void,
onCustomIconsChange: (customIcons: CustomIcon[]) => void
@@ -117,6 +117,7 @@ export interface ILayer {
getGeoFieldNames(): string[];
getStyleMetaDescriptorFromLocalFeatures(): Promise;
isBasemap(order: number): boolean;
+ getParent(): string | undefined;
}
export type LayerIcon = {
@@ -174,14 +175,14 @@ export class AbstractLayer implements ILayer {
return this._descriptor;
}
- async cloneDescriptor(): Promise {
+ async cloneDescriptor(): Promise {
const clonedDescriptor = copyPersistentState(this._descriptor);
// layer id is uuid used to track styles/layers in mapbox
clonedDescriptor.id = uuid();
const displayName = await this.getDisplayName();
clonedDescriptor.label = `Clone of ${displayName}`;
clonedDescriptor.sourceDescriptor = this.getSource().cloneDescriptor();
- return clonedDescriptor;
+ return [clonedDescriptor];
}
makeMbLayerId(layerNameSuffix: string): string {
@@ -383,11 +384,6 @@ export class AbstractLayer implements ILayer {
return areTilesLoading || this._dataRequests.some((dataRequest) => dataRequest.isLoading());
}
- isLoadingBounds() {
- const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID);
- return !!boundsDataRequest && boundsDataRequest.isLoading();
- }
-
hasErrors(): boolean {
return _.get(this._descriptor, '__isInErrorState', false);
}
@@ -427,7 +423,9 @@ export class AbstractLayer implements ILayer {
return sourceDataRequest ? sourceDataRequest.hasData() : false;
}
- async getBounds(dataRequestContext: DataRequestContext): Promise {
+ async getBounds(
+ getDataRequestContext: (layerId: string) => DataRequestContext
+ ): Promise {
return null;
}
@@ -488,6 +486,10 @@ export class AbstractLayer implements ILayer {
return false;
}
+ getParent(): string | undefined {
+ return this._descriptor.parent;
+ }
+
_getMetaFromTiles(): TileMetaFeature[] {
return this._descriptor.__metaFromTiles || [];
}
diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts
new file mode 100644
index 0000000000000..3b2848d03f5ff
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/layers/layer_group/index.ts
@@ -0,0 +1,8 @@
+/*
+ * 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 { isLayerGroup, LayerGroup } from './layer_group';
diff --git a/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx
new file mode 100644
index 0000000000000..c0e3c4ee56402
--- /dev/null
+++ b/x-pack/plugins/maps/public/classes/layers/layer_group/layer_group.tsx
@@ -0,0 +1,385 @@
+/*
+ * 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 _ from 'lodash';
+import { i18n } from '@kbn/i18n';
+import type { Map as MbMap } from '@kbn/mapbox-gl';
+import type { Query } from '@kbn/es-query';
+import { asyncMap } from '@kbn/std';
+import React, { ReactElement } from 'react';
+import { EuiIcon } from '@elastic/eui';
+import uuid from 'uuid/v4';
+import { LAYER_TYPE, MAX_ZOOM, MIN_ZOOM } from '../../../../common/constants';
+import { DataRequest } from '../../util/data_request';
+import { copyPersistentState } from '../../../reducers/copy_persistent_state';
+import {
+ Attribution,
+ CustomIcon,
+ LayerDescriptor,
+ LayerGroupDescriptor,
+ MapExtent,
+ StyleDescriptor,
+ StyleMetaDescriptor,
+} from '../../../../common/descriptor_types';
+import { ImmutableSourceProperty, ISource, SourceEditorArgs } from '../../sources/source';
+import { type DataRequestContext } from '../../../actions';
+import { getLayersExtent } from '../../../actions/get_layers_extent';
+import { ILayer, LayerIcon } from '../layer';
+import { IStyle } from '../../styles/style';
+import { LICENSED_FEATURES } from '../../../licensed_features';
+
+export function isLayerGroup(layer: ILayer) {
+ return layer instanceof LayerGroup;
+}
+
+export class LayerGroup implements ILayer {
+ protected readonly _descriptor: LayerGroupDescriptor;
+ private _children: ILayer[] = [];
+
+ static createDescriptor(options: Partial): LayerGroupDescriptor {
+ return {
+ ...options,
+ type: LAYER_TYPE.LAYER_GROUP,
+ id: typeof options.id === 'string' && options.id.length ? options.id : uuid(),
+ label:
+ typeof options.label === 'string' && options.label.length
+ ? options.label
+ : i18n.translate('xpack.maps.layerGroup.defaultName', {
+ defaultMessage: 'Layer group',
+ }),
+ sourceDescriptor: null,
+ visible: typeof options.visible === 'boolean' ? options.visible : true,
+ };
+ }
+
+ constructor({ layerDescriptor }: { layerDescriptor: LayerGroupDescriptor }) {
+ this._descriptor = LayerGroup.createDescriptor(layerDescriptor);
+ }
+
+ setChildren(children: ILayer[]) {
+ this._children = children;
+ }
+
+ getChildren(): ILayer[] {
+ return [...this._children];
+ }
+
+ async _asyncSomeChildren(methodName: string) {
+ const promises = this.getChildren().map(async (child) => {
+ // @ts-ignore
+ return (child[methodName] as () => Promise)();
+ });
+ return ((await Promise.all(promises)) as boolean[]).some((result) => {
+ return result;
+ });
+ }
+
+ getDescriptor(): LayerGroupDescriptor {
+ return this._descriptor;
+ }
+
+ async cloneDescriptor(): Promise {
+ const clonedDescriptor = copyPersistentState(this._descriptor);
+ clonedDescriptor.id = uuid();
+ const displayName = await this.getDisplayName();
+ clonedDescriptor.label = `Clone of ${displayName}`;
+
+ const childrenDescriptors = await asyncMap(this.getChildren(), async (childLayer) => {
+ return (await childLayer.cloneDescriptor()).map((childLayerDescriptor) => {
+ if (childLayerDescriptor.parent === this.getId()) {
+ childLayerDescriptor.parent = clonedDescriptor.id;
+ }
+ return childLayerDescriptor;
+ });
+ });
+
+ return [..._.flatten(childrenDescriptors), clonedDescriptor];
+ }
+
+ makeMbLayerId(layerNameSuffix: string): string {
+ throw new Error(
+ 'makeMbLayerId should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ isPreviewLayer(): boolean {
+ return !!this._descriptor.__isPreviewLayer;
+ }
+
+ supportsElasticsearchFilters(): boolean {
+ return this.getChildren().some((child) => {
+ return child.supportsElasticsearchFilters();
+ });
+ }
+
+ async supportsFitToBounds(): Promise {
+ return this._asyncSomeChildren('supportsFitToBounds');
+ }
+
+ async isFittable(): Promise {
+ return this._asyncSomeChildren('isFittable');
+ }
+
+ isIncludeInFitToBounds(): boolean {
+ return this.getChildren().some((child) => {
+ return child.isIncludeInFitToBounds();
+ });
+ }
+
+ async isFilteredByGlobalTime(): Promise {
+ return this._asyncSomeChildren('isFilteredByGlobalTime');
+ }
+
+ async getDisplayName(source?: ISource): Promise {
+ return this.getLabel();
+ }
+
+ async getAttributions(): Promise {
+ return [];
+ }
+
+ getStyleForEditing(): IStyle {
+ throw new Error(
+ 'getStyleForEditing should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getStyle(): IStyle {
+ throw new Error(
+ 'getStyle should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getCurrentStyle(): IStyle {
+ throw new Error(
+ 'getCurrentStyle should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getLabel(): string {
+ return this._descriptor.label ? this._descriptor.label : '';
+ }
+
+ getLocale(): string | null {
+ return null;
+ }
+
+ getLayerIcon(isTocIcon: boolean): LayerIcon {
+ return {
+ icon: ,
+ tooltipContent: '',
+ };
+ }
+
+ async hasLegendDetails(): Promise {
+ return this._children.length > 0;
+ }
+
+ renderLegendDetails(): ReactElement | null {
+ return null;
+ }
+
+ getId(): string {
+ return this._descriptor.id;
+ }
+
+ getSource(): ISource {
+ throw new Error(
+ 'getSource should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getSourceForEditing(): ISource {
+ throw new Error(
+ 'getSourceForEditing should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ isVisible(): boolean {
+ return !!this._descriptor.visible;
+ }
+
+ showAtZoomLevel(zoom: number): boolean {
+ return zoom >= this.getMinZoom() && zoom <= this.getMaxZoom();
+ }
+
+ getMinZoom(): number {
+ let min = MIN_ZOOM;
+ this._children.forEach((child) => {
+ min = Math.max(min, child.getMinZoom());
+ });
+ return min;
+ }
+
+ getMaxZoom(): number {
+ let max = MAX_ZOOM;
+ this._children.forEach((child) => {
+ max = Math.min(max, child.getMaxZoom());
+ });
+ return max;
+ }
+
+ getMinSourceZoom(): number {
+ let min = MIN_ZOOM;
+ this._children.forEach((child) => {
+ min = Math.max(min, child.getMinSourceZoom());
+ });
+ return min;
+ }
+
+ getMbSourceId(): string {
+ throw new Error(
+ 'getMbSourceId should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getAlpha(): number {
+ throw new Error(
+ 'getAlpha should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getQuery(): Query | null {
+ return null;
+ }
+
+ async getImmutableSourceProperties(): Promise {
+ return [];
+ }
+
+ renderSourceSettingsEditor(sourceEditorArgs: SourceEditorArgs) {
+ return null;
+ }
+
+ getPrevRequestToken(dataId: string): symbol | undefined {
+ return undefined;
+ }
+
+ getInFlightRequestTokens(): symbol[] {
+ return [];
+ }
+
+ getSourceDataRequest(): DataRequest | undefined {
+ return undefined;
+ }
+
+ getDataRequest(id: string): DataRequest | undefined {
+ return undefined;
+ }
+
+ isLayerLoading(): boolean {
+ return this._children.some((child) => {
+ return child.isLayerLoading();
+ });
+ }
+
+ hasErrors(): boolean {
+ return this._children.some((child) => {
+ return child.hasErrors();
+ });
+ }
+
+ getErrors(): string {
+ const firstChildWithError = this._children.find((child) => {
+ return child.hasErrors();
+ });
+ return firstChildWithError ? firstChildWithError.getErrors() : '';
+ }
+
+ async syncData(syncContext: DataRequestContext) {
+ // layer group does not render to map so there is never sync data request
+ }
+
+ getMbLayerIds(): string[] {
+ return [];
+ }
+
+ ownsMbLayerId(layerId: string): boolean {
+ return false;
+ }
+
+ ownsMbSourceId(mbSourceId: string): boolean {
+ return false;
+ }
+
+ syncLayerWithMB(mbMap: MbMap) {
+ // layer group does not render to map so there is never sync data request
+ }
+
+ getLayerTypeIconName(): string {
+ return 'layers';
+ }
+
+ isInitialDataLoadComplete(): boolean {
+ return true;
+ }
+
+ async getBounds(
+ getDataRequestContext: (layerId: string) => DataRequestContext
+ ): Promise {
+ return getLayersExtent(this.getChildren(), getDataRequestContext);
+ }
+
+ renderStyleEditor(
+ onStyleDescriptorChange: (styleDescriptor: StyleDescriptor) => void,
+ onCustomIconsChange: (customIcons: CustomIcon[]) => void
+ ): ReactElement | null {
+ return null;
+ }
+
+ getIndexPatternIds(): string[] {
+ return [];
+ }
+
+ getQueryableIndexPatternIds(): string[] {
+ return [];
+ }
+
+ syncVisibilityWithMb(mbMap: unknown, mbLayerId: string) {
+ throw new Error(
+ 'syncVisibilityWithMb should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ getType(): LAYER_TYPE {
+ return LAYER_TYPE.LAYER_GROUP;
+ }
+
+ areLabelsOnTop(): boolean {
+ return false;
+ }
+
+ supportsLabelsOnTop(): boolean {
+ return false;
+ }
+
+ supportsLabelLocales(): boolean {
+ return false;
+ }
+
+ async getLicensedFeatures(): Promise {
+ return [];
+ }
+
+ getGeoFieldNames(): string[] {
+ return [];
+ }
+
+ async getStyleMetaDescriptorFromLocalFeatures(): Promise {
+ throw new Error(
+ 'getStyleMetaDescriptorFromLocalFeatures should not be called on LayerGroup, LayerGroup does not render to map'
+ );
+ }
+
+ isBasemap(order: number): boolean {
+ return false;
+ }
+
+ getParent(): string | undefined {
+ return this._descriptor.parent;
+ }
+}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx
index f2ef7ca9588be..03da177cddbd9 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.test.tsx
@@ -141,7 +141,9 @@ describe('cloneDescriptor', () => {
customIcons,
});
- const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor();
+ const clones = await blendedVectorLayer.cloneDescriptor();
+ expect(clones.length).toBe(1);
+ const clonedLayerDescriptor = clones[0];
expect(clonedLayerDescriptor.sourceDescriptor!.type).toBe(SOURCE_TYPES.ES_SEARCH);
expect(clonedLayerDescriptor.label).toBe('Clone of myIndexPattern');
});
@@ -161,7 +163,9 @@ describe('cloneDescriptor', () => {
customIcons,
});
- const clonedLayerDescriptor = await blendedVectorLayer.cloneDescriptor();
+ const clones = await blendedVectorLayer.cloneDescriptor();
+ expect(clones.length).toBe(1);
+ const clonedLayerDescriptor = clones[0];
expect(clonedLayerDescriptor.sourceDescriptor!.type).toBe(SOURCE_TYPES.ES_SEARCH);
expect(clonedLayerDescriptor.label).toBe('Clone of myIndexPattern');
});
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts
index a4b06fe043ff2..ee9fdaf410abb 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/blended_vector_layer/blended_vector_layer.ts
@@ -250,8 +250,12 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay
return false;
}
- async cloneDescriptor(): Promise {
- const clonedDescriptor = await super.cloneDescriptor();
+ async cloneDescriptor(): Promise {
+ const clones = await super.cloneDescriptor();
+ if (clones.length === 0) {
+ return [];
+ }
+ const clonedDescriptor = clones[0];
// Use super getDisplayName instead of instance getDisplayName to avoid getting 'Clustered Clone of Clustered'
const displayName = await super.getDisplayName();
@@ -260,7 +264,7 @@ export class BlendedVectorLayer extends GeoJsonVectorLayer implements IVectorLay
// sourceDescriptor must be document source descriptor
clonedDescriptor.sourceDescriptor = this._documentSource.cloneDescriptor();
- return clonedDescriptor;
+ return [clonedDescriptor];
}
getSource(): IVectorSource {
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx
index bc7ba78c84d98..14a5092606b4d 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/geojson_vector_layer/geojson_vector_layer.tsx
@@ -15,6 +15,7 @@ import {
EMPTY_FEATURE_COLLECTION,
FEATURE_VISIBLE_PROPERTY_NAME,
LAYER_TYPE,
+ SOURCE_BOUNDS_DATA_REQUEST_ID,
} from '../../../../../common/constants';
import {
StyleMetaDescriptor,
@@ -59,11 +60,11 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer {
return layerDescriptor;
}
- async getBounds(syncContext: DataRequestContext) {
+ async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) {
const isStaticLayer = !this.getSource().isBoundsAware();
return isStaticLayer || this.hasJoins()
? getFeatureCollectionBounds(this._getSourceFeatureCollection(), this.hasJoins())
- : super.getBounds(syncContext);
+ : super.getBounds(getDataRequestContext);
}
getLayerIcon(isTocIcon: boolean): LayerIcon {
@@ -211,6 +212,11 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer {
await this._syncData(syncContext, this.getSource(), this.getCurrentStyle());
}
+ _isLoadingBounds() {
+ const boundsDataRequest = this.getDataRequest(SOURCE_BOUNDS_DATA_REQUEST_ID);
+ return !!boundsDataRequest && boundsDataRequest.isLoading();
+ }
+
// TLDR: Do not call getSource or getCurrentStyle in syncData flow. Use 'source' and 'style' arguments instead.
//
// 1) State is contained in the redux store. Layer instance state is readonly.
@@ -222,7 +228,7 @@ export class GeoJsonVectorLayer extends AbstractVectorLayer {
// Given 2 above, which source/style to use can not be pulled from data request state.
// Therefore, source and style are provided as arugments and must be used instead of calling getSource or getCurrentStyle.
async _syncData(syncContext: DataRequestContext, source: IVectorSource, style: IVectorStyle) {
- if (this.isLoadingBounds()) {
+ if (this._isLoadingBounds()) {
return;
}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx
index 7eaec94eac0a2..a16093af20426 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/mvt_vector_layer/mvt_vector_layer.tsx
@@ -45,6 +45,7 @@ import {
getAggsMeta,
getHitsMeta,
} from '../../../util/tile_meta_feature_utils';
+import { syncBoundsData } from '../bounds_data';
const MAX_RESULT_WINDOW_DATA_REQUEST_ID = 'maxResultWindow';
@@ -77,7 +78,7 @@ export class MvtVectorLayer extends AbstractVectorLayer {
: super.isInitialDataLoadComplete();
}
- async getBounds(syncContext: DataRequestContext) {
+ async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) {
// Add filter to narrow bounds to features with matching join keys
let joinKeyFilter;
if (this.getSource().isESSource()) {
@@ -93,12 +94,18 @@ export class MvtVectorLayer extends AbstractVectorLayer {
}
}
- return super.getBounds({
- ...syncContext,
- dataFilters: {
- ...syncContext.dataFilters,
- joinKeyFilter,
+ const syncContext = getDataRequestContext(this.getId());
+ return syncBoundsData({
+ layerId: this.getId(),
+ syncContext: {
+ ...syncContext,
+ dataFilters: {
+ ...syncContext.dataFilters,
+ joinKeyFilter,
+ },
},
+ source: this.getSource(),
+ sourceQuery: this.getQuery(),
});
}
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx
index b71fef484de01..d450f92467e46 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.test.tsx
@@ -87,7 +87,9 @@ describe('cloneDescriptor', () => {
source: new MockSource() as unknown as IVectorSource,
customIcons: [],
});
- const clonedDescriptor = await layer.cloneDescriptor();
+ const clones = await layer.cloneDescriptor();
+ expect(clones.length).toBe(1);
+ const clonedDescriptor = clones[0];
const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties;
// Should update style field belonging to join
// @ts-expect-error
@@ -124,7 +126,9 @@ describe('cloneDescriptor', () => {
source: new MockSource() as unknown as IVectorSource,
customIcons: [],
});
- const clonedDescriptor = await layer.cloneDescriptor();
+ const clones = await layer.cloneDescriptor();
+ expect(clones.length).toBe(1);
+ const clonedDescriptor = clones[0];
const clonedStyleProps = (clonedDescriptor.style as VectorStyleDescriptor).properties;
// Should update style field belonging to join
// @ts-expect-error
diff --git a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
index 35a5caa7ff9b8..27768dc717bd7 100644
--- a/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
+++ b/x-pack/plugins/maps/public/classes/layers/vector_layer/vector_layer.tsx
@@ -162,8 +162,13 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
);
}
- async cloneDescriptor(): Promise {
- const clonedDescriptor = (await super.cloneDescriptor()) as VectorLayerDescriptor;
+ async cloneDescriptor(): Promise {
+ const clones = await super.cloneDescriptor();
+ if (clones.length === 0) {
+ return [];
+ }
+
+ const clonedDescriptor = clones[0] as VectorLayerDescriptor;
if (clonedDescriptor.joins) {
clonedDescriptor.joins.forEach((joinDescriptor: JoinDescriptor) => {
if (joinDescriptor.right && joinDescriptor.right.type === SOURCE_TYPES.TABLE_SOURCE) {
@@ -215,7 +220,7 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
}
});
}
- return clonedDescriptor;
+ return [clonedDescriptor];
}
getSource(): IVectorSource {
@@ -295,10 +300,10 @@ export class AbstractVectorLayer extends AbstractLayer implements IVectorLayer {
return this.getCurrentStyle().renderLegendDetails();
}
- async getBounds(syncContext: DataRequestContext) {
+ async getBounds(getDataRequestContext: (layerId: string) => DataRequestContext) {
return syncBoundsData({
layerId: this.getId(),
- syncContext,
+ syncContext: getDataRequestContext(this.getId()),
source: this.getSource(),
sourceQuery: this.getQuery(),
});
diff --git a/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx b/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx
new file mode 100644
index 0000000000000..8c35750265cd6
--- /dev/null
+++ b/x-pack/plugins/maps/public/components/remove_layer_confirm_modal.tsx
@@ -0,0 +1,74 @@
+/*
+ * 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 { EuiConfirmModal, EuiText } from '@elastic/eui';
+import { ILayer } from '../classes/layers/layer';
+import { isLayerGroup, LayerGroup } from '../classes/layers/layer_group';
+
+export interface Props {
+ layer: ILayer;
+ onCancel: () => void;
+ onConfirm: () => void;
+}
+
+export function RemoveLayerConfirmModal(props: Props) {
+ function getChildrenCount(layerGroup: LayerGroup) {
+ let count = 0;
+ layerGroup.getChildren().forEach((childLayer) => {
+ count++;
+ if (isLayerGroup(childLayer)) {
+ count = count + getChildrenCount(childLayer as LayerGroup);
+ }
+ });
+ return count;
+ }
+
+ function renderMultiLayerWarning() {
+ if (!isLayerGroup(props.layer)) {
+ return null;
+ }
+
+ const numChildren = getChildrenCount(props.layer as LayerGroup);
+ return numChildren > 0 ? (
+
+ {i18n.translate('xpack.maps.deleteLayerConfirmModal.multiLayerWarning', {
+ defaultMessage: `Removing this layer also removes {numChildren} nested {numChildren, plural, one {layer} other {layers}}.`,
+ values: { numChildren },
+ })}
+
+ ) : null;
+ }
+
+ return (
+
+
+ {renderMultiLayerWarning()}
+
+ {i18n.translate('xpack.maps.deleteLayerConfirmModal.unrecoverableWarning', {
+ defaultMessage: `You can't recover removed layers.`,
+ })}
+
+
+
+ );
+}
diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx
index 906947562f940..8ef8319a82798 100644
--- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx
+++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/edit_layer_panel.tsx
@@ -35,6 +35,7 @@ import { ILayer } from '../../classes/layers/layer';
import { isVectorLayer, IVectorLayer } from '../../classes/layers/vector_layer';
import { ImmutableSourceProperty, OnSourceChangeArgs } from '../../classes/sources/source';
import { IField } from '../../classes/fields/field';
+import { isLayerGroup } from '../../classes/layers/layer_group';
const localStorage = new Storage(window.localStorage);
@@ -95,7 +96,7 @@ export class EditLayerPanel extends Component {
};
_loadImmutableSourceProperties = async () => {
- if (!this.props.selectedLayer) {
+ if (!this.props.selectedLayer || isLayerGroup(this.props.selectedLayer)) {
return;
}
@@ -160,7 +161,11 @@ export class EditLayerPanel extends Component {
}
_renderFilterSection() {
- if (!this.props.selectedLayer || !this.props.selectedLayer.supportsElasticsearchFilters()) {
+ if (
+ !this.props.selectedLayer ||
+ isLayerGroup(this.props.selectedLayer) ||
+ !this.props.selectedLayer.supportsElasticsearchFilters()
+ ) {
return null;
}
@@ -197,35 +202,70 @@ export class EditLayerPanel extends Component {
);
}
- _renderSourceProperties() {
- return this.state.immutableSourceProps.map(
- ({ label, value, link }: ImmutableSourceProperty) => {
- function renderValue() {
- if (link) {
- return (
-
- {value}
-
- );
- }
- return {value};
- }
- return (
-
- {label} {renderValue()}
-
- );
- }
+ _renderSourceDetails() {
+ return !this.props.selectedLayer || isLayerGroup(this.props.selectedLayer) ? null : (
+
+
+
+
+ {this.state.immutableSourceProps.map(
+ ({ label, value, link }: ImmutableSourceProperty) => {
+ function renderValue() {
+ if (link) {
+ return (
+
+ {value}
+
+ );
+ }
+ return {value};
+ }
+ return (
+
+ {label} {renderValue()}
+
+ );
+ }
+ )}
+
+
+
);
}
- render() {
+ _renderSourceEditor() {
if (!this.props.selectedLayer) {
return null;
}
const descriptor = this.props.selectedLayer.getDescriptor() as VectorLayerDescriptor;
const numberOfJoins = descriptor.joins ? descriptor.joins.length : 0;
+ return isLayerGroup(this.props.selectedLayer)
+ ? null
+ : this.props.selectedLayer.renderSourceSettingsEditor({
+ currentLayerType: this.props.selectedLayer.getType(),
+ numberOfJoins,
+ onChange: this._onSourceChange,
+ onStyleDescriptorChange: this.props.updateStyleDescriptor,
+ style: this.props.selectedLayer.getStyleForEditing(),
+ });
+ }
+
+ _renderStyleEditor() {
+ return !this.props.selectedLayer || isLayerGroup(this.props.selectedLayer) ? null : (
+
+ );
+ }
+
+ render() {
+ if (!this.props.selectedLayer) {
+ return null;
+ }
return (
{
-
-
-
-
- {this._renderSourceProperties()}
-
-
-
+ {this._renderSourceDetails()}
@@ -273,19 +301,13 @@ export class EditLayerPanel extends Component
{
supportsFitToBounds={this.state.supportsFitToBounds}
/>
- {this.props.selectedLayer.renderSourceSettingsEditor({
- currentLayerType: this.props.selectedLayer.getType(),
- numberOfJoins,
- onChange: this._onSourceChange,
- onStyleDescriptorChange: this.props.updateStyleDescriptor,
- style: this.props.selectedLayer.getStyleForEditing(),
- })}
+ {this._renderSourceEditor()}
{this._renderFilterSection()}
{this._renderJoinSection()}
-
+ {this._renderStyleEditor()}
diff --git a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx
index b9761f5d48430..614fbfcebe4e1 100644
--- a/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx
+++ b/x-pack/plugins/maps/public/connected_components/edit_layer_panel/flyout_footer/flyout_footer.tsx
@@ -5,69 +5,102 @@
* 2.0.
*/
-import React from 'react';
+import React, { Component } from 'react';
import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiButtonEmpty } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
+import { ILayer } from '../../../classes/layers/layer';
+import { RemoveLayerConfirmModal } from '../../../components/remove_layer_confirm_modal';
export interface Props {
+ selectedLayer?: ILayer;
cancelLayerPanel: () => void;
saveLayerEdits: () => void;
removeLayer: () => void;
hasStateChanged: boolean;
}
-export const FlyoutFooter = ({
- cancelLayerPanel,
- saveLayerEdits,
- removeLayer,
- hasStateChanged,
-}: Props) => {
- const removeBtn = (
-